diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 0c9f1ae4c..b68b2dd30 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -50,6 +50,7 @@ var ( ArgsUsage: "", Flags: []cli.Flag{ utils.DataDirFlag, + utils.OverrideMorph203TimeFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` @@ -208,12 +209,18 @@ func initGenesis(ctx *cli.Context) error { stack, _ := makeConfigNode(ctx) defer stack.Close() + var overrides core.ChainOverrides + if ctx.IsSet(utils.OverrideMorph203TimeFlag.Name) { + v := ctx.Uint64(utils.OverrideMorph203TimeFlag.Name) + overrides.Morph203Time = &v + } + for _, name := range []string{"chaindata", "lightchaindata"} { chaindb, err := stack.OpenDatabase(name, 0, 0, "", false) if err != nil { utils.Fatalf("Failed to open database: %v", err) } - _, hash, err := core.SetupGenesisBlock(chaindb, genesis) + _, hash, err := core.SetupGenesisBlockWithOverride(chaindb, genesis, &overrides) if err != nil { utils.Fatalf("Failed to write genesis block: %v", err) } diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 7e16b455d..c97d4ac6b 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -158,6 +158,11 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) { if ctx.GlobalIsSet(utils.OverrideArrowGlacierFlag.Name) { cfg.Eth.OverrideArrowGlacier = new(big.Int).SetUint64(ctx.GlobalUint64(utils.OverrideArrowGlacierFlag.Name)) } + if ctx.GlobalIsSet(utils.OverrideMorph203TimeFlag.Name) { + v := ctx.Uint64(utils.OverrideMorph203TimeFlag.Name) + cfg.Eth.OverrideMorph203Time = &v + } + backend, _ := utils.RegisterEthService(stack, &cfg.Eth) // Configure log filter RPC API. diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 20198b992..13000f3ba 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -71,6 +71,7 @@ var ( utils.USBFlag, utils.SmartCardDaemonPathFlag, utils.OverrideArrowGlacierFlag, + utils.OverrideMorph203TimeFlag, utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, utils.EthashCachesOnDiskFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 0f79394c4..429503ea5 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -267,6 +267,10 @@ var ( Name: "override.arrowglacier", Usage: "Manually specify Arrow Glacier fork-block, overriding the bundled setting", } + OverrideMorph203TimeFlag = &cli.Uint64Flag{ + Name: "override.morph203time", + Usage: "Manually specify the Morph203 fork timestamp, overriding the bundled setting", + } // Light server and client settings LightServeFlag = cli.IntFlag{ Name: "light.serve", diff --git a/core/genesis.go b/core/genesis.go index b0e168f66..1771d1339 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -142,6 +142,23 @@ func (e *GenesisMismatchError) Error() string { return fmt.Sprintf("database contains incompatible genesis (have %x, new %x)", e.Stored, e.New) } +// ChainOverrides contains the changes to chain config +// Typically, these modifications involve hardforks that are not enabled on the BSC mainnet, intended for testing purposes. +type ChainOverrides struct { + Morph203Time *uint64 +} + +// apply applies the chain overrides on the supplied chain config. +func (o *ChainOverrides) apply(cfg *params.ChainConfig) error { + if o == nil || cfg == nil { + return nil + } + if o.Morph203Time != nil { + cfg.Morph203Time = o.Morph203Time + } + return cfg.CheckConfigForkOrder() +} + // SetupGenesisBlock writes or updates the genesis block in db. // The block that will be used is: // @@ -159,7 +176,7 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig return SetupGenesisBlockWithOverride(db, genesis, nil) } -func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrideArrowGlacier *big.Int) (*params.ChainConfig, common.Hash, error) { +func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, overrides *ChainOverrides) (*params.ChainConfig, common.Hash, error) { if genesis != nil && genesis.Config == nil { return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig } @@ -172,6 +189,10 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override } else { log.Info("Writing custom genesis block") } + if err := overrides.apply(genesis.Config); err != nil { + return nil, common.Hash{}, err + } + block, err := genesis.Commit(db) if err != nil { return genesis.Config, common.Hash{}, err @@ -232,13 +253,18 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override } // Get the existing chain configuration. newcfg := genesis.configOrDefault(stored) - if overrideArrowGlacier != nil { - newcfg.ArrowGlacierBlock = overrideArrowGlacier + if err := overrides.apply(newcfg); err != nil { + return nil, common.Hash{}, err } if err := newcfg.CheckConfigForkOrder(); err != nil { return newcfg, common.Hash{}, err } + storedcfg := rawdb.ReadChainConfig(db, stored) + if err := overrides.apply(storedcfg); err != nil { + return nil, common.Hash{}, err + } + if storedcfg == nil { log.Warn("Found genesis block without chain config") rawdb.WriteChainConfig(db, stored, newcfg) @@ -262,12 +288,12 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override } // Check config compatibility and write the config. Compatibility errors // are returned to the caller unless we're already at block zero. - height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db)) - if height == nil { - return newcfg, stored, fmt.Errorf("missing block number for head header hash") + head := rawdb.ReadHeadHeader(db) + if head == nil { + return nil, common.Hash{}, errors.New("missing head header") } - compatErr := storedcfg.CheckCompatible(newcfg, *height) - if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 { + compatErr := storedcfg.CheckCompatible(newcfg, head.Number.Uint64(), head.Time) + if compatErr != nil && ((head.Number.Uint64() != 0 && compatErr.RewindToBlock != 0) || (head.Time != 0 && compatErr.RewindToTime != 0)) { return newcfg, stored, compatErr } rawdb.WriteChainConfig(db, stored, newcfg) @@ -288,6 +314,10 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { return params.RinkebyChainConfig case ghash == params.GoerliGenesisHash: return params.GoerliChainConfig + case ghash == params.MorphMainnetGenesisHash: + return params.MorphMainnetChainConfig + case ghash == params.MorphHoleskyGenesisHash: + return params.MorphHoleskyChainConfig default: return params.AllEthashProtocolChanges } diff --git a/core/genesis_test.go b/core/genesis_test.go index cf1a73524..483afd9af 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -130,10 +130,10 @@ func TestSetupGenesis(t *testing.T) { wantHash: customghash, wantConfig: customg.Config, wantErr: ¶ms.ConfigCompatError{ - What: "Homestead fork block", - StoredConfig: big.NewInt(2), - NewConfig: big.NewInt(3), - RewindTo: 1, + What: "Homestead fork block", + StoredBlock: big.NewInt(2), + NewBlock: big.NewInt(3), + RewindToBlock: 1, }, }, } diff --git a/eth/backend.go b/eth/backend.go index 986ca6740..ffab6fab7 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -128,7 +128,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideArrowGlacier) + + // Override the chain config with provided settings. + var overrides core.ChainOverrides + if config.OverrideMorph203Time != nil { + overrides.Morph203Time = config.OverrideMorph203Time + } + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, &overrides) if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { return nil, genesisErr } @@ -200,7 +206,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) - eth.blockchain.SetHead(compat.RewindTo) + eth.blockchain.SetHead(compat.RewindToBlock) rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } eth.bloomIndexer.Start(eth.blockchain) diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 13a92cd55..48aee81a1 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -211,6 +211,9 @@ type Config struct { // Max block range for eth_getLogs api method MaxBlockRange int64 + + // Morph203Time override + OverrideMorph203Time *uint64 `toml:",omitempty"` } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 3db674feb..9e6fe9af1 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -61,7 +61,9 @@ func (c Config) MarshalTOML() (interface{}, error) { Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` OverrideArrowGlacier *big.Int `toml:",omitempty"` + CheckCircuitCapacity bool MaxBlockRange int64 + OverrideMorph203Time *uint64 `toml:",omitempty"` } var enc Config enc.Genesis = c.Genesis @@ -107,7 +109,9 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.Checkpoint = c.Checkpoint enc.CheckpointOracle = c.CheckpointOracle enc.OverrideArrowGlacier = c.OverrideArrowGlacier + enc.CheckCircuitCapacity = c.CheckCircuitCapacity enc.MaxBlockRange = c.MaxBlockRange + enc.OverrideMorph203Time = c.OverrideMorph203Time return &enc, nil } @@ -157,7 +161,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { Checkpoint *params.TrustedCheckpoint `toml:",omitempty"` CheckpointOracle *params.CheckpointOracleConfig `toml:",omitempty"` OverrideArrowGlacier *big.Int `toml:",omitempty"` + CheckCircuitCapacity *bool MaxBlockRange *int64 + OverrideMorph203Time *uint64 `toml:",omitempty"` } var dec Config if err := unmarshal(&dec); err != nil { @@ -292,8 +298,14 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.OverrideArrowGlacier != nil { c.OverrideArrowGlacier = dec.OverrideArrowGlacier } + if dec.CheckCircuitCapacity != nil { + c.CheckCircuitCapacity = *dec.CheckCircuitCapacity + } if dec.MaxBlockRange != nil { c.MaxBlockRange = *dec.MaxBlockRange } + if dec.OverrideMorph203Time != nil { + c.OverrideMorph203Time = dec.OverrideMorph203Time + } return nil } diff --git a/les/client.go b/les/client.go index 9a05f0fca..dca7e493a 100644 --- a/les/client.go +++ b/les/client.go @@ -87,7 +87,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { if err != nil { return nil, err } - chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideArrowGlacier) + + // Override the chain config with provided settings. + var overrides core.ChainOverrides + if config.OverrideMorph203Time != nil { + overrides.Morph203Time = config.OverrideMorph203Time + } + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, &overrides) if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { return nil, genesisErr } @@ -161,7 +167,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) { // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat) - leth.blockchain.SetHead(compat.RewindTo) + leth.blockchain.SetHead(compat.RewindToBlock) rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) } diff --git a/params/config.go b/params/config.go index 4a52df94a..52331d58f 100644 --- a/params/config.go +++ b/params/config.go @@ -760,18 +760,25 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. -func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *ConfigCompatError { - bhead := new(big.Int).SetUint64(height) - +func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError { + var ( + bhead = new(big.Int).SetUint64(height) + btime = time + ) // Iterate checkCompatible to find the lowest conflict. var lasterr *ConfigCompatError for { - err := c.checkCompatible(newcfg, bhead) - if err == nil || (lasterr != nil && err.RewindTo == lasterr.RewindTo) { + err := c.checkCompatible(newcfg, bhead, btime) + if err == nil || (lasterr != nil && err.RewindToBlock == lasterr.RewindToBlock && err.RewindToTime == lasterr.RewindToTime) { break } lasterr = err - bhead.SetUint64(err.RewindTo) + + if err.RewindToTime > 0 { + btime = err.RewindToTime + } else { + bhead.SetUint64(err.RewindToBlock) + } } return lasterr } @@ -843,7 +850,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error { return nil } -func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *ConfigCompatError { +func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int, headTimestamp uint64) *ConfigCompatError { if isForkIncompatible(c.HomesteadBlock, newcfg.HomesteadBlock, head) { return newCompatError("Homestead fork block", c.HomesteadBlock, newcfg.HomesteadBlock) } @@ -905,6 +912,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi if isForkIncompatible(c.CurieBlock, newcfg.CurieBlock, head) { return newCompatError("Curie fork block", c.CurieBlock, newcfg.CurieBlock) } + if isForkTimestampIncompatible(c.Morph203Time, newcfg.Morph203Time, headTimestamp) { + return newTimestampCompatError("Morph203Time fork timestamp", c.Morph203Time, newcfg.Morph203Time) + } return nil } @@ -939,14 +949,47 @@ func configNumEqual(x, y *big.Int) bool { return x.Cmp(y) == 0 } +// isForkTimestampIncompatible returns true if a fork scheduled at timestamp s1 +// cannot be rescheduled to timestamp s2 because head is already past the fork. +func isForkTimestampIncompatible(s1, s2 *uint64, head uint64) bool { + return (isTimestampForked(s1, head) || isTimestampForked(s2, head)) && !configTimestampEqual(s1, s2) +} + +// isTimestampForked returns whether a fork scheduled at timestamp s is active +// at the given head timestamp. Whilst this method is the same as isBlockForked, +// they are explicitly separate for clearer reading. +func isTimestampForked(s *uint64, head uint64) bool { + if s == nil { + return false + } + return *s <= head +} + +func configTimestampEqual(x, y *uint64) bool { + if x == nil { + return y == nil + } + if y == nil { + return x == nil + } + return *x == *y +} + // ConfigCompatError is raised if the locally-stored blockchain is initialised with a // ChainConfig that would alter the past. type ConfigCompatError struct { What string // block numbers of the stored and new configurations - StoredConfig, NewConfig *big.Int + StoredBlock, NewBlock *big.Int + + // timestamps of the stored and new configurations if time based forking + StoredTime, NewTime *uint64 + // the block number to which the local chain must be rewound to correct the error - RewindTo uint64 + RewindToBlock uint64 + + // the timestamp to which the local chain must be rewound to correct the error + RewindToTime uint64 } func newCompatError(what string, storedblock, newblock *big.Int) *ConfigCompatError { @@ -959,15 +1002,53 @@ func newCompatError(what string, storedblock, newblock *big.Int) *ConfigCompatEr default: rew = newblock } - err := &ConfigCompatError{what, storedblock, newblock, 0} + err := &ConfigCompatError{ + What: what, + StoredBlock: storedblock, + NewBlock: newblock, + RewindToBlock: 0, + } if rew != nil && rew.Sign() > 0 { - err.RewindTo = rew.Uint64() - 1 + err.RewindToBlock = rew.Uint64() - 1 + } + return err +} + +func newTimestampCompatError(what string, storedtime, newtime *uint64) *ConfigCompatError { + var rew *uint64 + switch { + case storedtime == nil: + rew = newtime + case newtime == nil || *storedtime < *newtime: + rew = storedtime + default: + rew = newtime + } + err := &ConfigCompatError{ + What: what, + StoredTime: storedtime, + NewTime: newtime, + RewindToTime: 0, + } + if rew != nil && *rew != 0 { + err.RewindToTime = *rew - 1 } return err } func (err *ConfigCompatError) Error() string { - return fmt.Sprintf("mismatching %s in database (have %d, want %d, rewindto %d)", err.What, err.StoredConfig, err.NewConfig, err.RewindTo) + if err.StoredBlock != nil { + return fmt.Sprintf("mismatching %s in database (have block %d, want block %d, rewindto block %d)", err.What, err.StoredBlock, err.NewBlock, err.RewindToBlock) + } + + if err.StoredTime == nil && err.NewTime == nil { + return "" + } else if err.StoredTime == nil && err.NewTime != nil { + return fmt.Sprintf("mismatching %s in database (have timestamp nil, want timestamp %d, rewindto timestamp %d)", err.What, *err.NewTime, err.RewindToTime) + } else if err.StoredTime != nil && err.NewTime == nil { + return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp nil, rewindto timestamp %d)", err.What, *err.StoredTime, err.RewindToTime) + } + return fmt.Sprintf("mismatching %s in database (have timestamp %d, want timestamp %d, rewindto timestamp %d)", err.What, *err.StoredTime, *err.NewTime, err.RewindToTime) } // Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions diff --git a/params/config_test.go b/params/config_test.go index 26c8e6662..a17acc53b 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -17,122 +17,141 @@ package params import ( + "math" "math/big" "reflect" "testing" + "time" "github.com/stretchr/testify/require" ) func TestCheckCompatible(t *testing.T) { type test struct { - stored, new *ChainConfig - head uint64 - wantErr *ConfigCompatError + stored, new *ChainConfig + headBlock uint64 + headTimestamp uint64 + wantErr *ConfigCompatError } tests := []test{ - {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, head: 0, wantErr: nil}, - {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, head: 100, wantErr: nil}, + {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, headBlock: 0, headTimestamp: 0, wantErr: nil}, + {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, headBlock: 0, headTimestamp: uint64(time.Now().Unix()), wantErr: nil}, + {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, headBlock: 100, wantErr: nil}, { - stored: &ChainConfig{EIP150Block: big.NewInt(10)}, - new: &ChainConfig{EIP150Block: big.NewInt(20)}, - head: 9, - wantErr: nil, + stored: &ChainConfig{EIP150Block: big.NewInt(10)}, + new: &ChainConfig{EIP150Block: big.NewInt(20)}, + headBlock: 9, + wantErr: nil, }, { - stored: AllEthashProtocolChanges, - new: &ChainConfig{HomesteadBlock: nil}, - head: 3, + stored: AllEthashProtocolChanges, + new: &ChainConfig{HomesteadBlock: nil}, + headBlock: 3, wantErr: &ConfigCompatError{ - What: "Homestead fork block", - StoredConfig: big.NewInt(0), - NewConfig: nil, - RewindTo: 0, + What: "Homestead fork block", + StoredBlock: big.NewInt(0), + NewBlock: nil, + RewindToBlock: 0, }, }, { - stored: AllEthashProtocolChanges, - new: &ChainConfig{HomesteadBlock: big.NewInt(1)}, - head: 3, + stored: AllEthashProtocolChanges, + new: &ChainConfig{HomesteadBlock: big.NewInt(1)}, + headBlock: 3, wantErr: &ConfigCompatError{ - What: "Homestead fork block", - StoredConfig: big.NewInt(0), - NewConfig: big.NewInt(1), - RewindTo: 0, + What: "Homestead fork block", + StoredBlock: big.NewInt(0), + NewBlock: big.NewInt(1), + RewindToBlock: 0, }, }, { - stored: &ChainConfig{HomesteadBlock: big.NewInt(30), EIP150Block: big.NewInt(10)}, - new: &ChainConfig{HomesteadBlock: big.NewInt(25), EIP150Block: big.NewInt(20)}, - head: 25, + stored: &ChainConfig{HomesteadBlock: big.NewInt(30), EIP150Block: big.NewInt(10)}, + new: &ChainConfig{HomesteadBlock: big.NewInt(25), EIP150Block: big.NewInt(20)}, + headBlock: 25, wantErr: &ConfigCompatError{ - What: "EIP150 fork block", - StoredConfig: big.NewInt(10), - NewConfig: big.NewInt(20), - RewindTo: 9, + What: "EIP150 fork block", + StoredBlock: big.NewInt(10), + NewBlock: big.NewInt(20), + RewindToBlock: 9, }, }, { - stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, - new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(30)}, - head: 40, - wantErr: nil, + stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, + new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(30)}, + headBlock: 40, + wantErr: nil, }, { - stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, - new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(31)}, - head: 40, + stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, + new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(31)}, + headBlock: 40, wantErr: &ConfigCompatError{ - What: "Petersburg fork block", - StoredConfig: nil, - NewConfig: big.NewInt(31), - RewindTo: 30, + What: "Petersburg fork block", + StoredBlock: nil, + NewBlock: big.NewInt(31), + RewindToBlock: 30, + }, + }, + { + stored: &ChainConfig{Morph203Time: NewUint64(10)}, + new: &ChainConfig{Morph203Time: NewUint64(20)}, + headTimestamp: 9, + wantErr: nil, + }, + { + stored: &ChainConfig{Morph203Time: NewUint64(10)}, + new: &ChainConfig{Morph203Time: NewUint64(20)}, + headTimestamp: 25, + wantErr: &ConfigCompatError{ + What: "Morph203Time fork timestamp", + StoredTime: NewUint64(10), + NewTime: NewUint64(20), + RewindToTime: 9, }, }, } for _, test := range tests { - err := test.stored.CheckCompatible(test.new, test.head) + err := test.stored.CheckCompatible(test.new, test.headBlock, test.headTimestamp) if !reflect.DeepEqual(err, test.wantErr) { - t.Errorf("error mismatch:\nstored: %v\nnew: %v\nhead: %v\nerr: %v\nwant: %v", test.stored, test.new, test.head, err, test.wantErr) + t.Errorf("error mismatch:\nstored: %v\nnew: %v\nheadBlock: %v\nheadTimestamp: %v\nerr: %v\nwant: %v", test.stored, test.new, test.headBlock, test.headTimestamp, err, test.wantErr) } } } -func TestIsForkedTime(t *testing.T) { - timePtr := func(t uint64) *uint64 { - return &t +func TestConfigRules(t *testing.T) { + c := &ChainConfig{ + LondonBlock: new(big.Int), + Morph203Time: NewUint64(500), } - - tests := map[string]struct { - forkTime *uint64 - now uint64 - isForked bool - }{ - "not configured": { - forkTime: nil, - isForked: false, - }, - "before fork time": { - forkTime: timePtr(10), - now: 3, - isForked: false, - }, - "on fork time": { - forkTime: timePtr(10), - now: 10, - isForked: true, - }, - "after fork time": { - forkTime: timePtr(10), - now: 11, - isForked: true, - }, + var stamp uint64 + if r := c.Rules(big.NewInt(0), stamp); r.IsMorph203 { + t.Errorf("expected %v to not be morph203", stamp) } - - for desc, test := range tests { - t.Run(desc, func(t *testing.T) { - require.Equal(t, test.isForked, isForkedTime(test.now, test.forkTime)) - }) + stamp = 500 + if r := c.Rules(big.NewInt(0), stamp); !r.IsMorph203 { + t.Errorf("expected %v to be morph203", stamp) + } + stamp = math.MaxInt64 + if r := c.Rules(big.NewInt(0), stamp); !r.IsMorph203 { + t.Errorf("expected %v to be morph203", stamp) } } + +func TestTimestampCompatError(t *testing.T) { + require.Equal(t, new(ConfigCompatError).Error(), "") + + errWhat := "Morph203 fork timestamp" + require.Equal(t, newTimestampCompatError(errWhat, nil, NewUint64(1681338455)).Error(), + "mismatching Morph203 fork timestamp in database (have timestamp nil, want timestamp 1681338455, rewindto timestamp 1681338454)") + + require.Equal(t, newTimestampCompatError(errWhat, NewUint64(1681338455), nil).Error(), + "mismatching Morph203 fork timestamp in database (have timestamp 1681338455, want timestamp nil, rewindto timestamp 1681338454)") + + require.Equal(t, newTimestampCompatError(errWhat, NewUint64(1681338455), NewUint64(600624000)).Error(), + "mismatching Morph203 fork timestamp in database (have timestamp 1681338455, want timestamp 600624000, rewindto timestamp 600623999)") + + require.Equal(t, newTimestampCompatError(errWhat, NewUint64(0), NewUint64(1681338455)).Error(), + "mismatching Morph203 fork timestamp in database (have timestamp 0, want timestamp 1681338455, rewindto timestamp 0)") +}