From d3bc8322dc503fa1b927a60b518f0b195641ffdf Mon Sep 17 00:00:00 2001 From: Zhang Zhuo Date: Wed, 8 Jun 2022 12:26:50 +0800 Subject: [PATCH] zktrie part1: change storage proof from per step to per block (#102) * change storage proof from step-wise to block-wise. Step-wise proof will be reconstructed in prover side * minor * minor * Update worker.go * purge hexInt * Update l2trace.go * Refactor l2witness/opt-storage-proof (#112) * rename GetStateData to GetLiveStateObject * revert EvmTxTraces type * rename GetLiveStateObject to GetLiveStateAccount * fix typo * some renamings * format codes * fix typo * fix typos * format codes some reverts some renamings some renamings format codes * update comments update comments * update comments update comments update comments * update comments update comments update comments * rename * rename * update * update comments Co-authored-by: maskpp Co-authored-by: Ho Vei Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- .gitattributes | 1 + core/blockchain.go | 55 +++------------- core/state/statedb.go | 35 ++++++++++ core/types/l2trace.go | 72 ++++++++++++++------- core/types/l2trace_block.go | 18 +++--- core/vm/interface.go | 2 + core/vm/logger.go | 126 +++++++++++++++++++++++++----------- core/vm/logger_trace.go | 120 +++++++++++----------------------- miner/worker.go | 122 ++++++++++++++++++++++++++++------ 9 files changed, 334 insertions(+), 217 deletions(-) diff --git a/.gitattributes b/.gitattributes index 0269fab9cba2..c90b62c99e1c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ # Auto detect text files and perform LF normalization * text=auto *.sol linguist-language=Solidity +*.go text eol=lf diff --git a/core/blockchain.go b/core/blockchain.go index 4dccd5d5c03a..39d5fcc8018e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1193,17 +1193,17 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error { } // WriteBlockWithState writes the block and all associated state to the database. -func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, evmTraces []*types.ExecutionResult, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { +func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, evmTraces []*types.ExecutionResult, storageTrace *types.StorageTrace, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { if !bc.chainmu.TryLock() { return NonStatTy, errInsertionInterrupted } defer bc.chainmu.Unlock() - return bc.writeBlockWithState(block, receipts, logs, evmTraces, state, emitHeadEvent) + return bc.writeBlockWithState(block, receipts, logs, evmTraces, storageTrace, state, emitHeadEvent) } // writeBlockWithState writes the block and all associated state to the database, // but is expects the chain mutex to be held. -func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, evmTraces []*types.ExecutionResult, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { +func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, logs []*types.Log, evmTraces []*types.ExecutionResult, storageTrace *types.StorageTrace, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { if bc.insertStopped() { return NonStatTy, errInsertionInterrupted } @@ -1327,7 +1327,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // Fill blockResult content var blockResult *types.BlockResult if evmTraces != nil { - blockResult = bc.writeBlockResult(state, block, evmTraces) + blockResult = bc.writeBlockResult(state, block, evmTraces, storageTrace) bc.blockResultCache.Add(block.Hash(), blockResult) } @@ -1351,57 +1351,22 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } // Fill blockResult content -func (bc *BlockChain) writeBlockResult(state *state.StateDB, block *types.Block, evmTraces []*types.ExecutionResult) *types.BlockResult { +func (bc *BlockChain) writeBlockResult(state *state.StateDB, block *types.Block, evmTraces []*types.ExecutionResult, storageTrace *types.StorageTrace) *types.BlockResult { blockResult := &types.BlockResult{ ExecutionResults: evmTraces, + StorageTrace: storageTrace, } - coinbase := types.AccountProofWrapper{ + coinbase := types.AccountWrapper{ Address: block.Coinbase(), Nonce: state.GetNonce(block.Coinbase()), Balance: (*hexutil.Big)(state.GetBalance(block.Coinbase())), CodeHash: state.GetCodeHash(block.Coinbase()), } - // Get coinbase address's account proof. - proof, err := state.GetProof(block.Coinbase()) - if err != nil { - log.Error("Failed to get proof", "blockNumber", block.NumberU64(), "address", block.Coinbase().String(), "err", err) - } else { - coinbase.Proof = make([]string, len(proof)) - for i := range proof { - coinbase.Proof[i] = hexutil.Encode(proof[i]) - } - } blockResult.BlockTrace = types.NewTraceBlock(bc.chainConfig, block, &coinbase) + blockResult.StorageTrace.RootAfter = state.GetRootHash() for i, tx := range block.Transactions() { evmTrace := blockResult.ExecutionResults[i] - - from := evmTrace.From.Address - // Get 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.From.Proof = make([]string, len(proof)) - for i := range proof { - evmTrace.From.Proof[i] = hexutil.Encode(proof[i]) - } - } - - if evmTrace.To != nil { - to := evmTrace.To.Address - // Get proof - proof, err = state.GetProof(to) - if err != nil { - log.Error("Failed to get proof", "blockNumber", block.NumberU64(), "address", to.String(), "err", err) - } else { - evmTrace.To.Proof = make([]string, len(proof)) - for i := range proof { - evmTrace.To.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())) @@ -1727,8 +1692,8 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er // Write the block to the chain and get the status. substart = time.Now() - // 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) + // EvmTraces & StorageTrace being 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, nil, statedb, false) atomic.StoreUint32(&followupInterrupt, 1) if err != nil { return it.index, err diff --git a/core/state/statedb.go b/core/state/statedb.go index ed0d5bff068c..e0d06c3cc560 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -325,6 +325,41 @@ func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) { return proof, err } +func (s *StateDB) GetLiveStateAccount(addr common.Address) *types.StateAccount { + obj, ok := s.stateObjects[addr] + if !ok { + return nil + } + return &obj.data +} + +func (s *StateDB) GetRootHash() common.Hash { + return s.trie.Hash() +} + +// StorageTrieProof is not in Db interface and used explictily for reading proof in storage trie (not the dirty value) +func (s *StateDB) GetStorageTrieProof(a common.Address, key common.Hash) ([][]byte, error) { + // try the trie in stateObject first, else we would create one + stateObject := s.getStateObject(a) + if stateObject == nil { + return nil, errors.New("storage trie for requested address does not exist") + } + + trie := stateObject.trie + var err error + if trie == nil { + // use a new, temporary trie + trie, err = s.db.OpenStorageTrie(stateObject.addrHash, stateObject.data.Root) + if err != nil { + return nil, fmt.Errorf("can't create storage trie on root %s: %v ", stateObject.data.Root, err) + } + } + + var proof proofList + err = trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) + return proof, err +} + // GetStorageProof returns the Merkle proof for given storage slot. func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) { var proof proofList diff --git a/core/types/l2trace.go b/core/types/l2trace.go index 9766af59a4c9..f883238b2e7a 100644 --- a/core/types/l2trace.go +++ b/core/types/l2trace.go @@ -23,9 +23,24 @@ var ( // BlockResult contains block execution traces and results required for rollers. type BlockResult struct { BlockTrace *BlockTrace `json:"blockTrace"` + StorageTrace *StorageTrace `json:"storageTrace"` ExecutionResults []*ExecutionResult `json:"executionResults"` } +// StorageTrace stores proofs of storage needed by storage circuit +type StorageTrace struct { + // Root hash before block execution: + RootBefore common.Hash `json:"rootBefore,omitempty"` + // Root hash after block execution, is nil if execution has failed + RootAfter common.Hash `json:"rootAfter,omitempty"` + + // All proofs BEFORE execution, for accounts which would be used in tracing + Proofs map[string][]hexutil.Bytes `json:"proofs"` + + // All storage proofs BEFORE execution + StorageProofs map[string]map[string][]hexutil.Bytes `json:"storageProofs,omitempty"` +} + // 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 @@ -33,11 +48,19 @@ type ExecutionResult struct { Gas uint64 `json:"gas"` Failed bool `json:"failed"` ReturnValue string `json:"returnValue,omitempty"` - // Sender's account proof. - From *AccountProofWrapper `json:"from,omitempty"` - // Receiver's account proof. - To *AccountProofWrapper `json:"to,omitempty"` - // It's exist only when tx is a contract call. + // Sender's account state (before Tx) + From *AccountWrapper `json:"from,omitempty"` + // Receiver's account state (before Tx) + To *AccountWrapper `json:"to,omitempty"` + // AccountCreated record the account if the tx is "create" + // (for creating inside a contract, we just handle CREATE op) + AccountCreated *AccountWrapper `json:"accountCreated,omitempty"` + + // Record all accounts' state which would be affected AFTER tx executed + // currently they are just `from` and `to` account + AccountsAfter []*AccountWrapper `json:"accountAfter"` + + // `CodeHash` only exists when tx is a contract call. CodeHash *common.Hash `json:"codeHash,omitempty"` // If it is a contract call, the contract code is returned. ByteCode string `json:"byteCode,omitempty"` @@ -77,30 +100,35 @@ func NewStructLogResBasic(pc uint64, op string, gas, gasCost uint64, depth int, } type ExtraData struct { + // Indicate the call succeeds or not for CALL/CREATE op + CallFailed bool `json:"callFailed,omitempty"` // CALL | CALLCODE | DELEGATECALL | STATICCALL: [tx.to address’s code, stack.nth_last(1) address’s code] CodeList [][]byte `json:"codeList,omitempty"` // SSTORE | SLOAD: [storageProof] - // SELFDESTRUCT: [contract address’s accountProof, stack.nth_last(0) address’s accountProof] - // SELFBALANCE: [contract address’s accountProof] - // BALANCE | EXTCODEHASH: [stack.nth_last(0) address’s accountProof] - // CREATE | CREATE2: [sender's accountProof, created contract address’s accountProof] - // CALL | CALLCODE: [caller contract address’s accountProof, stack.nth_last(1) address’s accountProof] - ProofList []*AccountProofWrapper `json:"proofList,omitempty"` + // SELFDESTRUCT: [contract address’s account, stack.nth_last(0) address’s account] + // SELFBALANCE: [contract address’s account] + // BALANCE | EXTCODEHASH: [stack.nth_last(0) address’s account] + // CREATE | CREATE2: [created contract address’s account (before constructed), + // created contract address's account (after constructed)] + // CALL | CALLCODE: [caller contract address’s account, + // stack.nth_last(1) (i.e. callee) address’s account, + // callee contract address's account (value updated, before called)] + // STATICCALL: [stack.nth_last(1) (i.e. callee) address’s account, + // callee contract address's account (before called)] + StateList []*AccountWrapper `json:"proofList,omitempty"` } -type AccountProofWrapper struct { - Address common.Address `json:"address"` - Nonce uint64 `json:"nonce"` - Balance *hexutil.Big `json:"balance"` - CodeHash common.Hash `json:"codeHash,omitempty"` - Proof []string `json:"proof,omitempty"` - Storage *StorageProofWrapper `json:"storage,omitempty"` // StorageProofWrapper can be empty if irrelated to storage operation +type AccountWrapper struct { + Address common.Address `json:"address"` + Nonce uint64 `json:"nonce"` + Balance *hexutil.Big `json:"balance"` + CodeHash common.Hash `json:"codeHash,omitempty"` + Storage *StorageWrapper `json:"storage,omitempty"` // StorageWrapper can be empty if irrelated to storage operation } // while key & value can also be retrieved from StructLogRes.Storage, // we still stored in here for roller's processing convenience. -type StorageProofWrapper struct { - Key string `json:"key,omitempty"` - Value string `json:"value,omitempty"` - Proof []string `json:"proof,omitempty"` +type StorageWrapper struct { + Key string `json:"key,omitempty"` + Value string `json:"value,omitempty"` } diff --git a/core/types/l2trace_block.go b/core/types/l2trace_block.go index 7d8347b94c6a..93b6f089aa17 100644 --- a/core/types/l2trace_block.go +++ b/core/types/l2trace_block.go @@ -9,14 +9,14 @@ import ( ) type BlockTrace struct { - Number *hexutil.Big `json:"number"` - Hash common.Hash `json:"hash"` - GasLimit uint64 `json:"gasLimit"` - Difficulty *hexutil.Big `json:"difficulty"` - BaseFee *hexutil.Big `json:"baseFee"` - Coinbase *AccountProofWrapper `json:"coinbase"` - Time uint64 `json:"time"` - Transactions []*TransactionTrace `json:"transactions"` + Number *hexutil.Big `json:"number"` + Hash common.Hash `json:"hash"` + GasLimit uint64 `json:"gasLimit"` + Difficulty *hexutil.Big `json:"difficulty"` + BaseFee *hexutil.Big `json:"baseFee"` + Coinbase *AccountWrapper `json:"coinbase"` + Time uint64 `json:"time"` + Transactions []*TransactionTrace `json:"transactions"` } type TransactionTrace struct { @@ -36,7 +36,7 @@ type TransactionTrace struct { } // NewTraceBlock supports necessary fields for roller. -func NewTraceBlock(config *params.ChainConfig, block *Block, coinbase *AccountProofWrapper) *BlockTrace { +func NewTraceBlock(config *params.ChainConfig, block *Block, coinbase *AccountWrapper) *BlockTrace { txs := make([]*TransactionTrace, block.Transactions().Len()) for i, tx := range block.Transactions() { txs[i] = newTraceTransaction(tx, block.NumberU64(), config) diff --git a/core/vm/interface.go b/core/vm/interface.go index c7259e4fb5fe..bd594b9c8265 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -47,6 +47,8 @@ type StateDB interface { GetState(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) + GetRootHash() common.Hash + GetLiveStateAccount(addr common.Address) *types.StateAccount GetProof(addr common.Address) ([][]byte, error) GetProofByHash(addrHash common.Hash) ([][]byte, error) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) diff --git a/core/vm/logger.go b/core/vm/logger.go index a10182ba3b2a..9dd17f046bd7 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -165,16 +165,21 @@ type StructLogger struct { cfg LogConfig env *EVM - storage map[common.Address]Storage - logs []StructLog - output []byte - err error + statesAffected map[common.Address]struct{} + storage map[common.Address]Storage + createdAccount *types.AccountWrapper + + callStackLogInd []int + logs []StructLog + output []byte + err error } // NewStructLogger returns a new logger func NewStructLogger(cfg *LogConfig) *StructLogger { logger := &StructLogger{ - storage: make(map[common.Address]Storage), + storage: make(map[common.Address]Storage), + statesAffected: make(map[common.Address]struct{}), } if cfg != nil { logger.cfg = *cfg @@ -185,14 +190,30 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { // Reset clears the data held by the logger. func (l *StructLogger) Reset() { l.storage = make(map[common.Address]Storage) + l.statesAffected = make(map[common.Address]struct{}) l.output = make([]byte, 0) l.logs = l.logs[:0] + l.callStackLogInd = nil l.err = nil + l.createdAccount = nil } // CaptureStart implements the EVMLogger interface to initialize the tracing operation. -func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { +func (l *StructLogger) CaptureStart(env *EVM, from common.Address, to common.Address, isCreate bool, input []byte, gas uint64, value *big.Int) { l.env = env + + if isCreate { + // notice codeHash is set AFTER CreateTx has exited, so here codeHash is still empty + l.createdAccount = &types.AccountWrapper{ + Address: to, + // nonce is 1 after EIP158, so we query it from stateDb + Nonce: env.StateDB.GetNonce(to), + Balance: (*hexutil.Big)(value), + } + } + + l.statesAffected[from] = struct{}{} + l.statesAffected[to] = struct{}{} } // CaptureState logs a new structured log message and pushes it out to the environment @@ -242,7 +263,7 @@ func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scop l.storage[contractAddress][storageKey] = storageValue structlog.Storage = l.storage[contractAddress].Copy() - if err := traceStorageProof(l, scope, structlog.getOrInitExtraData()); err != nil { + if err := traceStorage(l, scope, structlog.getOrInitExtraData()); err != nil { log.Error("Failed to trace data", "opcode", op.String(), "err", err) } } @@ -264,36 +285,6 @@ func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scop } func (l *StructLogger) CaptureStateAfter(pc uint64, op OpCode, gas, cost uint64, scope *ScopeContext, rData []byte, depth int, err error) { - if !l.cfg.DisableStorage && op == SSTORE { - logLen := len(l.logs) - if logLen <= 0 { - log.Error("Failed to trace after_state for sstore", "err", "empty length log") - return - } - - lastLog := l.logs[logLen-1] - if lastLog.Op != SSTORE { - log.Error("Failed to trace after_state for sstore", "err", "op mismatch") - return - } - if lastLog.ExtraData == nil || len(lastLog.ExtraData.ProofList) == 0 { - log.Error("Failed to trace after_state for sstore", "err", "empty before_state ExtraData") - return - } - - contractAddress := scope.Contract.Address() - if len(lastLog.Stack) <= 0 { - log.Error("Failed to trace after_state for sstore", "err", "empty stack for last log") - return - } - storageKey := common.Hash(lastLog.Stack[len(lastLog.Stack)-1].Bytes32()) - proof, err := getWrappedProofForStorage(l, contractAddress, storageKey) - if err != nil { - log.Error("Failed to trace after_state storage_proof for sstore", "err", err) - } - - l.logs[logLen-1].ExtraData.ProofList = append(lastLog.ExtraData.ProofList, proof) - } } // CaptureFault implements the EVMLogger interface to trace an execution fault @@ -314,9 +305,68 @@ func (l *StructLogger) CaptureEnd(output []byte, gasUsed uint64, t time.Duration } func (l *StructLogger) CaptureEnter(typ OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + // the last logged op should be CALL/STATICCALL/CALLCODE/CREATE/CREATE2 + lastLogPos := len(l.logs) - 1 + log.Debug("mark call stack", "pos", lastLogPos, "op", l.logs[lastLogPos].Op) + l.callStackLogInd = append(l.callStackLogInd, lastLogPos) + // sanity check + if len(l.callStackLogInd) != l.env.depth { + panic("unexpected evm depth in capture enter") + } + l.statesAffected[to] = struct{}{} + theLog := l.logs[lastLogPos] + // handling additional updating for CALL/STATICCALL/CALLCODE/CREATE/CREATE2 only + // append extraData part for the log, capture the account status (the nonce / balance has been updated in capture enter) + wrappedStatus, _ := getWrappedAccountForAddr(l, to) + theLog.ExtraData.StateList = append(theLog.ExtraData.StateList, wrappedStatus) +} + +// in CaptureExit phase, a CREATE has its target address's code being set and queryable +func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) { + stackH := len(l.callStackLogInd) + if stackH == 0 { + panic("unexpected capture exit occur") + } + + theLogPos := l.callStackLogInd[stackH-1] + l.callStackLogInd = l.callStackLogInd[:stackH-1] + theLog := l.logs[theLogPos] + // update "forecast" data + if err != nil { + theLog.ExtraData.CallFailed = true + } + + // handling updating for CREATE only + switch theLog.Op { + case CREATE, CREATE2: + // append extraData part for the log whose op is CREATE(2), capture the account status (the codehash would be updated in capture exit) + dataLen := len(theLog.ExtraData.StateList) + if dataLen == 0 { + panic("unexpected data capture for target op") + } + + lastAccData := theLog.ExtraData.StateList[dataLen-1] + wrappedStatus, _ := getWrappedAccountForAddr(l, lastAccData.Address) + theLog.ExtraData.StateList = append(theLog.ExtraData.StateList, wrappedStatus) + default: + //do nothing for other op code + return + } + +} + +// UpdatedAccounts is used to collect all "touched" accounts +func (l *StructLogger) UpdatedAccounts() map[common.Address]struct{} { + return l.statesAffected +} + +// UpdatedStorages is used to collect all "touched" storage slots +func (l *StructLogger) UpdatedStorages() map[common.Address]Storage { + return l.storage } -func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} +// CreatedAccount return the account data in case it is a create tx +func (l *StructLogger) CreatedAccount() *types.AccountWrapper { return l.createdAccount } // StructLogs returns the captured log entries. func (l *StructLogger) StructLogs() []StructLog { return l.logs } diff --git a/core/vm/logger_trace.go b/core/vm/logger_trace.go index 5b0eccfd904a..0e2e1abe9e68 100644 --- a/core/vm/logger_trace.go +++ b/core/vm/logger_trace.go @@ -1,8 +1,6 @@ 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" @@ -13,18 +11,18 @@ type traceFunc func(l *StructLogger, scope *ScopeContext, extraData *types.Extra var ( // OpcodeExecs the map to load opcodes' trace funcs. OpcodeExecs = map[OpCode][]traceFunc{ - CALL: {traceToAddressCode, traceLastNAddressCode(1), traceCallerProof, traceLastNAddressProof(1)}, - CALLCODE: {traceToAddressCode, traceLastNAddressCode(1), traceCallerProof, traceLastNAddressProof(1)}, + CALL: {traceToAddressCode, traceLastNAddressCode(1), traceCaller, traceLastNAddressAccount(1)}, + CALLCODE: {traceToAddressCode, traceLastNAddressCode(1), traceCaller, traceLastNAddressAccount(1)}, DELEGATECALL: {traceToAddressCode, traceLastNAddressCode(1)}, - STATICCALL: {traceToAddressCode, traceLastNAddressCode(1)}, - CREATE: {traceCreatedContractProof}, // sender's wrapped_proof is already recorded in BlockChain.writeBlockResult - CREATE2: {traceCreatedContractProof}, // sender's wrapped_proof is already recorded in BlockChain.writeBlockResult - SLOAD: {}, // record storage_proof in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag - SSTORE: {}, // record storage_proof in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag - SELFDESTRUCT: {traceContractProof, traceLastNAddressProof(0)}, - SELFBALANCE: {traceContractProof}, - BALANCE: {traceLastNAddressProof(0)}, - EXTCODEHASH: {traceLastNAddressProof(0)}, + STATICCALL: {traceToAddressCode, traceLastNAddressCode(1), traceLastNAddressAccount(1)}, + CREATE: {}, // sender is already recorded in ExecutionResult, callee is recorded in CaptureEnter&CaptureExit + CREATE2: {}, // sender is already recorded in ExecutionResult, callee is recorded in CaptureEnter&CaptureExit + SLOAD: {}, // trace storage in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag + SSTORE: {}, // trace storage in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag + SELFDESTRUCT: {traceContractAccount, traceLastNAddressAccount(0)}, + SELFBALANCE: {traceContractAccount}, + BALANCE: {traceLastNAddressAccount(0)}, + EXTCODEHASH: {traceLastNAddressAccount(0)}, } ) @@ -52,49 +50,32 @@ func traceLastNAddressCode(n int) traceFunc { } } -// traceStorageProof get contract's storage proof at storage_address -func traceStorageProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { +// traceStorage get contract's storage at storage_address +func traceStorage(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { if scope.Stack.len() == 0 { return nil } key := common.Hash(scope.Stack.peek().Bytes32()) - proof, err := getWrappedProofForStorage(l, scope.Contract.Address(), key) - if err == nil { - extraData.ProofList = append(extraData.ProofList, proof) - } - return err -} - -// traceContractProof gets the contract's account proof -func traceContractProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { - // Get account proof. - proof, err := getWrappedProofForAddr(l, scope.Contract.Address()) + storage, err := getWrappedAccountForStorage(l, scope.Contract.Address(), key) if err == nil { - extraData.ProofList = append(extraData.ProofList, proof) + extraData.StateList = append(extraData.StateList, storage) } return err } -/// traceCreatedContractProof get created contract address’s 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 := getWrappedProofForAddr(l, address) +// traceContractAccount gets the contract's account +func traceContractAccount(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { + // Get account state. + state, err := getWrappedAccountForAddr(l, scope.Contract.Address()) if err == nil { - extraData.ProofList = append(extraData.ProofList, proof) + extraData.StateList = append(extraData.StateList, state) + l.statesAffected[scope.Contract.Address()] = struct{}{} } return err } -// traceLastNAddressProof returns func about the last N's address proof. -func traceLastNAddressProof(n int) traceFunc { +// traceLastNAddressAccount returns func about the last N's address account. +func traceLastNAddressAccount(n int) traceFunc { return func(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { stack := scope.Stack if stack.len() <= n { @@ -102,72 +83,45 @@ func traceLastNAddressProof(n int) traceFunc { } address := common.Address(stack.data[stack.len()-1-n].Bytes20()) - proof, err := getWrappedProofForAddr(l, address) + state, err := getWrappedAccountForAddr(l, address) if err == nil { - extraData.ProofList = append(extraData.ProofList, proof) + extraData.StateList = append(extraData.StateList, state) + l.statesAffected[scope.Contract.Address()] = struct{}{} } return err } } -// traceCallerProof gets caller address's proof. -func traceCallerProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { +// traceCaller gets caller address's account. +func traceCaller(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { address := scope.Contract.CallerAddress - proof, err := getWrappedProofForAddr(l, address) + state, err := getWrappedAccountForAddr(l, address) if err == nil { - extraData.ProofList = append(extraData.ProofList, proof) + extraData.StateList = append(extraData.StateList, state) + l.statesAffected[scope.Contract.Address()] = struct{}{} } return err } -// StorageProofWrapper will be empty -func getWrappedProofForAddr(l *StructLogger, address common.Address) (*types.AccountProofWrapper, error) { - proof, err := l.env.StateDB.GetProof(address) - if err != nil { - return nil, err - } - - return &types.AccountProofWrapper{ +// StorageWrapper will be empty +func getWrappedAccountForAddr(l *StructLogger, address common.Address) (*types.AccountWrapper, error) { + return &types.AccountWrapper{ Address: address, Nonce: l.env.StateDB.GetNonce(address), Balance: (*hexutil.Big)(l.env.StateDB.GetBalance(address)), CodeHash: l.env.StateDB.GetCodeHash(address), - Proof: encodeProof(proof), }, nil } -func getWrappedProofForStorage(l *StructLogger, address common.Address, key common.Hash) (*types.AccountProofWrapper, error) { - proof, err := l.env.StateDB.GetProof(address) - if err != nil { - return nil, err - } - - storageProof, err := l.env.StateDB.GetStorageProof(address, key) - if err != nil { - return nil, err - } - - return &types.AccountProofWrapper{ +func getWrappedAccountForStorage(l *StructLogger, address common.Address, key common.Hash) (*types.AccountWrapper, error) { + return &types.AccountWrapper{ Address: address, Nonce: l.env.StateDB.GetNonce(address), Balance: (*hexutil.Big)(l.env.StateDB.GetBalance(address)), CodeHash: l.env.StateDB.GetCodeHash(address), - Proof: encodeProof(proof), - Storage: &types.StorageProofWrapper{ + Storage: &types.StorageWrapper{ Key: key.String(), Value: l.env.StateDB.GetState(address, key).String(), - Proof: encodeProof(storageProof), }, }, nil } - -func encodeProof(proof [][]byte) []string { - if len(proof) == 0 { - return nil - } - res := make([]string, 0, len(proof)) - for _, node := range proof { - res = append(res, hexutil.Encode(node)) - } - return res -} diff --git a/miner/worker.go b/miner/worker.go index feabe8604743..cd92fed9b474 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -96,12 +96,15 @@ type environment struct { txs []*types.Transaction receipts []*types.Receipt executionResults []*types.ExecutionResult + proofs map[string][]hexutil.Bytes + storageProofs map[string]map[string][]hexutil.Bytes } // task contains all information for consensus engine sealing and result submitting. type task struct { receipts []*types.Receipt executionResults []*types.ExecutionResult + storageResults *types.StorageTrace state *state.StateDB block *types.Block createdAt time.Time @@ -639,9 +642,10 @@ func (w *worker) resultLoop() { } // Different block could share same sealhash, deep copy here to prevent write-write conflict. var ( - receipts = make([]*types.Receipt, len(task.receipts)) - evmTraces = make([]*types.ExecutionResult, len(task.executionResults)) - logs []*types.Log + receipts = make([]*types.Receipt, len(task.receipts)) + evmTraces = make([]*types.ExecutionResult, len(task.executionResults)) + logs []*types.Log + storageTrace = new(types.StorageTrace) ) for i, taskReceipt := range task.receipts { receipt := new(types.Receipt) @@ -651,6 +655,7 @@ func (w *worker) resultLoop() { evmTrace := new(types.ExecutionResult) evmTraces[i] = evmTrace *evmTrace = *task.executionResults[i] + *storageTrace = *task.storageResults // add block location fields receipt.BlockHash = hash @@ -669,7 +674,7 @@ func (w *worker) resultLoop() { logs = append(logs, receipt.Logs...) } // Commit block and state to database. - _, err := w.chain.WriteBlockWithState(block, receipts, logs, evmTraces, task.state, true) + _, err := w.chain.WriteBlockWithState(block, receipts, logs, evmTraces, storageTrace, task.state, true) if err != nil { log.Error("Failed writing block to chain", "err", err) continue @@ -700,12 +705,14 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { state.StartPrefetcher("miner") env := &environment{ - signer: types.MakeSigner(w.chainConfig, header.Number), - state: state, - ancestors: mapset.NewSet(), - family: mapset.NewSet(), - uncles: mapset.NewSet(), - header: header, + signer: types.MakeSigner(w.chainConfig, header.Number), + state: state, + ancestors: mapset.NewSet(), + family: mapset.NewSet(), + uncles: mapset.NewSet(), + header: header, + proofs: make(map[string][]hexutil.Bytes), + storageProofs: make(map[string]map[string][]hexutil.Bytes), } // when 08 is processed ancestors contain 07 (quick block) for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { @@ -788,17 +795,17 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres tracer.Reset() // Get sender's address. from, _ := types.Sender(w.current.signer, tx) - sender := &types.AccountProofWrapper{ + sender := &types.AccountWrapper{ Address: from, Nonce: w.current.state.GetNonce(from), Balance: (*hexutil.Big)(w.current.state.GetBalance(from)), CodeHash: w.current.state.GetCodeHash(from), } // Get receiver's address. - var receiver *types.AccountProofWrapper + var receiver *types.AccountWrapper if tx.To() != nil { to := *tx.To() - receiver = &types.AccountProofWrapper{ + receiver = &types.AccountWrapper{ Address: to, Nonce: w.current.state.GetNonce(to), Balance: (*hexutil.Big)(w.current.state.GetBalance(to)), @@ -812,15 +819,83 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres return nil, err } + createdAcc := tracer.CreatedAccount() + var after []*types.AccountWrapper + to := tx.To() + + if to == nil { + if createdAcc == nil { + panic("unexpected tx: address for created contract unavialable") + } + to = &createdAcc.Address + } + + // collect affected account after tx being applied + for _, acc := range []*common.Address{&from, to} { + after = append(after, &types.AccountWrapper{ + Address: *acc, + Nonce: w.current.state.GetNonce(*acc), + Balance: (*hexutil.Big)(w.current.state.GetBalance(*acc)), + CodeHash: w.current.state.GetCodeHash(*acc), + }) + } + + // merge required proof data + proofAccounts := tracer.UpdatedAccounts() + for addr := range proofAccounts { + addrStr := addr.String() + if _, existed := w.current.proofs[addrStr]; !existed { + proof, err := w.current.state.GetProof(addr) + if err != nil { + log.Error("Proof not available", "address", addrStr) + // but we still mark the proofs map with nil array + } + wrappedProof := make([]hexutil.Bytes, len(proof)) + for _, bt := range proof { + wrappedProof = append(wrappedProof, bt) + } + w.current.proofs[addrStr] = wrappedProof + } + } + + proofStorages := tracer.UpdatedStorages() + for addr, keys := range proofStorages { + for key := range keys { + addrStr := addr.String() + m, existed := w.current.storageProofs[addrStr] + if !existed { + m = make(map[string][]hexutil.Bytes) + w.current.storageProofs[addrStr] = m + } + + keyStr := key.String() + if _, existed := m[keyStr]; !existed { + proof, err := w.current.state.GetStorageTrieProof(addr, key) + if err != nil { + log.Error("Storage proof not available", "address", addrStr, "key", keyStr) + // but we still mark the proofs map with nil array + } + wrappedProof := make([]hexutil.Bytes, len(proof)) + for _, bt := range proof { + wrappedProof = append(wrappedProof, hexutil.Bytes(bt)) + } + m[keyStr] = wrappedProof + } + } + } + w.current.txs = append(w.current.txs, tx) w.current.receipts = append(w.current.receipts, receipt) + w.current.executionResults = append(w.current.executionResults, &types.ExecutionResult{ - Gas: receipt.GasUsed, - From: sender, - To: receiver, - Failed: receipt.Status != types.ReceiptStatusSuccessful, - ReturnValue: fmt.Sprintf("%x", receipt.ReturnValue), - StructLogs: vm.FormatLogs(tracer.StructLogs()), + Gas: receipt.GasUsed, + From: sender, + To: receiver, + AccountCreated: createdAcc, + AccountsAfter: after, + Failed: receipt.Status != types.ReceiptStatusSuccessful, + ReturnValue: fmt.Sprintf("%x", receipt.ReturnValue), + StructLogs: vm.FormatLogs(tracer.StructLogs()), }) return receipt.Logs, nil @@ -1077,6 +1152,13 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st // Deep copy receipts here to avoid interaction between different tasks. receipts := copyReceipts(w.current.receipts) s := w.current.state.Copy() + // complete storage before Finalize state (only RootAfter left unknown) + storage := &types.StorageTrace{ + RootBefore: s.GetRootHash(), + Proofs: w.current.proofs, + StorageProofs: w.current.storageProofs, + } + block, err := w.engine.FinalizeAndAssemble(w.chain, w.current.header, s, w.current.txs, uncles, receipts) if err != nil { return err @@ -1086,7 +1168,7 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st interval() } select { - case w.taskCh <- &task{receipts: receipts, executionResults: w.current.executionResults, state: s, block: block, createdAt: time.Now()}: + case w.taskCh <- &task{receipts: receipts, executionResults: w.current.executionResults, storageResults: storage, state: s, block: block, createdAt: time.Now()}: w.unconfirmed.Shift(block.NumberU64() - 1) log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount,