Skip to content

Commit a7f9523

Browse files
all: implement state history v2 (ethereum#30107)
This pull request delivers the new version of the state history, where the raw storage key is used instead of the hash. Before the cancun fork, it's supported by protocol to destruct a specific account and therefore, all the storage slot owned by it should be wiped in the same transition. Technically, storage wiping should be performed through storage iteration, and only the storage key hash will be available for traversal if the state snapshot is not available. Therefore, the storage key hash is chosen as the identifier in the old version state history. Fortunately, account self-destruction has been deprecated by the protocol since the Cancun fork, and there are no empty accounts eligible for deletion under EIP-158. Therefore, we can conclude that no storage wiping should occur after the Cancun fork. In this case, it makes no sense to keep using hash. Besides, another big reason for making this change is the current format state history is unusable if verkle is activated. Verkle tree has a different key derivation scheme (merkle uses keccak256), the preimage of key hash must be provided in order to make verkle rollback functional. This pull request is a prerequisite for landing verkle. Additionally, the raw storage key is more human-friendly for those who want to manually check the history, even though Solidity already performs some hashing to derive the storage location. --- This pull request doesn't bump the database version, as I believe the database should still be compatible if users degrade from the new geth version to old one, the only side effect is the persistent new version state history will be unusable. --------- Co-authored-by: Zsolt Felfoldi <[email protected]>
1 parent 4d94bd8 commit a7f9523

33 files changed

+356
-204
lines changed

cmd/evm/internal/t8ntool/execution.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
379379
}
380380

381381
// Commit block
382-
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber))
382+
root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), chainConfig.IsCancun(vmContext.BlockNumber, vmContext.Time))
383383
if err != nil {
384384
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err))
385385
}
@@ -437,7 +437,7 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB
437437
}
438438
}
439439
// Commit and re-open to start with a clean state.
440-
root, _ := statedb.Commit(0, false)
440+
root, _ := statedb.Commit(0, false, false)
441441
statedb, _ = state.New(root, sdb)
442442
return statedb
443443
}

cmd/evm/runner.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ func runCmd(ctx *cli.Context) error {
336336
output, stats, err := timedExec(bench, execFunc)
337337

338338
if ctx.Bool(DumpFlag.Name) {
339-
root, err := runtimeConfig.State.Commit(genesisConfig.Number, true)
339+
root, err := runtimeConfig.State.Commit(genesisConfig.Number, true, false)
340340
if err != nil {
341341
fmt.Printf("Failed to commit changes %v\n", err)
342342
return err

cmd/geth/dbcmd.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -829,8 +829,7 @@ func inspectAccount(db *triedb.Database, start uint64, end uint64, address commo
829829
func inspectStorage(db *triedb.Database, start uint64, end uint64, address common.Address, slot common.Hash, raw bool) error {
830830
// The hash of storage slot key is utilized in the history
831831
// rather than the raw slot key, make the conversion.
832-
slotHash := crypto.Keccak256Hash(slot.Bytes())
833-
stats, err := db.StorageHistory(address, slotHash, start, end)
832+
stats, err := db.StorageHistory(address, slot, start, end)
834833
if err != nil {
835834
return err
836835
}

core/blockchain.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1471,7 +1471,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.
14711471
log.Crit("Failed to write block into disk", "err", err)
14721472
}
14731473
// Commit all cached state changes into underlying memory database.
1474-
root, err := statedb.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()))
1474+
root, err := statedb.Commit(block.NumberU64(), bc.chainConfig.IsEIP158(block.Number()), bc.chainConfig.IsCancun(block.Number(), block.Time()))
14751475
if err != nil {
14761476
return err
14771477
}

core/blockchain_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
181181
blockchain.chainmu.MustLock()
182182
rawdb.WriteTd(blockchain.db, block.Hash(), block.NumberU64(), new(big.Int).Add(block.Difficulty(), blockchain.GetTd(block.ParentHash(), block.NumberU64()-1)))
183183
rawdb.WriteBlock(blockchain.db, block)
184-
statedb.Commit(block.NumberU64(), false)
184+
statedb.Commit(block.NumberU64(), false, false)
185185
blockchain.chainmu.Unlock()
186186
}
187187
return nil

core/chain_makers.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
405405
}
406406

407407
// Write state changes to db
408-
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number))
408+
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), config.IsCancun(b.header.Number, b.header.Time))
409409
if err != nil {
410410
panic(fmt.Sprintf("state write error: %v", err))
411411
}
@@ -510,7 +510,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine
510510
}
511511

512512
// Write state changes to DB.
513-
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number))
513+
root, err := statedb.Commit(b.header.Number.Uint64(), config.IsEIP158(b.header.Number), config.IsCancun(b.header.Number, b.header.Time))
514514
if err != nil {
515515
panic(fmt.Sprintf("state write error: %v", err))
516516
}

core/genesis.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
146146
statedb.SetState(addr, key, value)
147147
}
148148
}
149-
return statedb.Commit(0, false)
149+
return statedb.Commit(0, false, false)
150150
}
151151

152152
// flushAlloc is very similar with hash, but the main difference is all the
@@ -172,7 +172,7 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, e
172172
statedb.SetState(addr, key, value)
173173
}
174174
}
175-
root, err := statedb.Commit(0, false)
175+
root, err := statedb.Commit(0, false, false)
176176
if err != nil {
177177
return common.Hash{}, err
178178
}

core/state/state_object.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -399,10 +399,16 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
399399
op.storages = make(map[common.Hash][]byte)
400400
}
401401
op.storages[hash] = encode(val)
402-
if op.storagesOrigin == nil {
403-
op.storagesOrigin = make(map[common.Hash][]byte)
402+
403+
if op.storagesOriginByKey == nil {
404+
op.storagesOriginByKey = make(map[common.Hash][]byte)
405+
}
406+
if op.storagesOriginByHash == nil {
407+
op.storagesOriginByHash = make(map[common.Hash][]byte)
404408
}
405-
op.storagesOrigin[hash] = encode(s.originStorage[key])
409+
origin := encode(s.originStorage[key])
410+
op.storagesOriginByKey[key] = origin
411+
op.storagesOriginByHash[hash] = origin
406412

407413
// Overwrite the clean value of storage slots
408414
s.originStorage[key] = val

core/state/state_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func TestDump(t *testing.T) {
5656
// write some of them to the trie
5757
s.state.updateStateObject(obj1)
5858
s.state.updateStateObject(obj2)
59-
root, _ := s.state.Commit(0, false)
59+
root, _ := s.state.Commit(0, false, false)
6060

6161
// check that DumpToCollector contains the state objects that are in trie
6262
s.state, _ = New(root, tdb)
@@ -116,7 +116,7 @@ func TestIterativeDump(t *testing.T) {
116116
// write some of them to the trie
117117
s.state.updateStateObject(obj1)
118118
s.state.updateStateObject(obj2)
119-
root, _ := s.state.Commit(0, false)
119+
root, _ := s.state.Commit(0, false, false)
120120
s.state, _ = New(root, tdb)
121121

122122
b := &bytes.Buffer{}
@@ -142,7 +142,7 @@ func TestNull(t *testing.T) {
142142
var value common.Hash
143143

144144
s.state.SetState(address, common.Hash{}, value)
145-
s.state.Commit(0, false)
145+
s.state.Commit(0, false, false)
146146

147147
if value := s.state.GetState(address, common.Hash{}); value != (common.Hash{}) {
148148
t.Errorf("expected empty current value, got %x", value)

core/state/statedb.go

+17-8
Original file line numberDiff line numberDiff line change
@@ -1051,7 +1051,7 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root
10511051
// with their values be tracked as original value.
10521052
// In case (d), **original** account along with its storages should be deleted,
10531053
// with their values be tracked as original value.
1054-
func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) {
1054+
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) {
10551055
var (
10561056
nodes []*trienode.NodeSet
10571057
buf = crypto.NewKeccakState()
@@ -1080,6 +1080,9 @@ func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trieno
10801080
if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() {
10811081
continue
10821082
}
1083+
if noStorageWiping {
1084+
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
1085+
}
10831086
// Remove storage slots belonging to the account.
10841087
storages, storagesOrigin, set, err := s.deleteStorage(addr, addrHash, prev.Root)
10851088
if err != nil {
@@ -1101,7 +1104,7 @@ func (s *StateDB) GetTrie() Trie {
11011104

11021105
// commit gathers the state mutations accumulated along with the associated
11031106
// trie changes, resetting all internal flags with the new state as the base.
1104-
func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
1107+
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) {
11051108
// Short circuit in case any database failure occurred earlier.
11061109
if s.dbErr != nil {
11071110
return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
@@ -1155,7 +1158,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
11551158
// the same block, account deletions must be processed first. This ensures
11561159
// that the storage trie nodes deleted during destruction and recreated
11571160
// during subsequent resurrection can be combined correctly.
1158-
deletes, delNodes, err := s.handleDestruction()
1161+
deletes, delNodes, err := s.handleDestruction(noStorageWiping)
11591162
if err != nil {
11601163
return nil, err
11611164
}
@@ -1252,13 +1255,14 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
12521255

12531256
origin := s.originalRoot
12541257
s.originalRoot = root
1255-
return newStateUpdate(origin, root, deletes, updates, nodes), nil
1258+
1259+
return newStateUpdate(noStorageWiping, origin, root, deletes, updates, nodes), nil
12561260
}
12571261

12581262
// commitAndFlush is a wrapper of commit which also commits the state mutations
12591263
// to the configured data stores.
1260-
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateUpdate, error) {
1261-
ret, err := s.commit(deleteEmptyObjects)
1264+
func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (*stateUpdate, error) {
1265+
ret, err := s.commit(deleteEmptyObjects, noStorageWiping)
12621266
if err != nil {
12631267
return nil, err
12641268
}
@@ -1310,8 +1314,13 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
13101314
//
13111315
// The associated block number of the state transition is also provided
13121316
// for more chain context.
1313-
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, error) {
1314-
ret, err := s.commitAndFlush(block, deleteEmptyObjects)
1317+
//
1318+
// noStorageWiping is a flag indicating whether storage wiping is permitted.
1319+
// Since self-destruction was deprecated with the Cancun fork and there are
1320+
// no empty accounts left that could be deleted by EIP-158, storage wiping
1321+
// should not occur.
1322+
func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, error) {
1323+
ret, err := s.commitAndFlush(block, deleteEmptyObjects, noStorageWiping)
13151324
if err != nil {
13161325
return common.Hash{}, err
13171326
}

core/state/statedb_fuzz_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func (test *stateTest) run() bool {
228228
} else {
229229
state.IntermediateRoot(true) // call intermediateRoot at the transaction boundary
230230
}
231-
ret, err := state.commitAndFlush(0, true) // call commit at the block boundary
231+
ret, err := state.commitAndFlush(0, true, false) // call commit at the block boundary
232232
if err != nil {
233233
panic(err)
234234
}

core/state/statedb_hooked_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func TestBurn(t *testing.T) {
7171
hooked.AddBalance(addC, uint256.NewInt(200), tracing.BalanceChangeUnspecified)
7272
hooked.Finalise(true)
7373

74-
s.Commit(0, false)
74+
s.Commit(0, false, false)
7575
if have, want := burned, uint256.NewInt(600); !have.Eq(want) {
7676
t.Fatalf("burn-count wrong, have %v want %v", have, want)
7777
}

core/state/statedb_test.go

+14-15
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,15 @@ func TestIntermediateLeaks(t *testing.T) {
119119
}
120120

121121
// Commit and cross check the databases.
122-
transRoot, err := transState.Commit(0, false)
122+
transRoot, err := transState.Commit(0, false, false)
123123
if err != nil {
124124
t.Fatalf("failed to commit transition state: %v", err)
125125
}
126126
if err = transNdb.Commit(transRoot, false); err != nil {
127127
t.Errorf("can not commit trie %v to persistent database", transRoot.Hex())
128128
}
129129

130-
finalRoot, err := finalState.Commit(0, false)
130+
finalRoot, err := finalState.Commit(0, false, false)
131131
if err != nil {
132132
t.Fatalf("failed to commit final state: %v", err)
133133
}
@@ -240,7 +240,7 @@ func TestCopyWithDirtyJournal(t *testing.T) {
240240
obj.data.Root = common.HexToHash("0xdeadbeef")
241241
orig.updateStateObject(obj)
242242
}
243-
root, _ := orig.Commit(0, true)
243+
root, _ := orig.Commit(0, true, false)
244244
orig, _ = New(root, db)
245245

246246
// modify all in memory without finalizing
@@ -293,7 +293,7 @@ func TestCopyObjectState(t *testing.T) {
293293
t.Fatalf("Error in test itself, the 'done' flag should not be set before Commit, have %v want %v", have, want)
294294
}
295295
}
296-
orig.Commit(0, true)
296+
orig.Commit(0, true, false)
297297
for _, op := range cpy.mutations {
298298
if have, want := op.applied, false; have != want {
299299
t.Fatalf("Error: original state affected copy, have %v want %v", have, want)
@@ -696,7 +696,7 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
696696
func TestTouchDelete(t *testing.T) {
697697
s := newStateEnv()
698698
s.state.getOrNewStateObject(common.Address{})
699-
root, _ := s.state.Commit(0, false)
699+
root, _ := s.state.Commit(0, false, false)
700700
s.state, _ = New(root, s.state.db)
701701

702702
snapshot := s.state.Snapshot()
@@ -784,7 +784,7 @@ func TestCopyCommitCopy(t *testing.T) {
784784
t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, sval)
785785
}
786786
// Commit state, ensure states can be loaded from disk
787-
root, _ := state.Commit(0, false)
787+
root, _ := state.Commit(0, false, false)
788788
state, _ = New(root, tdb)
789789
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
790790
t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
@@ -898,11 +898,11 @@ func TestCommitCopy(t *testing.T) {
898898
if val := state.GetCommittedState(addr, skey1); val != (common.Hash{}) {
899899
t.Fatalf("initial committed storage slot mismatch: have %x, want %x", val, common.Hash{})
900900
}
901-
root, _ := state.Commit(0, true)
901+
root, _ := state.Commit(0, true, false)
902902

903903
state, _ = New(root, db)
904904
state.SetState(addr, skey2, sval2)
905-
state.Commit(1, true)
905+
state.Commit(1, true, false)
906906

907907
// Copy the committed state database, the copied one is not fully functional.
908908
copied := state.Copy()
@@ -943,7 +943,7 @@ func TestDeleteCreateRevert(t *testing.T) {
943943
addr := common.BytesToAddress([]byte("so"))
944944
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
945945

946-
root, _ := state.Commit(0, false)
946+
root, _ := state.Commit(0, false, false)
947947
state, _ = New(root, state.db)
948948

949949
// Simulate self-destructing in one transaction, then create-reverting in another
@@ -955,7 +955,7 @@ func TestDeleteCreateRevert(t *testing.T) {
955955
state.RevertToSnapshot(id)
956956

957957
// Commit the entire state and make sure we don't crash and have the correct state
958-
root, _ = state.Commit(0, true)
958+
root, _ = state.Commit(0, true, false)
959959
state, _ = New(root, state.db)
960960

961961
if state.getStateObject(addr) != nil {
@@ -998,7 +998,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
998998
a2 := common.BytesToAddress([]byte("another"))
999999
state.SetBalance(a2, uint256.NewInt(100), tracing.BalanceChangeUnspecified)
10001000
state.SetCode(a2, []byte{1, 2, 4})
1001-
root, _ = state.Commit(0, false)
1001+
root, _ = state.Commit(0, false, false)
10021002
t.Logf("root: %x", root)
10031003
// force-flush
10041004
tdb.Commit(root, false)
@@ -1022,7 +1022,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) {
10221022
}
10231023
// Modify the state
10241024
state.SetBalance(addr, uint256.NewInt(2), tracing.BalanceChangeUnspecified)
1025-
root, err := state.Commit(0, false)
1025+
root, err := state.Commit(0, false, false)
10261026
if err == nil {
10271027
t.Fatalf("expected error, got root :%x", root)
10281028
}
@@ -1213,7 +1213,7 @@ func TestFlushOrderDataLoss(t *testing.T) {
12131213
state.SetState(common.Address{a}, common.Hash{a, s}, common.Hash{a, s})
12141214
}
12151215
}
1216-
root, err := state.Commit(0, false)
1216+
root, err := state.Commit(0, false, false)
12171217
if err != nil {
12181218
t.Fatalf("failed to commit state trie: %v", err)
12191219
}
@@ -1288,8 +1288,7 @@ func TestDeleteStorage(t *testing.T) {
12881288
value := common.Hash(uint256.NewInt(uint64(10 * i)).Bytes32())
12891289
state.SetState(addr, slot, value)
12901290
}
1291-
root, _ := state.Commit(0, true)
1292-
1291+
root, _ := state.Commit(0, true, false)
12931292
// Init phase done, create two states, one with snap and one without
12941293
fastState, _ := New(root, NewDatabase(tdb, snaps))
12951294
slowState, _ := New(root, NewDatabase(tdb, nil))

0 commit comments

Comments
 (0)