Skip to content
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
35 changes: 28 additions & 7 deletions execution/stagedsync/calc_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,30 @@ func (cs *calcState) ensureStorage(addr accounts.Address, key accounts.StorageKe
// also relies on this — but blocks aren't single txs; the SD-then-recreate
// pattern there spans txs, and last-write-wins on acc.Deleted still holds.)
func (cs *calcState) ApplyWrites(writes state.VersionedWrites) {
// Pre-scan: which addresses are self-destructed BY THIS tx's writeset.
// For those, the trailing zero account-field writes that IBS emits as
// part of the SELFDESTRUCT (BalancePath=0, etc. — when not stripped by
// normalizeWriteSet) must NOT clear Deleted. For any OTHER address, an
// account-field write — even a zero one — means the address is alive at
// the end of this tx (e.g. a 0-value transfer that re-creates a
// previously-destroyed address as an empty account on a pre-EIP-161
// fork — EEST frontier/opcodes/test_double_kill under EXEC3_PARALLEL),
// so it must clear Deleted.
sdThisCall := make(map[accounts.Address]bool)
for _, w := range writes {
if w.Path == state.SelfDestructPath {
if destructed, ok := w.Val.(bool); ok {
// Last SelfDestructPath entry wins (matches normalizeWriteSet /
// applyVersionedWrites): a SELFDESTRUCT followed by a same-tx
// CREATE2-recreate ends ALIVE, so the recreate's writes must
// clear Deleted.
sdThisCall[w.Address] = destructed
}
}
}
clearsDeleted := func(addr accounts.Address, nonZero bool) bool {
return nonZero || !sdThisCall[addr]
}
for _, w := range writes {
if w.Val == nil {
continue
Expand All @@ -221,33 +245,30 @@ func (cs *calcState) ApplyWrites(writes state.VersionedWrites) {
acc := cs.ensureAccount(w.Address)
acc.Balance = w.Val.(uint256.Int)
acc.dirty = true
if !acc.Balance.IsZero() {
// Only a non-zero balance reflects real recreate or
// transfer-in. Zero is part of the SD emission (see
// invariant 1) and must not clear Deleted.
if clearsDeleted(w.Address, !acc.Balance.IsZero()) {
acc.Deleted = false
}
case state.NoncePath:
acc := cs.ensureAccount(w.Address)
acc.Nonce = w.Val.(uint64)
acc.dirty = true
if acc.Nonce != 0 {
if clearsDeleted(w.Address, acc.Nonce != 0) {
acc.Deleted = false
}
case state.CodeHashPath:
acc := cs.ensureAccount(w.Address)
v := w.Val.(accounts.CodeHash)
acc.CodeHash = v.Value()
acc.dirty = true
if v.Value() != empty.CodeHash {
if clearsDeleted(w.Address, v.Value() != empty.CodeHash) {
acc.Deleted = false
}
case state.CodePath:
acc := cs.ensureAccount(w.Address)
code := w.Val.([]byte)
acc.CodeHash = crypto.Keccak256Hash(code)
acc.dirty = true
if len(code) > 0 {
if clearsDeleted(w.Address, len(code) > 0) {
acc.Deleted = false
}
case state.SelfDestructPath:
Expand Down
6 changes: 3 additions & 3 deletions execution/stagedsync/calc_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ func (r *preBlockReader) TracePrefix() string
// BalancePath = 0
// StoragePath[k] = 0 for each k in stateObject.dirtyStorage
// → those land in blockIO.WriteSet → rawWrites
// → normalizeWriteSet(rawWrites, vm, txIndex, incarnation, stateReader)
// → normalizeWriteSet(rawWrites, vm, txIndex, incarnation, stateReader, nil, true)
// → calcState.ApplyWrites(normalized)
// → calcState.FlushToUpdates(updates)
//
Expand Down Expand Up @@ -378,7 +378,7 @@ func TestSDOfPreExistingContract_FullPipeline(t *testing.T) {
vm.Write(addr, state.BalancePath, accounts.NilKey, ver, uint256.Int{}, true)

stateReader := &preBlockReader{addr: addr, acc: original}
normalized := normalizeWriteSet(rawWrites, vm, 0, 0, stateReader)
normalized := normalizeWriteSet(rawWrites, vm, 0, 0, stateReader, nil, true)

// SD-aware filtering: only SelfDestructPath survives in the normalized
// writeset for the SD'd address. The raw IncarnationPath/BalancePath
Expand Down Expand Up @@ -487,7 +487,7 @@ func TestSDStorageCascade_EmitsPerSlotDeletes(t *testing.T) {
}

stateReader := &preBlockReader{addr: addr, acc: original}
normalized := normalizeWriteSet(rawWrites, vm, 0, 0, stateReader)
normalized := normalizeWriteSet(rawWrites, vm, 0, 0, stateReader, nil, true)

// Sanity: normalizeWriteSet should have appended one StoragePath=0
// entry per slot in vm.StorageKeys(addr) — this is the load-bearing
Expand Down
4 changes: 0 additions & 4 deletions execution/stagedsync/committer.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,10 +420,6 @@ func (cc *commitmentCalculator) computeAndCheck(ctx context.Context, br *blockRe
cc.lastComputedBlock = br.BlockNum
cc.hasComputed = true

// Trim old version entries from sd.mem — only keeps the latest
// entry at or before this block's txNum. Reclaims memory from
// accumulated inMemHistoryReads entries during batch processing.

// Only publish on mismatch — success is silent.
if mismatch := !bytes.Equal(rh, br.StateRoot.Bytes()); mismatch {
cc.publish(ctx, commitmentResult{
Expand Down
24 changes: 12 additions & 12 deletions execution/stagedsync/exec3_finalize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ func TestNormalizeWriteSet_StorageNoOp(t *testing.T) {
}
vm.FlushVersionedWrites(writeSet, true, "")

result := normalizeWriteSet(writeSet, vm, 1, 0, nil)
result := normalizeWriteSet(writeSet, vm, 1, 0, nil, nil, true)

storageCount := countPath(result, state.StoragePath)
assert.Equal(t, 0, storageCount, "no-op storage write should be filtered")
Expand Down Expand Up @@ -1079,7 +1079,7 @@ func TestNormalizeWriteSet_StorageChanged(t *testing.T) {
}
vm.FlushVersionedWrites(writeSet, true, "")

result := normalizeWriteSet(writeSet, vm, 1, 0, nil)
result := normalizeWriteSet(writeSet, vm, 1, 0, nil, nil, true)

storageCount := countPath(result, state.StoragePath)
assert.Equal(t, 1, storageCount, "changed storage write should be kept")
Expand All @@ -1104,7 +1104,7 @@ func TestNormalizeWriteSet_StorageNewKey(t *testing.T) {
}
vm.FlushVersionedWrites(writeSet, true, "")

result := normalizeWriteSet(writeSet, vm, 0, 0, nil)
result := normalizeWriteSet(writeSet, vm, 0, 0, nil, nil, true)

storageCount := countPath(result, state.StoragePath)
assert.Equal(t, 1, storageCount, "new storage key should be kept")
Expand Down Expand Up @@ -1145,7 +1145,7 @@ func TestNormalizeWriteSet_StaleIncarnation(t *testing.T) {
Version: state.Version{TxIndex: 5, Incarnation: 0}}, // stale
}

result := normalizeWriteSet(allWrites, vm, 5, 1, nil)
result := normalizeWriteSet(allWrites, vm, 5, 1, nil, nil, true)

storageCount := countPath(result, state.StoragePath)
assert.Equal(t, 1, storageCount, "only incarnation 1's slotA should survive")
Expand Down Expand Up @@ -1179,7 +1179,7 @@ func TestNormalizeWriteSet_SelfDestruct(t *testing.T) {
}
vm.FlushVersionedWrites(writeSet, true, "")

result := normalizeWriteSet(writeSet, vm, 1, 0, nil)
result := normalizeWriteSet(writeSet, vm, 1, 0, nil, nil, true)

// Should have: SelfDestructPath + DELETE for slotA + DELETE for slotB
sdCount := countPath(result, state.SelfDestructPath)
Expand Down Expand Up @@ -1220,7 +1220,7 @@ func TestNormalizeWriteSet_AccountFieldResolution(t *testing.T) {
Version: state.Version{TxIndex: 1, Incarnation: 0}},
}

result := normalizeWriteSet(writeSet, vm, 1, 0, nil)
result := normalizeWriteSet(writeSet, vm, 1, 0, nil, nil, true)

require.Equal(t, 1, len(result))
v := result[0].Val.(uint256.Int)
Expand All @@ -1241,7 +1241,7 @@ func TestNormalizeWriteSet_AddressPathExcluded(t *testing.T) {
}
vm.FlushVersionedWrites(writeSet, true, "")

result := normalizeWriteSet(writeSet, vm, 0, 0, nil)
result := normalizeWriteSet(writeSet, vm, 0, 0, nil, nil, true)

addrCount := countPath(result, state.AddressPath)
balCount := countPath(result, state.BalancePath)
Expand Down Expand Up @@ -1277,7 +1277,7 @@ func TestNormalizeWriteSet_StorageOnlyAddress(t *testing.T) {
}
vm.FlushVersionedWrites(writeSet, true, "")

result := normalizeWriteSet(writeSet, vm, 0, 0, reader)
result := normalizeWriteSet(writeSet, vm, 0, 0, reader, nil, true)

// Should have storage write AND account-level fields for addr.
// Serial emits UpdateAccountData for every dirty object.
Expand Down Expand Up @@ -1325,7 +1325,7 @@ func TestNormalizeWriteSet_StorageAllNoOps(t *testing.T) {
}
vm.FlushVersionedWrites(writeSet, true, "")

result := normalizeWriteSet(writeSet, vm, 1, 0, reader)
result := normalizeWriteSet(writeSet, vm, 1, 0, reader, nil, true)

// Storage write should be filtered (no-op).
// But account fields should still be emitted — the IBS would have
Expand Down Expand Up @@ -1362,7 +1362,7 @@ func TestNormalizeWriteSet_CreateContract(t *testing.T) {
}
vm.FlushVersionedWrites(writeSet, true, "")

result := normalizeWriteSet(writeSet, vm, 0, 0, nil)
result := normalizeWriteSet(writeSet, vm, 0, 0, nil, nil, true)

// Should have CreateContractPath + all 4 account fields.
// The empty balance should NOT cause deletion because CreateContractPath is present.
Expand Down Expand Up @@ -1395,7 +1395,7 @@ func TestNormalizeWriteSet_NewAccount(t *testing.T) {
// stateReader returns nil for this address (doesn't exist yet)
reader := newMapStateReader() // empty — no accounts

result := normalizeWriteSet(writeSet, vm, 0, 0, reader)
result := normalizeWriteSet(writeSet, vm, 0, 0, reader, nil, true)

balCount := countPath(result, state.BalancePath)
nonceCount := countPath(result, state.NoncePath)
Expand Down Expand Up @@ -1448,7 +1448,7 @@ func TestNormalizeWriteSet_EmptyAccountRemoval(t *testing.T) {
CodeHash: emptyCodeHash,
}

result := normalizeWriteSet(writeSet, vm, 5, 0, reader)
result := normalizeWriteSet(writeSet, vm, 5, 0, reader, nil, true)

// The normalized output should produce a Delete for this account,
// NOT a regular write with Balance=0, Nonce=0.
Expand Down
Loading
Loading