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 <maskpp266@gmail.com>
Co-authored-by: Ho Vei <noelwei@gmail.com>
Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com>
This commit is contained in:
Zhang Zhuo 2022-06-08 12:26:50 +08:00 committed by GitHub
parent 35f6a91cd5
commit d3bc8322dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 334 additions and 217 deletions

1
.gitattributes vendored
View file

@ -1,3 +1,4 @@
# Auto detect text files and perform LF normalization # Auto detect text files and perform LF normalization
* text=auto * text=auto
*.sol linguist-language=Solidity *.sol linguist-language=Solidity
*.go text eol=lf

View file

@ -1193,17 +1193,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, 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() { if !bc.chainmu.TryLock() {
return NonStatTy, errInsertionInterrupted return NonStatTy, errInsertionInterrupted
} }
defer bc.chainmu.Unlock() 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, // 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, 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() { if bc.insertStopped() {
return NonStatTy, errInsertionInterrupted return NonStatTy, errInsertionInterrupted
} }
@ -1327,7 +1327,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
// Fill blockResult content // Fill blockResult content
var blockResult *types.BlockResult var blockResult *types.BlockResult
if evmTraces != nil { if evmTraces != nil {
blockResult = bc.writeBlockResult(state, block, evmTraces) blockResult = bc.writeBlockResult(state, block, evmTraces, storageTrace)
bc.blockResultCache.Add(block.Hash(), blockResult) bc.blockResultCache.Add(block.Hash(), blockResult)
} }
@ -1351,57 +1351,22 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
} }
// Fill blockResult content // 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{ blockResult := &types.BlockResult{
ExecutionResults: evmTraces, ExecutionResults: evmTraces,
StorageTrace: storageTrace,
} }
coinbase := types.AccountProofWrapper{ coinbase := types.AccountWrapper{
Address: block.Coinbase(), Address: block.Coinbase(),
Nonce: state.GetNonce(block.Coinbase()), Nonce: state.GetNonce(block.Coinbase()),
Balance: (*hexutil.Big)(state.GetBalance(block.Coinbase())), Balance: (*hexutil.Big)(state.GetBalance(block.Coinbase())),
CodeHash: state.GetCodeHash(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.BlockTrace = types.NewTraceBlock(bc.chainConfig, block, &coinbase)
blockResult.StorageTrace.RootAfter = state.GetRootHash()
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
evmTrace := blockResult.ExecutionResults[i] 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 // Contract is called
if len(tx.Data()) != 0 && tx.To() != nil { if len(tx.Data()) != 0 && tx.To() != nil {
evmTrace.ByteCode = hexutil.Encode(state.GetCode(*tx.To())) 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. // Write the block to the chain and get the status.
substart = time.Now() substart = time.Now()
// EvmTraces is nil is safe because l2geth's p2p server is stoped and the code will not execute there. // 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, statedb, false) status, err := bc.writeBlockWithState(block, receipts, logs, nil, 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

View file

@ -325,6 +325,41 @@ func (s *StateDB) GetProofByHash(addrHash common.Hash) ([][]byte, error) {
return proof, err 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. // GetStorageProof returns the Merkle proof for given storage slot.
func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) { func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
var proof proofList var proof proofList

View file

@ -23,9 +23,24 @@ var (
// 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"` BlockTrace *BlockTrace `json:"blockTrace"`
StorageTrace *StorageTrace `json:"storageTrace"`
ExecutionResults []*ExecutionResult `json:"executionResults"` 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 // 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
@ -33,11 +48,19 @@ 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"`
// Sender's account proof. // Sender's account state (before Tx)
From *AccountProofWrapper `json:"from,omitempty"` From *AccountWrapper `json:"from,omitempty"`
// Receiver's account proof. // Receiver's account state (before Tx)
To *AccountProofWrapper `json:"to,omitempty"` To *AccountWrapper `json:"to,omitempty"`
// It's exist only when tx is a contract call. // 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"` CodeHash *common.Hash `json:"codeHash,omitempty"`
// If it is a contract call, the contract code is returned. // If it is a contract call, the contract code is returned.
ByteCode string `json:"byteCode,omitempty"` ByteCode string `json:"byteCode,omitempty"`
@ -77,30 +100,35 @@ func NewStructLogResBasic(pc uint64, op string, gas, gasCost uint64, depth int,
} }
type ExtraData struct { type ExtraData struct {
// Indicate the call succeeds or not for CALL/CREATE op
CallFailed bool `json:"callFailed,omitempty"`
// CALL | CALLCODE | DELEGATECALL | STATICCALL: [tx.to addresss code, stack.nth_last(1) addresss code] // CALL | CALLCODE | DELEGATECALL | STATICCALL: [tx.to addresss code, stack.nth_last(1) addresss code]
CodeList [][]byte `json:"codeList,omitempty"` CodeList [][]byte `json:"codeList,omitempty"`
// SSTORE | SLOAD: [storageProof] // SSTORE | SLOAD: [storageProof]
// SELFDESTRUCT: [contract addresss accountProof, stack.nth_last(0) addresss accountProof] // SELFDESTRUCT: [contract addresss account, stack.nth_last(0) addresss account]
// SELFBALANCE: [contract addresss accountProof] // SELFBALANCE: [contract addresss account]
// BALANCE | EXTCODEHASH: [stack.nth_last(0) addresss accountProof] // BALANCE | EXTCODEHASH: [stack.nth_last(0) addresss account]
// CREATE | CREATE2: [sender's accountProof, created contract addresss accountProof] // CREATE | CREATE2: [created contract addresss account (before constructed),
// CALL | CALLCODE: [caller contract addresss accountProof, stack.nth_last(1) addresss accountProof] // created contract address's account (after constructed)]
ProofList []*AccountProofWrapper `json:"proofList,omitempty"` // CALL | CALLCODE: [caller contract addresss account,
// stack.nth_last(1) (i.e. callee) addresss account,
// callee contract address's account (value updated, before called)]
// STATICCALL: [stack.nth_last(1) (i.e. callee) addresss account,
// callee contract address's account (before called)]
StateList []*AccountWrapper `json:"proofList,omitempty"`
} }
type AccountProofWrapper struct { type AccountWrapper struct {
Address common.Address `json:"address"` Address common.Address `json:"address"`
Nonce uint64 `json:"nonce"` Nonce uint64 `json:"nonce"`
Balance *hexutil.Big `json:"balance"` Balance *hexutil.Big `json:"balance"`
CodeHash common.Hash `json:"codeHash,omitempty"` CodeHash common.Hash `json:"codeHash,omitempty"`
Proof []string `json:"proof,omitempty"` Storage *StorageWrapper `json:"storage,omitempty"` // StorageWrapper can be empty if irrelated to storage operation
Storage *StorageProofWrapper `json:"storage,omitempty"` // StorageProofWrapper can be empty if irrelated to storage operation
} }
// while key & value can also be retrieved from StructLogRes.Storage, // while key & value can also be retrieved from StructLogRes.Storage,
// we still stored in here for roller's processing convenience. // we still stored in here for roller's processing convenience.
type StorageProofWrapper struct { type StorageWrapper struct {
Key string `json:"key,omitempty"` Key string `json:"key,omitempty"`
Value string `json:"value,omitempty"` Value string `json:"value,omitempty"`
Proof []string `json:"proof,omitempty"`
} }

View file

@ -14,7 +14,7 @@ type BlockTrace struct {
GasLimit uint64 `json:"gasLimit"` GasLimit uint64 `json:"gasLimit"`
Difficulty *hexutil.Big `json:"difficulty"` Difficulty *hexutil.Big `json:"difficulty"`
BaseFee *hexutil.Big `json:"baseFee"` BaseFee *hexutil.Big `json:"baseFee"`
Coinbase *AccountProofWrapper `json:"coinbase"` Coinbase *AccountWrapper `json:"coinbase"`
Time uint64 `json:"time"` Time uint64 `json:"time"`
Transactions []*TransactionTrace `json:"transactions"` Transactions []*TransactionTrace `json:"transactions"`
} }
@ -36,7 +36,7 @@ type TransactionTrace struct {
} }
// NewTraceBlock supports necessary fields for roller. // 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()) txs := make([]*TransactionTrace, block.Transactions().Len())
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
txs[i] = newTraceTransaction(tx, block.NumberU64(), config) txs[i] = newTraceTransaction(tx, block.NumberU64(), config)

View file

@ -47,6 +47,8 @@ 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)
GetRootHash() common.Hash
GetLiveStateAccount(addr common.Address) *types.StateAccount
GetProof(addr common.Address) ([][]byte, error) GetProof(addr common.Address) ([][]byte, error)
GetProofByHash(addrHash common.Hash) ([][]byte, error) GetProofByHash(addrHash common.Hash) ([][]byte, error)
GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error)

View file

@ -165,7 +165,11 @@ type StructLogger struct {
cfg LogConfig cfg LogConfig
env *EVM env *EVM
statesAffected map[common.Address]struct{}
storage map[common.Address]Storage storage map[common.Address]Storage
createdAccount *types.AccountWrapper
callStackLogInd []int
logs []StructLog logs []StructLog
output []byte output []byte
err error err error
@ -175,6 +179,7 @@ type StructLogger struct {
func NewStructLogger(cfg *LogConfig) *StructLogger { func NewStructLogger(cfg *LogConfig) *StructLogger {
logger := &StructLogger{ logger := &StructLogger{
storage: make(map[common.Address]Storage), storage: make(map[common.Address]Storage),
statesAffected: make(map[common.Address]struct{}),
} }
if cfg != nil { if cfg != nil {
logger.cfg = *cfg logger.cfg = *cfg
@ -185,14 +190,30 @@ func NewStructLogger(cfg *LogConfig) *StructLogger {
// Reset clears the data held by the logger. // Reset clears the data held by the logger.
func (l *StructLogger) Reset() { func (l *StructLogger) Reset() {
l.storage = make(map[common.Address]Storage) l.storage = make(map[common.Address]Storage)
l.statesAffected = make(map[common.Address]struct{})
l.output = make([]byte, 0) l.output = make([]byte, 0)
l.logs = l.logs[:0] l.logs = l.logs[:0]
l.callStackLogInd = nil
l.err = nil l.err = nil
l.createdAccount = nil
} }
// CaptureStart implements the EVMLogger interface to initialize the tracing operation. // 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 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 // 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 l.storage[contractAddress][storageKey] = storageValue
structlog.Storage = l.storage[contractAddress].Copy() 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) 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) { 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 // 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) { 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)
} }
func (l *StructLogger) CaptureExit(output []byte, gasUsed uint64, err error) {} // 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
}
// 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. // StructLogs returns the captured log entries.
func (l *StructLogger) StructLogs() []StructLog { return l.logs } func (l *StructLogger) StructLogs() []StructLog { return l.logs }

View file

@ -1,8 +1,6 @@
package vm package vm
import ( import (
"errors"
"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/hexutil"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
@ -13,18 +11,18 @@ type traceFunc func(l *StructLogger, scope *ScopeContext, extraData *types.Extra
var ( var (
// OpcodeExecs the map to load opcodes' trace funcs. // OpcodeExecs the map to load opcodes' trace funcs.
OpcodeExecs = map[OpCode][]traceFunc{ OpcodeExecs = map[OpCode][]traceFunc{
CALL: {traceToAddressCode, traceLastNAddressCode(1), traceCallerProof, traceLastNAddressProof(1)}, CALL: {traceToAddressCode, traceLastNAddressCode(1), traceCaller, traceLastNAddressAccount(1)},
CALLCODE: {traceToAddressCode, traceLastNAddressCode(1), traceCallerProof, traceLastNAddressProof(1)}, CALLCODE: {traceToAddressCode, traceLastNAddressCode(1), traceCaller, traceLastNAddressAccount(1)},
DELEGATECALL: {traceToAddressCode, traceLastNAddressCode(1)}, DELEGATECALL: {traceToAddressCode, traceLastNAddressCode(1)},
STATICCALL: {traceToAddressCode, traceLastNAddressCode(1)}, STATICCALL: {traceToAddressCode, traceLastNAddressCode(1), traceLastNAddressAccount(1)},
CREATE: {traceCreatedContractProof}, // sender's wrapped_proof is already recorded in BlockChain.writeBlockResult CREATE: {}, // sender is already recorded in ExecutionResult, callee is recorded in CaptureEnter&CaptureExit
CREATE2: {traceCreatedContractProof}, // sender's wrapped_proof is already recorded in BlockChain.writeBlockResult CREATE2: {}, // sender is already recorded in ExecutionResult, callee is recorded in CaptureEnter&CaptureExit
SLOAD: {}, // record storage_proof in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag SLOAD: {}, // trace storage 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 SSTORE: {}, // trace storage in `captureState` instead of here, to handle `l.cfg.DisableStorage` flag
SELFDESTRUCT: {traceContractProof, traceLastNAddressProof(0)}, SELFDESTRUCT: {traceContractAccount, traceLastNAddressAccount(0)},
SELFBALANCE: {traceContractProof}, SELFBALANCE: {traceContractAccount},
BALANCE: {traceLastNAddressProof(0)}, BALANCE: {traceLastNAddressAccount(0)},
EXTCODEHASH: {traceLastNAddressProof(0)}, EXTCODEHASH: {traceLastNAddressAccount(0)},
} }
) )
@ -52,49 +50,32 @@ func traceLastNAddressCode(n int) traceFunc {
} }
} }
// traceStorageProof get contract's storage proof at storage_address // traceStorage get contract's storage at storage_address
func traceStorageProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { func traceStorage(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
if scope.Stack.len() == 0 { if scope.Stack.len() == 0 {
return nil return nil
} }
key := common.Hash(scope.Stack.peek().Bytes32()) key := common.Hash(scope.Stack.peek().Bytes32())
proof, err := getWrappedProofForStorage(l, scope.Contract.Address(), key) storage, err := getWrappedAccountForStorage(l, scope.Contract.Address(), key)
if err == nil { if err == nil {
extraData.ProofList = append(extraData.ProofList, proof) extraData.StateList = append(extraData.StateList, storage)
} }
return err return err
} }
// traceContractProof gets the contract's account proof // traceContractAccount gets the contract's account
func traceContractProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { func traceContractAccount(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
// Get account proof. // Get account state.
proof, err := getWrappedProofForAddr(l, scope.Contract.Address()) state, err := getWrappedAccountForAddr(l, scope.Contract.Address())
if err == nil { if err == nil {
extraData.ProofList = append(extraData.ProofList, proof) extraData.StateList = append(extraData.StateList, state)
l.statesAffected[scope.Contract.Address()] = struct{}{}
} }
return err return err
} }
/// traceCreatedContractProof get created contract addresss accountProof // traceLastNAddressAccount returns func about the last N's address account.
func traceCreatedContractProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { func traceLastNAddressAccount(n int) traceFunc {
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)
if err == nil {
extraData.ProofList = append(extraData.ProofList, 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 { return func(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
stack := scope.Stack stack := scope.Stack
if stack.len() <= n { if stack.len() <= n {
@ -102,72 +83,45 @@ func traceLastNAddressProof(n int) traceFunc {
} }
address := common.Address(stack.data[stack.len()-1-n].Bytes20()) address := common.Address(stack.data[stack.len()-1-n].Bytes20())
proof, err := getWrappedProofForAddr(l, address) state, err := getWrappedAccountForAddr(l, address)
if err == nil { if err == nil {
extraData.ProofList = append(extraData.ProofList, proof) extraData.StateList = append(extraData.StateList, state)
l.statesAffected[scope.Contract.Address()] = struct{}{}
} }
return err return err
} }
} }
// traceCallerProof gets caller address's proof. // traceCaller gets caller address's account.
func traceCallerProof(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error { func traceCaller(l *StructLogger, scope *ScopeContext, extraData *types.ExtraData) error {
address := scope.Contract.CallerAddress address := scope.Contract.CallerAddress
proof, err := getWrappedProofForAddr(l, address) state, err := getWrappedAccountForAddr(l, address)
if err == nil { if err == nil {
extraData.ProofList = append(extraData.ProofList, proof) extraData.StateList = append(extraData.StateList, state)
l.statesAffected[scope.Contract.Address()] = struct{}{}
} }
return err return err
} }
// StorageProofWrapper will be empty // StorageWrapper will be empty
func getWrappedProofForAddr(l *StructLogger, address common.Address) (*types.AccountProofWrapper, error) { func getWrappedAccountForAddr(l *StructLogger, address common.Address) (*types.AccountWrapper, error) {
proof, err := l.env.StateDB.GetProof(address) return &types.AccountWrapper{
if err != nil {
return nil, err
}
return &types.AccountProofWrapper{
Address: address, Address: address,
Nonce: l.env.StateDB.GetNonce(address), Nonce: l.env.StateDB.GetNonce(address),
Balance: (*hexutil.Big)(l.env.StateDB.GetBalance(address)), Balance: (*hexutil.Big)(l.env.StateDB.GetBalance(address)),
CodeHash: l.env.StateDB.GetCodeHash(address), CodeHash: l.env.StateDB.GetCodeHash(address),
Proof: encodeProof(proof),
}, nil }, nil
} }
func getWrappedProofForStorage(l *StructLogger, address common.Address, key common.Hash) (*types.AccountProofWrapper, error) { func getWrappedAccountForStorage(l *StructLogger, address common.Address, key common.Hash) (*types.AccountWrapper, error) {
proof, err := l.env.StateDB.GetProof(address) return &types.AccountWrapper{
if err != nil {
return nil, err
}
storageProof, err := l.env.StateDB.GetStorageProof(address, key)
if err != nil {
return nil, err
}
return &types.AccountProofWrapper{
Address: address, Address: address,
Nonce: l.env.StateDB.GetNonce(address), Nonce: l.env.StateDB.GetNonce(address),
Balance: (*hexutil.Big)(l.env.StateDB.GetBalance(address)), Balance: (*hexutil.Big)(l.env.StateDB.GetBalance(address)),
CodeHash: l.env.StateDB.GetCodeHash(address), CodeHash: l.env.StateDB.GetCodeHash(address),
Proof: encodeProof(proof), Storage: &types.StorageWrapper{
Storage: &types.StorageProofWrapper{
Key: key.String(), Key: key.String(),
Value: l.env.StateDB.GetState(address, key).String(), Value: l.env.StateDB.GetState(address, key).String(),
Proof: encodeProof(storageProof),
}, },
}, nil }, 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
}

View file

@ -96,12 +96,15 @@ type environment struct {
txs []*types.Transaction txs []*types.Transaction
receipts []*types.Receipt receipts []*types.Receipt
executionResults []*types.ExecutionResult 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. // task contains all information for consensus engine sealing and result submitting.
type task struct { type task struct {
receipts []*types.Receipt receipts []*types.Receipt
executionResults []*types.ExecutionResult executionResults []*types.ExecutionResult
storageResults *types.StorageTrace
state *state.StateDB state *state.StateDB
block *types.Block block *types.Block
createdAt time.Time createdAt time.Time
@ -642,6 +645,7 @@ func (w *worker) resultLoop() {
receipts = make([]*types.Receipt, len(task.receipts)) receipts = make([]*types.Receipt, len(task.receipts))
evmTraces = make([]*types.ExecutionResult, len(task.executionResults)) evmTraces = make([]*types.ExecutionResult, len(task.executionResults))
logs []*types.Log logs []*types.Log
storageTrace = new(types.StorageTrace)
) )
for i, taskReceipt := range task.receipts { for i, taskReceipt := range task.receipts {
receipt := new(types.Receipt) receipt := new(types.Receipt)
@ -651,6 +655,7 @@ func (w *worker) resultLoop() {
evmTrace := new(types.ExecutionResult) evmTrace := new(types.ExecutionResult)
evmTraces[i] = evmTrace evmTraces[i] = evmTrace
*evmTrace = *task.executionResults[i] *evmTrace = *task.executionResults[i]
*storageTrace = *task.storageResults
// add block location fields // add block location fields
receipt.BlockHash = hash receipt.BlockHash = hash
@ -669,7 +674,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, evmTraces, task.state, true) _, err := w.chain.WriteBlockWithState(block, receipts, logs, evmTraces, storageTrace, 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
@ -706,6 +711,8 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
family: mapset.NewSet(), family: mapset.NewSet(),
uncles: mapset.NewSet(), uncles: mapset.NewSet(),
header: header, 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) // when 08 is processed ancestors contain 07 (quick block)
for _, ancestor := range w.chain.GetBlocksFromHash(parent.Hash(), 7) { 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() tracer.Reset()
// Get sender's address. // Get sender's address.
from, _ := types.Sender(w.current.signer, tx) from, _ := types.Sender(w.current.signer, tx)
sender := &types.AccountProofWrapper{ sender := &types.AccountWrapper{
Address: from, Address: from,
Nonce: w.current.state.GetNonce(from), Nonce: w.current.state.GetNonce(from),
Balance: (*hexutil.Big)(w.current.state.GetBalance(from)), Balance: (*hexutil.Big)(w.current.state.GetBalance(from)),
CodeHash: w.current.state.GetCodeHash(from), CodeHash: w.current.state.GetCodeHash(from),
} }
// Get receiver's address. // Get receiver's address.
var receiver *types.AccountProofWrapper var receiver *types.AccountWrapper
if tx.To() != nil { if tx.To() != nil {
to := *tx.To() to := *tx.To()
receiver = &types.AccountProofWrapper{ receiver = &types.AccountWrapper{
Address: to, Address: to,
Nonce: w.current.state.GetNonce(to), Nonce: w.current.state.GetNonce(to),
Balance: (*hexutil.Big)(w.current.state.GetBalance(to)), Balance: (*hexutil.Big)(w.current.state.GetBalance(to)),
@ -812,12 +819,80 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres
return nil, err 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.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{ w.current.executionResults = append(w.current.executionResults, &types.ExecutionResult{
Gas: receipt.GasUsed, Gas: receipt.GasUsed,
From: sender, From: sender,
To: receiver, To: receiver,
AccountCreated: createdAcc,
AccountsAfter: after,
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()),
@ -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. // Deep copy receipts here to avoid interaction between different tasks.
receipts := copyReceipts(w.current.receipts) receipts := copyReceipts(w.current.receipts)
s := w.current.state.Copy() 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) block, err := w.engine.FinalizeAndAssemble(w.chain, w.current.header, s, w.current.txs, uncles, receipts)
if err != nil { if err != nil {
return err return err
@ -1086,7 +1168,7 @@ func (w *worker) commit(uncles []*types.Header, interval func(), update bool, st
interval() interval()
} }
select { 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) 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,