Skip to content

Commit

Permalink
(feat)all: add EIP-1153 - transient storage (#721)
Browse files Browse the repository at this point in the history
* all: implement EIP-1153 transient storage (ethereum#26003)

Implements TSTORE and TLOAD as specified by the following EIP:

https://eips.ethereum.org/EIPS/eip-1153
https://ethereum-magicians.org/t/eip-1153-transient-storage-opcodes/553

Co-authored-by: Sara Reynolds <snreynolds2506@gmail.com>
Co-authored-by: Martin Holst Swende <martin@swende.se>
Co-authored-by: Gary Rong <garyrong0905@gmail.com>

* core/vm: move TSTORE,TLOAD to correct opcode nums (ethereum#27613)

* core/vm: move TSTORE,TLOAD to correct opcode nums

* core/vm: cleanup

* fix tests, rename

* goimports

* enable 1153 at Curie

* bump version

* comment fix

* improve test

* version

* testchainconfig

* fix another test that affects newly added

* fix previous test to clenaup after

---------

Co-authored-by: Mark Tyneway <mark.tyneway@gmail.com>
Co-authored-by: Sara Reynolds <snreynolds2506@gmail.com>
Co-authored-by: Martin Holst Swende <martin@swende.se>
Co-authored-by: Gary Rong <garyrong0905@gmail.com>
Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
  • Loading branch information
6 people committed Apr 28, 2024
1 parent a860446 commit 3592139
Show file tree
Hide file tree
Showing 24 changed files with 478 additions and 84 deletions.
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/execution.go
Expand Up @@ -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)
Expand Down
107 changes: 106 additions & 1 deletion core/blockchain_test.go
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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")
}
}
40 changes: 29 additions & 11 deletions core/chain_makers.go
Expand Up @@ -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.
//
Expand All @@ -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
Expand All @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions core/state/journal.go
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}
Expand Down
97 changes: 72 additions & 25 deletions core/state/statedb.go
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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).
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 3592139

Please sign in to comment.