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

all: implement EIP-1153 transient storage #26003

Merged
merged 8 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
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 @@ -173,7 +173,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
94 changes: 94 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4093,3 +4093,97 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) {
}
}
}

// TestTransientStorageReset ensures the transient storage is wiped correctly
// between transactions.
func TestTransientStorageReset(t *testing.T) {
var (
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},
},
}
nonce := uint64(0)
signer := types.HomesteadSigner{}
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 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(rawdb.NewMemoryDatabase(), nil, gspec, nil, 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
87 changes: 65 additions & 22 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
)
Expand Down Expand Up @@ -102,6 +103,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 @@ -146,6 +150,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 @@ -452,6 +457,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 @@ -708,6 +742,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 @@ -880,9 +916,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
}
Expand Down Expand Up @@ -1020,33 +1057,39 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
return root, nil
}

// PrepareAccessList handles the preparatory steps for executing a state transition with
// regards to both EIP-2929 and EIP-2930:
// 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)
//
// This method should only be called if Berlin/2929+2930 is applicable at the current number.
func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
// 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)
// Potential EIPs:
// - Reset transient storage(1153)
func (s *StateDB) Prepare(rules params.Rules, sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
if rules.IsBerlin {
// 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)
}
}
}
// Reset transient storage at the beginning of transaction execution
s.transientStorage = newTransientStorage()
}

// AddAddressToAccessList adds the given address to the access list
Expand Down
44 changes: 44 additions & 0 deletions core/state/statedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -954,3 +964,37 @@ func TestFlushOrderDataLoss(t *testing.T) {
}
}
}

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)
}
}
Loading