mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
refactor: remove unused storage traces (#902)
refactor: remove unused storage traces (#819) Co-authored-by: Ömer Faruk Irmak <omerfirmak@gmail.com>
This commit is contained in:
parent
fc5763e4bf
commit
74578fa06e
7 changed files with 17 additions and 1236 deletions
|
|
@ -19,7 +19,6 @@ type BlockTrace struct {
|
||||||
StorageTrace *StorageTrace `json:"storageTrace"`
|
StorageTrace *StorageTrace `json:"storageTrace"`
|
||||||
TxStorageTraces []*StorageTrace `json:"txStorageTraces,omitempty"`
|
TxStorageTraces []*StorageTrace `json:"txStorageTraces,omitempty"`
|
||||||
ExecutionResults []*ExecutionResult `json:"executionResults"`
|
ExecutionResults []*ExecutionResult `json:"executionResults"`
|
||||||
MPTWitness *json.RawMessage `json:"mptwitness,omitempty"`
|
|
||||||
WithdrawTrieRoot common.Hash `json:"withdraw_trie_root,omitempty"`
|
WithdrawTrieRoot common.Hash `json:"withdraw_trie_root,omitempty"`
|
||||||
StartL1QueueIndex uint64 `json:"startL1QueueIndex"`
|
StartL1QueueIndex uint64 `json:"startL1QueueIndex"`
|
||||||
}
|
}
|
||||||
|
|
@ -135,13 +134,12 @@ type ExtraData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountWrapper 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"`
|
||||||
KeccakCodeHash common.Hash `json:"keccakCodeHash,omitempty"`
|
KeccakCodeHash common.Hash `json:"keccakCodeHash,omitempty"`
|
||||||
PoseidonCodeHash common.Hash `json:"poseidonCodeHash,omitempty"`
|
PoseidonCodeHash common.Hash `json:"poseidonCodeHash,omitempty"`
|
||||||
CodeSize uint64 `json:"codeSize"`
|
CodeSize uint64 `json:"codeSize"`
|
||||||
Storage *StorageWrapper `json:"storage,omitempty"` // StorageWrapper can be empty if irrelated to storage operation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// StorageWrapper while key & value can also be retrieved from StructLogRes.Storage,
|
// StorageWrapper while key & value can also be retrieved from StructLogRes.Storage,
|
||||||
|
|
|
||||||
|
|
@ -219,7 +219,6 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
|
||||||
stackLen := len(stackData)
|
stackLen := len(stackData)
|
||||||
// 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
|
||||||
var recordStorageDetail bool
|
|
||||||
if op == vm.SLOAD || op == vm.SSTORE {
|
if op == vm.SLOAD || op == vm.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.
|
||||||
|
|
@ -233,7 +232,6 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
|
||||||
value = l.env.StateDB.GetState(contract.Address(), address)
|
value = l.env.StateDB.GetState(contract.Address(), address)
|
||||||
)
|
)
|
||||||
l.storage[contract.Address()][address] = value
|
l.storage[contract.Address()][address] = value
|
||||||
recordStorageDetail = true
|
|
||||||
if !l.cfg.DisableStorage {
|
if !l.cfg.DisableStorage {
|
||||||
storage = l.storage[contract.Address()].Copy()
|
storage = l.storage[contract.Address()].Copy()
|
||||||
}
|
}
|
||||||
|
|
@ -244,7 +242,6 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
|
||||||
address = common.Hash(stackData[stackLen-1].Bytes32())
|
address = common.Hash(stackData[stackLen-1].Bytes32())
|
||||||
)
|
)
|
||||||
l.storage[contract.Address()][address] = value
|
l.storage[contract.Address()][address] = value
|
||||||
recordStorageDetail = true
|
|
||||||
if !l.cfg.DisableStorage {
|
if !l.cfg.DisableStorage {
|
||||||
storage = l.storage[contract.Address()].Copy()
|
storage = l.storage[contract.Address()].Copy()
|
||||||
}
|
}
|
||||||
|
|
@ -258,12 +255,6 @@ func (l *StructLogger) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, s
|
||||||
// create a new snapshot of the EVM.
|
// create a new snapshot of the EVM.
|
||||||
structLog := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err, nil}
|
structLog := StructLog{pc, op, gas, cost, mem, memory.Len(), stck, rdata, storage, depth, l.env.StateDB.GetRefund(), err, nil}
|
||||||
|
|
||||||
if recordStorageDetail {
|
|
||||||
if err := traceStorage(l, scope, structLog.getOrInitExtraData()); err != nil {
|
|
||||||
log.Error("Failed to trace data", "opcode", op.String(), "err", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
execFuncList, ok := OpcodeExecs[op]
|
execFuncList, ok := OpcodeExecs[op]
|
||||||
if ok {
|
if ok {
|
||||||
// execute trace func list.
|
// execute trace func list.
|
||||||
|
|
|
||||||
|
|
@ -65,18 +65,6 @@ func traceContractCode(l *StructLogger, scope *vm.ScopeContext, extraData *types
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// traceStorage get contract's storage at storage_address
|
|
||||||
func traceStorage(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error {
|
|
||||||
if len(scope.Stack.Data()) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
key := common.Hash(scope.Stack.Peek().Bytes32())
|
|
||||||
storage := getWrappedAccountForStorage(l, scope.Contract.Address(), key)
|
|
||||||
extraData.StateList = append(extraData.StateList, storage)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// traceContractAccount gets the contract's account
|
// traceContractAccount gets the contract's account
|
||||||
func traceContractAccount(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error {
|
func traceContractAccount(l *StructLogger, scope *vm.ScopeContext, extraData *types.ExtraData) error {
|
||||||
// Get account state.
|
// Get account state.
|
||||||
|
|
@ -117,21 +105,6 @@ func getWrappedAccountForAddr(l *StructLogger, address common.Address) *types.Ac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getWrappedAccountForStorage(l *StructLogger, address common.Address, key common.Hash) *types.AccountWrapper {
|
|
||||||
return &types.AccountWrapper{
|
|
||||||
Address: address,
|
|
||||||
Nonce: l.env.StateDB.GetNonce(address),
|
|
||||||
Balance: (*hexutil.Big)(l.env.StateDB.GetBalance(address)),
|
|
||||||
KeccakCodeHash: l.env.StateDB.GetKeccakCodeHash(address),
|
|
||||||
PoseidonCodeHash: l.env.StateDB.GetPoseidonCodeHash(address),
|
|
||||||
CodeSize: l.env.StateDB.GetCodeSize(address),
|
|
||||||
Storage: &types.StorageWrapper{
|
|
||||||
Key: key.String(),
|
|
||||||
Value: l.env.StateDB.GetState(address, key).String(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCodeForAddr(l *StructLogger, address common.Address) []byte {
|
func getCodeForAddr(l *StructLogger, address common.Address) []byte {
|
||||||
return l.env.StateDB.GetCode(address)
|
return l.env.StateDB.GetCode(address)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -584,15 +584,6 @@ func (env *TraceEnv) fillBlockTrace(block *types.Block) (*types.BlockTrace, erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// only zktrie model has the ability to get `mptwitness`.
|
|
||||||
if env.chainConfig.Scroll.ZktrieEnabled() {
|
|
||||||
// // we use MPTWitnessNothing by default and do not allow switch among MPTWitnessType atm.
|
|
||||||
// // MPTWitness will be removed from traces in the future.
|
|
||||||
// if err := zkproof.FillBlockTraceForMPTWitness(zkproof.MPTWitnessNothing, blockTrace); err != nil {
|
|
||||||
// log.Error("fill mpt witness fail", "error", err)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
blockTrace.WithdrawTrieRoot = withdrawtrie.ReadWTRSlot(rcfg.L2MessageQueueAddress, env.state)
|
blockTrace.WithdrawTrieRoot = withdrawtrie.ReadWTRSlot(rcfg.L2MessageQueueAddress, env.state)
|
||||||
|
|
||||||
return blockTrace, nil
|
return blockTrace, nil
|
||||||
|
|
|
||||||
|
|
@ -1,349 +0,0 @@
|
||||||
package zkproof
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type opIterator interface {
|
|
||||||
next() *types.AccountWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
type opOrderer interface {
|
|
||||||
readonly(bool)
|
|
||||||
absorb(*types.AccountWrapper)
|
|
||||||
absorbStorage(*types.AccountWrapper, *types.StorageWrapper)
|
|
||||||
end_absorb() opIterator
|
|
||||||
}
|
|
||||||
|
|
||||||
type iterateOp []*types.AccountWrapper
|
|
||||||
|
|
||||||
func (ops *iterateOp) next() *types.AccountWrapper {
|
|
||||||
|
|
||||||
sl := *ops
|
|
||||||
|
|
||||||
if len(sl) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
*ops = sl[1:]
|
|
||||||
return sl[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
type simpleOrderer struct {
|
|
||||||
readOnly int
|
|
||||||
savedOp []*types.AccountWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (od *simpleOrderer) SavedOp() []*types.AccountWrapper { return od.savedOp }
|
|
||||||
|
|
||||||
func (od *simpleOrderer) readonly(mode bool) {
|
|
||||||
if mode {
|
|
||||||
od.readOnly += 1
|
|
||||||
} else if od.readOnly == 0 {
|
|
||||||
panic("unexpected readonly mode stack pop")
|
|
||||||
} else {
|
|
||||||
od.readOnly -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (od *simpleOrderer) absorb(st *types.AccountWrapper) {
|
|
||||||
if od.readOnly > 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
od.savedOp = append(od.savedOp, st)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (od *simpleOrderer) absorbStorage(st *types.AccountWrapper, _ *types.StorageWrapper) {
|
|
||||||
od.absorb(st)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (od *simpleOrderer) end_absorb() opIterator {
|
|
||||||
ret := iterateOp(od.savedOp)
|
|
||||||
return &ret
|
|
||||||
}
|
|
||||||
|
|
||||||
type multiOpIterator []opIterator
|
|
||||||
|
|
||||||
func (opss *multiOpIterator) next() *types.AccountWrapper {
|
|
||||||
|
|
||||||
sl := *opss
|
|
||||||
if len(sl) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
op := sl[0].next()
|
|
||||||
|
|
||||||
for op == nil {
|
|
||||||
|
|
||||||
sl = sl[1:]
|
|
||||||
*opss = sl
|
|
||||||
if len(sl) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
op = sl[0].next()
|
|
||||||
}
|
|
||||||
return op
|
|
||||||
}
|
|
||||||
|
|
||||||
type rwTblOrderer struct {
|
|
||||||
readOnly int
|
|
||||||
readOnlySnapshot struct {
|
|
||||||
accounts map[string]*types.AccountWrapper
|
|
||||||
storages map[string]map[string]*types.StorageWrapper
|
|
||||||
}
|
|
||||||
initedData map[common.Address]*types.AccountWrapper
|
|
||||||
|
|
||||||
// help to track all accounts being touched, and provide the
|
|
||||||
// completed account status for storage updating
|
|
||||||
traced map[string]*types.AccountWrapper
|
|
||||||
|
|
||||||
opAccNonce map[string]*types.AccountWrapper
|
|
||||||
opAccBalance map[string]*types.AccountWrapper
|
|
||||||
opAccCodeHash map[string]*types.AccountWrapper
|
|
||||||
opStorage map[string]map[string]*types.StorageWrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSimpleOrderer() *simpleOrderer { return &simpleOrderer{} }
|
|
||||||
|
|
||||||
func NewRWTblOrderer(inited map[common.Address]*types.StateAccount) *rwTblOrderer {
|
|
||||||
|
|
||||||
initedAcc := make(map[common.Address]*types.AccountWrapper)
|
|
||||||
for addr, data := range inited {
|
|
||||||
if data == nil {
|
|
||||||
initedAcc[addr] = &types.AccountWrapper{
|
|
||||||
Address: addr,
|
|
||||||
Balance: (*hexutil.Big)(big.NewInt(0)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bl := data.Balance
|
|
||||||
if bl == nil {
|
|
||||||
bl = big.NewInt(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
initedAcc[addr] = &types.AccountWrapper{
|
|
||||||
Address: addr,
|
|
||||||
Nonce: data.Nonce,
|
|
||||||
Balance: (*hexutil.Big)(bl),
|
|
||||||
KeccakCodeHash: common.BytesToHash(data.KeccakCodeHash),
|
|
||||||
PoseidonCodeHash: common.BytesToHash(data.PoseidonCodeHash),
|
|
||||||
CodeSize: data.CodeSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return &rwTblOrderer{
|
|
||||||
initedData: initedAcc,
|
|
||||||
traced: make(map[string]*types.AccountWrapper),
|
|
||||||
opAccNonce: make(map[string]*types.AccountWrapper),
|
|
||||||
opAccBalance: make(map[string]*types.AccountWrapper),
|
|
||||||
opAccCodeHash: make(map[string]*types.AccountWrapper),
|
|
||||||
opStorage: make(map[string]map[string]*types.StorageWrapper),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (od *rwTblOrderer) readonly(mode bool) {
|
|
||||||
if mode {
|
|
||||||
if od.readOnly == 0 {
|
|
||||||
od.readOnlySnapshot.accounts = make(map[string]*types.AccountWrapper)
|
|
||||||
od.readOnlySnapshot.storages = make(map[string]map[string]*types.StorageWrapper)
|
|
||||||
}
|
|
||||||
od.readOnly += 1
|
|
||||||
} else if od.readOnly == 0 {
|
|
||||||
panic("unexpected readonly mode stack pop")
|
|
||||||
} else {
|
|
||||||
od.readOnly -= 1
|
|
||||||
if od.readOnly == 0 {
|
|
||||||
for addrS, st := range od.readOnlySnapshot.accounts {
|
|
||||||
od.absorb(st)
|
|
||||||
if m, existed := od.readOnlySnapshot.storages[addrS]; existed {
|
|
||||||
for _, stg := range m {
|
|
||||||
st.Storage = stg
|
|
||||||
od.absorbStorage(st, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (od *rwTblOrderer) absorbStorage(st *types.AccountWrapper, before *types.StorageWrapper) {
|
|
||||||
if st.Storage == nil {
|
|
||||||
panic("do not call absorbStorage ")
|
|
||||||
}
|
|
||||||
|
|
||||||
od.absorb(st)
|
|
||||||
addrStr := st.Address.String()
|
|
||||||
|
|
||||||
if stg := st.Storage; stg != nil {
|
|
||||||
m, existed := od.opStorage[addrStr]
|
|
||||||
if !existed {
|
|
||||||
m = make(map[string]*types.StorageWrapper)
|
|
||||||
od.opStorage[addrStr] = m
|
|
||||||
}
|
|
||||||
|
|
||||||
// key must be unified into 32 bytes
|
|
||||||
keyBytes := hexutil.MustDecode(stg.Key)
|
|
||||||
keyStr := common.BytesToHash(keyBytes).String()
|
|
||||||
|
|
||||||
// trace every "touched" status for readOnly
|
|
||||||
if od.readOnly > 0 {
|
|
||||||
m, existed := od.readOnlySnapshot.storages[addrStr]
|
|
||||||
if !existed {
|
|
||||||
m = make(map[string]*types.StorageWrapper)
|
|
||||||
od.readOnlySnapshot.storages[addrStr] = m
|
|
||||||
}
|
|
||||||
if _, hashTraced := m[keyStr]; !hashTraced {
|
|
||||||
if before != nil {
|
|
||||||
m[keyStr] = before
|
|
||||||
} else {
|
|
||||||
m[keyStr] = stg
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m[keyStr] = stg
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (od *rwTblOrderer) absorb(st *types.AccountWrapper) {
|
|
||||||
|
|
||||||
initedRef, existed := od.initedData[st.Address]
|
|
||||||
if !existed {
|
|
||||||
panic("encounter unprepared status")
|
|
||||||
}
|
|
||||||
|
|
||||||
addrStr := st.Address.String()
|
|
||||||
|
|
||||||
// trace every "touched" status for readOnly
|
|
||||||
if od.readOnly > 0 {
|
|
||||||
snapShot, existed := od.traced[addrStr]
|
|
||||||
if !existed {
|
|
||||||
snapShot = initedRef
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, hasTraced := od.readOnlySnapshot.accounts[addrStr]; !hasTraced {
|
|
||||||
od.readOnlySnapshot.accounts[addrStr] = copyAccountState(snapShot)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDeletedAccount(st) {
|
|
||||||
// for account delete, made a safer data for status
|
|
||||||
st = &types.AccountWrapper{
|
|
||||||
Address: st.Address,
|
|
||||||
Balance: (*hexutil.Big)(big.NewInt(0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
od.traced[addrStr] = st
|
|
||||||
|
|
||||||
// notice there would be at least one entry for all 3 fields when accessing an address
|
|
||||||
// this may caused extract "read" op in mpt circuit which has no corresponding one in rwtable
|
|
||||||
// we can avoid it unless obtaining more tips from the understanding of opcode
|
|
||||||
// but it would be ok if we have adopted the new lookup way (root_prev, root_cur) under discussion:
|
|
||||||
// https://github.com/privacy-scaling-explorations/zkevm-specs/issues/217
|
|
||||||
|
|
||||||
if traced, existed := od.opAccNonce[addrStr]; !existed {
|
|
||||||
traced = copyAccountState(st)
|
|
||||||
traced.Balance = initedRef.Balance
|
|
||||||
traced.KeccakCodeHash = initedRef.KeccakCodeHash
|
|
||||||
traced.PoseidonCodeHash = initedRef.PoseidonCodeHash
|
|
||||||
traced.CodeSize = initedRef.CodeSize
|
|
||||||
traced.Storage = nil
|
|
||||||
od.opAccNonce[addrStr] = traced
|
|
||||||
} else {
|
|
||||||
traced.Nonce = st.Nonce
|
|
||||||
}
|
|
||||||
|
|
||||||
if traced, existed := od.opAccBalance[addrStr]; !existed {
|
|
||||||
traced = copyAccountState(st)
|
|
||||||
traced.KeccakCodeHash = initedRef.KeccakCodeHash
|
|
||||||
traced.PoseidonCodeHash = initedRef.PoseidonCodeHash
|
|
||||||
traced.CodeSize = initedRef.CodeSize
|
|
||||||
traced.Storage = nil
|
|
||||||
od.opAccBalance[addrStr] = traced
|
|
||||||
} else {
|
|
||||||
traced.Nonce = st.Nonce
|
|
||||||
traced.Balance = st.Balance
|
|
||||||
}
|
|
||||||
|
|
||||||
if traced, existed := od.opAccCodeHash[addrStr]; !existed {
|
|
||||||
traced = copyAccountState(st)
|
|
||||||
traced.Storage = nil
|
|
||||||
od.opAccCodeHash[addrStr] = traced
|
|
||||||
} else {
|
|
||||||
traced.Nonce = st.Nonce
|
|
||||||
traced.Balance = st.Balance
|
|
||||||
traced.KeccakCodeHash = st.KeccakCodeHash
|
|
||||||
traced.PoseidonCodeHash = st.PoseidonCodeHash
|
|
||||||
traced.CodeSize = st.CodeSize
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (od *rwTblOrderer) end_absorb() opIterator {
|
|
||||||
// now sort every map by address / key
|
|
||||||
// inited has collected all address, just sort address once
|
|
||||||
sortedAddrs := make([]string, 0, len(od.traced))
|
|
||||||
for addrs := range od.traced {
|
|
||||||
sortedAddrs = append(sortedAddrs, addrs)
|
|
||||||
}
|
|
||||||
sort.Strings(sortedAddrs)
|
|
||||||
|
|
||||||
var iterNonce []*types.AccountWrapper
|
|
||||||
var iterBalance []*types.AccountWrapper
|
|
||||||
var iterCodeHash []*types.AccountWrapper
|
|
||||||
var iterStorage []*types.AccountWrapper
|
|
||||||
|
|
||||||
for _, addrStr := range sortedAddrs {
|
|
||||||
|
|
||||||
if v, existed := od.opAccNonce[addrStr]; existed {
|
|
||||||
iterNonce = append(iterNonce, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, existed := od.opAccBalance[addrStr]; existed {
|
|
||||||
iterBalance = append(iterBalance, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, existed := od.opAccCodeHash[addrStr]; existed {
|
|
||||||
iterCodeHash = append(iterCodeHash, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stgM, existed := od.opStorage[addrStr]; existed {
|
|
||||||
|
|
||||||
tracedStatus := od.traced[addrStr]
|
|
||||||
if tracedStatus == nil {
|
|
||||||
panic("missed traced status found in storage slot")
|
|
||||||
}
|
|
||||||
|
|
||||||
sortedKeys := make([]string, 0, len(stgM))
|
|
||||||
for key := range stgM {
|
|
||||||
sortedKeys = append(sortedKeys, key)
|
|
||||||
}
|
|
||||||
sort.Strings(sortedKeys)
|
|
||||||
|
|
||||||
for _, key := range sortedKeys {
|
|
||||||
st := copyAccountState(tracedStatus)
|
|
||||||
st.Storage = stgM[key]
|
|
||||||
iterStorage = append(iterStorage, st)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var finalRet []opIterator
|
|
||||||
for _, arr := range [][]*types.AccountWrapper{iterNonce, iterBalance, iterCodeHash, iterStorage} {
|
|
||||||
wrappedIter := iterateOp(arr)
|
|
||||||
finalRet = append(finalRet, &wrappedIter)
|
|
||||||
}
|
|
||||||
|
|
||||||
wrappedRet := multiOpIterator(finalRet)
|
|
||||||
return &wrappedRet
|
|
||||||
}
|
|
||||||
|
|
@ -4,14 +4,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MPTWitnessType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
MPTWitnessNothing MPTWitnessType = iota
|
|
||||||
MPTWitnessNatural
|
|
||||||
MPTWitnessRWTbl
|
|
||||||
)
|
|
||||||
|
|
||||||
// SMTPathNode represent a node in the SMT Path, all hash is saved by the present of
|
// SMTPathNode represent a node in the SMT Path, all hash is saved by the present of
|
||||||
// zktype.Hash
|
// zktype.Hash
|
||||||
type SMTPathNode struct {
|
type SMTPathNode struct {
|
||||||
|
|
@ -57,3 +49,14 @@ type StorageTrace struct {
|
||||||
StatePath [2]*SMTPath `json:"statePath,omitempty"`
|
StatePath [2]*SMTPath `json:"statePath,omitempty"`
|
||||||
StateUpdate [2]*StateStorage `json:"stateUpdate,omitempty"`
|
StateUpdate [2]*StateStorage `json:"stateUpdate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProofList [][]byte
|
||||||
|
|
||||||
|
func (n *ProofList) Put(key []byte, value []byte) error {
|
||||||
|
*n = append(*n, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *ProofList) Delete(key []byte) error {
|
||||||
|
panic("not supported")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,826 +0,0 @@
|
||||||
package zkproof
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
zktrie "github.com/scroll-tech/zktrie/trie"
|
|
||||||
zkt "github.com/scroll-tech/zktrie/types"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProofList [][]byte
|
|
||||||
|
|
||||||
func (n *ProofList) Put(key []byte, value []byte) error {
|
|
||||||
*n = append(*n, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *ProofList) Delete(key []byte) error {
|
|
||||||
panic("not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func addressToKey(addr common.Address) *zkt.Hash {
|
|
||||||
var preImage zkt.Byte32
|
|
||||||
copy(preImage[:], addr.Bytes())
|
|
||||||
|
|
||||||
h, err := preImage.Hash()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("hash failure", "preImage", hexutil.Encode(preImage[:]))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return zkt.NewHashFromBigInt(h)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resume the proof bytes into db and return the leaf node
|
|
||||||
func resumeProofs(proof []hexutil.Bytes, db ethdb.Database) *zktrie.Node {
|
|
||||||
for _, buf := range proof {
|
|
||||||
n, err := zktrie.DecodeSMTProof(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("decode proof string fail", "error", err)
|
|
||||||
} else if n != nil {
|
|
||||||
hash, err := n.NodeHash()
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("node has no valid node hash", "error", err)
|
|
||||||
} else {
|
|
||||||
//notice: must consistent with trie/merkletree.go
|
|
||||||
bt := hash[:]
|
|
||||||
db.Put(bt, buf)
|
|
||||||
if n.Type == zktrie.NodeTypeLeaf_New || n.Type == zktrie.NodeTypeEmpty_New {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have a trick here which suppose the proof array include all middle nodes along the
|
|
||||||
// whole path in sequence, from root to leaf
|
|
||||||
func decodeProofForMPTPath(proof ProofList, path *SMTPath) {
|
|
||||||
var lastNode *zktrie.Node
|
|
||||||
keyPath := big.NewInt(0)
|
|
||||||
path.KeyPathPart = (*hexutil.Big)(keyPath)
|
|
||||||
|
|
||||||
keyCounter := big.NewInt(1)
|
|
||||||
|
|
||||||
for _, buf := range proof {
|
|
||||||
n, err := zktrie.DecodeSMTProof(buf)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("decode proof string fail", "error", err)
|
|
||||||
} else if n != nil {
|
|
||||||
hash, err := n.NodeHash()
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("node has no valid node hash", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if lastNode == nil {
|
|
||||||
// notice: use little-endian represent inside Hash ([:] or Byte32())
|
|
||||||
path.Root = hash[:]
|
|
||||||
} else {
|
|
||||||
if bytes.Equal(hash[:], lastNode.ChildL[:]) {
|
|
||||||
path.Path = append(path.Path, SMTPathNode{
|
|
||||||
Value: hash[:],
|
|
||||||
Sibling: lastNode.ChildR[:],
|
|
||||||
})
|
|
||||||
} else if bytes.Equal(hash[:], lastNode.ChildR[:]) {
|
|
||||||
path.Path = append(path.Path, SMTPathNode{
|
|
||||||
Value: hash[:],
|
|
||||||
Sibling: lastNode.ChildL[:],
|
|
||||||
})
|
|
||||||
keyPath.Add(keyPath, keyCounter)
|
|
||||||
} else {
|
|
||||||
panic("Unexpected proof form")
|
|
||||||
}
|
|
||||||
keyCounter.Mul(keyCounter, big.NewInt(2))
|
|
||||||
}
|
|
||||||
switch n.Type {
|
|
||||||
case zktrie.NodeTypeBranch_0, zktrie.NodeTypeBranch_1, zktrie.NodeTypeBranch_2, zktrie.NodeTypeBranch_3:
|
|
||||||
lastNode = n
|
|
||||||
case zktrie.NodeTypeLeaf_New:
|
|
||||||
vhash, _ := n.ValueHash()
|
|
||||||
path.Leaf = &SMTPathNode{
|
|
||||||
//here we just return the inner represent of hash (little endian, reversed byte order to common hash)
|
|
||||||
Value: vhash[:],
|
|
||||||
Sibling: n.NodeKey[:],
|
|
||||||
}
|
|
||||||
//sanity check
|
|
||||||
keyPart := keyPath.Bytes()
|
|
||||||
for i, b := range keyPart {
|
|
||||||
ri := len(keyPart) - i
|
|
||||||
cb := path.Leaf.Sibling[ri-1] //notice the output is little-endian
|
|
||||||
if b&cb != b {
|
|
||||||
panic(fmt.Errorf("path key not match: part is %x but key is %x", keyPart, []byte(path.Leaf.Sibling[:])))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case zktrie.NodeTypeEmpty_New:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("unknown node type %d", n.Type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("Unexpected finished here")
|
|
||||||
}
|
|
||||||
|
|
||||||
type zktrieProofWriter struct {
|
|
||||||
db *trie.ZktrieDatabase
|
|
||||||
tracingZktrie *trie.ZkTrie
|
|
||||||
tracingStorageTries map[common.Address]*trie.ZkTrie
|
|
||||||
tracingAccounts map[common.Address]*types.StateAccount
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wr *zktrieProofWriter) TracingAccounts() map[common.Address]*types.StateAccount {
|
|
||||||
return wr.tracingAccounts
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewZkTrieProofWriter(storage *types.StorageTrace) (*zktrieProofWriter, error) {
|
|
||||||
underlayerDb := rawdb.NewMemoryDatabase()
|
|
||||||
zkDb := trie.NewZktrieDatabase(underlayerDb)
|
|
||||||
accounts := make(map[common.Address]*types.StateAccount)
|
|
||||||
|
|
||||||
// resuming proof bytes to underlayerDb
|
|
||||||
for addrs, proof := range storage.Proofs {
|
|
||||||
if n := resumeProofs(proof, underlayerDb); n != nil {
|
|
||||||
addr := common.HexToAddress(addrs)
|
|
||||||
if n.Type == zktrie.NodeTypeEmpty_New {
|
|
||||||
accounts[addr] = nil
|
|
||||||
} else if acc, err := types.UnmarshalStateAccount(n.Data()); err == nil {
|
|
||||||
if bytes.Equal(n.NodeKey[:], addressToKey(addr)[:]) {
|
|
||||||
accounts[addr] = acc
|
|
||||||
} else {
|
|
||||||
// should still mark the address as being trace (data not existed yet)
|
|
||||||
accounts[addr] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("decode account bytes fail: %s, raw data [%x]", err, n.Data())
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("can not resume proof for address %s", addrs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
storages := make(map[common.Address]*trie.ZkTrie)
|
|
||||||
|
|
||||||
for addrs, stgLists := range storage.StorageProofs {
|
|
||||||
addr := common.HexToAddress(addrs)
|
|
||||||
accState, existed := accounts[addr]
|
|
||||||
if !existed {
|
|
||||||
// trace is malformed but currently we just warn about that
|
|
||||||
log.Warn("no account state found for this addr, mal records", "address", addrs)
|
|
||||||
continue
|
|
||||||
} else if accState == nil {
|
|
||||||
// create an empty zktrie for uninit address
|
|
||||||
storages[addr], _ = trie.NewZkTrie(common.Hash{}, zkDb)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for keys, proof := range stgLists {
|
|
||||||
if n := resumeProofs(proof, underlayerDb); n != nil {
|
|
||||||
var err error
|
|
||||||
storages[addr], err = trie.NewZkTrie(accState.Root, zkDb)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("zktrie create failure for storage in addr <%s>: %s, (root %s)", addrs, err, accState.Root)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("can not resume proof for storage %s@%s", keys, addrs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, delProof := range storage.DeletionProofs {
|
|
||||||
n, err := zktrie.DecodeSMTProof(delProof)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("decode delproof string fail", "error", err, "node", delProof)
|
|
||||||
} else if n != nil {
|
|
||||||
hash, err := n.NodeHash()
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("node has no valid node hash", "error", err)
|
|
||||||
} else {
|
|
||||||
//notice: must consistent with trie/merkletree.go
|
|
||||||
bt := hash[:]
|
|
||||||
underlayerDb.Put(bt, delProof)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zktrie, err := trie.NewZkTrie(
|
|
||||||
storage.RootBefore,
|
|
||||||
trie.NewZktrieDatabase(underlayerDb),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("zktrie create failure: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanity check
|
|
||||||
if !bytes.Equal(zktrie.Hash().Bytes(), storage.RootBefore.Bytes()) {
|
|
||||||
return nil, fmt.Errorf("unmatch init trie hash: expected %x but has %x", storage.RootBefore.Bytes(), zktrie.Hash().Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
return &zktrieProofWriter{
|
|
||||||
db: zkDb,
|
|
||||||
tracingZktrie: zktrie,
|
|
||||||
tracingAccounts: accounts,
|
|
||||||
tracingStorageTries: storages,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
posSSTOREBefore = 0
|
|
||||||
posCREATE = 0
|
|
||||||
posCREATEAfter = 1
|
|
||||||
posCALL = 2
|
|
||||||
posSTATICCALL = 0
|
|
||||||
|
|
||||||
// posSELFDESTRUCT = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
func getAccountState(l types.StructLogRes, pos int) *types.AccountWrapper {
|
|
||||||
if exData := l.ExtraData; exData == nil {
|
|
||||||
return nil
|
|
||||||
} else if len(exData.StateList) < pos {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return exData.StateList[pos]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyAccountState(st *types.AccountWrapper) *types.AccountWrapper {
|
|
||||||
var stg *types.StorageWrapper
|
|
||||||
if st.Storage != nil {
|
|
||||||
stg = &types.StorageWrapper{
|
|
||||||
Key: st.Storage.Key,
|
|
||||||
Value: st.Storage.Value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.AccountWrapper{
|
|
||||||
Nonce: st.Nonce,
|
|
||||||
Balance: (*hexutil.Big)(big.NewInt(0).Set(st.Balance.ToInt())),
|
|
||||||
KeccakCodeHash: st.KeccakCodeHash,
|
|
||||||
PoseidonCodeHash: st.PoseidonCodeHash,
|
|
||||||
CodeSize: st.CodeSize,
|
|
||||||
Address: st.Address,
|
|
||||||
Storage: stg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDeletedAccount(state *types.AccountWrapper) bool {
|
|
||||||
return state.Nonce == 0 && bytes.Equal(state.KeccakCodeHash.Bytes(), common.Hash{}.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAccountDataFromLogState(state *types.AccountWrapper) *types.StateAccount {
|
|
||||||
if isDeletedAccount(state) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &types.StateAccount{
|
|
||||||
Nonce: state.Nonce,
|
|
||||||
Balance: (*big.Int)(state.Balance),
|
|
||||||
KeccakCodeHash: state.KeccakCodeHash.Bytes(),
|
|
||||||
PoseidonCodeHash: state.PoseidonCodeHash.Bytes(),
|
|
||||||
CodeSize: state.CodeSize,
|
|
||||||
// Root omitted intentionally
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for sanity check
|
|
||||||
func verifyAccount(addr common.Address, data *types.StateAccount, leaf *SMTPathNode) error {
|
|
||||||
if leaf == nil {
|
|
||||||
if data != nil {
|
|
||||||
return fmt.Errorf("path has no corresponding leaf for account")
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addrKey := addressToKey(addr)
|
|
||||||
if !bytes.Equal(addrKey[:], leaf.Sibling) {
|
|
||||||
if data != nil {
|
|
||||||
return fmt.Errorf("unmatch leaf node in address: %s", addr)
|
|
||||||
}
|
|
||||||
} else if data != nil {
|
|
||||||
arr, flag := data.MarshalFields()
|
|
||||||
h, err := zkt.HandlingElemsAndByte32(flag, arr)
|
|
||||||
//log.Info("sanity check acc before", "addr", addr.String(), "key", leaf.Sibling.Text(16), "hash", h.Text(16))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("fail to hash account: %v", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(h[:], leaf.Value) {
|
|
||||||
return fmt.Errorf("unmatch data in leaf for address %s", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// for sanity check
|
|
||||||
func verifyStorage(key *zkt.Byte32, data *zkt.Byte32, leaf *SMTPathNode) error {
|
|
||||||
emptyData := bytes.Equal(data[:], common.Hash{}.Bytes())
|
|
||||||
|
|
||||||
if leaf == nil {
|
|
||||||
if !emptyData {
|
|
||||||
return fmt.Errorf("path has no corresponding leaf for storage")
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
keyHash, err := key.Hash()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(zkt.NewHashFromBigInt(keyHash)[:], leaf.Sibling) {
|
|
||||||
if !emptyData {
|
|
||||||
return fmt.Errorf("unmatch leaf node in storage: %x", key[:])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
h, err := data.Hash()
|
|
||||||
//log.Info("sanity check acc before", "addr", addr.String(), "key", leaf.Sibling.Text(16), "hash", h.Text(16))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("fail to hash data: %v", err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(zkt.NewHashFromBigInt(h)[:], leaf.Value) {
|
|
||||||
return fmt.Errorf("unmatch data in leaf for storage %x", key[:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// update traced account state, and return the corresponding trace object which
|
|
||||||
// is still opened for more infos
|
|
||||||
// the updated accData state is obtained by a closure which enable it being derived from current status
|
|
||||||
func (w *zktrieProofWriter) traceAccountUpdate(addr common.Address, updateAccData func(*types.StateAccount) *types.StateAccount) (*StorageTrace, error) {
|
|
||||||
out := new(StorageTrace)
|
|
||||||
//account trie
|
|
||||||
out.Address = addr.Bytes()
|
|
||||||
out.AccountPath = [2]*SMTPath{{}, {}}
|
|
||||||
//fill dummy
|
|
||||||
out.AccountUpdate = [2]*StateAccount{}
|
|
||||||
|
|
||||||
accDataBefore, existed := w.tracingAccounts[addr]
|
|
||||||
if !existed {
|
|
||||||
//sanity check
|
|
||||||
panic(fmt.Errorf("code do not add initialized status for account %s", addr))
|
|
||||||
}
|
|
||||||
|
|
||||||
var proof ProofList
|
|
||||||
s_key, _ := zkt.ToSecureKeyBytes(addr.Bytes())
|
|
||||||
if err := w.tracingZktrie.Prove(s_key.Bytes(), &proof); err != nil {
|
|
||||||
return nil, fmt.Errorf("prove BEFORE state fail: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeProofForMPTPath(proof, out.AccountPath[0])
|
|
||||||
if err := verifyAccount(addr, accDataBefore, out.AccountPath[0].Leaf); err != nil {
|
|
||||||
panic(fmt.Errorf("code fail to trace account status correctly: %s", err))
|
|
||||||
}
|
|
||||||
if accDataBefore != nil {
|
|
||||||
// we have ensured the nBefore has a key corresponding to the query one
|
|
||||||
out.AccountKey = out.AccountPath[0].Leaf.Sibling
|
|
||||||
out.AccountUpdate[0] = &StateAccount{
|
|
||||||
Nonce: int(accDataBefore.Nonce),
|
|
||||||
Balance: (*hexutil.Big)(big.NewInt(0).Set(accDataBefore.Balance)),
|
|
||||||
KeccakCodeHash: accDataBefore.KeccakCodeHash,
|
|
||||||
PoseidonCodeHash: accDataBefore.PoseidonCodeHash,
|
|
||||||
CodeSize: accDataBefore.CodeSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
accData := updateAccData(accDataBefore)
|
|
||||||
if accData != nil {
|
|
||||||
out.AccountUpdate[1] = &StateAccount{
|
|
||||||
Nonce: int(accData.Nonce),
|
|
||||||
Balance: (*hexutil.Big)(big.NewInt(0).Set(accData.Balance)),
|
|
||||||
KeccakCodeHash: accData.KeccakCodeHash,
|
|
||||||
PoseidonCodeHash: accData.PoseidonCodeHash,
|
|
||||||
CodeSize: accData.CodeSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if accData != nil {
|
|
||||||
if err := w.tracingZktrie.TryUpdateAccount(addr.Bytes32(), accData); err != nil {
|
|
||||||
return nil, fmt.Errorf("update zktrie account state fail: %s", err)
|
|
||||||
}
|
|
||||||
w.tracingAccounts[addr] = accData
|
|
||||||
} else if accDataBefore != nil {
|
|
||||||
if err := w.tracingZktrie.TryDelete(addr.Bytes32()); err != nil {
|
|
||||||
return nil, fmt.Errorf("delete zktrie account state fail: %s", err)
|
|
||||||
}
|
|
||||||
w.tracingAccounts[addr] = nil
|
|
||||||
} // notice if both before/after is nil, we do not touch zktrie
|
|
||||||
|
|
||||||
proof = ProofList{}
|
|
||||||
if err := w.tracingZktrie.Prove(s_key.Bytes(), &proof); err != nil {
|
|
||||||
return nil, fmt.Errorf("prove AFTER state fail: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeProofForMPTPath(proof, out.AccountPath[1])
|
|
||||||
if err := verifyAccount(addr, accData, out.AccountPath[1].Leaf); err != nil {
|
|
||||||
panic(fmt.Errorf("state AFTER has no valid account: %s", err))
|
|
||||||
}
|
|
||||||
if accData != nil {
|
|
||||||
if out.AccountKey == nil {
|
|
||||||
out.AccountKey = out.AccountPath[1].Leaf.Sibling[:]
|
|
||||||
}
|
|
||||||
//now accountKey must has been filled
|
|
||||||
}
|
|
||||||
|
|
||||||
// notice we have change that no leaf (account data) exist in either before or after,
|
|
||||||
// for that case we had to calculate the nodeKey here
|
|
||||||
if out.AccountKey == nil {
|
|
||||||
word := zkt.NewByte32FromBytesPaddingZero(addr.Bytes())
|
|
||||||
k, err := word.Hash()
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Errorf("unexpected hash error for address: %s", err))
|
|
||||||
}
|
|
||||||
kHash := zkt.NewHashFromBigInt(k)
|
|
||||||
out.AccountKey = hexutil.Bytes(kHash[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// update traced storage state, and return the corresponding trace object
|
|
||||||
func (w *zktrieProofWriter) traceStorageUpdate(addr common.Address, key, value []byte) (*StorageTrace, error) {
|
|
||||||
trie := w.tracingStorageTries[addr]
|
|
||||||
if trie == nil {
|
|
||||||
return nil, fmt.Errorf("no trace storage trie for %s", addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
statePath := [2]*SMTPath{{}, {}}
|
|
||||||
stateUpdate := [2]*StateStorage{}
|
|
||||||
|
|
||||||
storeKey := zkt.NewByte32FromBytesPaddingZero(common.BytesToHash(key).Bytes())
|
|
||||||
storeValueBefore := trie.Get(storeKey[:])
|
|
||||||
storeValue := zkt.NewByte32FromBytes(value)
|
|
||||||
valZero := zkt.Byte32{}
|
|
||||||
|
|
||||||
if storeValueBefore != nil && !bytes.Equal(storeValueBefore[:], common.Hash{}.Bytes()) {
|
|
||||||
stateUpdate[0] = &StateStorage{
|
|
||||||
Key: storeKey.Bytes(),
|
|
||||||
Value: storeValueBefore,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var storageBeforeProof, storageAfterProof ProofList
|
|
||||||
s_key, _ := zkt.ToSecureKeyBytes(storeKey.Bytes())
|
|
||||||
if err := trie.Prove(s_key.Bytes(), &storageBeforeProof); err != nil {
|
|
||||||
return nil, fmt.Errorf("prove BEFORE storage state fail: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeProofForMPTPath(storageBeforeProof, statePath[0])
|
|
||||||
if err := verifyStorage(storeKey, zkt.NewByte32FromBytes(storeValueBefore), statePath[0].Leaf); err != nil {
|
|
||||||
panic(fmt.Errorf("storage BEFORE has no valid data: %s (%v)", err, statePath[0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(storeValue.Bytes(), common.Hash{}.Bytes()) {
|
|
||||||
if err := trie.TryUpdate(storeKey.Bytes(), storeValue.Bytes()); err != nil {
|
|
||||||
return nil, fmt.Errorf("update zktrie storage fail: %s", err)
|
|
||||||
}
|
|
||||||
stateUpdate[1] = &StateStorage{
|
|
||||||
Key: storeKey.Bytes(),
|
|
||||||
Value: storeValue.Bytes(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := trie.TryDelete(storeKey.Bytes()); err != nil {
|
|
||||||
return nil, fmt.Errorf("delete zktrie storage fail: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := trie.Prove(s_key.Bytes(), &storageAfterProof); err != nil {
|
|
||||||
return nil, fmt.Errorf("prove AFTER storage state fail: %s", err)
|
|
||||||
}
|
|
||||||
decodeProofForMPTPath(storageAfterProof, statePath[1])
|
|
||||||
if err := verifyStorage(storeKey, storeValue, statePath[1].Leaf); err != nil {
|
|
||||||
panic(fmt.Errorf("storage AFTER has no valid data: %s (%v)", err, statePath[1]))
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := w.traceAccountUpdate(addr,
|
|
||||||
func(acc *types.StateAccount) *types.StateAccount {
|
|
||||||
if acc == nil {
|
|
||||||
// in case we read an unexist account
|
|
||||||
if !bytes.Equal(valZero.Bytes(), value) {
|
|
||||||
panic(fmt.Errorf("write to an unexist account [%s] which is not allowed", addr))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//sanity check
|
|
||||||
if accRootFromState := zkt.ReverseByteOrder(statePath[0].Root); !bytes.Equal(acc.Root[:], accRootFromState) {
|
|
||||||
panic(fmt.Errorf("unexpected storage root before: [%s] vs [%x]", acc.Root, accRootFromState))
|
|
||||||
}
|
|
||||||
return &types.StateAccount{
|
|
||||||
Nonce: acc.Nonce,
|
|
||||||
Balance: acc.Balance,
|
|
||||||
Root: common.BytesToHash(zkt.ReverseByteOrder(statePath[1].Root)),
|
|
||||||
KeccakCodeHash: acc.KeccakCodeHash,
|
|
||||||
PoseidonCodeHash: acc.PoseidonCodeHash,
|
|
||||||
CodeSize: acc.CodeSize,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("update account %s in SSTORE fail: %s", addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if stateUpdate[1] != nil {
|
|
||||||
out.StateKey = statePath[1].Leaf.Sibling
|
|
||||||
} else if stateUpdate[0] != nil {
|
|
||||||
out.StateKey = statePath[0].Leaf.Sibling
|
|
||||||
} else {
|
|
||||||
// it occurs when we are handling SLOAD with non-exist value
|
|
||||||
// still no pretty idea, had to touch the internal behavior in zktrie ....
|
|
||||||
if h, err := storeKey.Hash(); err != nil {
|
|
||||||
return nil, fmt.Errorf("hash storekey fail: %s", err)
|
|
||||||
} else {
|
|
||||||
out.StateKey = zkt.NewHashFromBigInt(h)[:]
|
|
||||||
}
|
|
||||||
stateUpdate[1] = &StateStorage{
|
|
||||||
Key: storeKey.Bytes(),
|
|
||||||
Value: valZero.Bytes(),
|
|
||||||
}
|
|
||||||
stateUpdate[0] = stateUpdate[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
out.StatePath = statePath
|
|
||||||
out.StateUpdate = stateUpdate
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *zktrieProofWriter) HandleNewState(accountState *types.AccountWrapper) (*StorageTrace, error) {
|
|
||||||
if accountState.Storage != nil {
|
|
||||||
storeAddr := hexutil.MustDecode(accountState.Storage.Key)
|
|
||||||
storeValue := hexutil.MustDecode(accountState.Storage.Value)
|
|
||||||
return w.traceStorageUpdate(accountState.Address, storeAddr, storeValue)
|
|
||||||
} else {
|
|
||||||
var stateRoot common.Hash
|
|
||||||
accData := getAccountDataFromLogState(accountState)
|
|
||||||
|
|
||||||
out, err := w.traceAccountUpdate(accountState.Address, func(accBefore *types.StateAccount) *types.StateAccount {
|
|
||||||
if accBefore != nil {
|
|
||||||
stateRoot = accBefore.Root
|
|
||||||
}
|
|
||||||
// we need to restore stateRoot from before
|
|
||||||
if accData != nil {
|
|
||||||
accData.Root = stateRoot
|
|
||||||
}
|
|
||||||
return accData
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("update account state %s fail: %s", accountState.Address, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := zkt.NewHashFromBytes(stateRoot[:])
|
|
||||||
out.CommonStateRoot = hash[:]
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLogs(od opOrderer, currentContract common.Address, logs []types.StructLogRes) {
|
|
||||||
logStack := []int{0}
|
|
||||||
contractStack := map[int]common.Address{}
|
|
||||||
callEnterAddress := currentContract
|
|
||||||
|
|
||||||
// now trace every OP which could cause changes on state:
|
|
||||||
for i, sLog := range logs {
|
|
||||||
//trace log stack by depth rather than scanning specified op
|
|
||||||
if sl := len(logStack); sl < sLog.Depth {
|
|
||||||
logStack = append(logStack, i)
|
|
||||||
//update currentContract according to previous op
|
|
||||||
contractStack[sl] = currentContract
|
|
||||||
currentContract = callEnterAddress
|
|
||||||
} else if sl > sLog.Depth {
|
|
||||||
logStack = logStack[:sl-1]
|
|
||||||
currentContract = contractStack[sLog.Depth]
|
|
||||||
resumePos := logStack[len(logStack)-1]
|
|
||||||
calledLog := logs[resumePos]
|
|
||||||
|
|
||||||
//no need to handle fail calling
|
|
||||||
if calledLog.ExtraData != nil {
|
|
||||||
if !calledLog.ExtraData.CallFailed {
|
|
||||||
//reentry the last log which "cause" the calling, some handling may needed
|
|
||||||
switch calledLog.Op {
|
|
||||||
case "CREATE", "CREATE2":
|
|
||||||
//addr, accDataBefore := getAccountDataFromProof(calledLog, posCALLBefore)
|
|
||||||
od.absorb(getAccountState(calledLog, posCREATEAfter))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
od.readonly(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logStack[sl-1] = i
|
|
||||||
}
|
|
||||||
//sanity check
|
|
||||||
if len(logStack) != sLog.Depth {
|
|
||||||
panic("tracking log stack failure")
|
|
||||||
}
|
|
||||||
callEnterAddress = currentContract
|
|
||||||
|
|
||||||
//check extra status for current op if it is a call
|
|
||||||
if extraData := sLog.ExtraData; extraData != nil {
|
|
||||||
if extraData.CallFailed || len(sLog.ExtraData.Caller) < 2 {
|
|
||||||
// no enough caller data (2) is being capture indicate we are in an immediate failure
|
|
||||||
// i.e. it fail before stack entry (like no enough balance for a "call with value"),
|
|
||||||
// or we just not handle this calling op correctly yet
|
|
||||||
|
|
||||||
// for a failed option, now we just purpose nothing happens (FIXME: it is inconsentent with mpt_table)
|
|
||||||
// except for CREATE, for which the callee's nonce would be increased
|
|
||||||
switch sLog.Op {
|
|
||||||
case "CREATE", "CREATE2":
|
|
||||||
st := copyAccountState(extraData.Caller[0])
|
|
||||||
st.Nonce += 1
|
|
||||||
od.absorb(st)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if extraData.CallFailed {
|
|
||||||
od.readonly(true)
|
|
||||||
}
|
|
||||||
// now trace caller's status first
|
|
||||||
if caller := extraData.Caller; len(caller) >= 2 {
|
|
||||||
od.absorb(caller[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch sLog.Op {
|
|
||||||
case "SELFDESTRUCT":
|
|
||||||
// NOTE: this op code has been disabled so we treat it as nothing now
|
|
||||||
|
|
||||||
//in SELFDESTRUCT, a call on target address is made so the balance would be updated
|
|
||||||
//in the last item
|
|
||||||
//stateTarget := getAccountState(sLog, posSELFDESTRUCT)
|
|
||||||
//od.absorb(stateTarget)
|
|
||||||
//then build an "deleted state", only address and other are default
|
|
||||||
//od.absorb(&types.AccountWrapper{Address: currentContract})
|
|
||||||
|
|
||||||
case "CREATE", "CREATE2":
|
|
||||||
// notice in immediate failure we have no enough tracing in extraData
|
|
||||||
if len(sLog.ExtraData.StateList) >= 2 {
|
|
||||||
state := getAccountState(sLog, posCREATE)
|
|
||||||
od.absorb(state)
|
|
||||||
//update contract to CREATE addr
|
|
||||||
callEnterAddress = state.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
case "CALL", "CALLCODE":
|
|
||||||
// notice in immediate failure we have no enough tracing in extraData
|
|
||||||
if len(sLog.ExtraData.StateList) >= 3 {
|
|
||||||
state := getAccountState(sLog, posCALL)
|
|
||||||
od.absorb(state)
|
|
||||||
callEnterAddress = state.Address
|
|
||||||
}
|
|
||||||
case "STATICCALL":
|
|
||||||
//static call has no update on target address (and no immediate failure?)
|
|
||||||
callEnterAddress = getAccountState(sLog, posSTATICCALL).Address
|
|
||||||
case "DELEGATECALL":
|
|
||||||
|
|
||||||
case "SLOAD":
|
|
||||||
accountState := getAccountState(sLog, posSSTOREBefore)
|
|
||||||
od.absorbStorage(accountState, nil)
|
|
||||||
case "SSTORE":
|
|
||||||
log.Debug("build SSTORE", "pc", sLog.Pc, "key", (*sLog.Stack)[len(*(sLog.Stack))-1])
|
|
||||||
accountState := copyAccountState(getAccountState(sLog, posSSTOREBefore))
|
|
||||||
// notice the log only provide the value BEFORE store and it is not suitable for our protocol,
|
|
||||||
// here we change it into value AFTER update
|
|
||||||
before := accountState.Storage
|
|
||||||
accountState.Storage = &types.StorageWrapper{
|
|
||||||
Key: (*sLog.Stack)[len(*(sLog.Stack))-1],
|
|
||||||
Value: (*sLog.Stack)[len(*(sLog.Stack))-2],
|
|
||||||
}
|
|
||||||
od.absorbStorage(accountState, before)
|
|
||||||
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleTx(od opOrderer, txResult *types.ExecutionResult) {
|
|
||||||
// the from state is read before tx is handled and nonce is added, we combine both
|
|
||||||
preTxSt := copyAccountState(txResult.From)
|
|
||||||
preTxSt.Nonce += 1
|
|
||||||
od.absorb(preTxSt)
|
|
||||||
|
|
||||||
if txResult.Failed {
|
|
||||||
od.readonly(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
var toAddr common.Address
|
|
||||||
if state := txResult.AccountCreated; state != nil {
|
|
||||||
od.absorb(state)
|
|
||||||
toAddr = state.Address
|
|
||||||
} else {
|
|
||||||
toAddr = txResult.To.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLogs(od, toAddr, txResult.StructLogs)
|
|
||||||
if txResult.Failed {
|
|
||||||
od.readonly(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, state := range txResult.AccountsAfter {
|
|
||||||
// special case: for suicide, the state has been captured in SELFDESTRUCT
|
|
||||||
// and we skip it here
|
|
||||||
if isDeletedAccount(state) {
|
|
||||||
log.Debug("skip suicide address", "address", state.Address)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
od.absorb(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultOrdererScheme = MPTWitnessRWTbl
|
|
||||||
|
|
||||||
var usedOrdererScheme = defaultOrdererScheme
|
|
||||||
|
|
||||||
func SetOrderScheme(t MPTWitnessType) { usedOrdererScheme = t }
|
|
||||||
|
|
||||||
// HandleBlockTrace only for backward compatibility
|
|
||||||
func HandleBlockTrace(block *types.BlockTrace) ([]*StorageTrace, error) {
|
|
||||||
return HandleBlockTraceEx(block, usedOrdererScheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleBlockTraceEx(block *types.BlockTrace, ordererScheme MPTWitnessType) ([]*StorageTrace, error) {
|
|
||||||
writer, err := NewZkTrieProofWriter(block.StorageTrace)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var od opOrderer
|
|
||||||
switch ordererScheme {
|
|
||||||
case MPTWitnessNothing:
|
|
||||||
panic("should not come here when scheme is 0")
|
|
||||||
case MPTWitnessNatural:
|
|
||||||
od = &simpleOrderer{}
|
|
||||||
case MPTWitnessRWTbl:
|
|
||||||
od = NewRWTblOrderer(writer.tracingAccounts)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unrecognized scheme %d", ordererScheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tx := range block.ExecutionResults {
|
|
||||||
HandleTx(od, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// notice some coinbase addr (like all zero) is in fact not exist and should not be update
|
|
||||||
// TODO: not a good solution, just for patch ...
|
|
||||||
if coinbaseData := writer.tracingAccounts[block.Coinbase.Address]; coinbaseData != nil {
|
|
||||||
od.absorb(block.Coinbase)
|
|
||||||
}
|
|
||||||
|
|
||||||
opDisp := od.end_absorb()
|
|
||||||
var outTrace []*StorageTrace
|
|
||||||
|
|
||||||
for op := opDisp.next(); op != nil; op = opDisp.next() {
|
|
||||||
trace, err := writer.HandleNewState(op)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
outTrace = append(outTrace, trace)
|
|
||||||
}
|
|
||||||
|
|
||||||
finalHash := writer.tracingZktrie.Hash()
|
|
||||||
if !bytes.Equal(finalHash.Bytes(), block.StorageTrace.RootAfter.Bytes()) {
|
|
||||||
return outTrace, fmt.Errorf("unmatch hash: [%x] vs [%x]", finalHash.Bytes(), block.StorageTrace.RootAfter.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
return outTrace, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func FillBlockTraceForMPTWitness(order MPTWitnessType, block *types.BlockTrace) error {
|
|
||||||
if order == MPTWitnessNothing {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
trace, err := HandleBlockTraceEx(block, order)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
msg, err := json.Marshal(trace)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rawmsg := json.RawMessage(msg)
|
|
||||||
|
|
||||||
block.MPTWitness = &rawmsg
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue