Skip to content

Commit b4ea2bf

Browse files
tynessnreynoldsholimanrjl493456442
authored
all: implement EIP-1153 transient storage (#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 <[email protected]> Co-authored-by: Martin Holst Swende <[email protected]> Co-authored-by: Gary Rong <[email protected]>
1 parent bc90a88 commit b4ea2bf

21 files changed

+450
-67
lines changed

cmd/evm/internal/t8ntool/execution.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
173173
}
174174
vmConfig.Tracer = tracer
175175
vmConfig.Debug = (tracer != nil)
176-
statedb.Prepare(tx.Hash(), txIndex)
176+
statedb.SetTxContext(tx.Hash(), txIndex)
177177
txContext := core.NewEVMTxContext(msg)
178178
snapshot := statedb.Snapshot()
179179
evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig)

core/blockchain_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4093,3 +4093,97 @@ func testCreateThenDelete(t *testing.T, config *params.ChainConfig) {
40934093
}
40944094
}
40954095
}
4096+
4097+
// TestTransientStorageReset ensures the transient storage is wiped correctly
4098+
// between transactions.
4099+
func TestTransientStorageReset(t *testing.T) {
4100+
var (
4101+
engine = ethash.NewFaker()
4102+
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
4103+
address = crypto.PubkeyToAddress(key.PublicKey)
4104+
destAddress = crypto.CreateAddress(address, 0)
4105+
funds = big.NewInt(1000000000000000)
4106+
vmConfig = vm.Config{
4107+
ExtraEips: []int{1153}, // Enable transient storage EIP
4108+
}
4109+
)
4110+
code := append([]byte{
4111+
// TLoad value with location 1
4112+
byte(vm.PUSH1), 0x1,
4113+
byte(vm.TLOAD),
4114+
4115+
// PUSH location
4116+
byte(vm.PUSH1), 0x1,
4117+
4118+
// SStore location:value
4119+
byte(vm.SSTORE),
4120+
}, make([]byte, 32-6)...)
4121+
initCode := []byte{
4122+
// TSTORE 1:1
4123+
byte(vm.PUSH1), 0x1,
4124+
byte(vm.PUSH1), 0x1,
4125+
byte(vm.TSTORE),
4126+
4127+
// Get the runtime-code on the stack
4128+
byte(vm.PUSH32)}
4129+
initCode = append(initCode, code...)
4130+
initCode = append(initCode, []byte{
4131+
byte(vm.PUSH1), 0x0, // offset
4132+
byte(vm.MSTORE),
4133+
byte(vm.PUSH1), 0x6, // size
4134+
byte(vm.PUSH1), 0x0, // offset
4135+
byte(vm.RETURN), // return 6 bytes of zero-code
4136+
}...)
4137+
gspec := &Genesis{
4138+
Config: params.TestChainConfig,
4139+
Alloc: GenesisAlloc{
4140+
address: {Balance: funds},
4141+
},
4142+
}
4143+
nonce := uint64(0)
4144+
signer := types.HomesteadSigner{}
4145+
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
4146+
fee := big.NewInt(1)
4147+
if b.header.BaseFee != nil {
4148+
fee = b.header.BaseFee
4149+
}
4150+
b.SetCoinbase(common.Address{1})
4151+
tx, _ := types.SignNewTx(key, signer, &types.LegacyTx{
4152+
Nonce: nonce,
4153+
GasPrice: new(big.Int).Set(fee),
4154+
Gas: 100000,
4155+
Data: initCode,
4156+
})
4157+
nonce++
4158+
b.AddTxWithVMConfig(tx, vmConfig)
4159+
4160+
tx, _ = types.SignNewTx(key, signer, &types.LegacyTx{
4161+
Nonce: nonce,
4162+
GasPrice: new(big.Int).Set(fee),
4163+
Gas: 100000,
4164+
To: &destAddress,
4165+
})
4166+
b.AddTxWithVMConfig(tx, vmConfig)
4167+
nonce++
4168+
})
4169+
4170+
// Initialize the blockchain with 1153 enabled.
4171+
chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), nil, gspec, nil, engine, vmConfig, nil, nil)
4172+
if err != nil {
4173+
t.Fatalf("failed to create tester chain: %v", err)
4174+
}
4175+
// Import the blocks
4176+
if _, err := chain.InsertChain(blocks); err != nil {
4177+
t.Fatalf("failed to insert into chain: %v", err)
4178+
}
4179+
// Check the storage
4180+
state, err := chain.StateAt(chain.CurrentHeader().Root)
4181+
if err != nil {
4182+
t.Fatalf("Failed to load state %v", err)
4183+
}
4184+
loc := common.BytesToHash([]byte{1})
4185+
slot := state.GetState(destAddress, loc)
4186+
if slot != (common.Hash{}) {
4187+
t.Fatalf("Unexpected dirty storage slot")
4188+
}
4189+
}

core/chain_makers.go

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,26 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) {
7979
b.header.Difficulty = diff
8080
}
8181

82+
// addTx adds a transaction to the generated block. If no coinbase has
83+
// been set, the block's coinbase is set to the zero address.
84+
//
85+
// There are a few options can be passed as well in order to run some
86+
// customized rules.
87+
// - bc: enables the ability to query historical block hashes for BLOCKHASH
88+
// - vmConfig: extends the flexibility for customizing evm rules, e.g. enable extra EIPs
89+
func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transaction) {
90+
if b.gasPool == nil {
91+
b.SetCoinbase(common.Address{})
92+
}
93+
b.statedb.SetTxContext(tx.Hash(), len(b.txs))
94+
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig)
95+
if err != nil {
96+
panic(err)
97+
}
98+
b.txs = append(b.txs, tx)
99+
b.receipts = append(b.receipts, receipt)
100+
}
101+
82102
// AddTx adds a transaction to the generated block. If no coinbase has
83103
// been set, the block's coinbase is set to the zero address.
84104
//
@@ -88,7 +108,7 @@ func (b *BlockGen) SetDifficulty(diff *big.Int) {
88108
// added. Notably, contract code relying on the BLOCKHASH instruction
89109
// will panic during execution.
90110
func (b *BlockGen) AddTx(tx *types.Transaction) {
91-
b.AddTxWithChain(nil, tx)
111+
b.addTx(nil, vm.Config{}, tx)
92112
}
93113

94114
// AddTxWithChain adds a transaction to the generated block. If no coinbase has
@@ -100,16 +120,14 @@ func (b *BlockGen) AddTx(tx *types.Transaction) {
100120
// added. If contract code relies on the BLOCKHASH instruction,
101121
// the block in chain will be returned.
102122
func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
103-
if b.gasPool == nil {
104-
b.SetCoinbase(common.Address{})
105-
}
106-
b.statedb.Prepare(tx.Hash(), len(b.txs))
107-
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
108-
if err != nil {
109-
panic(err)
110-
}
111-
b.txs = append(b.txs, tx)
112-
b.receipts = append(b.receipts, receipt)
123+
b.addTx(bc, vm.Config{}, tx)
124+
}
125+
126+
// AddTxWithVMConfig adds a transaction to the generated block. If no coinbase has
127+
// been set, the block's coinbase is set to the zero address.
128+
// The evm interpreter can be customized with the provided vm config.
129+
func (b *BlockGen) AddTxWithVMConfig(tx *types.Transaction, config vm.Config) {
130+
b.addTx(nil, config, tx)
113131
}
114132

115133
// GetBalance returns the balance of the given address at the generated block.

core/state/journal.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ type (
138138
address *common.Address
139139
slot *common.Hash
140140
}
141+
142+
transientStorageChange struct {
143+
account *common.Address
144+
key, prevalue common.Hash
145+
}
141146
)
142147

143148
func (ch createObjectChange) revert(s *StateDB) {
@@ -213,6 +218,14 @@ func (ch storageChange) dirtied() *common.Address {
213218
return ch.account
214219
}
215220

221+
func (ch transientStorageChange) revert(s *StateDB) {
222+
s.setTransientState(*ch.account, ch.key, ch.prevalue)
223+
}
224+
225+
func (ch transientStorageChange) dirtied() *common.Address {
226+
return nil
227+
}
228+
216229
func (ch refundChange) revert(s *StateDB) {
217230
s.refund = ch.prev
218231
}

core/state/statedb.go

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/ethereum/go-ethereum/crypto"
3232
"github.com/ethereum/go-ethereum/log"
3333
"github.com/ethereum/go-ethereum/metrics"
34+
"github.com/ethereum/go-ethereum/params"
3435
"github.com/ethereum/go-ethereum/rlp"
3536
"github.com/ethereum/go-ethereum/trie"
3637
)
@@ -102,6 +103,9 @@ type StateDB struct {
102103
// Per-transaction access list
103104
accessList *accessList
104105

106+
// Transient storage
107+
transientStorage transientStorage
108+
105109
// Journal of state modifications. This is the backbone of
106110
// Snapshot and RevertToSnapshot.
107111
journal *journal
@@ -146,6 +150,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
146150
preimages: make(map[common.Hash][]byte),
147151
journal: newJournal(),
148152
accessList: newAccessList(),
153+
transientStorage: newTransientStorage(),
149154
hasher: crypto.NewKeccakState(),
150155
}
151156
if sdb.snaps != nil {
@@ -452,6 +457,35 @@ func (s *StateDB) Suicide(addr common.Address) bool {
452457
return true
453458
}
454459

460+
// SetTransientState sets transient storage for a given account. It
461+
// adds the change to the journal so that it can be rolled back
462+
// to its previous value if there is a revert.
463+
func (s *StateDB) SetTransientState(addr common.Address, key, value common.Hash) {
464+
prev := s.GetTransientState(addr, key)
465+
if prev == value {
466+
return
467+
}
468+
469+
s.journal.append(transientStorageChange{
470+
account: &addr,
471+
key: key,
472+
prevalue: prev,
473+
})
474+
475+
s.setTransientState(addr, key, value)
476+
}
477+
478+
// setTransientState is a lower level setter for transient storage. It
479+
// is called during a revert to prevent modifications to the journal.
480+
func (s *StateDB) setTransientState(addr common.Address, key, value common.Hash) {
481+
s.transientStorage.Set(addr, key, value)
482+
}
483+
484+
// GetTransientState gets transient storage for a given account.
485+
func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common.Hash {
486+
return s.transientStorage.Get(addr, key)
487+
}
488+
455489
//
456490
// Setting, updating & deleting state object methods.
457491
//
@@ -708,6 +742,8 @@ func (s *StateDB) Copy() *StateDB {
708742
// to not blow up if we ever decide copy it in the middle of a transaction
709743
state.accessList = s.accessList.Copy()
710744

745+
state.transientStorage = s.transientStorage.Copy()
746+
711747
// If there's a prefetcher running, make an inactive copy of it that can
712748
// only access data but does not actively preload (since the user will not
713749
// know that they need to explicitly terminate an active copy).
@@ -880,9 +916,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
880916
return s.trie.Hash()
881917
}
882918

883-
// Prepare sets the current transaction hash and index which are
884-
// used when the EVM emits new state logs.
885-
func (s *StateDB) Prepare(thash common.Hash, ti int) {
919+
// SetTxContext sets the current transaction hash and index which are
920+
// used when the EVM emits new state logs. It should be invoked before
921+
// transaction execution.
922+
func (s *StateDB) SetTxContext(thash common.Hash, ti int) {
886923
s.thash = thash
887924
s.txIndex = ti
888925
}
@@ -1020,33 +1057,39 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
10201057
return root, nil
10211058
}
10221059

1023-
// PrepareAccessList handles the preparatory steps for executing a state transition with
1024-
// regards to both EIP-2929 and EIP-2930:
1060+
// Prepare handles the preparatory steps for executing a state transition with.
1061+
// This method must be invoked before state transition.
10251062
//
1063+
// Berlin fork:
10261064
// - Add sender to access list (2929)
10271065
// - Add destination to access list (2929)
10281066
// - Add precompiles to access list (2929)
10291067
// - Add the contents of the optional tx access list (2930)
10301068
//
1031-
// This method should only be called if Berlin/2929+2930 is applicable at the current number.
1032-
func (s *StateDB) PrepareAccessList(sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
1033-
// Clear out any leftover from previous executions
1034-
s.accessList = newAccessList()
1035-
1036-
s.AddAddressToAccessList(sender)
1037-
if dst != nil {
1038-
s.AddAddressToAccessList(*dst)
1039-
// If it's a create-tx, the destination will be added inside evm.create
1040-
}
1041-
for _, addr := range precompiles {
1042-
s.AddAddressToAccessList(addr)
1043-
}
1044-
for _, el := range list {
1045-
s.AddAddressToAccessList(el.Address)
1046-
for _, key := range el.StorageKeys {
1047-
s.AddSlotToAccessList(el.Address, key)
1069+
// Potential EIPs:
1070+
// - Reset transient storage(1153)
1071+
func (s *StateDB) Prepare(rules params.Rules, sender common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
1072+
if rules.IsBerlin {
1073+
// Clear out any leftover from previous executions
1074+
s.accessList = newAccessList()
1075+
1076+
s.AddAddressToAccessList(sender)
1077+
if dst != nil {
1078+
s.AddAddressToAccessList(*dst)
1079+
// If it's a create-tx, the destination will be added inside evm.create
1080+
}
1081+
for _, addr := range precompiles {
1082+
s.AddAddressToAccessList(addr)
1083+
}
1084+
for _, el := range list {
1085+
s.AddAddressToAccessList(el.Address)
1086+
for _, key := range el.StorageKeys {
1087+
s.AddSlotToAccessList(el.Address, key)
1088+
}
10481089
}
10491090
}
1091+
// Reset transient storage at the beginning of transaction execution
1092+
s.transientStorage = newTransientStorage()
10501093
}
10511094

10521095
// AddAddressToAccessList adds the given address to the access list

core/state/statedb_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,16 @@ func newTestAction(addr common.Address, r *rand.Rand) testAction {
342342
},
343343
args: make([]int64, 1),
344344
},
345+
{
346+
name: "SetTransientState",
347+
fn: func(a testAction, s *StateDB) {
348+
var key, val common.Hash
349+
binary.BigEndian.PutUint16(key[:], uint16(a.args[0]))
350+
binary.BigEndian.PutUint16(val[:], uint16(a.args[1]))
351+
s.SetTransientState(addr, key, val)
352+
},
353+
args: make([]int64, 2),
354+
},
345355
}
346356
action := actions[r.Intn(len(actions))]
347357
var nameargs []string
@@ -954,3 +964,37 @@ func TestFlushOrderDataLoss(t *testing.T) {
954964
}
955965
}
956966
}
967+
968+
func TestStateDBTransientStorage(t *testing.T) {
969+
memDb := rawdb.NewMemoryDatabase()
970+
db := NewDatabase(memDb)
971+
state, _ := New(common.Hash{}, db, nil)
972+
973+
key := common.Hash{0x01}
974+
value := common.Hash{0x02}
975+
addr := common.Address{}
976+
977+
state.SetTransientState(addr, key, value)
978+
if exp, got := 1, state.journal.length(); exp != got {
979+
t.Fatalf("journal length mismatch: have %d, want %d", got, exp)
980+
}
981+
// the retrieved value should equal what was set
982+
if got := state.GetTransientState(addr, key); got != value {
983+
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
984+
}
985+
986+
// revert the transient state being set and then check that the
987+
// value is now the empty hash
988+
state.journal.revert(state, 0)
989+
if got, exp := state.GetTransientState(addr, key), (common.Hash{}); exp != got {
990+
t.Fatalf("transient storage mismatch: have %x, want %x", got, exp)
991+
}
992+
993+
// set transient state and then copy the statedb and ensure that
994+
// the transient state is copied
995+
state.SetTransientState(addr, key, value)
996+
cpy := state.Copy()
997+
if got := cpy.GetTransientState(addr, key); got != value {
998+
t.Fatalf("transient storage mismatch: have %x, want %x", got, value)
999+
}
1000+
}

0 commit comments

Comments
 (0)