feat: add more data into the execution trace (#20)

This commit is contained in:
maskpp 2022-02-21 10:15:57 +08:00 committed by GitHub
parent 8b16b4cefa
commit 69c291cf7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 457 additions and 580 deletions

View file

@ -30,6 +30,7 @@ import (
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/common/mclock" "github.com/scroll-tech/go-ethereum/common/mclock"
"github.com/scroll-tech/go-ethereum/common/prque" "github.com/scroll-tech/go-ethereum/common/prque"
"github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus"
@ -84,13 +85,14 @@ var (
) )
const ( const (
bodyCacheLimit = 256 bodyCacheLimit = 256
blockCacheLimit = 256 blockCacheLimit = 256
receiptsCacheLimit = 32 receiptsCacheLimit = 32
txLookupCacheLimit = 1024 txLookupCacheLimit = 1024
maxFutureBlocks = 256 maxFutureBlocks = 256
maxTimeFutureBlocks = 30 maxTimeFutureBlocks = 30
TriesInMemory = 128 TriesInMemory = 128
blockResultCacheLimit = 128
// BlockChainVersion ensures that an incompatible database forces a resync from scratch. // BlockChainVersion ensures that an incompatible database forces a resync from scratch.
// //
@ -191,13 +193,14 @@ type BlockChain struct {
currentBlock atomic.Value // Current head of the block chain currentBlock atomic.Value // Current head of the block chain
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!) currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)
stateCache state.Database // State database to reuse between imports (contains state cache) stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies bodyCache *lru.Cache // Cache for the most recent block bodies
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
receiptsCache *lru.Cache // Cache for the most recent receipts per block receiptsCache *lru.Cache // Cache for the most recent receipts per block
blockCache *lru.Cache // Cache for the most recent entire blocks blockCache *lru.Cache // Cache for the most recent entire blocks
txLookupCache *lru.Cache // Cache for the most recent transaction lookup data. txLookupCache *lru.Cache // Cache for the most recent transaction lookup data.
futureBlocks *lru.Cache // future blocks are blocks added for later processing futureBlocks *lru.Cache // future blocks are blocks added for later processing
blockResultCache *lru.Cache // Cache for the most recent block results.
wg sync.WaitGroup // wg sync.WaitGroup //
quit chan struct{} // shutdown signal, closed in Stop. quit chan struct{} // shutdown signal, closed in Stop.
@ -226,6 +229,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
blockCache, _ := lru.New(blockCacheLimit) blockCache, _ := lru.New(blockCacheLimit)
txLookupCache, _ := lru.New(txLookupCacheLimit) txLookupCache, _ := lru.New(txLookupCacheLimit)
futureBlocks, _ := lru.New(maxFutureBlocks) futureBlocks, _ := lru.New(maxFutureBlocks)
blockResultCache, _ := lru.New(blockResultCacheLimit)
bc := &BlockChain{ bc := &BlockChain{
chainConfig: chainConfig, chainConfig: chainConfig,
@ -237,17 +241,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
Journal: cacheConfig.TrieCleanJournal, Journal: cacheConfig.TrieCleanJournal,
Preimages: cacheConfig.Preimages, Preimages: cacheConfig.Preimages,
}), }),
quit: make(chan struct{}), quit: make(chan struct{}),
chainmu: syncx.NewClosableMutex(), chainmu: syncx.NewClosableMutex(),
shouldPreserve: shouldPreserve, shouldPreserve: shouldPreserve,
bodyCache: bodyCache, bodyCache: bodyCache,
bodyRLPCache: bodyRLPCache, bodyRLPCache: bodyRLPCache,
receiptsCache: receiptsCache, receiptsCache: receiptsCache,
blockCache: blockCache, blockCache: blockCache,
txLookupCache: txLookupCache, txLookupCache: txLookupCache,
futureBlocks: futureBlocks, futureBlocks: futureBlocks,
engine: engine, blockResultCache: blockResultCache,
vmConfig: vmConfig, engine: engine,
vmConfig: vmConfig,
} }
bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.validator = NewBlockValidator(chainConfig, bc, engine)
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
@ -1215,7 +1220,6 @@ 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,6 +1318,12 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
} }
bc.futureBlocks.Remove(block.Hash()) bc.futureBlocks.Remove(block.Hash())
// Fill blockResult content
if blockResult != nil {
bc.writeBlockResult(state, block, blockResult)
bc.blockResultCache.Add(block.Hash(), blockResult)
}
if status == CanonStatTy { if status == CanonStatTy {
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs, BlockResult: blockResult}) bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs, BlockResult: blockResult})
if len(logs) > 0 { if len(logs) > 0 {
@ -1333,6 +1343,35 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
return status, nil return status, nil
} }
// Fill blockResult content
func (bc *BlockChain) writeBlockResult(state *state.StateDB, block *types.Block, blockResult *types.BlockResult) {
blockResult.BlockTrace = types.NewTraceBlock(bc.chainConfig, block)
for i, tx := range block.Transactions() {
evmTrace := blockResult.ExecutionResults[i]
// Get the sender's address.
from, _ := types.Sender(types.MakeSigner(bc.chainConfig, block.Number()), tx)
// Get account's proof.
proof, err := state.GetProof(from)
if err != nil {
log.Error("Failed to get proof", "blockNumber", block.NumberU64(), "address", from.String(), "err", err)
} else {
evmTrace.Proof = make([]string, len(proof))
for i := range proof {
evmTrace.Proof[i] = hexutil.Encode(proof[i])
}
}
// Contract is called
if len(tx.Data()) != 0 && tx.To() != nil {
evmTrace.ByteCode = hexutil.Encode(state.GetCode(*tx.To()))
// Get tx.to address's code hash.
codeHash := state.GetCodeHash(*tx.To())
evmTrace.CodeHash = &codeHash
} else if tx.To() == nil { // Contract is created.
evmTrace.ByteCode = hexutil.Encode(tx.Data())
}
}
}
// addFutureBlock checks if the block is within the max allowed window to get // addFutureBlock checks if the block is within the max allowed window to get
// accepted for future processing, and returns an error if the block is too far // accepted for future processing, and returns an error if the block is too far
// ahead and was not added. // ahead and was not added.

View file

@ -158,6 +158,13 @@ func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {
return bc.GetBlock(hash, *number) return bc.GetBlock(hash, *number)
} }
func (bc *BlockChain) GetBlockResultByHash(blockHash common.Hash) *types.BlockResult {
if blockResult, ok := bc.blockResultCache.Get(blockHash); ok {
return blockResult.(*types.BlockResult)
}
return nil
}
// GetBlockByNumber retrieves a block from the database by number, caching it // GetBlockByNumber retrieves a block from the database by number, caching it
// (associated with its hash) if found. // (associated with its hash) if found.
func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block { func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {

View file

@ -68,6 +68,7 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
func NewEVMTxContext(msg Message) vm.TxContext { func NewEVMTxContext(msg Message) vm.TxContext {
return vm.TxContext{ return vm.TxContext{
Origin: msg.From(), Origin: msg.From(),
To: msg.To(),
GasPrice: new(big.Int).Set(msg.GasPrice()), GasPrice: new(big.Int).Set(msg.GasPrice()),
} }
} }

View file

@ -1,39 +0,0 @@
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)
}
}

View file

@ -1,355 +0,0 @@
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,7 +89,6 @@ 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
@ -231,8 +230,3 @@ 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()...)
}

View file

@ -1,165 +1,80 @@
package types package types
import ( import (
"io" "github.com/scroll-tech/go-ethereum/common"
"sort"
"strings"
"github.com/scroll-tech/go-ethereum/rlp"
) )
// BlockResult contains block execution traces and results required for rollers. // BlockResult contains block execution traces and results required for rollers.
type BlockResult struct { type BlockResult struct {
BlockTrace *BlockTrace `json:"blockTrace"`
ExecutionResults []*ExecutionResult `json:"executionResults"` 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 // ExecutionResult groups all structured logs emitted by the EVM
// while replaying a transaction in debug mode as well as transaction // while replaying a transaction in debug mode as well as transaction
// execution status, the amount of gas used and the return value // execution status, the amount of gas used and the return value
type ExecutionResult struct { type ExecutionResult struct {
Gas uint64 `json:"gas"` Gas uint64 `json:"gas"`
Failed bool `json:"failed"` Failed bool `json:"failed"`
ReturnValue string `json:"returnValue,omitempty"` ReturnValue string `json:"returnValue,omitempty"`
StructLogs []StructLogRes `json:"structLogs"` // It's exist only when tx is a contract call.
} CodeHash *common.Hash `json:"codeHash,omitempty"`
// If it is a contract call, the contract code is returned.
type rlpExecutionResult struct { ByteCode string `json:"byteCode,omitempty"`
Gas uint64 // The account's proof.
Failed bool Proof []string `json:"proof,omitempty"`
ReturnValue string StructLogs []StructLogRes `json:"structLogs"`
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 // StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode // transaction in debug mode
type StructLogRes struct { type StructLogRes struct {
Pc uint64 `json:"pc"` Pc uint64 `json:"pc"`
Op string `json:"op"` Op string `json:"op"`
Gas uint64 `json:"gas"` Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"` GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"` Depth int `json:"depth"`
Error string `json:"error,omitempty"` Error string `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"` Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"` Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"` Storage *map[string]string `json:"storage,omitempty"`
ExtraData *ExtraData `json:"extraData,omitempty"`
} }
type rlpStructLogRes struct { type ExtraData struct {
Pc uint64 // CREATE | CREATE2: sender address
Op string From *common.Address `json:"from,omitempty"`
Gas uint64 // CREATE: sender nonce
GasCost uint64 Nonce *uint64 `json:"nonce,omitempty"`
Depth uint // CALL | CALLCODE | DELEGATECALL | STATICCALL: [tx.to addresss code_hash, stack.nth_last(1) addresss code_hash]
Error string CodeHashList []common.Hash `json:"codeHashList,omitempty"`
Stack []string // SSTORE | SLOAD: [storageProof]
Memory []string // SELFDESTRUCT: [contract addresss accountProof, stack.nth_last(0) addresss accountProof]
Storage []string // SELFBALANCE: [contract addresss accountProof]
// BALANCE | EXTCODEHASH: [stack.nth_last(0) addresss accountProof]
// CREATE | CREATE2: [created contract addresss accountProof]
// CALL | CALLCODE: [caller contract addresss accountProof, stack.nth_last(1) addresss accountProof]
ProofList [][]string `json:"proofList,omitempty"`
} }
// EncodeRLP implements rlp.Encoder. // NewExtraData create, init and return ExtraData
func (r *StructLogRes) EncodeRLP(w io.Writer) error { func NewExtraData() *ExtraData {
data := rlpStructLogRes{ return &ExtraData{
Pc: r.Pc, CodeHashList: make([]common.Hash, 0),
Op: r.Op, ProofList: make([][]string, 0),
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. // SealExtraData doesn't show empty fields.
func (r *StructLogRes) DecodeRLP(s *rlp.Stream) error { func (e *ExtraData) SealExtraData() *ExtraData {
var dec rlpStructLogRes if len(e.CodeHashList) == 0 {
err := s.Decode(&dec) e.CodeHashList = nil
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(e.ProofList) == 0 {
if len(dec.Stack) != 0 { e.ProofList = nil
stack := make([]string, len(dec.Stack))
for i, val := range dec.Stack {
stack[i] = val
}
r.Stack = &stack
} }
if len(dec.Memory) != 0 { if e.From == nil && e.Nonce == nil && e.CodeHashList == nil && e.ProofList == nil {
memory := make([]string, len(dec.Memory)) return nil
for i, val := range dec.Memory {
memory[i] = val
}
r.Memory = &memory
} }
if len(dec.Storage) != 0 { return e
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

@ -0,0 +1,78 @@
package types
import (
"math/big"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/params"
)
type BlockTrace struct {
Number *big.Int `json:"number"`
Hash common.Hash `json:"hash"`
GasLimit uint64 `json:"gasLimit"`
Difficulty *big.Int `json:"difficulty"`
BaseFee *big.Int `json:"baseFee"`
Coinbase common.Address `json:"coinbase"`
Time uint64 `json:"time"`
Transaction []*TransactionTrace `json:"transaction"`
}
type TransactionTrace struct {
Type uint8 `json:"type"`
Nonce uint64 `json:"nonce"`
Gas uint64 `json:"gas"`
GasPrice *big.Int `json:"gasPrice"`
From common.Address `json:"from"`
To *common.Address `json:"to"`
ChainId *big.Int `json:"chainId"`
Value *big.Int `json:"value"`
Data string `json:"data"`
IsCreate bool `json:"isCreate"`
V *big.Int `json:"v"`
R *big.Int `json:"r"`
S *big.Int `json:"s"`
}
// NewTraceBlock supports necessary fields for roller.
func NewTraceBlock(config *params.ChainConfig, block *Block) *BlockTrace {
txs := make([]*TransactionTrace, block.Transactions().Len())
for i, tx := range block.Transactions() {
txs[i] = newTraceTransaction(tx, block.NumberU64(), config)
}
return &BlockTrace{
Number: block.Number(),
Hash: block.Hash(),
GasLimit: block.GasLimit(),
Difficulty: block.Difficulty(),
BaseFee: block.BaseFee(),
Coinbase: block.Coinbase(),
Time: block.Time(),
Transaction: txs,
}
}
// newTraceTransaction returns a transaction that will serialize to the trace
// representation, with the given location metadata set (if available).
func newTraceTransaction(tx *Transaction, blockNumber uint64, config *params.ChainConfig) *TransactionTrace {
signer := MakeSigner(config, big.NewInt(0).SetUint64(blockNumber))
from, _ := Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
result := &TransactionTrace{
Type: tx.Type(),
Nonce: tx.Nonce(),
ChainId: tx.ChainId(),
From: from,
Gas: tx.Gas(),
GasPrice: tx.GasPrice(),
To: tx.To(),
Value: tx.Value(),
Data: hexutil.Encode(tx.Data()),
IsCreate: tx.To() == nil,
V: v,
R: r,
S: s,
}
return result
}

View file

@ -161,6 +161,10 @@ func (a *AccessListTracer) CaptureState(pc uint64, op OpCode, gas, cost uint64,
} }
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (*AccessListTracer) CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
}
func (*AccessListTracer) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { func (*AccessListTracer) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
} }

View file

@ -81,8 +81,9 @@ type BlockContext struct {
// All fields can change between transactions. // All fields can change between transactions.
type TxContext struct { type TxContext struct {
// Message information // Message information
Origin common.Address // Provides information for ORIGIN Origin common.Address // Provides information for ORIGIN
GasPrice *big.Int // Provides information for GASPRICE To *common.Address // Provides information for trace
GasPrice *big.Int // Provides information for GASPRICE
} }
// EVM is the Ethereum Virtual Machine base object and provides // EVM is the Ethereum Virtual Machine base object and provides

View file

@ -47,6 +47,10 @@ type StateDB interface {
GetState(common.Address, common.Hash) common.Hash GetState(common.Address, common.Hash) common.Hash
SetState(common.Address, common.Hash, common.Hash) SetState(common.Address, common.Hash, common.Hash)
GetProof(addr common.Address) ([][]byte, error)
GetProofByHash(addrHash common.Hash) ([][]byte, error)
GetStorageProof(a common.Address, key common.Hash) ([][]byte, error)
Suicide(common.Address) bool Suicide(common.Address) bool
HasSuicided(common.Address) bool HasSuicided(common.Address) bool

View file

@ -265,6 +265,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
in.returnData = res in.returnData = res
} }
if in.cfg.Debug {
in.cfg.Tracer.CaptureStateAfter(pc, op, gasCopy, cost, callContext, in.returnData, in.evm.depth, err)
}
switch { switch {
case err != nil: case err != nil:
return nil, err return nil, err

View file

@ -29,6 +29,7 @@ import (
"github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/common/math" "github.com/scroll-tech/go-ethereum/common/math"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/params"
) )
@ -72,6 +73,7 @@ type StructLog struct {
Storage map[common.Hash]common.Hash `json:"-"` Storage map[common.Hash]common.Hash `json:"-"`
Depth int `json:"depth"` Depth int `json:"depth"`
RefundCounter uint64 `json:"refund"` RefundCounter uint64 `json:"refund"`
ExtraData *types.ExtraData `json:"extraData"`
Err error `json:"-"` Err error `json:"-"`
} }
@ -106,6 +108,7 @@ func (s *StructLog) ErrorString() string {
type EVMLogger interface { type EVMLogger interface {
CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int)
CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error)
CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int)
CaptureExit(output []byte, gasUsed uint64, err error) CaptureExit(output []byte, gasUsed uint64, err error)
CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error)
@ -177,7 +180,10 @@ func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scop
} }
} }
// Copy a snapshot of the current storage to a new container // Copy a snapshot of the current storage to a new container
var storage Storage var (
storage Storage
extraData *types.ExtraData
)
if !l.cfg.DisableStorage && (op == SLOAD || op == SSTORE) { if !l.cfg.DisableStorage && (op == SLOAD || op == SSTORE) {
// initialise new changed values storage container for this contract // initialise new changed values storage container for this contract
// if not present. // if not present.
@ -200,6 +206,11 @@ func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scop
) )
l.storage[contract.Address()][address] = value l.storage[contract.Address()][address] = value
storage = l.storage[contract.Address()].Copy() storage = l.storage[contract.Address()].Copy()
extraData = types.NewExtraData()
if err := traceStorageProof(l, scope, extraData); err != nil {
log.Warn("Failed to get proof", "contract address", contract.Address().String(), "key", address.String(), "err", err)
}
} }
} }
var rdata []byte var rdata []byte
@ -207,8 +218,32 @@ func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scop
rdata = make([]byte, len(rData)) rdata = make([]byte, len(rData))
copy(rdata, rData) copy(rdata, rData)
} }
// create a new snapshot of the EVM. // create a new snapshot of the EVM.
log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err} log := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), extraData, err}
l.logs = append(l.logs, log)
}
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (l *StructLogger) CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
// check if already accumulated the specified number of logs
if l.cfg.Limit != 0 && l.cfg.Limit <= len(l.logs) {
return
}
execFuncList, ok := OpcodeExecs[op]
if !ok {
return
}
extraData := types.NewExtraData()
// execute trace func list.
for _, exec := range execFuncList {
if err = exec(l, scope, extraData); err != nil {
log.Error("Failed to trace data", "opcode", op.String(), "err", err)
}
}
log := StructLog{pc, op, gas, cost, nil, scope.Memory.Len(), nil, nil, nil, depth, l.env.StateDB.GetRefund(), extraData, err}
l.logs = append(l.logs, log) l.logs = append(l.logs, log)
} }
@ -345,6 +380,10 @@ func (t *mdLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope *S
} }
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *mdLogger) CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
}
func (t *mdLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) { func (t *mdLogger) CaptureFault(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, depth int, err error) {
fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err) fmt.Fprintf(t.out, "\nError: at pc=%d, op=%v: %v\n", pc, op, err)
} }
@ -371,27 +410,31 @@ func FormatLogs(logs []StructLog) []types.StructLogRes {
Depth: trace.Depth, Depth: trace.Depth,
Error: trace.ErrorString(), Error: trace.ErrorString(),
} }
if trace.Stack != nil { if len(trace.Stack) != 0 {
stack := make([]string, len(trace.Stack)) stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack { for i, stackValue := range trace.Stack {
stack[i] = stackValue.Hex() stack[i] = stackValue.Hex()
} }
formatted[index].Stack = &stack formatted[index].Stack = &stack
} }
if trace.Memory != nil { if len(trace.Memory) != 0 {
memory := make([]string, 0, (len(trace.Memory)+31)/32) memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 { for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32])) bytes := new(big.Int).SetBytes(trace.Memory[i : i+32]).Bytes()
memory = append(memory, hexutil.Encode(bytes))
} }
formatted[index].Memory = &memory formatted[index].Memory = &memory
} }
if trace.Storage != nil { if len(trace.Storage) != 0 {
storage := make(map[string]string) storage := make(map[string]string)
for i, storageValue := range trace.Storage { for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue) storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
} }
formatted[index].Storage = &storage formatted[index].Storage = &storage
} }
if trace.ExtraData != nil {
formatted[index].ExtraData = trace.ExtraData.SealExtraData()
}
} }
return formatted return formatted
} }

View file

@ -75,6 +75,10 @@ func (l *JSONLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scope
l.encoder.Encode(log) l.encoder.Encode(log)
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (l *JSONLogger) CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) {
}
// CaptureEnd is triggered at end of execution. // CaptureEnd is triggered at end of execution.
func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) { func (l *JSONLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration, err error) {
type endLog struct { type endLog struct {

View file

@ -21,7 +21,9 @@ import (
"testing" "testing"
"github.com/holiman/uint256" "github.com/holiman/uint256"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/params"
) )
@ -42,6 +44,14 @@ func (d *dummyContractRef) SetBalance(*big.Int) {}
func (d *dummyContractRef) SetNonce(uint64) {} func (d *dummyContractRef) SetNonce(uint64) {}
func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) } func (d *dummyContractRef) Balance() *big.Int { return new(big.Int) }
// makeTestState create a sample test state to test node-wise reconstruction.
func makeTestState() *state.StateDB {
// Create an empty state
db := state.NewDatabase(rawdb.NewMemoryDatabase())
stateDb, _ := state.New(common.Hash{}, db, nil)
return stateDb
}
type dummyStatedb struct { type dummyStatedb struct {
state.StateDB state.StateDB
} }
@ -50,7 +60,7 @@ func (*dummyStatedb) GetRefund() uint64 { return 1337 }
func TestStoreCapture(t *testing.T) { func TestStoreCapture(t *testing.T) {
var ( var (
env = NewEVM(BlockContext{}, TxContext{}, &dummyStatedb{}, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, makeTestState(), params.TestChainConfig, Config{})
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0) contract = NewContract(&dummyContractRef{}, &dummyContractRef{}, new(big.Int), 0)
scope = &ScopeContext{ scope = &ScopeContext{

146
core/vm/logger_trace.go Normal file
View file

@ -0,0 +1,146 @@
package vm
import (
"errors"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/core/types"
)
type traceFunc func(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error
var (
// OpcodeExecs the map to load opcodes' trace funcs.
OpcodeExecs = map[OpCode][]traceFunc{
CALL: {traceToAddressCodeHash, traceLastNAddressCodeHash(1), traceCallerProof, traceLastNAddressProof(1)},
CALLCODE: {traceToAddressCodeHash, traceLastNAddressCodeHash(1), traceCallerProof, traceLastNAddressProof(1)},
DELEGATECALL: {traceToAddressCodeHash, traceLastNAddressCodeHash(1)},
STATICCALL: {traceToAddressCodeHash, traceLastNAddressCodeHash(1)},
CREATE: {traceSenderAddress, traceCreatedContractProof, traceNonce},
CREATE2: {traceSenderAddress, traceCreatedContractProof},
SSTORE: {traceStorageProof},
SLOAD: {traceStorageProof},
SELFDESTRUCT: {traceContractProof, traceLastNAddressProof(0)},
SELFBALANCE: {traceContractProof},
BALANCE: {traceLastNAddressProof(0)},
EXTCODEHASH: {traceLastNAddressProof(0)},
}
)
// traceToAddressCodeHash gets tx.to addresss code_hash
func traceToAddressCodeHash(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
if l.env.To == nil {
return nil
}
codeHash := l.env.StateDB.GetCodeHash(*l.env.To)
extraData.CodeHashList = append(extraData.CodeHashList, codeHash)
return nil
}
// traceLastNAddressCodeHash
func traceLastNAddressCodeHash(n int) traceFunc {
return func(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
stack := scope.Stack
if stack.len() <= n {
return nil
}
address := common.Address(stack.data[stack.len()-1-n].Bytes20())
codeHash := l.env.StateDB.GetCodeHash(address)
extraData.CodeHashList = append(extraData.CodeHashList, codeHash)
return nil
}
}
// traceSenderAddress gets sender address
func traceSenderAddress(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
extraData.From = &l.env.Origin
return nil
}
// traceNonce gets sender nonce
func traceNonce(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
nonce := l.env.StateDB.GetNonce(l.env.Origin)
extraData.Nonce = &nonce
return nil
}
// traceStorageProof get contract's storage proof at storage_address
func traceStorageProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
if scope.Stack.len() == 0 {
return nil
}
address := common.Hash(scope.Stack.peek().Bytes32())
contract := scope.Contract
// Get storage proof.
storageProof, err := l.env.StateDB.GetStorageProof(contract.Address(), address)
if err == nil {
extraData.ProofList = append(extraData.ProofList, encodeProof(storageProof))
}
return err
}
// traceContractProof gets the contract's account proof
func traceContractProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
// Get account proof.
proof, err := l.env.StateDB.GetProof(scope.Contract.Address())
if err == nil {
extraData.ProofList = append(extraData.ProofList, encodeProof(proof))
}
return err
}
/// traceCreatedContractProof get created contract addresss accountProof
func traceCreatedContractProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
stack := scope.Stack
if stack.len() < 1 {
return nil
}
stackvalue := stack.peek()
if stackvalue.IsZero() {
return errors.New("can't get created contract address from stack")
}
address := common.BytesToAddress(stackvalue.Bytes())
proof, err := l.env.StateDB.GetProof(address)
if err == nil {
extraData.ProofList = append(extraData.ProofList, encodeProof(proof))
}
return err
}
// traceLastNAddressProof returns func about the last N's address proof.
func traceLastNAddressProof(n int) traceFunc {
return func(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
stack := scope.Stack
if stack.len() <= n {
return nil
}
address := common.Address(stack.data[stack.len()-1-n].Bytes20())
proof, err := l.env.StateDB.GetProof(address)
if err == nil {
extraData.ProofList = append(extraData.ProofList, encodeProof(proof))
}
return err
}
}
// traceCallerProof gets caller address's proof.
func traceCallerProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
address := scope.Contract.CallerAddress
proof, err := l.env.StateDB.GetProof(address)
if err == nil {
extraData.ProofList = append(extraData.ProofList, encodeProof(proof))
}
return err
}
func encodeProof(proof [][]byte) (res []string) {
if len(proof) == 0 {
return nil
}
for _, node := range proof {
res = append(res, hexutil.Encode(node))
}
return
}

View file

@ -618,7 +618,10 @@ func NewPublicTraceAPI(eth *Ethereum) *PublicTraceAPI {
return &PublicTraceAPI{eth} return &PublicTraceAPI{eth}
} }
// BlockResultByHash returns the blockResult by blockHash. // GetBlockResultByHash returns the blockResult by blockHash.
func (api *PublicTraceAPI) BlockResultByHash(blockHash common.Hash) (*types.BlockResult, error) { func (api *PublicTraceAPI) GetBlockResultByHash(blockHash common.Hash) (*types.BlockResult, error) {
return rawdb.ReadBlockResult(api.e.chainDb, blockHash), nil if blockResult := api.e.blockchain.GetBlockResultByHash(blockHash); blockResult != nil {
return blockResult, nil
}
return nil, fmt.Errorf("No block result found")
} }

View file

@ -371,8 +371,10 @@ 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] { if ev.BlockResult != nil {
f.blockResults <- ev.BlockResult 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) {

View file

@ -746,6 +746,10 @@ func (jst *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco
} }
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (jst *jsTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements the Tracer interface to trace an execution fault // CaptureFault implements the Tracer interface to trace an execution fault
func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { func (jst *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
if jst.err != nil { if jst.err != nil {

View file

@ -96,6 +96,10 @@ func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to commo
func (t *fourByteTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *fourByteTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *fourByteTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct). // CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
// Skip if tracing was interrupted // Skip if tracing was interrupted

View file

@ -96,6 +96,10 @@ func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration,
func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *callTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements the EVMLogger interface to trace an execution fault. // CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
} }

View file

@ -51,6 +51,10 @@ func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration,
func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
} }
// CaptureStateAfter for special needs, tracks SSTORE ops and records the storage change.
func (t *noopTracer) CaptureStateAfter(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
}
// CaptureFault implements the EVMLogger interface to trace an execution fault. // CaptureFault implements the EVMLogger interface to trace an execution fault.
func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) { func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
} }

View file

@ -325,10 +325,10 @@ 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. // GetBlockResultByHash returns the blockResult.
func (ec *Client) BlockResultByHash(ctx context.Context, blockHash common.Hash) (*types.BlockResult, error) { func (ec *Client) GetBlockResultByHash(ctx context.Context, blockHash common.Hash) (*types.BlockResult, error) {
var blockResult types.BlockResult var blockResult types.BlockResult
if err := ec.c.CallContext(ctx, &blockResult, "eth_blockResultByHash", blockHash); err != nil { if err := ec.c.CallContext(ctx, &blockResult, "eth_getBlockResultByHash", blockHash); err != nil {
return nil, err return nil, err
} }
return &blockResult, nil return &blockResult, nil

View file

@ -794,7 +794,7 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres
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{ w.current.executionResults = append(w.current.executionResults, &types.ExecutionResult{
Gas: receipt.GasUsed, Gas: receipt.GasUsed,
Failed: receipt.Status == types.ReceiptStatusSuccessful, Failed: receipt.Status != types.ReceiptStatusSuccessful,
ReturnValue: fmt.Sprintf("%x", receipt.ReturnValue), ReturnValue: fmt.Sprintf("%x", receipt.ReturnValue),
StructLogs: vm.FormatLogs(tracer.StructLogs()), StructLogs: vm.FormatLogs(tracer.StructLogs()),
}) })

View file

@ -236,7 +236,7 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) {
b.genesis.MustCommit(db2) b.genesis.MustCommit(db2)
chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{ chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{
Debug: true, Debug: true,
Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true})}, nil, nil) Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil)
defer chain.Stop() defer chain.Stop()
// Ignore empty commit here for less noise. // Ignore empty commit here for less noise.