@@ -32,6 +32,7 @@ import (
3232 "errors"
3333 "fmt"
3434 "math/big"
35+ "strings"
3536 "time"
3637
3738 "github.com/ava-labs/avalanchego/vms/evm/sync/customrawdb"
@@ -177,33 +178,25 @@ func SetupGenesisBlock(
177178 return genesis .Config , common.Hash {}, & GenesisMismatchError {stored , hash }
178179 }
179180 // Get the existing chain configuration.
180- newcfg := genesis .Config
181- if err := newcfg .CheckConfigForkOrder (); err != nil {
182- return newcfg , common.Hash {}, err
181+ newCfg := genesis .Config
182+ if err := newCfg .CheckConfigForkOrder (); err != nil {
183+ return newCfg , common.Hash {}, err
183184 }
184185
185186 // Read stored config into a local extras copy to avoid mutating the
186187 // caller's attached extras (which may be concurrently accessed in tests).
187188 // We'll persist the new config (including upgrade bytes) using the attached
188189 // extras below to ensure on-disk state is up to date.
189- readExtra := * params .GetExtra (newcfg )
190+ readExtra := * params .GetExtra (newCfg )
190191 storedCfg := customrawdb .ReadChainConfig (db , stored , & readExtra )
191192 // If there is no previously stored chain config, write the chain config to disk.
192193 if storedCfg == nil {
193194 // Note: this can happen since we did not previously write the genesis block and chain config in the same batch.
194195 log .Warn ("Found genesis block without chain config" )
195- customrawdb .WriteChainConfig (db , stored , newcfg , * params .GetExtra (newcfg ))
196- return newcfg , stored , nil
196+ customrawdb .WriteChainConfig (db , stored , newCfg , * params .GetExtra (newCfg ))
197+ return newCfg , stored , nil
197198 }
198199
199- // Persist the new chain config (and upgrade bytes) to disk now to avoid
200- // spurious compatibility failures due to missing upgrade bytes on older databases.
201- customrawdb .WriteChainConfig (db , stored , newcfg , * params .GetExtra (newcfg ))
202-
203- // Re-read stored config into a fresh extras copy for a clean comparison.
204- compareExtra := * params .GetExtra (newcfg )
205- storedCfg = customrawdb .ReadChainConfig (db , stored , & compareExtra )
206-
207200 // Notes on the following line:
208201 // - this is needed in coreth to handle the case where existing nodes do not
209202 // have the Berlin or London forks initialized by block number on disk.
@@ -222,24 +215,31 @@ func SetupGenesisBlock(
222215 // when we start syncing from scratch, the last accepted block
223216 // will be genesis block
224217 if lastBlock == nil {
225- return newcfg , common.Hash {}, errors .New ("missing last accepted block" )
218+ return newCfg , common.Hash {}, errors .New ("missing last accepted block" )
226219 }
227220 height := lastBlock .NumberU64 ()
228221 timestamp := lastBlock .Time ()
229- if skipChainConfigCheckCompatible {
230- log .Info ("skipping verifying activated network upgrades on chain config" )
231- } else {
232- compatErr := storedCfg .CheckCompatible (newcfg , height , timestamp )
233- if compatErr != nil && ((height != 0 && compatErr .RewindToBlock != 0 ) || (timestamp != 0 && compatErr .RewindToTime != 0 )) {
234- storedData , _ := params .ToWithUpgradesJSON (storedCfg ).MarshalJSON ()
235- newData , _ := params .ToWithUpgradesJSON (newcfg ).MarshalJSON ()
236- log .Error ("found mismatch between config on database vs. new config" , "storedConfig" , string (storedData ), "newConfig" , string (newData ), "err" , compatErr )
237- return newcfg , stored , compatErr
222+
223+ // If the chain hasn't advanced past genesis (height 0), persist the new
224+ // chain config (including upgrade bytes) before compatibility checks so
225+ // subsequent restarts see the updated upgrades and do not flag them as
226+ // retroactive enables.
227+ if height == 0 {
228+ storedCfg = persistChainConfigAndReload (db , stored , newCfg )
229+ }
230+
231+ if ! skipChainConfigCheckCompatible {
232+ if _ , err := reconcileCompatibility (db , stored , storedCfg , newCfg , height , timestamp ); err != nil {
233+ logCompatibilityMismatch (storedCfg , newCfg , err )
234+ return newCfg , stored , err
238235 }
236+ } else {
237+ log .Info ("skipping verifying activated network upgrades on chain config" )
239238 }
240- // Chain config has already been written above.
241- // Write is idempotent but not required here.
242- return newcfg , stored , nil
239+
240+ // Persist the new chain config (including upgrade bytes) now that compatibility is verified.
241+ customrawdb .WriteChainConfig (db , stored , newCfg , * params .GetExtra (newCfg ))
242+ return newCfg , stored , nil
243243}
244244
245245// IsVerkle indicates whether the state is already stored in a verkle
@@ -468,3 +468,54 @@ func ReadBlockByHash(db ethdb.Reader, hash common.Hash) *types.Block {
468468 }
469469 return rawdb .ReadBlock (db , hash , * blockNumber )
470470}
471+
472+ // reconcileCompatibility checks compatibility between the stored and new chain configs.
473+ // If the only incompatibility is missing precompile upgrade metadata, it persists the
474+ // new config (including upgrade bytes) and returns a refreshed stored config view.
475+ // Otherwise, it returns the compatibility error.
476+ func reconcileCompatibility (
477+ db ethdb.Database ,
478+ genesisHash common.Hash ,
479+ storedCfg * params.ChainConfig ,
480+ newCfg * params.ChainConfig ,
481+ height uint64 ,
482+ timestamp uint64 ,
483+ ) (* params.ChainConfig , error ) {
484+ compatErr := storedCfg .CheckCompatible (newCfg , height , timestamp )
485+ needsRewind := compatErr != nil && ((height != 0 && compatErr .RewindToBlock != 0 ) || (timestamp != 0 && compatErr .RewindToTime != 0 ))
486+ if ! needsRewind {
487+ return storedCfg , nil
488+ }
489+ if ! isPrecompileUpgradeOnlyIncompatibility (compatErr ) {
490+ return nil , compatErr
491+ }
492+ // Only precompile upgrade metadata differs: write upgrades and proceed.
493+ refreshed := persistChainConfigAndReload (db , genesisHash , newCfg )
494+ return refreshed , nil
495+ }
496+
497+ // persistChainConfigAndReload writes the provided chain config (including upgrade bytes)
498+ // and returns a fresh view of the stored config using a separate extras copy.
499+ func persistChainConfigAndReload (db ethdb.Database , genesisHash common.Hash , cfg * params.ChainConfig ) * params.ChainConfig {
500+ customrawdb .WriteChainConfig (db , genesisHash , cfg , * params .GetExtra (cfg ))
501+ refreshedExtra := * params .GetExtra (cfg )
502+ return customrawdb .ReadChainConfig (db , genesisHash , & refreshedExtra )
503+ }
504+
505+ // isPrecompileUpgradeOnlyIncompatibility returns true if the compatibility error
506+ // pertains exclusively to precompile upgrade activation metadata being absent on disk.
507+ // In such cases, persisting the new chain config (including upgrade bytes) resolves
508+ // the mismatch without requiring a rewind.
509+ func isPrecompileUpgradeOnlyIncompatibility (err * ethparams.ConfigCompatError ) bool {
510+ if err == nil {
511+ return false
512+ }
513+ return strings .Contains (err .What , "PrecompileUpgrade[" )
514+ }
515+
516+ // logCompatibilityMismatch emits a structured error comparing stored and new configs.
517+ func logCompatibilityMismatch (storedCfg , newCfg * params.ChainConfig , err error ) {
518+ storedData , _ := params .ToWithUpgradesJSON (storedCfg ).MarshalJSON ()
519+ newData , _ := params .ToWithUpgradesJSON (newCfg ).MarshalJSON ()
520+ log .Error ("found mismatch between config on database vs. new config" , "storedConfig" , string (storedData ), "newConfig" , string (newData ), "err" , err )
521+ }
0 commit comments