diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index e101b2c3bf..6e8cb9f446 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -162,7 +162,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, } vmConfig.Tracer = tracer vmConfig.Debug = (tracer != nil) - statedb.Prepare(tx.Hash(), txIndex) + statedb.SetTxContext(tx.Hash(), txIndex) txContext := core.NewEVMTxContext(msg) snapshot := statedb.Snapshot() evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 365497c359..b0f601f455 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -3188,6 +3188,9 @@ func TestTransactionCountLimit(t *testing.T) { config := params.TestChainConfig config.Scroll.MaxTxPerBlock = new(int) *config.Scroll.MaxTxPerBlock = 1 + defer func() { + config.Scroll.MaxTxPerBlock = nil + }() var ( engine = ethash.NewFaker() @@ -3359,6 +3362,10 @@ func TestL1MessageValidationFailure(t *testing.T) { config.Scroll.L1Config.NumL1MessagesPerBlock = 1 maxPayload := 1024 config.Scroll.MaxTxPayloadBytesPerBlock = &maxPayload + defer func() { + config.Scroll.MaxTxPayloadBytesPerBlock = nil + config.Scroll.L1Config.NumL1MessagesPerBlock = 0 + }() genspec := &Genesis{ Config: config, @@ -3437,7 +3444,9 @@ func TestBlockPayloadSizeLimit(t *testing.T) { config := params.TestChainConfig config.Scroll.MaxTxPayloadBytesPerBlock = new(int) *config.Scroll.MaxTxPayloadBytesPerBlock = 150 - config.Scroll.MaxTxPerBlock = nil + defer func() { + config.Scroll.MaxTxPayloadBytesPerBlock = nil + }() var ( engine = ethash.NewFaker() @@ -3607,3 +3616,99 @@ func TestEIP3651(t *testing.T) { t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) } } + +// TestTransientStorageReset ensures the transient storage is wiped correctly +// between transactions. +func TestTransientStorageReset(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + engine = ethash.NewFaker() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + destAddress = crypto.CreateAddress(address, 0) + funds = big.NewInt(1000000000000000) + vmConfig = vm.Config{ + ExtraEips: []int{1153}, // Enable transient storage EIP + } + ) + code := append([]byte{ + // TLoad value with location 1 + byte(vm.PUSH1), 0x1, + byte(vm.TLOAD), + + // PUSH location + byte(vm.PUSH1), 0x1, + + // SStore location:value + byte(vm.SSTORE), + }, make([]byte, 32-6)...) + initCode := []byte{ + // TSTORE 1:1 + byte(vm.PUSH1), 0x1, + byte(vm.PUSH1), 0x1, + byte(vm.TSTORE), + + // Get the runtime-code on the stack + byte(vm.PUSH32)} + initCode = append(initCode, code...) + initCode = append(initCode, []byte{ + byte(vm.PUSH1), 0x0, // offset + byte(vm.MSTORE), + byte(vm.PUSH1), 0x6, // size + byte(vm.PUSH1), 0x0, // offset + byte(vm.RETURN), // return 6 bytes of zero-code + }...) + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + }, + } + genesis := gspec.MustCommit(db) + nonce := uint64(0) + signer := types.HomesteadSigner{} + blocks, _ := GenerateChain(gspec.Config, genesis, engine, db, 1, func(i int, b *BlockGen) { + fee := big.NewInt(1) + if b.header.BaseFee != nil { + fee = b.header.BaseFee + } + b.SetCoinbase(common.Address{1}) + tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 100000, + Data: initCode, + }) + nonce++ + b.AddTxWithVMConfig(tx, vmConfig) + + tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{ + Nonce: nonce, + GasPrice: new(big.Int).Set(fee), + Gas: 100000, + To: &destAddress, + }) + b.AddTxWithVMConfig(tx, vmConfig) + nonce++ + }) + + // Initialize the blockchain with 1153 enabled. + chain, err := NewBlockChain(db, nil, gspec.Config, engine, vmConfig, nil, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + // Import the blocks + if _, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("failed to insert into chain: %v", err) + } + // Check the storage + state, err := chain.StateAt(chain.CurrentHeader().Root) + if err != nil { + t.Fatalf("Failed to load state %v", err) + } + loc := common.BytesToHash([]byte{1}) + slot := state.GetState(destAddress, loc) + if slot != (common.Hash{}) { + t.Fatalf("Unexpected dirty storage slot") + } +} diff --git a/core/chain_makers.go b/core/chain_makers.go index 99c527f353..143dbb0059 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -79,6 +79,26 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) { b.header.Difficulty = diff } +// addTx adds a transaction to the generated block. If no coinbase has +// been set, the block's coinbase is set to the zero address. +// +// There are a few options can be passed as well in order to run some +// customized rules. +// - bc: enables the ability to query historical block hashes for BLOCKHASH +// - vmConfig: extends the flexibility for customizing evm rules, e.g. enable extra EIPs +func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transaction) { + if b.gasPool == nil { + b.SetCoinbase(common.Address{}) + } + b.statedb.SetTxContext(tx.Hash(), len(b.txs)) + receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig) + if err != nil { + panic(err) + } + b.txs = append(b.txs, tx) + b.receipts = append(b.receipts, receipt) +} + // AddTx adds a transaction to the generated block. If no coinbase has // been set, the block's coinbase is set to the zero address. // @@ -88,7 +108,7 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) { // added. Notably, contract code relying on the BLOCKHASH instruction // will panic during execution. func (b *BlockGen) AddTx(tx *types.Transaction) { - b.AddTxWithChain(nil, tx) + b.addTx(nil, vm.Config{}, tx) } // AddTxWithChain adds a transaction to the generated block. If no coinbase has @@ -100,16 +120,14 @@ func (b *BlockGen) AddTx(tx *types.Transaction) { // added. If contract code relies on the BLOCKHASH instruction, // the block in chain will be returned. func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { - if b.gasPool == nil { - b.SetCoinbase(common.Address{}) - } - b.statedb.Prepare(tx.Hash(), len(b.txs)) - receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{}) - if err != nil { - panic(err) - } - b.txs = append(b.txs, tx) - b.receipts = append(b.receipts, receipt) + b.addTx(bc, vm.Config{}, tx) +} + +// AddTxWithVMConfig adds a transaction to the generated block. If no coinbase has +// been set, the block's coinbase is set to the zero address. +// The evm interpreter can be customized with the provided vm config. +func (b *BlockGen) AddTxWithVMConfig(tx *types.Transaction, config vm.Config) { + b.addTx(nil, config, tx) } // GetBalance returns the balance of the given address at the generated block. diff --git a/core/state/journal.go b/core/state/journal.go index 72239bfe52..3ba2b53688 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -138,6 +138,11 @@ type ( address *common.Address slot *common.Hash } + + transientStorageChange struct { + account *common.Address + key, prevalue common.Hash + } ) func (ch createObjectChange) revert(s *StateDB) { @@ -213,6 +218,14 @@ func (ch storageChange) dirtied() *common.Address { return ch.account } +func (ch transientStorageChange) revert(s *StateDB) { + s.setTransientState(*ch.account, ch.key, ch.prevalue) +} + +func (ch transientStorageChange) dirtied() *common.Address { + return nil +} + func (ch refundChange) revert(s *StateDB) { s.refund = ch.prev } diff --git a/core/state/statedb.go b/core/state/statedb.go index 5caea2a605..6629a50eae 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -97,6 +97,9 @@ type StateDB struct { // Per-transaction access list accessList *accessList + // Transient storage + transientStorage transientStorage + // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. journal *journal @@ -140,6 +143,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) preimages: make(map[common.Hash][]byte), journal: newJournal(), accessList: newAccessList(), + transientStorage: newTransientStorage(), hasher: crypto.NewKeccakState(), } if sdb.snaps != nil { @@ -472,6 +476,35 @@ func (s *StateDB) Suicide(addr common.Address) bool { return true } +// SetTransientState sets transient storage for a given account. It +// adds the change to the journal so that it can be rolled back +// to its previous value if there is a revert. +func (s *StateDB) SetTransientState(addr common.Address, key, value common.Hash) { + prev := s.GetTransientState(addr, key) + if prev == value { + return + } + + s.journal.append(transientStorageChange{ + account: &addr, + key: key, + prevalue: prev, + }) + + s.setTransientState(addr, key, value) +} + +// setTransientState is a lower level setter for transient storage. It +// is called during a revert to prevent modifications to the journal. +func (s *StateDB) setTransientState(addr common.Address, key, value common.Hash) { + s.transientStorage.Set(addr, key, value) +} + +// GetTransientState gets transient storage for a given account. +func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash { + return s.transientStorage.Get(addr, key) +} + // // Setting, updating & deleting state object methods. // @@ -635,8 +668,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // CreateAccount is called during the EVM CREATE operation. The situation might arise that // a contract does the following: // -// 1. sends funds to sha(account ++ (nonce + 1)) -// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) +// 1. sends funds to sha(account ++ (nonce + 1)) +// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) // // Carrying over the balance ensures that Ether doesn't disappear. func (s *StateDB) CreateAccount(addr common.Address) { @@ -741,6 +774,8 @@ func (s *StateDB) Copy() *StateDB { // to not blow up if we ever decide copy it in the middle of a transaction state.accessList = s.accessList.Copy() + state.transientStorage = s.transientStorage.Copy() + // If there's a prefetcher running, make an inactive copy of it that can // only access data but does not actively preload (since the user will not // know that they need to explicitly terminate an active copy). @@ -913,9 +948,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { return s.trie.Hash() } -// Prepare sets the current transaction hash and index which are -// used when the EVM emits new state logs. -func (s *StateDB) Prepare(thash common.Hash, ti int) { +// SetTxContext sets the current transaction hash and index which are +// used when the EVM emits new state logs. It should be invoked before +// transaction execution. +func (s *StateDB) SetTxContext(thash common.Hash, ti int) { s.thash = thash s.txIndex = ti s.accessList = newAccessList() @@ -1018,34 +1054,45 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { return root, err } -// PrepareAccessList handles the preparatory steps for executing a state transition with -// regards to EIP-2929, EIP-2930 and EIP-3651: +// Prepare handles the preparatory steps for executing a state transition with. +// This method must be invoked before state transition. // +// Berlin fork: // - Add sender to access list (2929) // - Add destination to access list (2929) // - Add precompiles to access list (2929) // - Add the contents of the optional tx access list (2930) -// - Add coinbase to access list (3651) // -// This method should only be called if Berlin/2929+2930 is applicable at the current number. -func (s *StateDB) PrepareAccessList(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { - s.AddAddressToAccessList(sender) - if dst != nil { - s.AddAddressToAccessList(*dst) - // If it's a create-tx, the destination will be added inside evm.create - } - for _, addr := range precompiles { - s.AddAddressToAccessList(addr) - } - for _, el := range list { - s.AddAddressToAccessList(el.Address) - for _, key := range el.StorageKeys { - s.AddSlotToAccessList(el.Address, key) +// Potential EIPs: +// - Reset access list (Berlin) +// - Add coinbase to access list (EIP-3651) +// - Reset transient storage (EIP-1153) +func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + if rules.IsBerlin { + // Clear out any leftover from previous executions + al := newAccessList() + s.accessList = al + + al.AddAddress(sender) + if dst != nil { + al.AddAddress(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range precompiles { + al.AddAddress(addr) + } + for _, el := range list { + al.AddAddress(el.Address) + for _, key := range el.StorageKeys { + al.AddSlot(el.Address, key) + } + } + if rules.IsShanghai { // EIP-3651: warm coinbase + al.AddAddress(coinbase) } } - if rules.IsShanghai { // EIP-3651: warm coinbase - s.AddAddressToAccessList(coinbase) - } + // Reset transient storage at the beginning of transaction execution + s.transientStorage = newTransientStorage() } // AddAddressToAccessList adds the given address to the access list diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 90ef2bbd2d..167b1e5099 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -342,6 +342,16 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction { }, args: make([]int64, 1), }, + { + name: "SetTransientState", + fn: func(a testAction, s *StateDB) { + var key, val common.Hash + binary.BigEndian.PutUint16(key[:], uint16(a.args[0])) + binary.BigEndian.PutUint16(val[:], uint16(a.args[1])) + s.SetTransientState(addr, key, val) + }, + args: make([]int64, 2), + }, } action := actions[r.Intn(len(actions))] var nameargs []string @@ -916,3 +926,37 @@ func TestStateDBAccessList(t *testing.T) { t.Fatalf("expected empty, got %d", got) } } + +func TestStateDBTransientStorage(t *testing.T) { + memDb := rawdb.NewMemoryDatabase() + db := NewDatabase(memDb) + state, _ := New(common.Hash{}, db, nil) + + key := common.Hash{0x01} + value := common.Hash{0x02} + addr := common.Address{} + + state.SetTransientState(addr, key, value) + if exp, got := 1, state.journal.length(); exp != got { + t.Fatalf("journal length mismatch: have %d, want %d", got, exp) + } + // the retrieved value should equal what was set + if got := state.GetTransientState(addr, key); got != value { + t.Fatalf("transient storage mismatch: have %x, want %x", got, value) + } + + // revert the transient state being set and then check that the + // value is now the empty hash + state.journal.revert(state, 0) + if got, exp := state.GetTransientState(addr, key), (common.Hash{}); exp != got { + t.Fatalf("transient storage mismatch: have %x, want %x", got, exp) + } + + // set transient state and then copy the statedb and ensure that + // the transient state is copied + state.SetTransientState(addr, key, value) + cpy := state.Copy() + if got := cpy.GetTransientState(addr, key); got != value { + t.Fatalf("transient storage mismatch: have %x, want %x", got, value) + } +} diff --git a/core/state/transient_storage.go b/core/state/transient_storage.go new file mode 100644 index 0000000000..578b821a27 --- /dev/null +++ b/core/state/transient_storage.go @@ -0,0 +1,55 @@ +// Copyright 2022 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 ( + "github.com/scroll-tech/go-ethereum/common" +) + +// transientStorage is a representation of EIP-1153 "Transient Storage". +type transientStorage map[common.Address]Storage + +// newTransientStorage creates a new instance of a transientStorage. +func newTransientStorage() transientStorage { + return make(transientStorage) +} + +// Set sets the transient-storage `value` for `key` at the given `addr`. +func (t transientStorage) Set(addr common.Address, key, value common.Hash) { + if _, ok := t[addr]; !ok { + t[addr] = make(Storage) + } + t[addr][key] = value +} + +// Get gets the transient storage for `key` at the given `addr`. +func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash { + val, ok := t[addr] + if !ok { + return common.Hash{} + } + return val[key] +} + +// Copy does a deep copy of the transientStorage +func (t transientStorage) Copy() transientStorage { + storage := make(transientStorage) + for key, value := range t { + storage[key] = value.Copy() + } + return storage +} diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index c0f669d9c7..1fc7e02893 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -69,7 +69,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c if err != nil { return // Also invalid block, bail out } - statedb.Prepare(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) if err != nil { diff --git a/core/state_processor.go b/core/state_processor.go index faf93be448..95952a4c19 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -95,7 +95,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } - statedb.Prepare(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i) receipt, err := applyTransaction(msg, p.config, p.bc, nil, gp, statedb, blockNumber, blockHash, tx, usedGas, vmenv) if err != nil { return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) diff --git a/core/state_transition.go b/core/state_transition.go index b9f514c853..eb292ba27e 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -381,11 +381,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if rules.IsShanghai && contractCreation && len(st.data) > params.MaxInitCodeSize { return nil, fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(st.data), params.MaxInitCodeSize) } - - // Set up the initial access list. - if rules.IsBerlin { - st.state.PrepareAccessList(rules, msg.From(), st.evm.Context.Coinbase, msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) - } + // Execute the preparatory steps for state transition which includes: + // - prepare accessList(post-berlin) + // - reset transient storage(eip 1153) + st.state.Prepare(rules, msg.From(), st.evm.Context.Coinbase, msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) var ( ret []byte diff --git a/core/vm/eips.go b/core/vm/eips.go index 146e3f0432..fe2c8fa5aa 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -22,6 +22,8 @@ import ( "github.com/holiman/uint256" + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/params" ) @@ -35,6 +37,7 @@ var activators = map[int]func(*JumpTable){ 2200: enable2200, 1884: enable1884, 1344: enable1344, + 1153: enable1153, } // EnableEIP enables the given EIP on the config. @@ -173,6 +176,45 @@ func enable3198(jt *JumpTable) { } } +// enable1153 applies EIP-1153 "Transient Storage" +// - Adds TLOAD that reads from transient storage +// - Adds TSTORE that writes to transient storage +func enable1153(jt *JumpTable) { + jt[TLOAD] = &operation{ + execute: opTload, + constantGas: params.WarmStorageReadCostEIP2929, + minStack: minStack(1, 1), + maxStack: maxStack(1, 1), + } + + jt[TSTORE] = &operation{ + execute: opTstore, + constantGas: params.WarmStorageReadCostEIP2929, + minStack: minStack(2, 0), + maxStack: maxStack(2, 0), + } +} + +// opTload implements TLOAD opcode +func opTload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + loc := scope.Stack.peek() + hash := common.Hash(loc.Bytes32()) + val := interpreter.evm.StateDB.GetTransientState(scope.Contract.Address(), hash) + loc.SetBytes(val.Bytes()) + return nil, nil +} + +// opTstore implements TSTORE opcode +func opTstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + if interpreter.readOnly { + return nil, ErrWriteProtection + } + loc := scope.Stack.pop() + val := scope.Stack.pop() + interpreter.evm.StateDB.SetTransientState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) + return nil, nil +} + // opBaseFee implements BASEFEE opcode func opBaseFee(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { baseFee, _ := uint256.FromBig(interpreter.evm.Context.BaseFee) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f43ab576c2..aba8fdb0a3 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -534,8 +534,7 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by func opSstore(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { loc := scope.Stack.pop() val := scope.Stack.pop() - interpreter.evm.StateDB.SetState(scope.Contract.Address(), - loc.Bytes32(), val.Bytes32()) + interpreter.evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) return nil, nil } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index e4a50c42e0..513222b305 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -21,12 +21,16 @@ import ( "encoding/json" "fmt" "io/ioutil" + "math/big" "testing" "strings" "github.com/holiman/uint256" + "github.com/scroll-tech/go-ethereum/core/rawdb" + "github.com/scroll-tech/go-ethereum/core/state" + "github.com/scroll-tech/go-ethereum/common/math" "github.com/scroll-tech/go-ethereum/common" @@ -49,6 +53,14 @@ var alphabetSoup = "ABCDEF090807060504030201ffffffffffffffffffffffffffffffffffff var commonParams []*twoOperandParams var twoOpMethods map[string]executionFunc +type contractRef struct { + addr common.Address +} + +func (c contractRef) Address() common.Address { + return c.addr +} + func init() { // Params is a list of common edgecases that should be used for some common tests @@ -563,6 +575,49 @@ func BenchmarkOpMstore(bench *testing.B) { } } +func TestOpTstore(t *testing.T) { + var ( + statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + env = NewEVM(BlockContext{}, TxContext{}, statedb, params.TestChainConfig, Config{}) + stack = newstack() + mem = NewMemory() + evmInterpreter = NewEVMInterpreter(env, env.Config) + caller = common.Address{} + to = common.Address{1} + contractRef = contractRef{caller} + contract = NewContract(contractRef, AccountRef(to), new(big.Int), 0) + scopeContext = ScopeContext{mem, stack, contract} + value = common.Hex2Bytes("abcdef00000000000000abba000000000deaf000000c0de00100000000133700") + ) + + // Add a stateObject for the caller and the contract being called + statedb.CreateAccount(caller) + statedb.CreateAccount(to) + + env.interpreter = evmInterpreter + pc := uint64(0) + // push the value to the stack + stack.push(new(uint256.Int).SetBytes(value)) + // push the location to the stack + stack.push(new(uint256.Int)) + opTstore(&pc, evmInterpreter, &scopeContext) + // there should be no elements on the stack after TSTORE + if stack.len() != 0 { + t.Fatal("stack wrong size") + } + // push the location to the stack + stack.push(new(uint256.Int)) + opTload(&pc, evmInterpreter, &scopeContext) + // there should be one element on the stack after TLOAD + if stack.len() != 1 { + t.Fatal("stack wrong size") + } + val := stack.peek() + if !bytes.Equal(val.Bytes(), value) { + t.Fatal("incorrect element read from transient storage") + } +} + func BenchmarkOpSHA3(bench *testing.B) { var ( env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) diff --git a/core/vm/interface.go b/core/vm/interface.go index a0173ddca5..fe27905e91 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -55,6 +55,9 @@ type StateDB interface { GetProofByHash(addrHash common.Hash) ([][]byte, error) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) + GetTransientState(addr common.Address, key common.Hash) common.Hash + SetTransientState(addr common.Address, key, value common.Hash) + Suicide(common.Address) bool HasSuicided(common.Address) bool @@ -65,7 +68,6 @@ type StateDB interface { // is defined according to EIP161 (balance = nonce = code = 0). Empty(common.Address) bool - PrepareAccessList(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) AddressInAccessList(addr common.Address) bool SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform @@ -74,6 +76,7 @@ type StateDB interface { // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform // even if the feature/fork is not active yet AddSlotToAccessList(addr common.Address, slot common.Hash) + Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) RevertToSnapshot(int) Snapshot() int diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 55e739f098..d8826e9d34 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -70,7 +70,6 @@ type JumpTable [256]*operation // contantinople, istanbul, petersburg, berlin, london, shanghai, curie, and descartes instructions. func newDescartesInstructionSet() JumpTable { instructionSet := newCurieInstructionSet() - return instructionSet } @@ -80,6 +79,7 @@ func newCurieInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 enable5656(&instructionSet) // EIP-5656 (MCOPY opcode) + enable1153(&instructionSet) // EIP-1153 (TLOAD, TSTORE opcodes) return instructionSet } diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 01d2af5d04..c4ced9ad55 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -120,6 +120,8 @@ const ( MSIZE OpCode = 0x59 GAS OpCode = 0x5a JUMPDEST OpCode = 0x5b + TLOAD OpCode = 0x5c + TSTORE OpCode = 0x5d MCOPY OpCode = 0x5e PUSH0 OpCode = 0x5f ) @@ -303,6 +305,8 @@ var opCodeToString = map[OpCode]string{ MSIZE: "MSIZE", GAS: "GAS", JUMPDEST: "JUMPDEST", + TLOAD: "TLOAD", + TSTORE: "TSTORE", MCOPY: "MCOPY", PUSH0: "PUSH0", @@ -379,7 +383,7 @@ var opCodeToString = map[OpCode]string{ LOG3: "LOG3", LOG4: "LOG4", - // 0xf0 range. + // 0xf0 range - closures. CREATE: "CREATE", CALL: "CALL", RETURN: "RETURN", @@ -468,6 +472,8 @@ var stringToOp = map[string]OpCode{ "MSIZE": MSIZE, "GAS": GAS, "JUMPDEST": JUMPDEST, + "TLOAD": TLOAD, + "TSTORE": TSTORE, "MCOPY": MCOPY, "PUSH0": PUSH0, "PUSH1": PUSH1, diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index a27bfbe1bd..bdfedde8c9 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -122,10 +122,13 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { address = common.BytesToAddress([]byte("contract")) vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber) ) - if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { - cfg.State.PrepareAccessList(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) - } + // Execute the preparatory steps for state transition which includes: + // - prepare accessList(post-berlin) + // - reset transient storage(eip 1153) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code) @@ -137,7 +140,6 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { cfg.GasLimit, cfg.Value, ) - return ret, cfg.State, err } @@ -154,10 +156,13 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { var ( vmenv = NewEnv(cfg) sender = vm.AccountRef(cfg.Origin) + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber) ) - if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { - cfg.State.PrepareAccessList(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) - } + // Execute the preparatory steps for state transition which includes: + // - prepare accessList(post-berlin) + // - reset transient storage(eip 1153) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) + // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( sender, @@ -176,14 +181,17 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, error) { setDefaults(cfg) - vmenv := NewEnv(cfg) + var ( + vmenv = NewEnv(cfg) + sender = cfg.State.GetOrNewStateObject(cfg.Origin) + statedb = cfg.State + rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber) + ) + // Execute the preparatory steps for state transition which includes: + // - prepare accessList(post-berlin) + // - reset transient storage(eip 1153) + statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) - sender := cfg.State.GetOrNewStateObject(cfg.Origin) - statedb := cfg.State - - if rules := cfg.ChainConfig.Rules(vmenv.Context.BlockNumber); rules.IsBerlin { - statedb.PrepareAccessList(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) - } // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( sender, diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 8caa3e9e83..c4bd3feb8a 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -179,7 +179,7 @@ func (api *consensusAPI) AssembleBlock(params assembleBlockParams) (*executableD from, _ := types.Sender(signer, tx) // Execute the transaction - env.state.Prepare(tx.Hash(), env.tcount) + env.state.SetTxContext(tx.Hash(), env.tcount) err = env.commitTransaction(tx, coinbase) switch err { case core.ErrGasLimitReached: diff --git a/eth/state_accessor.go b/eth/state_accessor.go index a82bc05288..11eaa365b6 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -37,15 +37,15 @@ import ( // base layer statedb can be passed then it's regarded as the statedb of the // parent block. // Parameters: -// - block: The block for which we want the state (== state at the stateRoot of the parent) -// - reexec: The maximum number of blocks to reprocess trying to obtain the desired state -// - base: If the caller is tracing multiple blocks, the caller can provide the parent state -// continuously from the callsite. -// - checklive: if true, then the live 'blockchain' state database is used. If the caller want to -// perform Commit or other 'save-to-disk' changes, this should be set to false to avoid -// storing trash persistently -// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided, -// it would be preferrable to start from a fresh state, if we have it on disk. +// - block: The block for which we want the state (== state at the stateRoot of the parent) +// - reexec: The maximum number of blocks to reprocess trying to obtain the desired state +// - base: If the caller is tracing multiple blocks, the caller can provide the parent state +// continuously from the callsite. +// - checklive: if true, then the live 'blockchain' state database is used. If the caller want to +// perform Commit or other 'save-to-disk' changes, this should be set to false to avoid +// storing trash persistently +// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is provided, +// it would be preferrable to start from a fresh state, if we have it on disk. func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) { var ( current *types.Block @@ -191,7 +191,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) - statedb.Prepare(tx.Hash(), idx) + statedb.SetTxContext(tx.Hash(), idx) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) if err != nil { return nil, vm.BlockContext{}, nil, err diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 833d20cba7..e4c3957c60 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -543,7 +543,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config txContext = core.NewEVMTxContext(msg) vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) ) - statedb.Prepare(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) if err != nil { @@ -649,7 +649,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(signer, block.BaseFee()) - statedb.Prepare(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i) vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) if err != nil { @@ -768,7 +768,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block } // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) - statedb.Prepare(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i) l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) if err == nil { _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee) @@ -948,7 +948,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex } // Call Prepare to clear out the statedb access list - statedb.Prepare(txctx.TxHash, txctx.TxIndex) + statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), l1DataFee) if err != nil { diff --git a/les/state_accessor.go b/les/state_accessor.go index e343beae69..80cfbac936 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -59,7 +59,7 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. msg, _ := tx.AsMessage(signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(block.Header(), leth.blockchain, leth.blockchain.Config(), nil) - statedb.Prepare(tx.Hash(), idx) + statedb.SetTxContext(tx.Hash(), idx) if idx == txIndex { return msg, context, statedb, nil } diff --git a/miner/worker.go b/miner/worker.go index 7764786fcd..f171a5b073 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1109,7 +1109,7 @@ loop: continue } // Start executing the transaction - w.current.state.Prepare(tx.Hash(), w.current.tcount) + w.current.state.SetTxContext(tx.Hash(), w.current.tcount) logs, traces, err := w.commitTransaction(tx, coinbase) switch { diff --git a/params/version.go b/params/version.go index fe018b48a4..3964490acc 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 3 // Minor version component of the current release - VersionPatch = 6 // Patch version component of the current release + VersionPatch = 7 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string ) diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go index 9c78445c6d..dbb725dedb 100644 --- a/rollup/tracing/tracing.go +++ b/rollup/tracing/tracing.go @@ -226,7 +226,7 @@ func (env *TraceEnv) GetBlockTrace(block *types.Block) (*types.BlockTrace, error // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(env.signer, block.BaseFee()) - env.state.Prepare(tx.Hash(), i) + env.state.SetTxContext(tx.Hash(), i) vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{}) l1DataFee, err := fees.CalculateL1DataFee(tx, env.state) if err != nil { @@ -331,7 +331,7 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B vmenv := vm.NewEVM(env.blockCtx, txContext, state, env.chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) // Call Prepare to clear out the statedb access list - state.Prepare(txctx.TxHash, txctx.TxIndex) + state.SetTxContext(txctx.TxHash, txctx.TxIndex) // Computes the new state by applying the given message. l1DataFee, err := fees.CalculateL1DataFee(tx, state)