Skip to content

Commit 035c621

Browse files
rjl493456442qianhh
authored andcommitted
core/state/snapshot: handle legacy journal (#30802)
This workaround is meant to minimize the possibility for snapshot generation once the geth node upgrades to new version (specifically #30752 ) In #30752, the journal format in state snapshot is modified by removing the destruct set. Therefore, the existing old format (version = 0) will be discarded and all in-memory layers will be lost. Unfortunately, the lost in-memory layers can't be recovered by some other approaches, and the entire state snapshot will be regenerated (it will last about 2.5 hours). This pull request introduces a workaround to adopt the legacy journal if the destruct set contained is empty. Since self-destruction has been deprecated following the cancun fork, the destruct set is expected to be nil for layers above the fork block. However, an exception occurs during contract deployment: pre-funded accounts may self-destruct, causing accounts with non-zero balances to be removed from the state. For example, https://etherscan.io/tx/0xa087333d83f0cd63b96bdafb686462e1622ce25f40bd499e03efb1051f31fe49). For nodes with a fully synced state, the legacy journal is likely compatible with the updated definition, eliminating the need for regeneration. Unfortunately, nodes performing a full sync of historical chain segments or encountering pre-funded account deletions may face incompatibilities, leading to automatic snapshot regeneration.
1 parent aa80ae2 commit 035c621

File tree

2 files changed

+43
-6
lines changed

2 files changed

+43
-6
lines changed

core/state/snapshot/journal.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ import (
3333
"github.com/ethereum/go-ethereum/triedb"
3434
)
3535

36-
// 0: initial version
37-
// 1: destruct flag in diff layer is removed
38-
const journalVersion uint64 = 1
36+
const (
37+
journalV0 uint64 = 0 // initial version
38+
journalV1 uint64 = 1 // current version, with destruct flag (in diff layers) removed
39+
journalCurrentVersion = journalV1
40+
)
3941

4042
// journalGenerator is a disk layer entry containing the generator progress marker.
4143
type journalGenerator struct {
@@ -50,6 +52,11 @@ type journalGenerator struct {
5052
Storage uint64
5153
}
5254

55+
// journalDestruct is an account deletion entry in a diffLayer's disk journal.
56+
type journalDestruct struct {
57+
Hash common.Hash
58+
}
59+
5360
// journalAccount is an account entry in a diffLayer's disk journal.
5461
type journalAccount struct {
5562
Hash common.Hash
@@ -285,8 +292,8 @@ func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error {
285292
log.Warn("Failed to resolve the journal version", "error", err)
286293
return errors.New("failed to resolve journal version")
287294
}
288-
if version != journalVersion {
289-
log.Warn("Discarded the snapshot journal with wrong version", "required", journalVersion, "got", version)
295+
if version != journalV0 && version != journalCurrentVersion {
296+
log.Warn("Discarded journal with wrong version", "required", journalCurrentVersion, "got", version)
290297
return errors.New("wrong journal version")
291298
}
292299
// Secondly, resolve the disk layer root, ensure it's continuous
@@ -316,6 +323,36 @@ func iterateJournal(db ethdb.KeyValueReader, callback journalCallback) error {
316323
}
317324
return fmt.Errorf("load diff root: %v", err)
318325
}
326+
// If a legacy journal is detected, decode the destruct set from the stream.
327+
// The destruct set has been deprecated. If the journal contains non-empty
328+
// destruct set, then it is deemed incompatible.
329+
//
330+
// Since self-destruction has been deprecated following the cancun fork,
331+
// the destruct set is expected to be nil for layers above the fork block.
332+
// However, an exception occurs during contract deployment: pre-funded accounts
333+
// may self-destruct, causing accounts with non-zero balances to be removed
334+
// from the state. For example,
335+
// https://etherscan.io/tx/0xa087333d83f0cd63b96bdafb686462e1622ce25f40bd499e03efb1051f31fe49).
336+
//
337+
// For nodes with a fully synced state, the legacy journal is likely compatible
338+
// with the updated definition, eliminating the need for regeneration. Unfortunately,
339+
// nodes performing a full sync of historical chain segments or encountering
340+
// pre-funded account deletions may face incompatibilities, leading to automatic
341+
// snapshot regeneration.
342+
//
343+
// This approach minimizes snapshot regeneration for Geth nodes upgrading from a
344+
// legacy version that are already synced. The workaround can be safely removed
345+
// after the next hard fork.
346+
if version == journalV0 {
347+
var destructs []journalDestruct
348+
if err := r.Decode(&destructs); err != nil {
349+
return fmt.Errorf("load diff destructs: %v", err)
350+
}
351+
if len(destructs) > 0 {
352+
log.Warn("Incompatible legacy journal detected", "version", journalV0)
353+
return fmt.Errorf("incompatible legacy journal detected")
354+
}
355+
}
319356
if err := r.Decode(&accounts); err != nil {
320357
return fmt.Errorf("load diff accounts: %v", err)
321358
}

core/state/snapshot/snapshot.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ func (t *Tree) Journal(root common.Hash) (common.Hash, error) {
664664

665665
// Firstly write out the metadata of journal
666666
journal := new(bytes.Buffer)
667-
if err := rlp.Encode(journal, journalVersion); err != nil {
667+
if err := rlp.Encode(journal, journalCurrentVersion); err != nil {
668668
return common.Hash{}, err
669669
}
670670
diskroot := t.diskRoot()

0 commit comments

Comments
 (0)