diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index b7ea3fcd72..07798aa710 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -43,7 +43,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/bloombits" "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/state" - "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/crypto" @@ -738,7 +737,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // Set infinite balance to the fake caller account. from := stateDB.GetOrNewStateObject(call.From) - from.SetBalance(math.MaxBig256, tracing.BalanceChangeUnspecified) + from.SetBalance(math.MaxBig256) // Execute the call. msg := &core.Message{ diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index a9e20b4c42..09dfec9f5b 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -30,6 +30,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus/clique" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/log" @@ -276,7 +277,7 @@ func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given, and returns the final block. -func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { +func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state vm.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { switch x.config.BlockConsensusVersion(header.Number) { case params.ConsensusEngineVersion2: return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts) diff --git a/consensus/XDPoS/engines/engine_v1/engine.go b/consensus/XDPoS/engines/engine_v1/engine.go index 1d8c6b453c..a65da6137e 100644 --- a/consensus/XDPoS/engines/engine_v1/engine.go +++ b/consensus/XDPoS/engines/engine_v1/engine.go @@ -21,6 +21,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus/misc/eip1559" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" @@ -53,7 +54,7 @@ type XDPoS_v1 struct { signFn clique.SignerFn // Signer function to authorize hashes with lock sync.RWMutex // Protects the signer fields - HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) + HookReward func(chain consensus.ChainReader, state vm.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) HookPenalty func(chain consensus.ChainReader, blockNumberEpoc uint64) ([]common.Address, error) HookPenaltyTIPSigning func(chain consensus.ChainReader, header *types.Header, candidate []common.Address) ([]common.Address, error) HookValidator func(header *types.Header, signers []common.Address) ([]byte, error) @@ -823,7 +824,7 @@ func (x *XDPoS_v1) UpdateMasternodes(chain consensus.ChainReader, header *types. // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given, and returns the final block. -func (x *XDPoS_v1) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { +func (x *XDPoS_v1) Finalize(chain consensus.ChainReader, header *types.Header, state vm.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // set block reward number := header.Number.Uint64() rCheckpoint := chain.Config().XDPoS.RewardCheckpoint diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 88c7787dc9..0b636ec220 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -23,6 +23,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" @@ -74,7 +75,7 @@ type XDPoS_v2 struct { latestReward map[string]interface{} latestRewardBlocknum uint64 - HookReward func(chain consensus.ChainReader, state *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) + HookReward func(chain consensus.ChainReader, state vm.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) HookPenalty func(chain consensus.ChainReader, number *big.Int, parentHash common.Hash, candidates []common.Address) ([]common.Address, error) ForensicsProcessor *Forensics @@ -410,7 +411,7 @@ func (x *XDPoS_v2) Prepare(chain consensus.ChainReader, header *types.Header) er // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given, and returns the final block. -func (x *XDPoS_v2) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { +func (x *XDPoS_v2) Finalize(chain consensus.ChainReader, header *types.Header, state vm.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // set block reward isEpochSwitch, _, err := x.IsEpochSwitch(header) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index ec2cff203b..b9f6ec343f 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -34,6 +34,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus/misc" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/log" @@ -584,7 +585,7 @@ func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header) erro // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given, and returns the final block. -func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { +func (c *Clique) Finalize(chain consensus.ChainReader, header *types.Header, state vm.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // No block rewards in PoA, so the state remains as is and uncles are dropped header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) header.UncleHash = types.CalcUncleHash(nil) diff --git a/consensus/consensus.go b/consensus/consensus.go index 080b22406f..d50479b359 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -23,6 +23,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/rpc" ) @@ -83,7 +84,7 @@ type Engine interface { // and assembles the final block. // Note: The block header and state database might be updated to reflect any // consensus rules that happen at finalization (e.g. block rewards). - Finalize(chain ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, + Finalize(chain ChainReader, header *types.Header, state vm.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) // Seal generates a new block for the given input block with the local miner's diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 3ee22bbd13..82809f2bb3 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -29,6 +29,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/trie" mapset "github.com/deckarep/golang-set/v2" @@ -433,7 +434,7 @@ func (ethash *Ethash) Prepare(chain consensus.ChainReader, header *types.Header) // Finalize implements consensus.Engine, accumulating the block and uncle rewards, // setting the final state and assembling the block. -func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { +func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state vm.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // Accumulate any block and uncle rewards and commit the final state root accumulateRewards(chain.Config(), state, header, uncles) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) @@ -451,7 +452,7 @@ var ( // AccumulateRewards credits the coinbase of the given block with the mining // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. -func accumulateRewards(config *params.ChainConfig, stateDB *state.StateDB, header *types.Header, uncles []*types.Header) { +func accumulateRewards(config *params.ChainConfig, stateDB vm.StateDB, header *types.Header, uncles []*types.Header) { // Select the correct block reward based on chain progression blockReward := FrontierBlockReward if config.IsByzantium(header.Number) { diff --git a/consensus/misc/dao.go b/consensus/misc/dao.go index da873dd0b2..f25e751849 100644 --- a/consensus/misc/dao.go +++ b/consensus/misc/dao.go @@ -21,9 +21,9 @@ import ( "errors" "math/big" - "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/params" ) @@ -73,7 +73,7 @@ func VerifyDAOHeaderExtraData(config *params.ChainConfig, header *types.Header) // ApplyDAOHardFork modifies the state database according to the DAO hard-fork // rules, transferring all balances of a set of DAO accounts to a single refund // contract. -func ApplyDAOHardFork(statedb *state.StateDB) { +func ApplyDAOHardFork(statedb vm.StateDB) { // Retrieve the contract to refund balances into if !statedb.Exist(params.DAORefundContract) { statedb.CreateAccount(params.DAORefundContract) @@ -81,7 +81,8 @@ func ApplyDAOHardFork(statedb *state.StateDB) { // Move every DAO account and extra-balance account funds into the refund contract for _, addr := range params.DAODrainList() { - statedb.AddBalance(params.DAORefundContract, statedb.GetBalance(addr), tracing.BalanceIncreaseDaoContract) - statedb.SetBalance(addr, new(big.Int), tracing.BalanceDecreaseDaoAccount) + balance := statedb.GetBalance(addr) + statedb.AddBalance(params.DAORefundContract, balance, tracing.BalanceIncreaseDaoContract) + statedb.SubBalance(addr, balance, tracing.BalanceDecreaseDaoAccount) } } diff --git a/core/blockchain.go b/core/blockchain.go index 36ddd4be89..fab8628e7c 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1552,7 +1552,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] if err != nil { return it.index, events, coalescedLogs, err } - statedb.SetLogger(bc.logger) // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. diff --git a/core/evm.go b/core/evm.go index e8493f8279..70129c9980 100644 --- a/core/evm.go +++ b/core/evm.go @@ -117,7 +117,7 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash } } -// CanTransfer checks wether there are enough funds in the address' account to make a transfer. +// CanTransfer checks whether there are enough funds in the address' account to make a transfer. // This does not take the necessary gas in to account to make the transfer valid. func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { return db.GetBalance(addr).Cmp(amount) >= 0 diff --git a/core/state/journal.go b/core/state/journal.go index 6d266a0542..1ffeef1ad0 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -85,11 +85,21 @@ func (j *journal) length() int { return len(j.entries) } +func (j *journal) createContract(addr common.Address) { + j.append(createContractChange{account: addr}) +} + type ( // Changes to the account trie. createObjectChange struct { account common.Address } + // createContractChange represents an account becoming a contract-account. + // This event happens prior to executing initcode. The journal-event simply + // manages the created-flag, in order to allow same-tx destruction. + createContractChange struct { + account common.Address + } resetObjectChange struct { account common.Address prev *stateObject @@ -156,6 +166,14 @@ func (ch createObjectChange) dirtied() *common.Address { return &ch.account } +func (ch createContractChange) revert(s *StateDB) { + s.getStateObject(ch.account).created = false +} + +func (ch createContractChange) dirtied() *common.Address { + return nil +} + func (ch resetObjectChange) revert(s *StateDB) { s.setStateObject(ch.prev) if !ch.prevdestruct { diff --git a/core/state/state_object.go b/core/state/state_object.go index 41351e7f71..2a874868a9 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -25,7 +25,6 @@ import ( "time" "github.com/XinFinOrg/XDPoSChain/common" - "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/rlp" @@ -218,11 +217,12 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has } // SetState updates a value in account storage. -func (s *stateObject) SetState(db Database, key, value common.Hash) { - // If the new value is the same as old, don't set +func (s *stateObject) SetState(db Database, key, value common.Hash) common.Hash { + // If the new value is the same as old, don't set. Otherwise, track only the + // dirty changes, supporting reverting all of it back to no change. prev := s.GetState(db, key) if prev == value { - return + return prev } // New value is different, update and journal the change s.db.journal.append(storageChange{ @@ -230,10 +230,8 @@ func (s *stateObject) SetState(db Database, key, value common.Hash) { key: key, prevalue: prev, }) - if s.db.logger != nil && s.db.logger.OnStorageChange != nil { - s.db.logger.OnStorageChange(s.address, key, prev, value) - } s.setState(key, value) + return prev } func (s *stateObject) setState(key, value common.Hash) { @@ -338,36 +336,28 @@ func (s *stateObject) commitTrie(db Database) (*trie.NodeSet, error) { // AddBalance adds amount to s's balance. // It is used to add funds to the destination account of a transfer. -func (s *stateObject) AddBalance(amount *big.Int, reason tracing.BalanceChangeReason) { +// returns the previous balance +func (s *stateObject) AddBalance(amount *big.Int) *big.Int { // EIP161: We must check emptiness for the objects such that the account // clearing (0,0,0 objects) can take effect. if amount.Sign() == 0 { if s.empty() { s.touch() } - return + return new(big.Int).Set(s.Balance()) } - s.SetBalance(new(big.Int).Add(s.Balance(), amount), reason) + return s.SetBalance(new(big.Int).Add(s.Balance(), amount)) } -// SubBalance removes amount from s's balance. -// It is used to remove funds from the origin account of a transfer. -func (s *stateObject) SubBalance(amount *big.Int, reason tracing.BalanceChangeReason) { - if amount.Sign() == 0 { - return - } - s.SetBalance(new(big.Int).Sub(s.Balance(), amount), reason) -} - -func (s *stateObject) SetBalance(amount *big.Int, reason tracing.BalanceChangeReason) { +// SetBalance sets the balance for the object, and returns the previous balance. +func (s *stateObject) SetBalance(amount *big.Int) *big.Int { + prev := new(big.Int).Set(s.data.Balance) s.db.journal.append(balanceChange{ account: s.address, prev: new(big.Int).Set(s.data.Balance), }) - if s.db.logger != nil && s.db.logger.OnBalanceChange != nil { - s.db.logger.OnBalanceChange(s.address, s.Balance(), amount, reason) - } s.setBalance(amount) + return prev } func (s *stateObject) setBalance(amount *big.Int) { @@ -437,9 +427,6 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { account: s.address, prevCode: prevCode, }) - if s.db.logger != nil && s.db.logger.OnCodeChange != nil { - s.db.logger.OnCodeChange(s.address, common.BytesToHash(s.CodeHash()), prevCode, codeHash, code) - } s.setCode(codeHash, code) } @@ -454,9 +441,6 @@ func (s *stateObject) SetNonce(nonce uint64) { account: s.address, prev: s.data.Nonce, }) - if s.db.logger != nil && s.db.logger.OnNonceChange != nil { - s.db.logger.OnNonceChange(s.address, s.data.Nonce, nonce) - } s.setNonce(nonce) } diff --git a/core/state/state_test.go b/core/state/state_test.go index ae4fa91d40..7dcd1447a5 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -24,7 +24,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/rawdb" - "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/ethdb" @@ -49,11 +48,11 @@ func TestDump(t *testing.T) { // generate a few entries obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) - obj1.AddBalance(big.NewInt(22), tracing.BalanceChangeUnspecified) + obj1.AddBalance(big.NewInt(22)) obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02})) - obj3.SetBalance(big.NewInt(44), tracing.BalanceChangeUnspecified) + obj3.SetBalance(big.NewInt(44)) // write some of them to the trie s.state.updateStateObject(obj1) @@ -101,13 +100,13 @@ func TestIterativeDump(t *testing.T) { // generate a few entries obj1 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01})) - obj1.AddBalance(big.NewInt(22), tracing.BalanceChangeUnspecified) + obj1.AddBalance(big.NewInt(22)) obj2 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x01, 0x02})) obj2.SetCode(crypto.Keccak256Hash([]byte{3, 3, 3, 3, 3, 3, 3}), []byte{3, 3, 3, 3, 3, 3, 3}) obj3 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x02})) - obj3.SetBalance(big.NewInt(44), tracing.BalanceChangeUnspecified) + obj3.SetBalance(big.NewInt(44)) obj4 := s.state.GetOrNewStateObject(common.BytesToAddress([]byte{0x00})) - obj4.AddBalance(big.NewInt(1337), tracing.BalanceChangeUnspecified) + obj4.AddBalance(big.NewInt(1337)) // write some of them to the trie s.state.updateStateObject(obj1) @@ -203,7 +202,7 @@ func TestSnapshot2(t *testing.T) { // db, trie are already non-empty values so0 := state.getStateObject(stateobjaddr0) - so0.SetBalance(big.NewInt(42), tracing.BalanceChangeUnspecified) + so0.SetBalance(big.NewInt(42)) so0.SetNonce(43) so0.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e'}), []byte{'c', 'a', 'f', 'e'}) so0.selfDestructed = false @@ -215,7 +214,7 @@ func TestSnapshot2(t *testing.T) { // and one with deleted == true so1 := state.getStateObject(stateobjaddr1) - so1.SetBalance(big.NewInt(52), tracing.BalanceChangeUnspecified) + so1.SetBalance(big.NewInt(52)) so1.SetNonce(53) so1.SetCode(crypto.Keccak256Hash([]byte{'c', 'a', 'f', 'e', '2'}), []byte{'c', 'a', 'f', 'e', '2'}) so1.selfDestructed = true diff --git a/core/state/statedb.go b/core/state/statedb.go index d0c42f7dba..39b4552d44 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -47,9 +47,8 @@ type revision struct { // * Contracts // * Accounts type StateDB struct { - db Database - trie Trie - logger *tracing.Hooks + db Database + trie Trie // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. @@ -139,11 +138,6 @@ func New(root common.Hash, db Database) (*StateDB, error) { }, nil } -// SetLogger sets the logger for account update hooks. -func (s *StateDB) SetLogger(l *tracing.Hooks) { - s.logger = l -} - // setError remembers the first non-nil error it is called with. func (s *StateDB) setError(err error) { if s.dbErr == nil { @@ -183,9 +177,6 @@ func (s *StateDB) AddLog(log *types.Log) { log.TxHash = s.thash log.TxIndex = uint(s.txIndex) log.Index = s.logSize - if s.logger != nil && s.logger.OnLog != nil { - s.logger.OnLog(log) - } s.logs[s.thash] = append(s.logs[s.thash], log) s.logSize++ } @@ -390,25 +381,31 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { */ // AddBalance adds amount to the account associated with addr. -func (s *StateDB) AddBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) { +func (s *StateDB) AddBalance(addr common.Address, amount *big.Int, _ tracing.BalanceChangeReason) *big.Int { stateObject := s.GetOrNewStateObject(addr) - if stateObject != nil { - stateObject.AddBalance(amount, reason) + if stateObject == nil { + return new(big.Int) } + return stateObject.AddBalance(amount) } // SubBalance subtracts amount from the account associated with addr. -func (s *StateDB) SubBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) { +func (s *StateDB) SubBalance(addr common.Address, amount *big.Int, _ tracing.BalanceChangeReason) *big.Int { stateObject := s.GetOrNewStateObject(addr) - if stateObject != nil { - stateObject.SubBalance(amount, reason) + if stateObject == nil { + return new(big.Int) } + prev := stateObject.Balance() + if amount.Sign() == 0 { + return new(big.Int).Set(prev) + } + return stateObject.SetBalance(new(big.Int).Sub(prev, amount)) } -func (s *StateDB) SetBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) { +func (s *StateDB) SetBalance(addr common.Address, amount *big.Int, _ tracing.BalanceChangeReason) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - stateObject.SetBalance(amount, reason) + stateObject.SetBalance(amount) } } @@ -426,11 +423,11 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { } } -func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { - stateObject := s.GetOrNewStateObject(addr) - if stateObject != nil { - stateObject.SetState(s.db, key, value) +func (s *StateDB) SetState(addr common.Address, key, value common.Hash) common.Hash { + if stateObject := s.GetOrNewStateObject(addr); stateObject != nil { + return stateObject.SetState(s.db, key, value) } + return common.Hash{} } // SetStorage replaces the entire storage for the specified account with given @@ -458,7 +455,7 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common if obj != nil { newObj.SetCode(common.BytesToHash(obj.CodeHash()), obj.code) newObj.SetNonce(obj.Nonce()) - newObj.SetBalance(obj.Balance(), tracing.BalanceChangeUnspecified) + newObj.SetBalance(obj.Balance()) } } @@ -467,36 +464,40 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // // The account's state object is still available until the state is committed, // getStateObject will return a non-nil account after SelfDestruct. -func (s *StateDB) SelfDestruct(addr common.Address) { +func (s *StateDB) SelfDestruct(addr common.Address) *big.Int { stateObject := s.getStateObject(addr) + prevBalance := new(big.Int) if stateObject == nil { - return + return prevBalance } - var ( - prev = new(big.Int).Set(stateObject.Balance()) - n = new(big.Int) - ) - s.journal.append(selfDestructChange{ - account: addr, - prev: stateObject.selfDestructed, - prevbalance: prev, - }) - if s.logger != nil && s.logger.OnBalanceChange != nil && prev.Sign() > 0 { - s.logger.OnBalanceChange(addr, prev, n, tracing.BalanceDecreaseSelfdestruct) + prevBalance.Set(stateObject.Balance()) + // Regardless of whether it is already destructed or not, we do have to + // journal the balance-change, if we set it to zero here. + if prevBalance.Sign() != 0 { + stateObject.SetBalance(new(big.Int)) } - stateObject.markSelfdestructed() - stateObject.data.Balance = n + // If it is already marked as self-destructed, we do not need to add it + // for journalling a second time. + if !stateObject.selfDestructed { + s.journal.append(selfDestructChange{ + account: addr, + prev: stateObject.selfDestructed, + prevbalance: prevBalance, + }) + stateObject.markSelfdestructed() + } + return prevBalance } -func (s *StateDB) Selfdestruct6780(addr common.Address) { +func (s *StateDB) SelfDestruct6780(addr common.Address) (*big.Int, bool) { stateObject := s.getStateObject(addr) if stateObject == nil { - return + return new(big.Int), false } - if stateObject.created { - s.SelfDestruct(addr) + return s.SelfDestruct(addr), true } + return new(big.Int).Set(stateObject.Balance()), false } // SetTransientState sets transient storage for a given account. It @@ -653,6 +654,19 @@ func (s *StateDB) CreateAccount(addr common.Address) { } } +// CreateContract is used whenever a contract is created. This may be preceded +// by CreateAccount, but that is not required if it already existed in the +// state due to funds sent beforehand. +// This operation sets the 'newContract'-flag, which is required in order to +// correctly handle EIP-6780 'delete-in-same-transaction' logic. +func (s *StateDB) CreateContract(addr common.Address) { + obj := s.getStateObject(addr) + if obj != nil && !obj.created { + obj.created = true + s.journal.createContract(addr) + } +} + func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { so := s.getStateObject(addr) if so == nil { @@ -801,14 +815,11 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { if obj.selfDestructed || (deleteEmptyObjects && obj.empty()) { obj.deleted = true - // We need to maintain account deletions explicitly (will remain - // set indefinitely). - s.stateObjectsDestruct[obj.address] = struct{}{} - - // If ether was sent to account post-selfdestruct it is burnt. - if bal := obj.Balance(); s.logger != nil && s.logger.OnBalanceChange != nil && obj.selfDestructed && bal.Sign() != 0 { - s.logger.OnBalanceChange(obj.address, bal, new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) + // set indefinitely). Note only the first occurred self-destruct + // event is tracked. + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + s.stateObjectsDestruct[obj.address] = struct{}{} } } else { obj.finalise() diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go new file mode 100644 index 0000000000..ea5431306b --- /dev/null +++ b/core/state/statedb_hooked.go @@ -0,0 +1,277 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "math/big" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/tracing" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/params" +) + +// hookedStateDB represents a statedb which emits calls to tracing-hooks +// on state operations. +type hookedStateDB struct { + inner *StateDB + hooks *tracing.Hooks +} + +// NewHookedState wraps the given stateDb with the given hooks +func NewHookedState(stateDb *StateDB, hooks *tracing.Hooks) *hookedStateDB { + s := &hookedStateDB{stateDb, hooks} + if s.hooks == nil { + s.hooks = new(tracing.Hooks) + } + return s +} + +func (s *hookedStateDB) CreateAccount(addr common.Address) { + s.inner.CreateAccount(addr) +} + +func (s *hookedStateDB) CreateContract(addr common.Address) { + s.inner.CreateContract(addr) +} + +func (s *hookedStateDB) GetBalance(addr common.Address) *big.Int { + return s.inner.GetBalance(addr) +} + +func (s *hookedStateDB) GetNonce(addr common.Address) uint64 { + return s.inner.GetNonce(addr) +} + +func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash { + return s.inner.GetCodeHash(addr) +} + +func (s *hookedStateDB) GetCode(addr common.Address) []byte { + return s.inner.GetCode(addr) +} + +func (s *hookedStateDB) GetCodeSize(addr common.Address) int { + return s.inner.GetCodeSize(addr) +} + +func (s *hookedStateDB) AddRefund(u uint64) { + s.inner.AddRefund(u) +} + +func (s *hookedStateDB) SubRefund(u uint64) { + s.inner.SubRefund(u) +} + +func (s *hookedStateDB) GetRefund() uint64 { + return s.inner.GetRefund() +} + +func (s *hookedStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + return s.inner.GetCommittedState(addr, hash) +} + +func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + return s.inner.GetState(addr, hash) +} + +func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash { + return s.inner.GetStorageRoot(addr) +} + +func (s *hookedStateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash { + return s.inner.GetTransientState(addr, key) +} + +func (s *hookedStateDB) SetTransientState(addr common.Address, key, value common.Hash) { + s.inner.SetTransientState(addr, key, value) +} + +func (s *hookedStateDB) HasSelfDestructed(addr common.Address) bool { + return s.inner.HasSelfDestructed(addr) +} + +func (s *hookedStateDB) Exist(addr common.Address) bool { + return s.inner.Exist(addr) +} + +func (s *hookedStateDB) Empty(addr common.Address) bool { + return s.inner.Empty(addr) +} + +func (s *hookedStateDB) AddressInAccessList(addr common.Address) bool { + return s.inner.AddressInAccessList(addr) +} + +func (s *hookedStateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { + return s.inner.SlotInAccessList(addr, slot) +} + +func (s *hookedStateDB) AddAddressToAccessList(addr common.Address) { + s.inner.AddAddressToAccessList(addr) +} + +func (s *hookedStateDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + s.inner.AddSlotToAccessList(addr, slot) +} + +func (s *hookedStateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) { + s.inner.Prepare(rules, sender, coinbase, dest, precompiles, txAccesses) +} + +func (s *hookedStateDB) RevertToSnapshot(i int) { + s.inner.RevertToSnapshot(i) +} + +func (s *hookedStateDB) Snapshot() int { + return s.inner.Snapshot() +} + +func (s *hookedStateDB) AddPreimage(hash common.Hash, bytes []byte) { + s.inner.AddPreimage(hash, bytes) +} + +func (s *hookedStateDB) SubBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) *big.Int { + prev := s.inner.SubBalance(addr, amount, reason) + if s.hooks.OnBalanceChange != nil && amount.Sign() != 0 { + newBalance := new(big.Int).Sub(prev, amount) + s.hooks.OnBalanceChange(addr, prev, newBalance, reason) + } + return prev +} + +func (s *hookedStateDB) AddBalance(addr common.Address, amount *big.Int, reason tracing.BalanceChangeReason) *big.Int { + prev := s.inner.AddBalance(addr, amount, reason) + if s.hooks.OnBalanceChange != nil && amount.Sign() != 0 { + newBalance := new(big.Int).Add(prev, amount) + s.hooks.OnBalanceChange(addr, prev, newBalance, reason) + } + return prev +} + +func (s *hookedStateDB) SetNonce(address common.Address, nonce uint64) { + prev := s.inner.GetNonce(address) + s.inner.SetNonce(address, nonce) + if s.hooks.OnNonceChange != nil { + s.hooks.OnNonceChange(address, prev, nonce) + } +} + +func (s *hookedStateDB) SetCode(address common.Address, code []byte) { + prevCode := s.inner.GetCode(address) + s.inner.SetCode(address, code) + if s.hooks.OnCodeChange != nil { + prevHash := crypto.Keccak256Hash(prevCode) + codeHash := crypto.Keccak256Hash(code) + + // Invoke the hooks only if the contract code is changed + if prevHash != codeHash { + s.hooks.OnCodeChange(address, prevHash, prevCode, codeHash, code) + } + } +} + +func (s *hookedStateDB) SetState(address common.Address, key common.Hash, value common.Hash) common.Hash { + prev := s.inner.SetState(address, key, value) + if s.hooks.OnStorageChange != nil && prev != value { + s.hooks.OnStorageChange(address, key, prev, value) + } + return prev +} + +func (s *hookedStateDB) SelfDestruct(address common.Address) *big.Int { + prev := s.inner.SelfDestruct(address) + if s.hooks.OnBalanceChange != nil { + s.hooks.OnBalanceChange(address, prev, new(big.Int), tracing.BalanceDecreaseSelfdestruct) + } + return prev +} + +func (s *hookedStateDB) SelfDestruct6780(address common.Address) (*big.Int, bool) { + prev, changed := s.inner.SelfDestruct6780(address) + if changed { + if s.hooks.OnBalanceChange != nil { + s.hooks.OnBalanceChange(address, prev, new(big.Int), tracing.BalanceDecreaseSelfdestruct) + } + } + return prev, changed +} + +func (s *hookedStateDB) AddLog(log *types.Log) { + // The inner will modify the log (add fields), so invoke that first + s.inner.AddLog(log) + if s.hooks.OnLog != nil { + s.hooks.OnLog(log) + } +} + +func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { + defer s.inner.Finalise(deleteEmptyObjects) + if s.hooks.OnBalanceChange == nil { + return + } + for addr := range s.inner.journal.dirties { + obj := s.inner.stateObjects[addr] + if obj != nil && obj.selfDestructed { + // If ether was sent to account post-selfdestruct it is burnt. + if bal := obj.Balance(); bal.Sign() != 0 { + s.hooks.OnBalanceChange(addr, bal, new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) + } + } + } +} + +func (s *hookedStateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { + return s.inner.IntermediateRoot(deleteEmptyObjects) +} + +func (s *hookedStateDB) UpdateTRC21Fee(newBalance map[common.Address]*big.Int, totalFeeUsed *big.Int) { + s.inner.UpdateTRC21Fee(newBalance, totalFeeUsed) +} + +func (s *hookedStateDB) PutMintedRecordOnsetBlock(value common.Hash) { + s.inner.PutMintedRecordOnsetBlock(value) +} + +func (s *hookedStateDB) PutMintedRecordOnsetEpoch(value common.Hash) { + s.inner.PutMintedRecordOnsetEpoch(value) +} + +func (s *hookedStateDB) GetPostBurned(epoch uint64) common.Hash { + return s.inner.GetPostBurned(epoch) +} + +func (s *hookedStateDB) PutPostBurned(epoch uint64, value common.Hash) { + s.inner.PutPostBurned(epoch, value) +} + +func (s *hookedStateDB) GetPostMinted(epoch uint64) common.Hash { + return s.inner.GetPostMinted(epoch) +} + +func (s *hookedStateDB) PutPostMinted(epoch uint64, value common.Hash) { + s.inner.PutPostMinted(epoch, value) +} + +func (s *hookedStateDB) PutPostRewardBlock(epoch uint64, value common.Hash) { + s.inner.PutPostRewardBlock(epoch, value) +} + +func (s *hookedStateDB) IncrementMintedRecordNonce() { + s.inner.IncrementMintedRecordNonce() +} diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go new file mode 100644 index 0000000000..a39670f67e --- /dev/null +++ b/core/state/statedb_hooked_test.go @@ -0,0 +1,129 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package state + +import ( + "fmt" + "math/big" + "testing" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core/tracing" + "github.com/XinFinOrg/XDPoSChain/core/types" +) + +// This method tests that the 'burn' from sending-to-selfdestructed accounts +// is accounted for. +// (There is also a higher-level test in eth/tracers: TestSupplySelfDestruct ) +func TestBurn(t *testing.T) { + // Note: burn can happen even after EIP-6780, if within one single transaction, + // the following occur: + // 1. contract B creates contract A + // 2. contract A is destructed + // 3. contract B sends ether to A + + var burned = new(big.Int) + s, _ := New(types.EmptyRootHash, NewDatabaseForTesting()) + hooked := NewHookedState(s, &tracing.Hooks{ + OnBalanceChange: func(addr common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { + if reason == tracing.BalanceDecreaseSelfdestructBurn { + burned.Add(burned, prev) + } + }, + }) + createAndDestroy := func(addr common.Address) { + hooked.AddBalance(addr, big.NewInt(100), tracing.BalanceChangeUnspecified) + hooked.CreateContract(addr) + hooked.SelfDestruct(addr) + // sanity-check that balance is now 0 + if have, want := hooked.GetBalance(addr), new(big.Int); have.Cmp(want) != 0 { + t.Fatalf("post-destruct balance wrong: have %v want %v", have, want) + } + } + addA := common.Address{0xaa} + addB := common.Address{0xbb} + addC := common.Address{0xcc} + + // Tx 1: create and destroy address A and B in one tx + createAndDestroy(addA) + createAndDestroy(addB) + hooked.AddBalance(addA, big.NewInt(200), tracing.BalanceChangeUnspecified) + hooked.AddBalance(addB, big.NewInt(200), tracing.BalanceChangeUnspecified) + hooked.Finalise(true) + + // Tx 2: create and destroy address C, then commit + createAndDestroy(addC) + hooked.AddBalance(addC, big.NewInt(200), tracing.BalanceChangeUnspecified) + hooked.Finalise(true) + + s.Commit(false) + if have, want := burned, big.NewInt(600); have.Cmp(want) != 0 { + t.Fatalf("burn-count wrong, have %v want %v", have, want) + } +} + +// TestHooks is a basic sanity-check of all hooks +func TestHooks(t *testing.T) { + inner, _ := New(types.EmptyRootHash, NewDatabaseForTesting()) + inner.SetTxContext(common.Hash{0x11}, 100) // For the log + var result []string + var wants = []string{ + "0xaa00000000000000000000000000000000000000.balance: 0->100 (BalanceChangeUnspecified)", + "0xaa00000000000000000000000000000000000000.balance: 100->50 (BalanceChangeTransfer)", + "0xaa00000000000000000000000000000000000000.nonce: 0->1337", + "0xaa00000000000000000000000000000000000000.code: (0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470) ->0x1325 (0xa12ae05590de0c93a00bc7ac773c2fdb621e44f814985e72194f921c0050f728)", + "0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000000 ->0x0000000000000000000000000000000000000000000000000000000000000011", + "0xaa00000000000000000000000000000000000000.storage slot 0x0000000000000000000000000000000000000000000000000000000000000001: 0x0000000000000000000000000000000000000000000000000000000000000011 ->0x0000000000000000000000000000000000000000000000000000000000000022", + "log 100", + } + emitF := func(format string, a ...any) { + result = append(result, fmt.Sprintf(format, a...)) + } + sdb := NewHookedState(inner, &tracing.Hooks{ + OnBalanceChange: func(addr common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { + emitF("%v.balance: %v->%v (%v)", addr, prev, new, reason) + }, + OnNonceChange: func(addr common.Address, prev, new uint64) { + emitF("%v.nonce: %v->%v", addr, prev, new) + }, + OnCodeChange: func(addr common.Address, prevCodeHash common.Hash, prevCode []byte, codeHash common.Hash, code []byte) { + emitF("%v.code: %#x (%v) ->%#x (%v)", addr, prevCode, prevCodeHash, code, codeHash) + }, + OnStorageChange: func(addr common.Address, slot common.Hash, prev, new common.Hash) { + emitF("%v.storage slot %v: %v ->%v", addr, slot, prev, new) + }, + OnLog: func(log *types.Log) { + emitF("log %v", log.TxIndex) + }, + }) + sdb.AddBalance(common.Address{0xaa}, big.NewInt(100), tracing.BalanceChangeUnspecified) + sdb.SubBalance(common.Address{0xaa}, big.NewInt(50), tracing.BalanceChangeTransfer) + sdb.SetNonce(common.Address{0xaa}, 1337) + sdb.SetCode(common.Address{0xaa}, []byte{0x13, 37}) + sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x11")) + sdb.SetState(common.Address{0xaa}, common.HexToHash("0x01"), common.HexToHash("0x22")) + sdb.SetTransientState(common.Address{0xaa}, common.HexToHash("0x02"), common.HexToHash("0x01")) + sdb.SetTransientState(common.Address{0xaa}, common.HexToHash("0x02"), common.HexToHash("0x02")) + sdb.AddLog(&types.Log{ + Address: common.Address{0xbb}, + }) + for i, want := range wants { + if have := result[i]; have != want { + t.Fatalf("error event %d\nhave: %v\nwant: %v", i, have, want) + } + } +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index c28854afaa..e3a0d27842 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -155,7 +155,7 @@ func TestCopy(t *testing.T) { for i := byte(0); i < 255; i++ { obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) - obj.AddBalance(big.NewInt(int64(i)), tracing.BalanceChangeUnspecified) + obj.AddBalance(big.NewInt(int64(i))) orig.updateStateObject(obj) } orig.Finalise(false) @@ -172,9 +172,9 @@ func TestCopy(t *testing.T) { copyObj := copy.GetOrNewStateObject(common.BytesToAddress([]byte{i})) ccopyObj := ccopy.GetOrNewStateObject(common.BytesToAddress([]byte{i})) - origObj.AddBalance(big.NewInt(2*int64(i)), tracing.BalanceChangeUnspecified) - copyObj.AddBalance(big.NewInt(3*int64(i)), tracing.BalanceChangeUnspecified) - ccopyObj.AddBalance(big.NewInt(4*int64(i)), tracing.BalanceChangeUnspecified) + origObj.AddBalance(big.NewInt(2 * int64(i))) + copyObj.AddBalance(big.NewInt(3 * int64(i))) + ccopyObj.AddBalance(big.NewInt(4 * int64(i))) orig.updateStateObject(origObj) copy.updateStateObject(copyObj) @@ -292,6 +292,27 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { s.CreateAccount(addr) }, }, + { + name: "CreateContract", + fn: func(a testAction, s *StateDB) { + if !s.Exist(addr) { + s.CreateAccount(addr) + } + contractHash := s.GetCodeHash(addr) + emptyCode := contractHash == (common.Hash{}) || contractHash == types.EmptyCodeHash + storageRoot := s.GetStorageRoot(addr) + emptyStorage := storageRoot == (common.Hash{}) || storageRoot == types.EmptyRootHash + if s.GetNonce(addr) == 0 && emptyCode && emptyStorage { + s.CreateContract(addr) + // We also set some code here, to prevent the + // CreateContract action from being performed twice in a row, + // which would cause a difference in state when unrolling + // the journal. (CreateContract assumes created was false prior to + // invocation, and the journal rollback sets it to false). + s.SetCode(addr, []byte{1}) + } + }, + }, { name: "SelfDestruct", fn: func(a testAction, s *StateDB) { diff --git a/core/state/sync_test.go b/core/state/sync_test.go index 0b7c7a5de2..34d8ea5573 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -23,7 +23,6 @@ import ( "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/core/rawdb" - "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/ethdb" @@ -52,7 +51,7 @@ func makeTestState() (ethdb.Database, Database, common.Hash, []*testAccount) { obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i})) acc := &testAccount{address: common.BytesToAddress([]byte{i})} - obj.AddBalance(big.NewInt(11*int64(i)), tracing.BalanceChangeUnspecified) + obj.AddBalance(big.NewInt(11 * int64(i))) acc.balance = big.NewInt(11 * int64(i)) obj.SetNonce(uint64(42 * i)) diff --git a/core/state_processor.go b/core/state_processor.go index 84d3e1ce13..70c38135fe 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -76,9 +76,15 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) ) + + var tracingStateDB = vm.StateDB(statedb) + if hooks := cfg.Tracer; hooks != nil { + tracingStateDB = state.NewHookedState(statedb, hooks) + } + // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { - misc.ApplyDAOHardFork(statedb) + misc.ApplyDAOHardFork(tracingStateDB) } if common.TIPSigning.Cmp(blockNumber) == 0 { statedb.DeleteAddress(common.BlockSignersBinary) @@ -87,10 +93,13 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra InitSignerInTransactions(p.config, header, block.Transactions()) balanceUpdated := map[common.Address]*big.Int{} totalFeeUsed := big.NewInt(0) + + // Apply pre-execution system calls. blockContext := NewEVMBlockContext(header, p.bc, nil) - vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, tradingState, p.config, cfg) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, tracingStateDB, tradingState, p.config, cfg) signer := types.MakeSigner(p.config, blockNumber) coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil) + // Iterate over and process the individual transactions for i, tx := range block.Transactions() { // check black-list txs after hf @@ -144,9 +153,11 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, tra totalFeeUsed = totalFeeUsed.Add(totalFeeUsed, fee) } } - statedb.UpdateTRC21Fee(balanceUpdated, totalFeeUsed) + tracingStateDB.UpdateTRC21Fee(balanceUpdated, totalFeeUsed) + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.engine.Finalize(p.bc, header, statedb, parentState, block.Transactions(), block.Uncles(), receipts) + p.engine.Finalize(p.bc, header, tracingStateDB, parentState, block.Transactions(), block.Uncles(), receipts) + return receipts, allLogs, *usedGas, nil } @@ -161,9 +172,15 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated allLogs []*types.Log gp = new(GasPool).AddGas(block.GasLimit()) ) + + var tracingStateDB = vm.StateDB(statedb) + if hooks := cfg.Tracer; hooks != nil { + tracingStateDB = state.NewHookedState(statedb, hooks) + } + // Mutate the block and state according to any hard-fork specs if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { - misc.ApplyDAOHardFork(statedb) + misc.ApplyDAOHardFork(tracingStateDB) } if common.TIPSigning.Cmp(blockNumber) == 0 { statedb.DeleteAddress(common.BlockSignersBinary) @@ -179,10 +196,13 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated if cBlock.stop { return nil, nil, 0, ErrStopPreparingBlock } + + // Apply pre-execution system calls. blockContext := NewEVMBlockContext(header, p.bc, nil) - vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, tradingState, p.config, cfg) + vmenv := vm.NewEVM(blockContext, vm.TxContext{}, tracingStateDB, tradingState, p.config, cfg) signer := types.MakeSigner(p.config, blockNumber) coinbaseOwner := getCoinbaseOwner(p.bc, statedb, header, nil) + // Iterate over and process the individual transactions receipts = make([]*types.Receipt, block.Transactions().Len()) for i, tx := range block.Transactions() { @@ -222,6 +242,7 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated return nil, nil, 0, err } statedb.SetTxContext(tx.Hash(), i) + receipt, gas, tokenFeeUsed, err := ApplyTransactionWithEVM(msg, p.config, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv, balanceFee, coinbaseOwner) if err != nil { return nil, nil, 0, err @@ -238,9 +259,10 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated totalFeeUsed = totalFeeUsed.Add(totalFeeUsed, fee) } } - statedb.UpdateTRC21Fee(balanceUpdated, totalFeeUsed) + tracingStateDB.UpdateTRC21Fee(balanceUpdated, totalFeeUsed) + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.engine.Finalize(p.bc, header, statedb, parentState, block.Transactions(), block.Uncles(), receipts) + p.engine.Finalize(p.bc, header, tracingStateDB, parentState, block.Transactions(), block.Uncles(), receipts) return receipts, allLogs, *usedGas, nil } @@ -250,39 +272,39 @@ func (p *StateProcessor) ProcessBlockNoValidator(cBlock *CalculatedBlock, stated func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM, balanceFee *big.Int, coinbaseOwner common.Address) (receipt *types.Receipt, gasUsed uint64, tokenFeeUsed bool, err error) { // Initialize tracer at the beginning to ensure all transaction types // (including non-EVM special transactions) are properly traced. - if evm.Config.Tracer != nil && evm.Config.Tracer.OnTxStart != nil { - evm.Config.Tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) - if evm.Config.Tracer.OnTxEnd != nil { - defer func() { - evm.Config.Tracer.OnTxEnd(receipt, err) - }() + var tracingStateDB = vm.StateDB(statedb) + if hooks := evm.Config.Tracer; hooks != nil { + tracingStateDB = state.NewHookedState(statedb, hooks) + if hooks.OnTxStart != nil { + hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) + } + if hooks.OnTxEnd != nil { + defer func() { hooks.OnTxEnd(receipt, err) }() } } to := tx.To() if to != nil { if *to == common.BlockSignersBinary && config.IsTIPSigning(blockNumber) { - return ApplySignTransaction(config, statedb, blockNumber, blockHash, tx, usedGas) + return ApplySignTransaction(msg, config, statedb, blockNumber, blockHash, tx, usedGas, evm) } if *to == common.TradingStateAddrBinary && config.IsTIPXDCXReceiver(blockNumber) { - return ApplyEmptyTransaction(config, statedb, blockNumber, blockHash, tx, usedGas) + return ApplyEmptyTransaction(msg, config, statedb, blockNumber, blockHash, tx, usedGas, evm) } if *to == common.XDCXLendingAddressBinary && config.IsTIPXDCXReceiver(blockNumber) { - return ApplyEmptyTransaction(config, statedb, blockNumber, blockHash, tx, usedGas) + return ApplyEmptyTransaction(msg, config, statedb, blockNumber, blockHash, tx, usedGas, evm) } } if tx.IsTradingTransaction() && config.IsTIPXDCXReceiver(blockNumber) { - return ApplyEmptyTransaction(config, statedb, blockNumber, blockHash, tx, usedGas) + return ApplyEmptyTransaction(msg, config, statedb, blockNumber, blockHash, tx, usedGas, evm) } if tx.IsLendingFinalizedTradeTransaction() && config.IsTIPXDCXReceiver(blockNumber) { - return ApplyEmptyTransaction(config, statedb, blockNumber, blockHash, tx, usedGas) + return ApplyEmptyTransaction(msg, config, statedb, blockNumber, blockHash, tx, usedGas, evm) } // Create a new context to be used in the EVM environment txContext := NewEVMTxContext(msg) - - // Update the evm with the new transaction context. - evm.Reset(txContext, statedb) + evm.Reset(txContext, tracingStateDB) // Bypass blacklist address maxBlockNumber := new(big.Int).SetInt64(9147459) @@ -437,7 +459,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo // Update the state with pending changes. var root []byte if config.IsByzantium(blockNumber) { - statedb.Finalise(true) + tracingStateDB.Finalise(true) } else { root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes() } @@ -512,7 +534,7 @@ func ApplyTransaction(config *params.ChainConfig, tokensFee map[common.Address]* return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv, balanceFee, coinbaseOwner) } -func ApplySignTransaction(config *params.ChainConfig, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64) (*types.Receipt, uint64, bool, error) { +func ApplySignTransaction(msg *Message, config *params.ChainConfig, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, gasUsed uint64, tokenFeeUsed bool, err error) { // Update the state with pending changes var root []byte if config.IsByzantium(blockNumber) { @@ -532,8 +554,8 @@ func ApplySignTransaction(config *params.ChainConfig, statedb *state.StateDB, bl } statedb.SetNonce(from, nonce+1) // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx - // based on the eip phase, we're passing wether the root touch-delete accounts. - receipt := types.NewReceipt(root, false, *usedGas) + // based on the eip phase, we're passing whether the root touch-delete accounts. + receipt = types.NewReceipt(root, false, *usedGas) receipt.TxHash = tx.Hash() receipt.GasUsed = 0 // if the transaction created a contract, store the creation address in the receipt. @@ -550,7 +572,7 @@ func ApplySignTransaction(config *params.ChainConfig, statedb *state.StateDB, bl return receipt, 0, false, nil } -func ApplyEmptyTransaction(config *params.ChainConfig, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64) (*types.Receipt, uint64, bool, error) { +func ApplyEmptyTransaction(msg *Message, config *params.ChainConfig, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, gasUsed uint64, tokenFeeUsed bool, err error) { // Update the state with pending changes var root []byte if config.IsByzantium(blockNumber) { @@ -559,8 +581,8 @@ func ApplyEmptyTransaction(config *params.ChainConfig, statedb *state.StateDB, b root = statedb.IntermediateRoot(config.IsEIP158(blockNumber)).Bytes() } // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx - // based on the eip phase, we're passing wether the root touch-delete accounts. - receipt := types.NewReceipt(root, false, *usedGas) + // based on the eip phase, we're passing whether the root touch-delete accounts. + receipt = types.NewReceipt(root, false, *usedGas) receipt.TxHash = tx.Hash() receipt.GasUsed = 0 // if the transaction created a contract, store the creation address in the receipt. diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 8bb34521d6..00d20de1c0 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -859,7 +859,7 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro balance := evm.StateDB.GetBalance(scope.Contract.Address()) evm.StateDB.SubBalance(scope.Contract.Address(), balance, tracing.BalanceDecreaseSelfdestruct) evm.StateDB.AddBalance(beneficiary.Bytes20(), balance, tracing.BalanceIncreaseSelfdestruct) - evm.StateDB.Selfdestruct6780(scope.Contract.Address()) + evm.StateDB.SelfDestruct6780(scope.Contract.Address()) if tracer := evm.Config.Tracer; tracer != nil { if tracer.OnEnter != nil { tracer.OnEnter(evm.depth, byte(SELFDESTRUCT), scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) diff --git a/core/vm/interface.go b/core/vm/interface.go index 2109922399..bc6eea24fd 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -29,8 +29,8 @@ import ( type StateDB interface { CreateAccount(common.Address) - SubBalance(common.Address, *big.Int, tracing.BalanceChangeReason) - AddBalance(common.Address, *big.Int, tracing.BalanceChangeReason) + SubBalance(common.Address, *big.Int, tracing.BalanceChangeReason) *big.Int + AddBalance(common.Address, *big.Int, tracing.BalanceChangeReason) *big.Int GetBalance(common.Address) *big.Int GetNonce(common.Address) uint64 @@ -47,16 +47,21 @@ type StateDB interface { GetCommittedState(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) common.Hash GetStorageRoot(addr common.Address) common.Hash GetTransientState(addr common.Address, key common.Hash) common.Hash SetTransientState(addr common.Address, key, value common.Hash) - SelfDestruct(common.Address) + SelfDestruct(common.Address) *big.Int HasSelfDestructed(common.Address) bool - Selfdestruct6780(common.Address) + // SelfDestruct6780 is post-EIP6780 selfdestruct, which means that it's a + // send-all-to-beneficiary, unless the contract was created in this same + // transaction, in which case it will be destructed. + // This method returns the prior balance, along with a boolean which is + // true iff the object was indeed destructed. + SelfDestruct6780(common.Address) (*big.Int, bool) // Exist reports whether the given account exists in state. // Notably this should also return true for self-destructed accounts. @@ -80,6 +85,26 @@ type StateDB interface { AddLog(*types.Log) AddPreimage(common.Hash, []byte) + + // Finalise must be invoked at the end of a transaction + Finalise(bool) + + IntermediateRoot(deleteEmptyObjects bool) common.Hash + + UpdateTRC21Fee(newBalance map[common.Address]*big.Int, totalFeeUsed *big.Int) + + PutMintedRecordOnsetBlock(value common.Hash) + PutMintedRecordOnsetEpoch(value common.Hash) + + GetPostBurned(epoch uint64) common.Hash + PutPostBurned(epoch uint64, value common.Hash) + + GetPostMinted(epoch uint64) common.Hash + PutPostMinted(epoch uint64, value common.Hash) + + PutPostRewardBlock(epoch uint64, value common.Hash) + + IncrementMintedRecordNonce() } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/eth/hooks/engine_v1_hooks.go b/eth/hooks/engine_v1_hooks.go index a65d5ed6f9..e0ebf5a83d 100644 --- a/eth/hooks/engine_v1_hooks.go +++ b/eth/hooks/engine_v1_hooks.go @@ -18,6 +18,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/eth/util" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" @@ -255,7 +256,7 @@ func AttachConsensusV1Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf } // Hook calculates reward for masternodes - adaptor.EngineV1.HookReward = func(chain consensus.ChainReader, stateBlock *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) { + adaptor.EngineV1.HookReward = func(chain consensus.ChainReader, stateBlock vm.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) { number := header.Number.Uint64() rCheckpoint := chain.Config().XDPoS.RewardCheckpoint foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr diff --git a/eth/hooks/engine_v2_hooks.go b/eth/hooks/engine_v2_hooks.go index 1ecfc6fc4a..f9e677287c 100644 --- a/eth/hooks/engine_v2_hooks.go +++ b/eth/hooks/engine_v2_hooks.go @@ -17,6 +17,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/eth/util" "github.com/XinFinOrg/XDPoSChain/log" "github.com/XinFinOrg/XDPoSChain/params" @@ -265,7 +266,7 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf } // Hook calculates reward for masternodes - adaptor.EngineV2.HookReward = func(chain consensus.ChainReader, stateBlock *state.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) { + adaptor.EngineV2.HookReward = func(chain consensus.ChainReader, stateBlock vm.StateDB, parentState *state.StateDB, header *types.Header) (map[string]interface{}, error) { number := header.Number.Uint64() foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr if foundationWalletAddr == (common.Address{}) { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index e85d43f522..28d59a493e 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -881,7 +881,6 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor } // The actual TxContext will be created as part of ApplyTransactionWithEVM. vmenv := vm.NewEVM(vmctx, vm.TxContext{GasPrice: message.GasPrice}, statedb, nil, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true}) - statedb.SetLogger(tracer.Hooks) // Define a meaningful timeout of a single transaction trace if config.Timeout != nil { diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index b757f8abd1..14dd6ae533 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -30,6 +30,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/rawdb" + "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/crypto" @@ -126,20 +127,22 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { GasLimit: uint64(test.Context.GasLimit), BaseFee: test.Genesis.BaseFee, } - state = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) + st = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) ) tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig, test.Genesis.Config) if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - - state.SetLogger(tracer.Hooks) + logState := vm.StateDB(st) + if tracer.Hooks != nil { + logState = state.NewHookedState(st, tracer.Hooks) + } msg, err := core.TransactionToMessage(tx, signer, nil, nil, context.BaseFee) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - evm := vm.NewEVM(context, core.NewEVMTxContext(msg), state, nil, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) + evm := vm.NewEVM(context, core.NewEVMTxContext(msg), logState, nil, test.Genesis.Config, vm.Config{Tracer: tracer.Hooks}) tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) vmRet, err := core.ApplyMessage(evm, msg, new(core.GasPool).AddGas(tx.Gas()), common.Address{}) if err != nil { @@ -348,7 +351,7 @@ func TestInternals(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - state := tests.MakePreState(rawdb.NewMemoryDatabase(), + st := tests.MakePreState(rawdb.NewMemoryDatabase(), types.GenesisAlloc{ to: types.Account{ Code: tc.code, @@ -357,7 +360,12 @@ func TestInternals(t *testing.T) { Balance: big.NewInt(500000000000000), }, }) - state.SetLogger(tc.tracer.Hooks) + + logState := vm.StateDB(st) + if hooks := tc.tracer.Hooks; hooks != nil { + logState = state.NewHookedState(st, hooks) + } + tx, err := types.SignNewTx(key, signer, &types.LegacyTx{ To: &to, Value: big.NewInt(0), @@ -371,7 +379,7 @@ func TestInternals(t *testing.T) { Origin: origin, GasPrice: tx.GasPrice(), } - evm := vm.NewEVM(context, txContext, state, nil, config, vm.Config{Tracer: tc.tracer.Hooks}) + evm := vm.NewEVM(context, txContext, logState, nil, config, vm.Config{Tracer: tc.tracer.Hooks}) msg, err := core.TransactionToMessage(tx, signer, nil, nil, big.NewInt(0)) if err != nil { t.Fatalf("test %v: failed to create message: %v", tc.name, err) diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index 5f567732e6..7e38f76e1d 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -101,7 +101,6 @@ func flatCallTracerTestRunner(tracerName string, filename string, dirPath string return fmt.Errorf("failed to create call tracer: %v", err) } - state.SetLogger(tracer.Hooks) msg, err := core.TransactionToMessage(tx, signer, nil, context.BlockNumber, context.BaseFee) if err != nil { return fmt.Errorf("failed to prepare transaction for tracing: %v", err) diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index ee8368a0e5..c44220be2f 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -109,18 +109,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to create call tracer: %v", err) } - // msg, err := core.TransactionToMessage(tx, signer, nil, context.BlockNumber, context.BaseFee) - // if err != nil { - // t.Fatalf("failed to prepare transaction for tracing: %v", err) - // } - // evm := vm.NewEVM(context, core.NewEVMTxContext(msg), statedb, nil, test.Genesis.Config, vm.Config{Tracer: tracer}) - // st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) - // if _, err = st.TransitionDb(common.Address{}); err != nil { - // t.Fatalf("failed to execute transaction: %v", err) - // } - // // Retrieve the trace result and compare against the expected. - state.SetLogger(tracer.Hooks) msg, err := core.TransactionToMessage(tx, signer, nil, context.BlockNumber, context.BaseFee) if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) diff --git a/eth/tracers/logger/logger_test.go b/eth/tracers/logger/logger_test.go index 39a7b99ddb..1e319683b2 100644 --- a/eth/tracers/logger/logger_test.go +++ b/eth/tracers/logger/logger_test.go @@ -48,9 +48,11 @@ type dummyStatedb struct { state.StateDB } -func (*dummyStatedb) GetRefund() uint64 { return 1337 } -func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} } -func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) {} +func (*dummyStatedb) GetRefund() uint64 { return 1337 } +func (*dummyStatedb) GetState(_ common.Address, _ common.Hash) common.Hash { return common.Hash{} } +func (*dummyStatedb) SetState(_ common.Address, _ common.Hash, _ common.Hash) common.Hash { + return common.Hash{} +} func TestStoreCapture(t *testing.T) { var ( diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index c6ab68551c..602ab19e70 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1292,10 +1292,16 @@ func applyMessage(ctx context.Context, b Backend, args TransactionArgs, state *s evm.SetPrecompiles(precompiles) } - return applyMessageWithEVM(ctx, evm, msg, state, timeout, gp) + res, err := applyMessageWithEVM(ctx, evm, msg, timeout, gp) + // If an internal state error occurred, let that have precedence. Otherwise, + // a "trie root missing" type of error will masquerade as e.g. "insufficient gas" + if err := state.Error(); err != nil { + return nil, err + } + return res, err } -func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, state *state.StateDB, timeout time.Duration, gp *core.GasPool) (*core.ExecutionResult, error) { +func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, timeout time.Duration, gp *core.GasPool) (*core.ExecutionResult, error) { // Wait for the context to be done and cancel the evm. Even if the // EVM has finished, cancelling may be done (repeatedly) go func() { @@ -1305,9 +1311,6 @@ func applyMessageWithEVM(ctx context.Context, evm *vm.EVM, msg *core.Message, st // Execute the message. result, err := core.ApplyMessage(evm, msg, gp, common.Address{}) - if err := state.Error(); err != nil { - return nil, err - } // If the timer caused an abort, return an appropriate error message if evm.Cancelled() { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index a3b631f1c1..5bed782b90 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -177,7 +177,10 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } evm = vm.NewEVM(blockContext, vm.TxContext{GasPrice: new(big.Int)}, sim.state, nil, sim.chainConfig, *vmConfig) ) - sim.state.SetLogger(tracer.Hooks()) + var tracingStateDB = vm.StateDB(sim.state) + if hooks := tracer.Hooks(); hooks != nil { + tracingStateDB = state.NewHookedState(sim.state, hooks) + } // It is possible to override precompiles with EVM bytecode, or // move them to another address. if precompiles != nil { @@ -195,8 +198,8 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, tracer.reset(tx.Hash(), uint(i)) // EoA check is always skipped, even in validation mode. msg := call.ToMessage(sim.b, header.BaseFee, !sim.validate, true) - evm.Reset(core.NewEVMTxContext(msg), sim.state) - result, err := applyMessageWithEVM(ctx, evm, msg, sim.state, timeout, sim.gp) + evm.Reset(core.NewEVMTxContext(msg), tracingStateDB) + result, err := applyMessageWithEVM(ctx, evm, msg, timeout, sim.gp) if err != nil { txErr := txValidationError(err) return nil, nil, txErr @@ -204,7 +207,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, // Update the state with pending changes. var root []byte if sim.chainConfig.IsByzantium(blockContext.BlockNumber) { - sim.state.Finalise(true) + tracingStateDB.Finalise(true) } else { root = sim.state.IntermediateRoot(sim.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes() }