Skip to content

Commit af1fe00

Browse files
fix(state): history writes only dirty nonce and class hash (#3607)
* fix(state): history writes only dirty nonce and class hash * chore: linter * chore: address comments * refactor: abstract the history writing into separate helper
1 parent dd42f66 commit af1fe00

2 files changed

Lines changed: 197 additions & 20 deletions

File tree

core/state/state.go

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ func (s *State) Update(
140140
}
141141
}
142142

143-
return s.flush(blockNum, &stateUpdate, dirtyClasses, true)
143+
if err := s.flush(blockNum, &stateUpdate, dirtyClasses); err != nil {
144+
return err
145+
}
146+
return s.writeHistory(blockNum, update.StateDiff)
144147
}
145148

146149
// Revert a given state update. The block number is the block number of the state update.
@@ -223,7 +226,7 @@ func (s *State) Revert(header *core.Header, update *core.StateUpdate) error {
223226
return fmt.Errorf("state commitment mismatch: %v (expected) != %v (actual)", update.OldRoot, &newComm)
224227
}
225228

226-
if err := s.flush(blockNum, &stateUpdate, dirtyClasses, false); err != nil {
229+
if err := s.flush(blockNum, &stateUpdate, dirtyClasses); err != nil {
227230
return err
228231
}
229232
return s.deleteHistory(blockNum, update.StateDiff)
@@ -344,12 +347,10 @@ func (s *State) commit(protocolVersion string) (felt.Felt, stateUpdate, error) {
344347
return newComm, su, nil
345348
}
346349

347-
//nolint:gocyclo
348350
func (s *State) flush(
349351
blockNum uint64,
350352
update *stateUpdate,
351353
classes map[felt.Felt]core.ClassDefinition,
352-
storeHistory bool,
353354
) error {
354355
err := s.db.triedb.Update(
355356
(*felt.StateRootHash)(&update.curComm),
@@ -378,22 +379,6 @@ func (s *State) flush(
378379
if err := WriteContract(s.batch, &addr, obj.contract); err != nil {
379380
return err
380381
}
381-
382-
if storeHistory {
383-
for key, val := range obj.dirtyStorage {
384-
if err := WriteStorageHistory(s.batch, &addr, &key, blockNum, val); err != nil {
385-
return err
386-
}
387-
}
388-
389-
if err := WriteNonceHistory(s.batch, &addr, blockNum, &obj.contract.Nonce); err != nil {
390-
return err
391-
}
392-
393-
if err := WriteClassHashHistory(s.batch, &addr, blockNum, &obj.contract.ClassHash); err != nil {
394-
return err
395-
}
396-
}
397382
}
398383
}
399384

@@ -528,6 +513,36 @@ func (s *State) getStateObject(addr *felt.Felt) (*stateObject, error) {
528513
return obj, nil
529514
}
530515

516+
func (s *State) writeHistory(blockNum uint64, diff *core.StateDiff) error {
517+
for addr, storage := range diff.StorageDiffs {
518+
for key, val := range storage {
519+
if err := WriteStorageHistory(s.batch, &addr, &key, blockNum, val); err != nil {
520+
return err
521+
}
522+
}
523+
}
524+
525+
for addr, nonce := range diff.Nonces {
526+
if err := WriteNonceHistory(s.batch, &addr, blockNum, nonce); err != nil {
527+
return err
528+
}
529+
}
530+
531+
for addr, classHash := range diff.ReplacedClasses {
532+
if err := WriteClassHashHistory(s.batch, &addr, blockNum, classHash); err != nil {
533+
return err
534+
}
535+
}
536+
537+
for addr, classHash := range diff.DeployedContracts {
538+
if err := WriteClassHashHistory(s.batch, &addr, blockNum, classHash); err != nil {
539+
return err
540+
}
541+
}
542+
543+
return nil
544+
}
545+
531546
func (s *State) deleteHistory(blockNum uint64, diff *core.StateDiff) error {
532547
for addr, storage := range diff.StorageDiffs {
533548
for key := range storage {

core/state/state_test.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,120 @@ func TestRevert(t *testing.T) {
699699
})
700700
}
701701

702+
func TestStateUpdateWritesHistoryOnlyOnFieldChange(t *testing.T) {
703+
addr := felt.NewFromUint64[felt.Felt](16)
704+
classH0 := felt.NewFromUint64[felt.Felt](192)
705+
classH1 := felt.NewFromUint64[felt.Felt](193)
706+
storageKey := felt.NewFromUint64[felt.Felt](16)
707+
val1 := felt.NewFromUint64[felt.Felt](66)
708+
val2 := felt.NewFromUint64[felt.Felt](67)
709+
nonce1 := felt.NewFromUint64[felt.Felt](1)
710+
nonce2 := felt.NewFromUint64[felt.Felt](2)
711+
712+
updates := []*core.StateUpdate{
713+
// block 0: deploy A with classHash H0 — classHash entry expected
714+
{
715+
OldRoot: &felt.Zero,
716+
NewRoot: felt.NewUnsafeFromString[felt.Felt](
717+
"0x15fc57d3df218117c4baa5b894357bfe195a27874f52efee3914d8594756d6c",
718+
),
719+
StateDiff: &core.StateDiff{
720+
DeployedContracts: map[felt.Felt]*felt.Felt{*addr: classH0},
721+
},
722+
},
723+
// block 1: nonce change only — nonce entry expected
724+
{
725+
OldRoot: &felt.Zero,
726+
NewRoot: felt.NewUnsafeFromString[felt.Felt](
727+
"0x19be1c130f862c06576c14163ffd597ef49de2dd3eed0cd0329fc24a972f40d",
728+
),
729+
StateDiff: &core.StateDiff{
730+
Nonces: map[felt.Felt]*felt.Felt{*addr: nonce1},
731+
},
732+
},
733+
// block 2: storage only — must NOT produce nonce or classHash entries
734+
{
735+
OldRoot: &felt.Zero,
736+
NewRoot: felt.NewUnsafeFromString[felt.Felt](
737+
"0x7b5f15dda7999552e8af72b55965c0f033c77f0e2d3b54a448ebbfdec22d7c4",
738+
),
739+
StateDiff: &core.StateDiff{
740+
StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{
741+
*addr: {*storageKey: val1},
742+
},
743+
},
744+
},
745+
// block 3: nonce change only — nonce entry expected
746+
{
747+
OldRoot: &felt.Zero,
748+
NewRoot: felt.NewUnsafeFromString[felt.Felt](
749+
"0xa6e0bf8b99f39da84226ab00e8ede81133f60c72a86215f8c62eb2e9458d95",
750+
),
751+
StateDiff: &core.StateDiff{
752+
Nonces: map[felt.Felt]*felt.Felt{*addr: nonce2},
753+
},
754+
},
755+
// block 4: classHash replacement — classHash entry expected
756+
{
757+
OldRoot: &felt.Zero,
758+
NewRoot: felt.NewUnsafeFromString[felt.Felt](
759+
"0x66e9a93ed05f4561da6527bcb1c98c1a6724a585c18167711291ef6e56d3875",
760+
),
761+
StateDiff: &core.StateDiff{
762+
ReplacedClasses: map[felt.Felt]*felt.Felt{*addr: classH1},
763+
},
764+
},
765+
// block 5: storage only — must NOT produce nonce or classHash entries
766+
{
767+
OldRoot: &felt.Zero,
768+
NewRoot: felt.NewUnsafeFromString[felt.Felt](
769+
"0x4829eceed3f9be83ece51f354dbbb028172410e17ea7c4d5e98ad7c0e475e2a",
770+
),
771+
StateDiff: &core.StateDiff{
772+
StorageDiffs: map[felt.Felt]map[felt.Felt]*felt.Felt{
773+
*addr: {*storageKey: val2},
774+
},
775+
},
776+
},
777+
}
778+
779+
stateDB := setupState(t, updates, uint64(len(updates)))
780+
781+
t.Run("total entry counts match exact field-change count", func(t *testing.T) {
782+
assert.Equal(t, 2, countNonceHistory(t, stateDB, addr),
783+
"contract should have exactly 2 nonce history entries (blocks 1, 3)")
784+
assert.Equal(t, 2, countClassHashHistory(t, stateDB, addr),
785+
"contract should have exactly 2 classHash history entries (blocks 0, 4)")
786+
assert.Equal(t, 2, countStorageHistory(t, stateDB, addr, storageKey),
787+
"storage key should have exactly 2 history entries (blocks 2, 5)")
788+
})
789+
790+
t.Run("entries exist on the change blocks", func(t *testing.T) {
791+
assert.True(t, hasNonceHistory(t, stateDB, addr, 1))
792+
assert.True(t, hasNonceHistory(t, stateDB, addr, 3))
793+
assert.True(t, hasClassHashHistory(t, stateDB, addr, 0))
794+
assert.True(t, hasClassHashHistory(t, stateDB, addr, 4))
795+
assert.True(t, hasStorageHistory(t, stateDB, addr, storageKey, 2))
796+
assert.True(t, hasStorageHistory(t, stateDB, addr, storageKey, 5))
797+
})
798+
799+
t.Run("no nonce / classHash entries on storage-only blocks", func(t *testing.T) {
800+
assert.False(t, hasNonceHistory(t, stateDB, addr, 2),
801+
"storage-only block must not write nonce history")
802+
assert.False(t, hasNonceHistory(t, stateDB, addr, 5),
803+
"storage-only block must not write nonce history")
804+
assert.False(t, hasClassHashHistory(t, stateDB, addr, 2),
805+
"storage-only block must not write classHash history")
806+
assert.False(t, hasClassHashHistory(t, stateDB, addr, 5),
807+
"storage-only block must not write classHash history")
808+
})
809+
810+
t.Run("no nonce entry at deployment block (initial nonce is zero by default)", func(t *testing.T) {
811+
assert.False(t, hasNonceHistory(t, stateDB, addr, 0),
812+
"deployment block must not write a redundant zero-nonce history entry")
813+
})
814+
}
815+
702816
func BenchmarkStateUpdate(b *testing.B) {
703817
client := feeder.NewTestClient(b, &networks.Mainnet)
704818
gw := adaptfeeder.New(client)
@@ -780,3 +894,51 @@ func newTestStateDB() *StateDB {
780894
trieDB := triedb.New(memDB, nil)
781895
return NewStateDB(memDB, trieDB)
782896
}
897+
898+
func hasNonceHistory(t *testing.T, stateDB *StateDB, addr *felt.Felt, blockNum uint64) bool {
899+
t.Helper()
900+
ok, err := stateDB.disk.Has(db.ContractNonceHistoryAtBlockKey(addr, blockNum))
901+
require.NoError(t, err)
902+
return ok
903+
}
904+
905+
func hasClassHashHistory(t *testing.T, stateDB *StateDB, addr *felt.Felt, blockNum uint64) bool {
906+
t.Helper()
907+
ok, err := stateDB.disk.Has(db.ContractClassHashHistoryAtBlockKey(addr, blockNum))
908+
require.NoError(t, err)
909+
return ok
910+
}
911+
912+
func hasStorageHistory(t *testing.T, stateDB *StateDB, addr, key *felt.Felt, blockNum uint64) bool {
913+
t.Helper()
914+
ok, err := stateDB.disk.Has(db.ContractStorageHistoryAtBlockKey(addr, key, blockNum))
915+
require.NoError(t, err)
916+
return ok
917+
}
918+
919+
func countByPrefix(t *testing.T, stateDB *StateDB, prefix []byte) int {
920+
t.Helper()
921+
it, err := stateDB.disk.NewIterator(prefix, true)
922+
require.NoError(t, err)
923+
defer it.Close()
924+
count := 0
925+
for it.First(); it.Valid(); it.Next() {
926+
count++
927+
}
928+
return count
929+
}
930+
931+
func countNonceHistory(t *testing.T, stateDB *StateDB, addr *felt.Felt) int {
932+
t.Helper()
933+
return countByPrefix(t, stateDB, db.ContractNonceHistoryKey(addr))
934+
}
935+
936+
func countClassHashHistory(t *testing.T, stateDB *StateDB, addr *felt.Felt) int {
937+
t.Helper()
938+
return countByPrefix(t, stateDB, db.ContractClassHashHistoryKey(addr))
939+
}
940+
941+
func countStorageHistory(t *testing.T, stateDB *StateDB, addr, key *felt.Felt) int {
942+
t.Helper()
943+
return countByPrefix(t, stateDB, db.ContractStorageHistoryKey(addr, key))
944+
}

0 commit comments

Comments
 (0)