Fix state diff repetitive anchor slot bug (#16037)

**What type of PR is this?**
 Bug fix

**What does this PR do? Why is it needed?**
This PR fixes a bug in the state diff `getBaseAndDiffChain()` method. In
the process of finding the diff chain indices, there could be a scenario
where multiple levels return the same diff slot number not equal to the
base (full snapshot) slot number.
This resulted in multiple diff items being added to the diff chain for
the same slot, but on different levels, which resulted in errors reading
the diff.

Fix: we keep a `lastSeenAnchorSlot` equal to the `BaseAnchorSlot` and
update it every time we see a new anchor slot. We ignore if the current
found anchor slot is equal to the `lastSeenAnchorSlot`.

Scenario example: 
exponents: [20, 14, 10, 7, 5]
offset: 0
slots saved: slot 2^11, and slot (2^11 + 2^5)
slot to read: slot (2^11 + 2^5)
resulting list of anchor slots (diff chain indices): [0, 0, 2^11, 2^11,
2^11 + 2^5]
This commit is contained in:
Bastin
2025-11-21 16:42:27 +01:00
committed by GitHub
parent 2f067c4164
commit 3d7f7b588b
3 changed files with 46 additions and 1 deletions

View File

@@ -221,13 +221,15 @@ func (s *Store) getBaseAndDiffChain(offset uint64, slot primitives.Slot) (state.
}
var diffChainItems []diffItem
lastSeenAnchorSlot := baseAnchorSlot
for i, exp := range exponents[1 : lvl+1] {
span := math.PowerOf2(uint64(exp))
diffSlot := rel / span * span
if diffSlot == baseAnchorSlot {
if diffSlot == lastSeenAnchorSlot {
continue
}
diffChainItems = append(diffChainItems, diffItem{level: i + 1, slot: diffSlot + offset})
lastSeenAnchorSlot = diffSlot
}
baseSnapshot, err := s.getFullSnapshot(baseAnchorSlot)

View File

@@ -278,6 +278,46 @@ func TestStateDiff_SaveAndReadDiff(t *testing.T) {
}
}
func TestStateDiff_SaveAndReadDiff_WithRepetitiveAnchorSlots(t *testing.T) {
globalFlags := flags.GlobalFlags{
StateDiffExponents: []int{20, 14, 10, 7, 5},
}
flags.Init(&globalFlags)
for v := range version.All() {
t.Run(version.String(v), func(t *testing.T) {
db := setupDB(t)
err := setOffsetInDB(db, 0)
st, _ := createState(t, 0, v)
require.NoError(t, err)
err = db.saveStateByDiff(context.Background(), st)
require.NoError(t, err)
slot := primitives.Slot(math.PowerOf2(11))
st, _ = createState(t, slot, v)
err = db.saveStateByDiff(context.Background(), st)
require.NoError(t, err)
slot = primitives.Slot(math.PowerOf2(11) + math.PowerOf2(5))
st, _ = createState(t, slot, v)
err = db.saveStateByDiff(context.Background(), st)
require.NoError(t, err)
readSt, err := db.stateByDiff(context.Background(), slot)
require.NoError(t, err)
require.NotNil(t, readSt)
stSSZ, err := st.MarshalSSZ()
require.NoError(t, err)
readStSSZ, err := readSt.MarshalSSZ()
require.NoError(t, err)
require.DeepSSZEqual(t, stSSZ, readStSSZ)
})
}
}
func TestStateDiff_SaveAndReadDiff_MultipleLevels(t *testing.T) {
setDefaultExponents()

View File

@@ -0,0 +1,3 @@
### Fixed
- Fix state diff repetitive anchor slot bug.