diff --git a/core/state/statedb.go b/core/state/statedb.go index 8dc4cc8abff2..3968b6a198e9 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -352,37 +352,59 @@ func (s *StateDB) GetRootHash() common.Hash { // StorageTrieProof is not in Db interface and used explictily for reading proof in storage trie (not the dirty value) // For zktrie it also provide required data for predict the deletion, else it just fallback to GetStorageProof -func (s *StateDB) GetStorageTrieProof(a common.Address, key common.Hash) ([][]byte, []byte, error) { +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, nil, errors.New("storage trie for requested address does not exist") + return nil, errors.New("storage trie for requested address does not exist") } - trieS := stateObject.trie + trie := stateObject.trie var err error - if trieS == nil { + if trie == nil { // use a new, temporary trie - trieS, err = s.db.OpenStorageTrie(stateObject.addrHash, stateObject.data.Root) + trie, err = s.db.OpenStorageTrie(stateObject.addrHash, stateObject.data.Root) if err != nil { - return nil, nil, fmt.Errorf("can't create storage trie on root %s: %v ", stateObject.data.Root, err) + return nil, fmt.Errorf("can't create storage trie on root %s: %v ", stateObject.data.Root, err) } } var proof proofList - var sibling []byte if s.IsZktrie() { - zkTrie := trieS.(*trie.ZkTrie) - if zkTrie == nil { - panic("unexpected trie type for zktrie") - } key_s, _ := zkt.ToSecureKeyBytes(key.Bytes()) - sibling, err = zkTrie.ProveWithDeletion(key_s.Bytes(), 0, &proof) + err = trie.Prove(key_s.Bytes(), 0, &proof) } else { - err = trieS.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) + err = trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) + } + return proof, err +} + +// GetDeletetionProof return the sibling of a leaf node. For any deletion, +// information for this sibling node for the leaf node being deleted is required to be passed to +// witness generator for predicting the trie root after deletion +func (s *StateDB) GetDeletetionProof(a common.Address, key common.Hash) ([]byte, error) { + + // mpt trie do not need this + if !s.IsZktrie() { + return nil, nil } - return proof, sibling, err + + trieS := s.StorageTrie(a) + if trieS == nil { + return nil, errors.New("storage trie for requested address does not exist") + } + + zkTrie := trieS.(*trie.ZkTrie) + if zkTrie == nil { + panic("unexpected trie type for zktrie") + } + + key_s, _ := zkt.ToSecureKeyBytes(key.Bytes()) + + // notice proof is not need, we just need the sibling bytes being returned + var proof proofList + return zkTrie.ProveWithDeletion(key_s.Bytes(), 0, &proof) } // GetStorageProof returns the Merkle proof for given storage slot. diff --git a/core/vm/interface.go b/core/vm/interface.go index 809ce4484d5c..79b6e7ae2ecd 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -53,6 +53,7 @@ type StateDB interface { GetProof(addr common.Address) ([][]byte, error) GetProofByHash(addrHash common.Hash) ([][]byte, error) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) + GetDeletetionProof(a common.Address, key common.Hash) ([]byte, error) Suicide(common.Address) bool HasSuicided(common.Address) bool diff --git a/core/vm/logger.go b/core/vm/logger.go index 5ad22540669e..0744109dafba 100644 --- a/core/vm/logger.go +++ b/core/vm/logger.go @@ -169,6 +169,7 @@ type StructLogger struct { statesAffected map[common.Address]struct{} storage map[common.Address]Storage createdAccount *types.AccountWrapper + deletionProof [][]byte callStackLogInd []int logs []*StructLog @@ -192,6 +193,7 @@ func NewStructLogger(cfg *LogConfig) *StructLogger { func (l *StructLogger) Reset() { l.storage = make(map[common.Address]Storage) l.statesAffected = make(map[common.Address]struct{}) + l.deletionProof = nil l.output = make([]byte, 0) l.logs = l.logs[:0] l.callStackLogInd = nil @@ -267,6 +269,18 @@ func (l *StructLogger) CaptureState(pc uint64, op OpCode, gas, cost uint64, scop if err := traceStorage(l, scope, structlog.getOrInitExtraData()); err != nil { log.Error("Failed to trace data", "opcode", op.String(), "err", err) } + + oldValue := l.env.StateDB.GetState(contractAddress, storageKey) + if !bytes.Equal(oldValue.Bytes(), storageValue.Bytes()) && + bytes.Equal(storageValue.Bytes(), common.Hash{}.Bytes()) { + + if delSibling, err := l.env.StateDB.GetDeletetionProof(contractAddress, storageKey); err != nil { + log.Error("Fail to obtain deletion proof", "err", err, "address", contractAddress, "key", storageKey) + } else { + l.deletionProof = append(l.deletionProof, delSibling) + } + } + } if l.cfg.EnableReturnData { structlog.ReturnData.Write(rData) @@ -381,6 +395,11 @@ func (l *StructLogger) UpdatedStorages() map[common.Address]Storage { return l.storage } +// DeletionProofs is used to collect all deletion proof collected in handling +func (l *StructLogger) DeletionProofs() [][]byte { + return l.deletionProof +} + // CreatedAccount return the account data in case it is a create tx func (l *StructLogger) CreatedAccount() *types.AccountWrapper { return l.createdAccount } diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go index 18dac4539633..c6adcd1fe633 100644 --- a/eth/tracers/api_blocktrace.go +++ b/eth/tracers/api_blocktrace.go @@ -13,6 +13,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" + "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/rcfg" @@ -42,6 +43,8 @@ type traceEnv struct { // this lock is used to protect StorageTrace's read and write mutual exclusion. sMu sync.Mutex *types.StorageTrace + // used for de-duplication of deletion proofs when handling mutiple txs + deletionProofAgg map[common.Hash][]byte executionResults []*types.ExecutionResult } @@ -119,6 +122,7 @@ func (api *API) createTraceEnv(ctx context.Context, config *TraceConfig, block * Proofs: make(map[string][]hexutil.Bytes), StorageProofs: make(map[string]map[string][]hexutil.Bytes), }, + deletionProofAgg: make(map[common.Hash][]byte), executionResults: make([]*types.ExecutionResult, block.Transactions().Len()), } @@ -314,7 +318,7 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc } env.sMu.Unlock() - proof, sibling, err := state.GetStorageTrieProof(addr, key) + proof, err := state.GetStorageTrieProof(addr, key) if err != nil { log.Error("Storage proof not available", "error", err, "address", addrStr, "key", keyStr) // but we still mark the proofs map with nil array @@ -325,13 +329,23 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc } env.sMu.Lock() m[keyStr] = wrappedProof - if sibling != nil { - env.DeletionProofs = append(env.DeletionProofs, sibling) - } env.sMu.Unlock() } } + deletionProofs := tracer.DeletionProofs() + // calc key and cache first, so we lock env shorter + // while merging + mergedProofs := make(map[common.Hash][]byte) + for _, bt := range deletionProofs { + mergedProofs[crypto.Keccak256Hash(bt)] = bt + } + env.sMu.Lock() + for key, bt := range mergedProofs { + env.deletionProofAgg[key] = bt + } + env.sMu.Unlock() + var l1Fee uint64 if result.L1Fee != nil { l1Fee = result.L1Fee.Uint64() @@ -360,6 +374,10 @@ func (api *API) fillBlockTrace(env *traceEnv, block *types.Block) (*types.BlockT txs[i] = types.NewTransactionData(tx, block.NumberU64(), api.backend.ChainConfig()) } + for _, bt := range env.deletionProofAgg { + env.DeletionProofs = append(env.DeletionProofs, bt) + } + blockTrace := &types.BlockTrace{ ChainID: api.backend.ChainConfig().ChainID.Uint64(), Version: params.ArchiveVersion(params.CommitHash), diff --git a/params/version.go b/params/version.go index 6804f33aa569..80a7671e196e 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 3 // Major version component of the current release VersionMinor = 1 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release + VersionPatch = 11 // Patch version component of the current release VersionMeta = "alpha" // Version metadata to append to the version string )