Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) implement EIP-1153 - transient storage #701

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
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
96 changes: 96 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3607,3 +3607,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(params.AllCliqueProtocolChanges, 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, params.AllCliqueProtocolChanges, 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
96 changes: 71 additions & 25 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
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,44 @@ 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 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Clear out any leftover from previous executions
s.accessList = newAccessList()

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)
}
}
if rules.IsShanghai { // EIP-3651: warm coinbase
s.AddAddressToAccessList(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