Extract execution trace from EVM execution and provide subscription API (#19)

* Create go.yml

* Merge from zkrollup and fix conflict

* go mod tidy

* fix worker_test test case

* fix worker_test test case

* Delete go.yml

* add log content, enable memory trace

* add tracer pool handler

* Add comments and format code

* add evmTrace subscribe api

* Move the evmTrace struct.

* Fix miner bug.

* upgrade evmTrace api

* fix bug about evmTracesByHash api

* Fix the bug about block.timestamp and remove unnecessary copy.

* Update eth/filters/api.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Upgrade comments.

* Delete useless code in test file

* Update miner/worker.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Change the return result to BlockResult.

* Change return type.

* Change blockResult to blockResults.

* Add ReturnValue.

* Update core/rawdb/l2trace.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update core/rawdb/l2trace.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update core/types/l2trace.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Add indent to the comment and rm json encoding flag.

* Rm json encoding flag.

* Update core/rawdb/l2trace.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Rm json encoding flag.

* Update ethclient/ethclient.go

Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>

* Update eth/filters/api.go

Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>

* Use  as the blockResult prefix flag.

* Update eth/filters/filter_system.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update eth/filters/filter_system.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update ethclient/ethclient.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

* Update eth/filters/api.go

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>

Co-authored-by: Haichen Shen <shenhaichen@gmail.com>
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
This commit is contained in:
maskpp 2022-01-23 20:41:15 +08:00 committed by GitHub
parent b4d60884d2
commit 7745fd5840
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 770 additions and 108 deletions

1
.gitignore vendored
View file

@ -47,3 +47,4 @@ profile.cov
/dashboard/assets/package-lock.json
**/yarn-error.log

View file

@ -1182,17 +1182,17 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
}
// WriteBlockWithState writes the block and all associated state to the database.
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, blockResult *types.BlockResult, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
if !bc.chainmu.TryLock() {
return NonStatTy, errInsertionInterrupted
}
defer bc.chainmu.Unlock()
return bc.writeBlockWithState(block, receipts, logs, state, emitHeadEvent)
return bc.writeBlockWithState(block, receipts, logs, blockResult, state, emitHeadEvent)
}
// writeBlockWithState writes the block and all associated state to the database,
// but is expects the chain mutex to be held.
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, blockResult *types.BlockResult, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) {
if bc.insertStopped() {
return NonStatTy, errInsertionInterrupted
}
@ -1215,6 +1215,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd)
rawdb.WriteBlock(blockBatch, block)
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
rawdb.WriteBlockResult(blockBatch, block.Hash(), blockResult)
rawdb.WritePreimages(blockBatch, state.Preimages())
if err := blockBatch.Write(); err != nil {
log.Crit("Failed to write block into disk", "err", err)
@ -1314,7 +1315,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
bc.futureBlocks.Remove(block.Hash())
if status == CanonStatTy {
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs, BlockResult: blockResult})
if len(logs) > 0 {
bc.logsFeed.Send(logs)
}
@ -1644,7 +1645,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
// Write the block to the chain and get the status.
substart = time.Now()
status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false)
// EvmTraces is nil is safe because l2geth's p2p server is stoped and the code will not execute there.
status, err := bc.writeBlockWithState(block, receipts, logs, nil, statedb, false)
atomic.StoreUint32(&followupInterrupt, 1)
if err != nil {
return it.index, err

View file

@ -31,9 +31,10 @@ type NewMinedBlockEvent struct{ Block *types.Block }
type RemovedLogsEvent struct{ Logs []*types.Log }
type ChainEvent struct {
Block *types.Block
Hash common.Hash
Logs []*types.Log
Block *types.Block
Hash common.Hash
Logs []*types.Log
BlockResult *types.BlockResult
}
type ChainSideEvent struct {

39
core/rawdb/l2trace.go Normal file
View file

@ -0,0 +1,39 @@
package rawdb
import (
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/ethdb"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rlp"
)
// ReadBlockResult retrieves all data required by roller.
func ReadBlockResult(db ethdb.Reader, hash common.Hash) *types.BlockResult {
data, _ := db.Get(blockResultKey(hash))
if len(data) == 0 {
return nil
}
var blockResult types.BlockResult
if err := rlp.DecodeBytes(data, &blockResult); err != nil {
log.Error("Failed to decode BlockResult", "err", err)
return nil
}
return &blockResult
}
// WriteBlockResult stores blockResult into leveldb.
func WriteBlockResult(db ethdb.KeyValueWriter, hash common.Hash, blockResult *types.BlockResult) {
bytes, err := rlp.EncodeToBytes(blockResult)
if err != nil {
log.Crit("Failed to RLP encode BlockResult", "err", err)
}
db.Put(blockResultKey(hash), bytes)
}
// DeleteBlockResult removes blockResult with a block hash.
func DeleteBlockResult(db ethdb.KeyValueWriter, hash common.Hash) {
if err := db.Delete(blockResultKey(hash)); err != nil {
log.Crit("Failed to delete BlockResult", "err", err)
}
}

355
core/rawdb/l2trace_test.go Normal file
View file

@ -0,0 +1,355 @@
package rawdb
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"testing"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/rlp"
)
func TestBlockEvmTracesStorage(t *testing.T) {
db := NewMemoryDatabase()
data1 := []byte(`{
"gas": 1,
"failed": false,
"returnValue": "",
"structLogs": [
{
"pc": 0,
"op": "PUSH1",
"gas": 1000000,
"gasCost": 3,
"depth": 1,
"stack": [],
"memory": []
},
{
"pc": 2,
"op": "SLOAD",
"gas": 999997,
"gasCost": 2100,
"depth": 1,
"stack": [
"0x1"
],
"memory": [],
"storage": {
"0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000000"
}
},
{
"pc": 3,
"op": "POP",
"gas": 997897,
"gasCost": 2,
"depth": 1,
"stack": [
"0x0"
],
"memory": []
},
{
"pc": 4,
"op": "PUSH1",
"gas": 997895,
"gasCost": 3,
"depth": 1,
"stack": [],
"memory": []
},
{
"pc": 6,
"op": "PUSH1",
"gas": 997892,
"gasCost": 3,
"depth": 1,
"stack": [
"0x11"
],
"memory": []
},
{
"pc": 8,
"op": "SSTORE",
"gas": 997889,
"gasCost": 20000,
"depth": 1,
"stack": [
"0x11",
"0x1"
],
"memory": [],
"storage": {
"0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000011"
}
},
{
"pc": 9,
"op": "PUSH1",
"gas": 977889,
"gasCost": 3,
"depth": 1,
"stack": [],
"memory": []
},
{
"pc": 11,
"op": "PUSH1",
"gas": 977886,
"gasCost": 3,
"depth": 1,
"stack": [
"0x11"
],
"memory": []
},
{
"pc": 13,
"op": "SSTORE",
"gas": 977883,
"gasCost": 22100,
"depth": 1,
"stack": [
"0x11",
"0x2"
],
"memory": [],
"storage": {
"0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000011",
"0000000000000000000000000000000000000000000000000000000000000002": "0000000000000000000000000000000000000000000000000000000000000011"
}
},
{
"pc": 14,
"op": "PUSH1",
"gas": 955783,
"gasCost": 3,
"depth": 1,
"stack": [],
"memory": []
},
{
"pc": 16,
"op": "PUSH1",
"gas": 955780,
"gasCost": 3,
"depth": 1,
"stack": [
"0x11"
],
"memory": []
},
{
"pc": 18,
"op": "SSTORE",
"gas": 955777,
"gasCost": 100,
"depth": 1,
"stack": [
"0x11",
"0x2"
],
"memory": [],
"storage": {
"0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000011",
"0000000000000000000000000000000000000000000000000000000000000002": "0000000000000000000000000000000000000000000000000000000000000011"
}
},
{
"pc": 19,
"op": "PUSH1",
"gas": 955677,
"gasCost": 3,
"depth": 1,
"stack": [],
"memory": []
},
{
"pc": 21,
"op": "SLOAD",
"gas": 955674,
"gasCost": 100,
"depth": 1,
"stack": [
"0x2"
],
"memory": [],
"storage": {
"0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000011",
"0000000000000000000000000000000000000000000000000000000000000002": "0000000000000000000000000000000000000000000000000000000000000011"
}
},
{
"pc": 22,
"op": "PUSH1",
"gas": 955574,
"gasCost": 3,
"depth": 1,
"stack": [
"0x11"
],
"memory": []
},
{
"pc": 24,
"op": "SLOAD",
"gas": 955571,
"gasCost": 100,
"depth": 1,
"stack": [
"0x11",
"0x1"
],
"memory": [],
"storage": {
"0000000000000000000000000000000000000000000000000000000000000001": "0000000000000000000000000000000000000000000000000000000000000011",
"0000000000000000000000000000000000000000000000000000000000000002": "0000000000000000000000000000000000000000000000000000000000000011"
}
},
{
"pc": 25,
"op": "STOP",
"gas": 955471,
"gasCost": 0,
"depth": 1,
"stack": [
"0x11",
"0x11"
],
"memory": []
}
]
}`)
evmTrace1 := &types.ExecutionResult{ReturnValue: "0xaaa"}
if err := json.Unmarshal(data1, evmTrace1); err != nil {
t.Fatalf(err.Error())
}
data2 := []byte(`{
"gas": 1,
"failed": false,
"returnValue": "000000000000000000000000000000000000000000000000000000000000000a",
"structLogs": [
{
"pc": 0,
"op": "PUSH1",
"gas": 1000000,
"gasCost": 3,
"depth": 1,
"stack": [],
"memory": []
},
{
"pc": 2,
"op": "PUSH1",
"gas": 999997,
"gasCost": 3,
"depth": 1,
"stack": [
"0xa"
],
"memory": []
},
{
"pc": 4,
"op": "MSTORE",
"gas": 999994,
"gasCost": 6,
"depth": 1,
"stack": [
"0xa",
"0x0"
],
"memory": [
"0000000000000000000000000000000000000000000000000000000000000000"
]
},
{
"pc": 5,
"op": "PUSH1",
"gas": 999988,
"gasCost": 3,
"depth": 1,
"stack": [],
"memory": [
"000000000000000000000000000000000000000000000000000000000000000a"
]
},
{
"pc": 7,
"op": "PUSH1",
"gas": 999985,
"gasCost": 3,
"depth": 1,
"stack": [
"0x20"
],
"memory": [
"000000000000000000000000000000000000000000000000000000000000000a"
]
},
{
"pc": 9,
"op": "RETURN",
"gas": 999982,
"gasCost": 0,
"depth": 1,
"stack": [
"0x20",
"0x0"
],
"memory": [
"000000000000000000000000000000000000000000000000000000000000000a"
]
}
]
}`)
evmTrace2 := &types.ExecutionResult{ReturnValue: "0xbbb"}
if err := json.Unmarshal(data2, evmTrace2); err != nil {
t.Fatalf(err.Error())
}
evmTraces := []*types.ExecutionResult{evmTrace1, evmTrace2}
hash := common.BytesToHash([]byte{0x03, 0x04})
// Insert the blockResult into the database and check presence.
WriteBlockResult(db, hash, &types.BlockResult{ExecutionResults: evmTraces})
// Read blockResult from db.
if blockResult := ReadBlockResult(db, hash); len(blockResult.ExecutionResults) == 0 {
t.Fatalf("No evmTraces returned")
} else {
if err := checkEvmTracesRLP(blockResult.ExecutionResults, evmTraces); err != nil {
t.Fatalf(err.Error())
}
}
// Delete blockResult by blockHash.
DeleteBlockResult(db, hash)
if blockResult := ReadBlockResult(db, hash); blockResult != nil && len(blockResult.ExecutionResults) != 0 {
t.Fatalf("The evmTrace list should be empty.")
}
}
func checkEvmTracesRLP(have, want []*types.ExecutionResult) error {
if len(have) != len(want) {
return fmt.Errorf("evmTraces sizes mismatch: have: %d, want: %d", len(have), len(want))
}
for i := 0; i < len(want); i++ {
rlpHave, err := rlp.EncodeToBytes(have[i])
if err != nil {
return err
}
rlpWant, err := rlp.EncodeToBytes(want[i])
if err != nil {
return err
}
if !bytes.Equal(rlpHave, rlpWant) {
return fmt.Errorf("evmTrace #%d: evmTrace mismatch: have %s, want %s", i, hex.EncodeToString(rlpHave), hex.EncodeToString(rlpWant))
}
}
return nil
}

View file

@ -89,6 +89,7 @@ var (
SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value
SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value
CodePrefix = []byte("c") // CodePrefix + code hash -> account code
blockResultPrefix = []byte("T") // blockResultPrefix + hash -> blockResult
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db
@ -230,3 +231,8 @@ func IsCodeKey(key []byte) (bool, []byte) {
func configKey(hash common.Hash) []byte {
return append(configPrefix, hash.Bytes()...)
}
// blockResultKey = blockResultPrefix + hash
func blockResultKey(hash common.Hash) []byte {
return append(blockResultPrefix, hash.Bytes()...)
}

View file

@ -112,9 +112,14 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
}
*usedGas += result.UsedGas
// If the result contains a revert reason, return it.
returnVal := result.Return()
if len(result.Revert()) > 0 {
returnVal = result.Revert()
}
// Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas}
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: *usedGas, ReturnValue: returnVal}
if result.Failed() {
receipt.Status = types.ReceiptStatusFailed
} else {

165
core/types/l2trace.go Normal file
View file

@ -0,0 +1,165 @@
package types
import (
"io"
"sort"
"strings"
"github.com/scroll-tech/go-ethereum/rlp"
)
// BlockResult contains block execution traces and results required for rollers.
type BlockResult struct {
ExecutionResults []*ExecutionResult `json:"executionResults"`
}
type rlpBlockResult struct {
ExecutionResults []*ExecutionResult
}
func (b *BlockResult) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &rlpBlockResult{
ExecutionResults: b.ExecutionResults,
})
}
func (b *BlockResult) DecodeRLP(s *rlp.Stream) error {
var dec rlpBlockResult
err := s.Decode(&dec)
if err == nil {
b.ExecutionResults = dec.ExecutionResults
}
return err
}
// ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value
type ExecutionResult struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue,omitempty"`
StructLogs []StructLogRes `json:"structLogs"`
}
type rlpExecutionResult struct {
Gas uint64
Failed bool
ReturnValue string
StructLogs []StructLogRes
}
func (e *ExecutionResult) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, rlpExecutionResult{
Gas: e.Gas,
Failed: e.Failed,
ReturnValue: e.ReturnValue,
StructLogs: e.StructLogs,
})
}
func (e *ExecutionResult) DecodeRLP(s *rlp.Stream) error {
var dec rlpExecutionResult
err := s.Decode(&dec)
if err == nil {
e.Gas, e.Failed, e.ReturnValue, e.StructLogs = dec.Gas, dec.Failed, dec.ReturnValue, dec.StructLogs
}
return err
}
// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error string `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
}
type rlpStructLogRes struct {
Pc uint64
Op string
Gas uint64
GasCost uint64
Depth uint
Error string
Stack []string
Memory []string
Storage []string
}
// EncodeRLP implements rlp.Encoder.
func (r *StructLogRes) EncodeRLP(w io.Writer) error {
data := rlpStructLogRes{
Pc: r.Pc,
Op: r.Op,
Gas: r.Gas,
GasCost: r.GasCost,
Depth: uint(r.Depth),
Error: r.Error,
}
if r.Stack != nil {
data.Stack = make([]string, len(*r.Stack))
for i, val := range *r.Stack {
data.Stack[i] = val
}
}
if r.Memory != nil {
data.Memory = make([]string, len(*r.Memory))
for i, val := range *r.Memory {
data.Memory[i] = val
}
}
if r.Storage != nil {
keys := make([]string, 0, len(*r.Storage))
for key := range *r.Storage {
keys = append(keys, key)
}
sort.Slice(keys, func(i, j int) bool {
return strings.Compare(keys[i], keys[j]) >= 0
})
data.Storage = make([]string, 0, len(*r.Storage)*2)
for _, key := range keys {
data.Storage = append(data.Storage, []string{key, (*r.Storage)[key]}...)
}
}
return rlp.Encode(w, data)
}
// DecodeRLP implements rlp.Decoder.
func (r *StructLogRes) DecodeRLP(s *rlp.Stream) error {
var dec rlpStructLogRes
err := s.Decode(&dec)
if err != nil {
return err
}
r.Pc, r.Op, r.Gas, r.GasCost, r.Depth, r.Error = dec.Pc, dec.Op, dec.Gas, dec.GasCost, int(dec.Depth), dec.Error
if len(dec.Stack) != 0 {
stack := make([]string, len(dec.Stack))
for i, val := range dec.Stack {
stack[i] = val
}
r.Stack = &stack
}
if len(dec.Memory) != 0 {
memory := make([]string, len(dec.Memory))
for i, val := range dec.Memory {
memory[i] = val
}
r.Memory = &memory
}
if len(dec.Storage) != 0 {
storage := make(map[string]string, len(dec.Storage)*2)
for i := 0; i < len(dec.Storage); i += 2 {
key, val := dec.Storage[i], dec.Storage[i+1]
storage[key] = val
}
r.Storage = &storage
}
return nil
}

View file

@ -70,6 +70,9 @@ type Receipt struct {
BlockHash common.Hash `json:"blockHash,omitempty"`
BlockNumber *big.Int `json:"blockNumber,omitempty"`
TransactionIndex uint `json:"transactionIndex"`
// The value of evm execution result.
ReturnValue []byte `json:"returnValue,omitempty"`
}
type receiptMarshaling struct {

View file

@ -358,3 +358,40 @@ func (t *mdLogger) CaptureEnter(typ OpCode, from common.Address, to common.Addre
}
func (t *mdLogger) CaptureExit(output []byte, gasUsed uint64, err error) {}
// FormatLogs formats EVM returned structured logs for json output
func FormatLogs(logs []StructLog) []types.StructLogRes {
formatted := make([]types.StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = types.StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.ErrorString(),
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = stackValue.Hex()
}
formatted[index].Stack = &stack
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
}
}
return formatted
}

View file

@ -607,3 +607,18 @@ func (api *PrivateDebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64
}
return 0, fmt.Errorf("No state found")
}
// PublicTraceAPI provides an API to get evmTrace, mpt proof.
type PublicTraceAPI struct {
e *Ethereum
}
// NewPublicTraceAPI creates a new Ethereum trace API.
func NewPublicTraceAPI(eth *Ethereum) *PublicTraceAPI {
return &PublicTraceAPI{eth}
}
// BlockResultByHash returns the blockResult by blockHash.
func (api *PublicTraceAPI) BlockResultByHash(blockHash common.Hash) (*types.BlockResult, error) {
return rawdb.ReadBlockResult(api.e.chainDb, blockHash), nil
}

View file

@ -175,6 +175,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
var (
vmConfig = vm.Config{
EnablePreimageRecording: config.EnablePreimageRecording,
Debug: true,
Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true}),
}
cacheConfig = &core.CacheConfig{
TrieCleanLimit: config.TrieCleanCache,
@ -314,6 +316,11 @@ func (s *Ethereum) APIs() []rpc.API {
Version: "1.0",
Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
Public: true,
}, {
Namespace: "eth",
Version: "1.0",
Service: NewPublicTraceAPI(s),
Public: true,
}, {
Namespace: "miner",
Version: "1.0",

View file

@ -278,6 +278,35 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc
return rpcSub, nil
}
// NewBlockResult sends the block execution result when a new block is created.
func (api *PublicFilterAPI) NewBlockResult(ctx context.Context) (*rpc.Subscription, error) {
notifier, supported := rpc.NotifierFromContext(ctx)
if !supported {
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
}
rpcSub := notifier.CreateSubscription()
go func() {
blockResults := make(chan *types.BlockResult)
blockResultsSub := api.events.SubscribeBlockResult(blockResults)
for {
select {
case blockResult := <-blockResults:
notifier.Notify(rpcSub.ID, blockResult)
case <-rpcSub.Err():
blockResultsSub.Unsubscribe()
return
case <-notifier.Closed():
blockResultsSub.Unsubscribe()
return
}
}
}()
return rpcSub, nil
}
// FilterCriteria represents a request to create a new filter.
// Same as ethereum.FilterQuery but with UnmarshalJSON() method.
type FilterCriteria ethereum.FilterQuery

View file

@ -52,6 +52,8 @@ const (
PendingTransactionsSubscription
// BlocksSubscription queries hashes for blocks that are imported
BlocksSubscription
// BlockResultsSubscription queries for block execution traces
BlockResultsSubscription
// LastSubscription keeps track of the last index
LastIndexSubscription
)
@ -69,15 +71,16 @@ const (
)
type subscription struct {
id rpc.ID
typ Type
created time.Time
logsCrit ethereum.FilterQuery
logs chan []*types.Log
hashes chan []common.Hash
headers chan *types.Header
installed chan struct{} // closed when the filter is installed
err chan error // closed when the filter is uninstalled
id rpc.ID
typ Type
created time.Time
logsCrit ethereum.FilterQuery
logs chan []*types.Log
hashes chan []common.Hash
headers chan *types.Header
blockResults chan *types.BlockResult
installed chan struct{} // closed when the filter is installed
err chan error // closed when the filter is uninstalled
}
// EventSystem creates subscriptions, processes events and broadcasts them to the
@ -290,6 +293,19 @@ func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscripti
return es.subscribe(sub)
}
// SubscribeBlockResult creates a subscription that writes the block trace when a new block is created.
func (es *EventSystem) SubscribeBlockResult(blockResult chan *types.BlockResult) *Subscription {
sub := &subscription{
id: rpc.NewID(),
typ: BlockResultsSubscription,
created: time.Now(),
blockResults: blockResult,
installed: make(chan struct{}),
err: make(chan error),
}
return es.subscribe(sub)
}
// SubscribePendingTxs creates a subscription that writes transaction hashes for
// transactions that enter the transaction pool.
func (es *EventSystem) SubscribePendingTxs(hashes chan []common.Hash) *Subscription {
@ -355,6 +371,9 @@ func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent)
for _, f := range filters[BlocksSubscription] {
f.headers <- ev.Block.Header()
}
for _, f := range filters[BlockResultsSubscription] {
f.blockResults <- ev.BlockResult
}
if es.lightMode && len(filters[LogsSubscription]) > 0 {
es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) {
for _, f := range filters[LogsSubscription] {

View file

@ -912,11 +912,11 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
if len(result.Revert()) > 0 {
returnVal = fmt.Sprintf("%x", result.Revert())
}
return &ethapi.ExecutionResult{
return &types.ExecutionResult{
Gas: result.UsedGas,
Failed: result.Failed(),
ReturnValue: returnVal,
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
StructLogs: vm.FormatLogs(tracer.StructLogs()),
}, nil
case Tracer:

View file

@ -213,11 +213,11 @@ func TestTraceCall(t *testing.T) {
},
config: nil,
expectErr: nil,
expect: &ethapi.ExecutionResult{
expect: &types.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []ethapi.StructLogRes{},
StructLogs: []types.StructLogRes{},
},
},
// Standard JSON trace upon the head, plain transfer.
@ -230,11 +230,11 @@ func TestTraceCall(t *testing.T) {
},
config: nil,
expectErr: nil,
expect: &ethapi.ExecutionResult{
expect: &types.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []ethapi.StructLogRes{},
StructLogs: []types.StructLogRes{},
},
},
// Standard JSON trace upon the non-existent block, error expects
@ -259,11 +259,11 @@ func TestTraceCall(t *testing.T) {
},
config: nil,
expectErr: nil,
expect: &ethapi.ExecutionResult{
expect: &types.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []ethapi.StructLogRes{},
StructLogs: []types.StructLogRes{},
},
},
// Standard JSON trace upon the pending block
@ -276,11 +276,11 @@ func TestTraceCall(t *testing.T) {
},
config: nil,
expectErr: nil,
expect: &ethapi.ExecutionResult{
expect: &types.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []ethapi.StructLogRes{},
StructLogs: []types.StructLogRes{},
},
},
}
@ -329,11 +329,11 @@ func TestTraceTransaction(t *testing.T) {
if err != nil {
t.Errorf("Failed to trace transaction %v", err)
}
if !reflect.DeepEqual(result, &ethapi.ExecutionResult{
if !reflect.DeepEqual(result, &types.ExecutionResult{
Gas: params.TxGas,
Failed: false,
ReturnValue: "",
StructLogs: []ethapi.StructLogRes{},
StructLogs: []types.StructLogRes{},
}) {
t.Error("Transaction tracing result is different")
}

View file

@ -325,6 +325,20 @@ func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header)
return ec.c.EthSubscribe(ctx, ch, "newHeads")
}
// BlockResultByHash returns the blockResult.
func (ec *Client) BlockResultByHash(ctx context.Context, blockHash common.Hash) (*types.BlockResult, error) {
var blockResult types.BlockResult
if err := ec.c.CallContext(ctx, &blockResult, "eth_blockResultByHash", blockHash); err != nil {
return nil, err
}
return &blockResult, nil
}
// SubscribeNewBlockResult subscribes to block execution trace when a new block is created.
func (ec *Client) SubscribeNewBlockResult(ctx context.Context, ch chan<- *types.BlockResult) (ethereum.Subscription, error) {
return ec.c.EthSubscribe(ctx, ch, "newBlockResult")
}
// State Access
// NetworkID returns the network ID (also known as the chain ID) for this chain.

1
go.mod
View file

@ -52,6 +52,7 @@ require (
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
github.com/olekukonko/tablewriter v0.0.5
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
github.com/pkg/errors v0.9.1
github.com/prometheus/tsdb v0.7.1
github.com/rjeczalik/notify v0.9.1
github.com/rs/cors v1.7.0

View file

@ -1113,67 +1113,6 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args TransactionA
return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap())
}
// ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value
type ExecutionResult struct {
Gas uint64 `json:"gas"`
Failed bool `json:"failed"`
ReturnValue string `json:"returnValue"`
StructLogs []StructLogRes `json:"structLogs"`
}
// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error string `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
}
// FormatLogs formats EVM returned structured logs for json output
func FormatLogs(logs []vm.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.ErrorString(),
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = stackValue.Hex()
}
formatted[index].Stack = &stack
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
}
}
return formatted
}
// RPCMarshalHeader converts the given header to the RPC output .
func RPCMarshalHeader(head *types.Header) map[string]interface{} {
result := map[string]interface{}{

View file

@ -19,6 +19,7 @@ package miner
import (
"bytes"
"errors"
"fmt"
"math/big"
"sync"
"sync/atomic"
@ -31,6 +32,7 @@ import (
"github.com/scroll-tech/go-ethereum/core"
"github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/core/vm"
"github.com/scroll-tech/go-ethereum/event"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/params"
@ -88,17 +90,19 @@ type environment struct {
tcount int // tx count in cycle
gasPool *core.GasPool // available gas used to pack transactions
header *types.Header
txs []*types.Transaction
receipts []*types.Receipt
header *types.Header
txs []*types.Transaction
receipts []*types.Receipt
executionResults []*types.ExecutionResult
}
// task contains all information for consensus engine sealing and result submitting.
type task struct {
receipts []*types.Receipt
state *state.StateDB
block *types.Block
createdAt time.Time
receipts []*types.Receipt
executionResults []*types.ExecutionResult
state *state.StateDB
block *types.Block
createdAt time.Time
}
const (
@ -393,10 +397,10 @@ func (w *worker) newWorkLoop(recommit time.Duration) {
timestamp = time.Now().Unix()
commit(false, commitInterruptNewHead)
/*case head := <-w.chainHeadCh:
clearPending(head.Block.NumberU64())
timestamp = time.Now().Unix()
commit(false, commitInterruptNewHead)*/
case head := <-w.chainHeadCh:
clearPending(head.Block.NumberU64())
timestamp = time.Now().Unix()
// commit(false, commitInterruptNewHead)
case <-timer.C:
// If mining is running resubmit a new work cycle periodically to pull in
@ -462,6 +466,7 @@ func (w *worker) mainLoop() {
select {
case req := <-w.newWorkCh:
w.commitNewWork(req.interrupt, req.noempty, req.timestamp)
// new block created.
case ev := <-w.chainSideCh:
// Short circuit for duplicate side blocks
@ -632,14 +637,19 @@ func (w *worker) resultLoop() {
}
// Different block could share same sealhash, deep copy here to prevent write-write conflict.
var (
receipts = make([]*types.Receipt, len(task.receipts))
logs []*types.Log
receipts = make([]*types.Receipt, len(task.receipts))
evmTraces = make([]*types.ExecutionResult, len(task.executionResults))
logs []*types.Log
)
for i, taskReceipt := range task.receipts {
receipt := new(types.Receipt)
receipts[i] = receipt
*receipt = *taskReceipt
evmTrace := new(types.ExecutionResult)
evmTraces[i] = evmTrace
*evmTrace = *task.executionResults[i]
// add block location fields
receipt.BlockHash = hash
receipt.BlockNumber = block.Number()
@ -657,7 +667,7 @@ func (w *worker) resultLoop() {
logs = append(logs, receipt.Logs...)
}
// Commit block and state to database.
_, err := w.chain.WriteBlockWithState(block, receipts, logs, task.state, true)
_, err := w.chain.WriteBlockWithState(block, receipts, logs, &types.BlockResult{ExecutionResults: evmTraces}, task.state, true)
if err != nil {
log.Error("Failed writing block to chain", "err", err)
continue
@ -771,6 +781,10 @@ func (w *worker) updateSnapshot() {
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
snap := w.current.state.Snapshot()
// reset tracer.
tracer := w.chain.GetVMConfig().Tracer.(*vm.StructLogger)
tracer.Reset()
receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig())
if err != nil {
w.current.state.RevertToSnapshot(snap)
@ -778,6 +792,12 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres
}
w.current.txs = append(w.current.txs, tx)
w.current.receipts = append(w.current.receipts, receipt)
w.current.executionResults = append(w.current.executionResults, &types.ExecutionResult{
Gas: receipt.GasUsed,
Failed: receipt.Status == types.ReceiptStatusSuccessful,
ReturnValue: fmt.Sprintf("%x", receipt.ReturnValue),
StructLogs: vm.FormatLogs(tracer.StructLogs()),
})
return receipt.Logs, nil
}
@ -1042,7 +1062,7 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
interval()
}
select {
case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}:
case w.taskCh <- &task{receipts: receipts, executionResults: w.current.executionResults, state: s, block: block, createdAt: time.Now()}:
w.unconfirmed.Shift(block.NumberU64() - 1)
log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
"uncles", len(uncles), "txs", w.current.tcount,

View file

@ -135,7 +135,9 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine
}
genesis := gspec.MustCommit(db)
chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{}, nil, nil)
chain, _ := core.NewBlockChain(db, &core.CacheConfig{TrieDirtyDisabled: true}, gspec.Config, engine, vm.Config{
Debug: true,
Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true})}, nil, nil)
txpool := core.NewTxPool(testTxPoolConfig, chainConfig, chain)
// Generate a small n-block chain and an uncle block for it
@ -232,7 +234,9 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
// This test chain imports the mined blocks.
db2 := rawdb.NewMemoryDatabase()
b.genesis.MustCommit(db2)
chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil, nil)
chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{
Debug: true,
Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true})}, nil, nil)
defer chain.Stop()
// Ignore empty commit here for less noise.