diff --git a/core/state/intra_block_state.go b/core/state/intra_block_state.go index 92d625cf3c2..5e3e5877269 100644 --- a/core/state/intra_block_state.go +++ b/core/state/intra_block_state.go @@ -61,6 +61,8 @@ type IntraBlockState struct { stateObjects map[libcommon.Address]*stateObject stateObjectsDirty map[libcommon.Address]struct{} + seenStateObjects map[libcommon.Address]struct{} // State objects that have been seen at least once + nilAccounts map[libcommon.Address]struct{} // Remember non-existent account to avoid reading them again // DB error. @@ -86,11 +88,12 @@ type IntraBlockState struct { // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. - journal *journal - validRevisions []revision - nextRevisionID int - trace bool - balanceInc map[libcommon.Address]*BalanceIncrease // Map of balance increases (without first reading the account) + journal *journal + validRevisions []revision + nextRevisionID int + trace bool + balanceInc map[libcommon.Address]*BalanceIncrease // Map of balance increases (without first reading the account) + disableBalanceInc bool // Disable balance increase tracking and eagerly read accounts } // Create a new state from a given trie @@ -98,6 +101,7 @@ func New(stateReader StateReader) *IntraBlockState { return &IntraBlockState{ stateReader: stateReader, stateObjects: map[libcommon.Address]*stateObject{}, + seenStateObjects: map[libcommon.Address]struct{}{}, stateObjectsDirty: map[libcommon.Address]struct{}{}, nilAccounts: map[libcommon.Address]struct{}{}, logs: map[libcommon.Hash][]*types.Log{}, @@ -112,6 +116,10 @@ func (sdb *IntraBlockState) SetTrace(trace bool) { sdb.trace = trace } +func (sdb *IntraBlockState) SetDisableBalanceInc(disable bool) { + sdb.disableBalanceInc = disable +} + // setErrorUnsafe sets error but should be called in medhods that already have locks func (sdb *IntraBlockState) setErrorUnsafe(err error) { if sdb.savedErr == nil { @@ -306,24 +314,27 @@ func (sdb *IntraBlockState) AddBalance(addr libcommon.Address, amount *uint256.I if sdb.trace { fmt.Printf("AddBalance %x, %d\n", addr, amount) } - // If this account has not been read, add to the balance increment map - _, needAccount := sdb.stateObjects[addr] - if !needAccount && addr == ripemd && amount.IsZero() { - needAccount = true - } - if !needAccount { - sdb.journal.append(balanceIncrease{ - account: &addr, - increase: *amount, - }) - bi, ok := sdb.balanceInc[addr] - if !ok { - bi = &BalanceIncrease{} - sdb.balanceInc[addr] = bi + + if !sdb.disableBalanceInc { + // If this account has not been read, add to the balance increment map + _, needAccount := sdb.stateObjects[addr] + if !needAccount && addr == ripemd && amount.IsZero() { + needAccount = true + } + if !needAccount { + sdb.journal.append(balanceIncrease{ + account: &addr, + increase: *amount, + }) + bi, ok := sdb.balanceInc[addr] + if !ok { + bi = &BalanceIncrease{} + sdb.balanceInc[addr] = bi + } + bi.increase.Add(&bi.increase, amount) + bi.count++ + return } - bi.increase.Add(&bi.increase, amount) - bi.count++ - return } stateObject := sdb.GetOrNewStateObject(addr) @@ -414,6 +425,11 @@ func (sdb *IntraBlockState) HasLiveAccount(addr libcommon.Address) bool { return false } +func (sdb *IntraBlockState) SeenAccount(addr libcommon.Address) bool { + _, ok := sdb.seenStateObjects[addr] + return ok +} + func (sdb *IntraBlockState) HasLiveState(addr libcommon.Address, key *libcommon.Hash) bool { if stateObject := sdb.stateObjects[addr]; stateObject != nil { if _, ok := stateObject.originStorage[*key]; ok { @@ -503,6 +519,7 @@ func (sdb *IntraBlockState) getStateObject(addr libcommon.Address) (stateObject return nil } account, err := sdb.stateReader.ReadAccountData(addr) + sdb.seenStateObjects[addr] = struct{}{} if err != nil { sdb.setErrorUnsafe(err) return nil @@ -528,6 +545,7 @@ func (sdb *IntraBlockState) setStateObject(addr libcommon.Address, object *state sdb.journal.append(balanceIncreaseTransfer{bi: bi}) } sdb.stateObjects[addr] = object + sdb.seenStateObjects[addr] = struct{}{} } // Retrieve a state object or create a new state object if nil. diff --git a/core/vm/evmtypes/evmtypes.go b/core/vm/evmtypes/evmtypes.go index 9e05f0d86d9..4f8570f84b0 100644 --- a/core/vm/evmtypes/evmtypes.go +++ b/core/vm/evmtypes/evmtypes.go @@ -83,6 +83,7 @@ type IntraBlockState interface { GetState(address common.Address, slot *common.Hash, outValue *uint256.Int) SetState(common.Address, *common.Hash, uint256.Int) HasLiveAccount(addr common.Address) bool + SeenAccount(addr common.Address) bool HasLiveState(addr common.Address, key *common.Hash) bool GetTransientState(addr common.Address, key common.Hash) uint256.Int @@ -118,4 +119,5 @@ type IntraBlockState interface { GetLogs(hash common.Hash) []*types.Log GetBlockStateRoot(blockNum *uint256.Int) *uint256.Int GetBlockNumber() *uint256.Int + SetDisableBalanceInc(disable bool) } diff --git a/core/vm/instructions_zkevm_test.go b/core/vm/instructions_zkevm_test.go index 33ee754574e..c2398b68c95 100644 --- a/core/vm/instructions_zkevm_test.go +++ b/core/vm/instructions_zkevm_test.go @@ -139,6 +139,11 @@ func (ibs TestIntraBlockState) AddLog(log *types.Log) { panic("implement me") } +func (ibs TestIntraBlockState) SeenAccount(addr libcommon.Address) bool { + //TODO implement me + panic("implement me") +} + func (ibs TestIntraBlockState) GetLogs(hash libcommon.Hash) []*types.Log { //TODO implement me panic("implement me") @@ -204,3 +209,5 @@ func (ibs TestIntraBlockState) Prepare(rules *chain.Rules, sender, coinbase comm } func (ibs TestIntraBlockState) Selfdestruct6780(common.Address) {} + +func (ibs TestIntraBlockState) SetDisableBalanceInc(disable bool) {} diff --git a/eth/tracers/native/zero.go b/eth/tracers/native/zero.go index cdcbbc99760..3d4c05b51a6 100644 --- a/eth/tracers/native/zero.go +++ b/eth/tracers/native/zero.go @@ -54,6 +54,8 @@ func (t *zeroTracer) CaptureStart(env *vm.EVM, from libcommon.Address, to libcom t.to = &to t.env = env + t.env.IntraBlockState().SetDisableBalanceInc(true) + t.addAccountToTrace(from) t.addAccountToTrace(to) t.addAccountToTrace(env.Context.Coinbase) @@ -62,6 +64,14 @@ func (t *zeroTracer) CaptureStart(env *vm.EVM, from libcommon.Address, to libcom t.addOpCodeToAccount(to, vm.CALL) } + for _, a := range t.ctx.Txn.GetAccessList() { + t.addAccountToTrace(a.Address) + + for _, k := range a.StorageKeys { + t.addSLOADToAccount(a.Address, k) + } + } + // The recipient balance includes the value transferred. toBal := new(big.Int).Sub(t.tx.Traces[to].Balance.ToBig(), value.ToBig()) t.tx.Traces[to].Balance = uint256.MustFromBig(toBal) @@ -90,11 +100,6 @@ func (t *zeroTracer) CaptureTxStart(gasLimit uint64) { // CaptureState implements the EVMLogger interface to trace a single step of VM execution. func (t *zeroTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - // Only continue if the error is nil or if the error is out of gas and the opcode is SSTORE, CALL, or SELFDESTRUCT - if !(err == nil || (err == vm.ErrOutOfGas && (op == vm.SSTORE || op == vm.CALL || op == vm.SELFDESTRUCT))) { - return - } - // Skip if tracing was interrupted if t.interrupt.Load() { return @@ -112,42 +117,21 @@ func (t *zeroTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, sco t.addSLOADToAccount(caller, slot) case stackLen >= 1 && op == vm.SSTORE: slot := libcommon.Hash(stackData[stackLen-1].Bytes32()) - - // If the SSTORE is out of gas and the slot is in live state, we will add the slot to account read - if err == vm.ErrOutOfGas { - if t.env.IntraBlockState().HasLiveState(caller, &slot) { - t.addAccountToTrace(caller) - t.addSLOADToAccount(caller, slot) - } - return - } t.addAccountToTrace(caller) t.addSSTOREToAccount(caller, slot, stackData[stackLen-2].Clone()) case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT): addr := libcommon.Address(stackData[stackLen-1].Bytes20()) - - if err == vm.ErrOutOfGas && op == vm.SELFDESTRUCT { - if t.env.IntraBlockState().HasLiveAccount(addr) { - t.addAccountToTrace(addr) - } - return - } t.addAccountToTrace(addr) t.addOpCodeToAccount(addr, op) case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): addr := libcommon.Address(stackData[stackLen-2].Bytes20()) - - // If the call is out of gas, we will add account but not the opcode - if err == vm.ErrOutOfGas && op == vm.CALL { - if t.env.IntraBlockState().HasLiveAccount(addr) { - t.addAccountToTrace(addr) - } - return - } t.addAccountToTrace(addr) t.addOpCodeToAccount(addr, op) case op == vm.CREATE: - nonce := t.env.IntraBlockState().GetNonce(caller) + nonce := uint64(0) + if t.env.IntraBlockState().HasLiveAccount(caller) { + nonce = t.env.IntraBlockState().GetNonce(caller) + } addr := crypto.CreateAddress(caller, nonce) t.addAccountToTrace(addr) t.addOpCodeToAccount(addr, op) @@ -195,7 +179,16 @@ func (t *zeroTracer) CaptureTxEnd(restGas uint64) { t.tx.Meta.GasUsed = t.gasLimit - restGas *t.ctx.CumulativeGasUsed += t.tx.Meta.GasUsed + toDelete := make([]libcommon.Address, 0) for addr := range t.tx.Traces { + // Check again if the account was accessed through IntraBlockState + seenAccount := t.env.IntraBlockState().SeenAccount(addr) + // If an account was never accessed through IntraBlockState, it means that never there was an OpCode that read into it or checks whether it exists in the state trie, and therefore we don't need the trace of it. + if !seenAccount { + toDelete = append(toDelete, addr) + continue + } + trace := t.tx.Traces[addr] hasLiveAccount := t.env.IntraBlockState().HasLiveAccount(addr) newBalance := t.env.IntraBlockState().GetBalance(addr) @@ -219,7 +212,9 @@ func (t *zeroTracer) CaptureTxEnd(restGas uint64) { if len(trace.StorageReadMap) > 0 && hasLiveAccount { trace.StorageRead = make([]libcommon.Hash, 0, len(trace.StorageReadMap)) for k := range trace.StorageReadMap { - trace.StorageRead = append(trace.StorageRead, k) + if t.env.IntraBlockState().HasLiveState(addr, &k) { + trace.StorageRead = append(trace.StorageRead, k) + } } } else { trace.StorageRead = nil @@ -238,8 +233,7 @@ func (t *zeroTracer) CaptureTxEnd(restGas uint64) { if !bytes.Equal(codeHash[:], emptyCodeHash) && !bytes.Equal(codeHash[:], trace.CodeUsage.Read[:]) { trace.CodeUsage.Read = nil - trace.CodeUsage.Write = make([]byte, len(code)) - copy(trace.CodeUsage.Write, code) + trace.CodeUsage.Write = bytes.Clone(code) } else if code != nil { codeHashCopy := libcommon.BytesToHash(codeHash.Bytes()) trace.CodeUsage.Read = &codeHashCopy @@ -254,8 +248,6 @@ func (t *zeroTracer) CaptureTxEnd(restGas uint64) { // fmt.Printf("Address: %s, opcodes: %v\n", addr.String(), t.addrOpCodes[addr]) // } - // We don't need to provide the actual bytecode UNLESS the opcode is the following: - // DELEGATECALL, CALL, STATICCALL, CALLCODE, EXTCODECOPY, EXTCODEHASH, EXTCODESIZE if trace.CodeUsage != nil && trace.CodeUsage.Read != nil { if t.addrOpCodes[addr] != nil { // We don't need to provide the actual bytecode UNLESS the opcode is the following: @@ -283,6 +275,10 @@ func (t *zeroTracer) CaptureTxEnd(restGas uint64) { } } + for _, addr := range toDelete { + delete(t.tx.Traces, addr) + } + receipt := &types.Receipt{Type: t.ctx.Txn.Type(), CumulativeGasUsed: *t.ctx.CumulativeGasUsed} receipt.Status = t.txStatus receipt.TxHash = t.ctx.Txn.Hash() @@ -316,7 +312,6 @@ func (t *zeroTracer) CaptureTxEnd(restGas uint64) { return } - t.tx.Meta.NewTxnTrieNode = txBuffer.Bytes() t.tx.Meta.ByteCode = txBuffer.Bytes() } @@ -357,12 +352,19 @@ func (t *zeroTracer) addAccountToTrace(addr libcommon.Address) { return } - nonce := uint256.NewInt(t.env.IntraBlockState().GetNonce(addr)) - codeHash := t.env.IntraBlockState().GetCodeHash(addr) + balance := uint256.NewInt(0) + nonce := uint256.NewInt(0) + codeHash := libcommon.Hash{} + + if t.env.IntraBlockState().HasLiveAccount(addr) { + nonce = uint256.NewInt(t.env.IntraBlockState().GetNonce(addr)) + balance = t.env.IntraBlockState().GetBalance(addr) + codeHash = t.env.IntraBlockState().GetCodeHash(addr) + } t.tx.Traces[addr] = &types.TxnTrace{ - Balance: t.env.IntraBlockState().GetBalance(addr).Clone(), - Nonce: nonce, + Balance: balance.Clone(), + Nonce: nonce.Clone(), CodeUsage: &types.ContractCodeUsage{Read: &codeHash}, StorageWritten: make(map[libcommon.Hash]*uint256.Int), StorageRead: make([]libcommon.Hash, 0), @@ -371,10 +373,7 @@ func (t *zeroTracer) addAccountToTrace(addr libcommon.Address) { } func (t *zeroTracer) addSLOADToAccount(addr libcommon.Address, key libcommon.Hash) { - var value uint256.Int - t.env.IntraBlockState().GetState(addr, &key, &value) t.tx.Traces[addr].StorageReadMap[key] = struct{}{} - t.addOpCodeToAccount(addr, vm.SLOAD) }