mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-26 14:46:42 +00:00
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:
parent
b4d60884d2
commit
7745fd5840
21 changed files with 770 additions and 108 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -47,3 +47,4 @@ profile.cov
|
||||||
/dashboard/assets/package-lock.json
|
/dashboard/assets/package-lock.json
|
||||||
|
|
||||||
**/yarn-error.log
|
**/yarn-error.log
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1182,17 +1182,17 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteBlockWithState writes the block and all associated state to the database.
|
// 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() {
|
if !bc.chainmu.TryLock() {
|
||||||
return NonStatTy, errInsertionInterrupted
|
return NonStatTy, errInsertionInterrupted
|
||||||
}
|
}
|
||||||
defer bc.chainmu.Unlock()
|
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,
|
// writeBlockWithState writes the block and all associated state to the database,
|
||||||
// but is expects the chain mutex to be held.
|
// 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() {
|
if bc.insertStopped() {
|
||||||
return NonStatTy, errInsertionInterrupted
|
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.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd)
|
||||||
rawdb.WriteBlock(blockBatch, block)
|
rawdb.WriteBlock(blockBatch, block)
|
||||||
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
|
||||||
|
rawdb.WriteBlockResult(blockBatch, block.Hash(), blockResult)
|
||||||
rawdb.WritePreimages(blockBatch, state.Preimages())
|
rawdb.WritePreimages(blockBatch, state.Preimages())
|
||||||
if err := blockBatch.Write(); err != nil {
|
if err := blockBatch.Write(); err != nil {
|
||||||
log.Crit("Failed to write block into disk", "err", err)
|
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())
|
bc.futureBlocks.Remove(block.Hash())
|
||||||
|
|
||||||
if status == CanonStatTy {
|
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 {
|
if len(logs) > 0 {
|
||||||
bc.logsFeed.Send(logs)
|
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.
|
// Write the block to the chain and get the status.
|
||||||
substart = time.Now()
|
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)
|
atomic.StoreUint32(&followupInterrupt, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it.index, err
|
return it.index, err
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,10 @@ type NewMinedBlockEvent struct{ Block *types.Block }
|
||||||
type RemovedLogsEvent struct{ Logs []*types.Log }
|
type RemovedLogsEvent struct{ Logs []*types.Log }
|
||||||
|
|
||||||
type ChainEvent struct {
|
type ChainEvent struct {
|
||||||
Block *types.Block
|
Block *types.Block
|
||||||
Hash common.Hash
|
Hash common.Hash
|
||||||
Logs []*types.Log
|
Logs []*types.Log
|
||||||
|
BlockResult *types.BlockResult
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChainSideEvent struct {
|
type ChainSideEvent struct {
|
||||||
|
|
|
||||||
39
core/rawdb/l2trace.go
Normal file
39
core/rawdb/l2trace.go
Normal 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
355
core/rawdb/l2trace_test.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -89,6 +89,7 @@ var (
|
||||||
SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value
|
SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value
|
||||||
SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value
|
SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value
|
||||||
CodePrefix = []byte("c") // CodePrefix + code hash -> account code
|
CodePrefix = []byte("c") // CodePrefix + code hash -> account code
|
||||||
|
blockResultPrefix = []byte("T") // blockResultPrefix + hash -> blockResult
|
||||||
|
|
||||||
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
|
PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage
|
||||||
configPrefix = []byte("ethereum-config-") // config prefix for the db
|
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 {
|
func configKey(hash common.Hash) []byte {
|
||||||
return append(configPrefix, hash.Bytes()...)
|
return append(configPrefix, hash.Bytes()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blockResultKey = blockResultPrefix + hash
|
||||||
|
func blockResultKey(hash common.Hash) []byte {
|
||||||
|
return append(blockResultPrefix, hash.Bytes()...)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -112,9 +112,14 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon
|
||||||
}
|
}
|
||||||
*usedGas += result.UsedGas
|
*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
|
// Create a new receipt for the transaction, storing the intermediate root and gas used
|
||||||
// by the tx.
|
// 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() {
|
if result.Failed() {
|
||||||
receipt.Status = types.ReceiptStatusFailed
|
receipt.Status = types.ReceiptStatusFailed
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
165
core/types/l2trace.go
Normal file
165
core/types/l2trace.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -70,6 +70,9 @@ type Receipt struct {
|
||||||
BlockHash common.Hash `json:"blockHash,omitempty"`
|
BlockHash common.Hash `json:"blockHash,omitempty"`
|
||||||
BlockNumber *big.Int `json:"blockNumber,omitempty"`
|
BlockNumber *big.Int `json:"blockNumber,omitempty"`
|
||||||
TransactionIndex uint `json:"transactionIndex"`
|
TransactionIndex uint `json:"transactionIndex"`
|
||||||
|
|
||||||
|
// The value of evm execution result.
|
||||||
|
ReturnValue []byte `json:"returnValue,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type receiptMarshaling struct {
|
type receiptMarshaling struct {
|
||||||
|
|
|
||||||
|
|
@ -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) {}
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
15
eth/api.go
15
eth/api.go
|
|
@ -607,3 +607,18 @@ func (api *PrivateDebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf("No state found")
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
var (
|
var (
|
||||||
vmConfig = vm.Config{
|
vmConfig = vm.Config{
|
||||||
EnablePreimageRecording: config.EnablePreimageRecording,
|
EnablePreimageRecording: config.EnablePreimageRecording,
|
||||||
|
Debug: true,
|
||||||
|
Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true}),
|
||||||
}
|
}
|
||||||
cacheConfig = &core.CacheConfig{
|
cacheConfig = &core.CacheConfig{
|
||||||
TrieCleanLimit: config.TrieCleanCache,
|
TrieCleanLimit: config.TrieCleanCache,
|
||||||
|
|
@ -314,6 +316,11 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
|
Service: downloader.NewPublicDownloaderAPI(s.handler.downloader, s.eventMux),
|
||||||
Public: true,
|
Public: true,
|
||||||
|
}, {
|
||||||
|
Namespace: "eth",
|
||||||
|
Version: "1.0",
|
||||||
|
Service: NewPublicTraceAPI(s),
|
||||||
|
Public: true,
|
||||||
}, {
|
}, {
|
||||||
Namespace: "miner",
|
Namespace: "miner",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
|
|
|
||||||
|
|
@ -278,6 +278,35 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc
|
||||||
return rpcSub, nil
|
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.
|
// FilterCriteria represents a request to create a new filter.
|
||||||
// Same as ethereum.FilterQuery but with UnmarshalJSON() method.
|
// Same as ethereum.FilterQuery but with UnmarshalJSON() method.
|
||||||
type FilterCriteria ethereum.FilterQuery
|
type FilterCriteria ethereum.FilterQuery
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@ const (
|
||||||
PendingTransactionsSubscription
|
PendingTransactionsSubscription
|
||||||
// BlocksSubscription queries hashes for blocks that are imported
|
// BlocksSubscription queries hashes for blocks that are imported
|
||||||
BlocksSubscription
|
BlocksSubscription
|
||||||
|
// BlockResultsSubscription queries for block execution traces
|
||||||
|
BlockResultsSubscription
|
||||||
// LastSubscription keeps track of the last index
|
// LastSubscription keeps track of the last index
|
||||||
LastIndexSubscription
|
LastIndexSubscription
|
||||||
)
|
)
|
||||||
|
|
@ -69,15 +71,16 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type subscription struct {
|
type subscription struct {
|
||||||
id rpc.ID
|
id rpc.ID
|
||||||
typ Type
|
typ Type
|
||||||
created time.Time
|
created time.Time
|
||||||
logsCrit ethereum.FilterQuery
|
logsCrit ethereum.FilterQuery
|
||||||
logs chan []*types.Log
|
logs chan []*types.Log
|
||||||
hashes chan []common.Hash
|
hashes chan []common.Hash
|
||||||
headers chan *types.Header
|
headers chan *types.Header
|
||||||
installed chan struct{} // closed when the filter is installed
|
blockResults chan *types.BlockResult
|
||||||
err chan error // closed when the filter is uninstalled
|
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
|
// 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)
|
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
|
// SubscribePendingTxs creates a subscription that writes transaction hashes for
|
||||||
// transactions that enter the transaction pool.
|
// transactions that enter the transaction pool.
|
||||||
func (es *EventSystem) SubscribePendingTxs(hashes chan []common.Hash) *Subscription {
|
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] {
|
for _, f := range filters[BlocksSubscription] {
|
||||||
f.headers <- ev.Block.Header()
|
f.headers <- ev.Block.Header()
|
||||||
}
|
}
|
||||||
|
for _, f := range filters[BlockResultsSubscription] {
|
||||||
|
f.blockResults <- ev.BlockResult
|
||||||
|
}
|
||||||
if es.lightMode && len(filters[LogsSubscription]) > 0 {
|
if es.lightMode && len(filters[LogsSubscription]) > 0 {
|
||||||
es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) {
|
es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) {
|
||||||
for _, f := range filters[LogsSubscription] {
|
for _, f := range filters[LogsSubscription] {
|
||||||
|
|
|
||||||
|
|
@ -912,11 +912,11 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
||||||
if len(result.Revert()) > 0 {
|
if len(result.Revert()) > 0 {
|
||||||
returnVal = fmt.Sprintf("%x", result.Revert())
|
returnVal = fmt.Sprintf("%x", result.Revert())
|
||||||
}
|
}
|
||||||
return ðapi.ExecutionResult{
|
return &types.ExecutionResult{
|
||||||
Gas: result.UsedGas,
|
Gas: result.UsedGas,
|
||||||
Failed: result.Failed(),
|
Failed: result.Failed(),
|
||||||
ReturnValue: returnVal,
|
ReturnValue: returnVal,
|
||||||
StructLogs: ethapi.FormatLogs(tracer.StructLogs()),
|
StructLogs: vm.FormatLogs(tracer.StructLogs()),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
case Tracer:
|
case Tracer:
|
||||||
|
|
|
||||||
|
|
@ -213,11 +213,11 @@ func TestTraceCall(t *testing.T) {
|
||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
expect: ðapi.ExecutionResult{
|
expect: &types.ExecutionResult{
|
||||||
Gas: params.TxGas,
|
Gas: params.TxGas,
|
||||||
Failed: false,
|
Failed: false,
|
||||||
ReturnValue: "",
|
ReturnValue: "",
|
||||||
StructLogs: []ethapi.StructLogRes{},
|
StructLogs: []types.StructLogRes{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Standard JSON trace upon the head, plain transfer.
|
// Standard JSON trace upon the head, plain transfer.
|
||||||
|
|
@ -230,11 +230,11 @@ func TestTraceCall(t *testing.T) {
|
||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
expect: ðapi.ExecutionResult{
|
expect: &types.ExecutionResult{
|
||||||
Gas: params.TxGas,
|
Gas: params.TxGas,
|
||||||
Failed: false,
|
Failed: false,
|
||||||
ReturnValue: "",
|
ReturnValue: "",
|
||||||
StructLogs: []ethapi.StructLogRes{},
|
StructLogs: []types.StructLogRes{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Standard JSON trace upon the non-existent block, error expects
|
// Standard JSON trace upon the non-existent block, error expects
|
||||||
|
|
@ -259,11 +259,11 @@ func TestTraceCall(t *testing.T) {
|
||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
expect: ðapi.ExecutionResult{
|
expect: &types.ExecutionResult{
|
||||||
Gas: params.TxGas,
|
Gas: params.TxGas,
|
||||||
Failed: false,
|
Failed: false,
|
||||||
ReturnValue: "",
|
ReturnValue: "",
|
||||||
StructLogs: []ethapi.StructLogRes{},
|
StructLogs: []types.StructLogRes{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Standard JSON trace upon the pending block
|
// Standard JSON trace upon the pending block
|
||||||
|
|
@ -276,11 +276,11 @@ func TestTraceCall(t *testing.T) {
|
||||||
},
|
},
|
||||||
config: nil,
|
config: nil,
|
||||||
expectErr: nil,
|
expectErr: nil,
|
||||||
expect: ðapi.ExecutionResult{
|
expect: &types.ExecutionResult{
|
||||||
Gas: params.TxGas,
|
Gas: params.TxGas,
|
||||||
Failed: false,
|
Failed: false,
|
||||||
ReturnValue: "",
|
ReturnValue: "",
|
||||||
StructLogs: []ethapi.StructLogRes{},
|
StructLogs: []types.StructLogRes{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -329,11 +329,11 @@ func TestTraceTransaction(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Failed to trace transaction %v", err)
|
t.Errorf("Failed to trace transaction %v", err)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(result, ðapi.ExecutionResult{
|
if !reflect.DeepEqual(result, &types.ExecutionResult{
|
||||||
Gas: params.TxGas,
|
Gas: params.TxGas,
|
||||||
Failed: false,
|
Failed: false,
|
||||||
ReturnValue: "",
|
ReturnValue: "",
|
||||||
StructLogs: []ethapi.StructLogRes{},
|
StructLogs: []types.StructLogRes{},
|
||||||
}) {
|
}) {
|
||||||
t.Error("Transaction tracing result is different")
|
t.Error("Transaction tracing result is different")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,20 @@ func (ec *Client) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header)
|
||||||
return ec.c.EthSubscribe(ctx, ch, "newHeads")
|
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
|
// State Access
|
||||||
|
|
||||||
// NetworkID returns the network ID (also known as the chain ID) for this chain.
|
// NetworkID returns the network ID (also known as the chain ID) for this chain.
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -52,6 +52,7 @@ require (
|
||||||
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
|
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
|
||||||
github.com/olekukonko/tablewriter v0.0.5
|
github.com/olekukonko/tablewriter v0.0.5
|
||||||
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7
|
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/prometheus/tsdb v0.7.1
|
||||||
github.com/rjeczalik/notify v0.9.1
|
github.com/rjeczalik/notify v0.9.1
|
||||||
github.com/rs/cors v1.7.0
|
github.com/rs/cors v1.7.0
|
||||||
|
|
|
||||||
|
|
@ -1113,67 +1113,6 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args TransactionA
|
||||||
return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap())
|
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 .
|
// RPCMarshalHeader converts the given header to the RPC output .
|
||||||
func RPCMarshalHeader(head *types.Header) map[string]interface{} {
|
func RPCMarshalHeader(head *types.Header) map[string]interface{} {
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package miner
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
@ -31,6 +32,7 @@ import (
|
||||||
"github.com/scroll-tech/go-ethereum/core"
|
"github.com/scroll-tech/go-ethereum/core"
|
||||||
"github.com/scroll-tech/go-ethereum/core/state"
|
"github.com/scroll-tech/go-ethereum/core/state"
|
||||||
"github.com/scroll-tech/go-ethereum/core/types"
|
"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/event"
|
||||||
"github.com/scroll-tech/go-ethereum/log"
|
"github.com/scroll-tech/go-ethereum/log"
|
||||||
"github.com/scroll-tech/go-ethereum/params"
|
"github.com/scroll-tech/go-ethereum/params"
|
||||||
|
|
@ -88,17 +90,19 @@ type environment struct {
|
||||||
tcount int // tx count in cycle
|
tcount int // tx count in cycle
|
||||||
gasPool *core.GasPool // available gas used to pack transactions
|
gasPool *core.GasPool // available gas used to pack transactions
|
||||||
|
|
||||||
header *types.Header
|
header *types.Header
|
||||||
txs []*types.Transaction
|
txs []*types.Transaction
|
||||||
receipts []*types.Receipt
|
receipts []*types.Receipt
|
||||||
|
executionResults []*types.ExecutionResult
|
||||||
}
|
}
|
||||||
|
|
||||||
// task contains all information for consensus engine sealing and result submitting.
|
// task contains all information for consensus engine sealing and result submitting.
|
||||||
type task struct {
|
type task struct {
|
||||||
receipts []*types.Receipt
|
receipts []*types.Receipt
|
||||||
state *state.StateDB
|
executionResults []*types.ExecutionResult
|
||||||
block *types.Block
|
state *state.StateDB
|
||||||
createdAt time.Time
|
block *types.Block
|
||||||
|
createdAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -393,10 +397,10 @@ func (w *worker) newWorkLoop(recommit time.Duration) {
|
||||||
timestamp = time.Now().Unix()
|
timestamp = time.Now().Unix()
|
||||||
commit(false, commitInterruptNewHead)
|
commit(false, commitInterruptNewHead)
|
||||||
|
|
||||||
/*case head := <-w.chainHeadCh:
|
case head := <-w.chainHeadCh:
|
||||||
clearPending(head.Block.NumberU64())
|
clearPending(head.Block.NumberU64())
|
||||||
timestamp = time.Now().Unix()
|
timestamp = time.Now().Unix()
|
||||||
commit(false, commitInterruptNewHead)*/
|
// commit(false, commitInterruptNewHead)
|
||||||
|
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
// If mining is running resubmit a new work cycle periodically to pull in
|
// If mining is running resubmit a new work cycle periodically to pull in
|
||||||
|
|
@ -462,6 +466,7 @@ func (w *worker) mainLoop() {
|
||||||
select {
|
select {
|
||||||
case req := <-w.newWorkCh:
|
case req := <-w.newWorkCh:
|
||||||
w.commitNewWork(req.interrupt, req.noempty, req.timestamp)
|
w.commitNewWork(req.interrupt, req.noempty, req.timestamp)
|
||||||
|
// new block created.
|
||||||
|
|
||||||
case ev := <-w.chainSideCh:
|
case ev := <-w.chainSideCh:
|
||||||
// Short circuit for duplicate side blocks
|
// 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.
|
// Different block could share same sealhash, deep copy here to prevent write-write conflict.
|
||||||
var (
|
var (
|
||||||
receipts = make([]*types.Receipt, len(task.receipts))
|
receipts = make([]*types.Receipt, len(task.receipts))
|
||||||
logs []*types.Log
|
evmTraces = make([]*types.ExecutionResult, len(task.executionResults))
|
||||||
|
logs []*types.Log
|
||||||
)
|
)
|
||||||
for i, taskReceipt := range task.receipts {
|
for i, taskReceipt := range task.receipts {
|
||||||
receipt := new(types.Receipt)
|
receipt := new(types.Receipt)
|
||||||
receipts[i] = receipt
|
receipts[i] = receipt
|
||||||
*receipt = *taskReceipt
|
*receipt = *taskReceipt
|
||||||
|
|
||||||
|
evmTrace := new(types.ExecutionResult)
|
||||||
|
evmTraces[i] = evmTrace
|
||||||
|
*evmTrace = *task.executionResults[i]
|
||||||
|
|
||||||
// add block location fields
|
// add block location fields
|
||||||
receipt.BlockHash = hash
|
receipt.BlockHash = hash
|
||||||
receipt.BlockNumber = block.Number()
|
receipt.BlockNumber = block.Number()
|
||||||
|
|
@ -657,7 +667,7 @@ func (w *worker) resultLoop() {
|
||||||
logs = append(logs, receipt.Logs...)
|
logs = append(logs, receipt.Logs...)
|
||||||
}
|
}
|
||||||
// Commit block and state to database.
|
// 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 {
|
if err != nil {
|
||||||
log.Error("Failed writing block to chain", "err", err)
|
log.Error("Failed writing block to chain", "err", err)
|
||||||
continue
|
continue
|
||||||
|
|
@ -771,6 +781,10 @@ func (w *worker) updateSnapshot() {
|
||||||
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
|
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
|
||||||
snap := w.current.state.Snapshot()
|
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())
|
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 {
|
if err != nil {
|
||||||
w.current.state.RevertToSnapshot(snap)
|
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.txs = append(w.current.txs, tx)
|
||||||
w.current.receipts = append(w.current.receipts, receipt)
|
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
|
return receipt.Logs, nil
|
||||||
}
|
}
|
||||||
|
|
@ -1042,7 +1062,7 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
|
||||||
interval()
|
interval()
|
||||||
}
|
}
|
||||||
select {
|
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)
|
w.unconfirmed.Shift(block.NumberU64() - 1)
|
||||||
log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
|
log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
|
||||||
"uncles", len(uncles), "txs", w.current.tcount,
|
"uncles", len(uncles), "txs", w.current.tcount,
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,9 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine
|
||||||
}
|
}
|
||||||
genesis := gspec.MustCommit(db)
|
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)
|
txpool := core.NewTxPool(testTxPoolConfig, chainConfig, chain)
|
||||||
|
|
||||||
// Generate a small n-block chain and an uncle block for it
|
// 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.
|
// This test chain imports the mined blocks.
|
||||||
db2 := rawdb.NewMemoryDatabase()
|
db2 := rawdb.NewMemoryDatabase()
|
||||||
b.genesis.MustCommit(db2)
|
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()
|
defer chain.Stop()
|
||||||
|
|
||||||
// Ignore empty commit here for less noise.
|
// Ignore empty commit here for less noise.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue