core, les, params: add timestamp based fork compatibility checks

This commit is contained in:
Péter Szilágyi 2022-12-15 09:40:33 +02:00
parent a4e19c5ca3
commit 08481028fe
No known key found for this signature in database
GPG Key ID: E9AE538CEDF8293D
7 changed files with 272 additions and 155 deletions

@ -427,7 +427,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
// Rewind the chain in case of an incompatible config upgrade. // Rewind the chain in case of an incompatible config upgrade.
if compat, ok := genesisErr.(*params.ConfigCompatError); ok { if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
log.Warn("Rewinding chain to upgrade configuration", "err", compat) log.Warn("Rewinding chain to upgrade configuration", "err", compat)
bc.SetHead(compat.RewindTo) if compat.RewindToTime > 0 {
log.Crit("Timestamp based rewinds not implemented yet /sad")
} else {
bc.SetHead(compat.RewindToBlock)
}
rawdb.WriteChainConfig(db, genesisHash, chainConfig) rawdb.WriteChainConfig(db, genesisHash, chainConfig)
} }
// Start tx indexer/unindexer if required. // Start tx indexer/unindexer if required.

@ -4275,7 +4275,7 @@ func TestEIP3651(t *testing.T) {
gspec.Config.BerlinBlock = common.Big0 gspec.Config.BerlinBlock = common.Big0
gspec.Config.LondonBlock = common.Big0 gspec.Config.LondonBlock = common.Big0
gspec.Config.ShanghaiBlock = common.Big0 gspec.Config.ShanghaiTime = common.Big0
signer := types.LatestSigner(gspec.Config) signer := types.LatestSigner(gspec.Config)
_, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { _, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {

@ -371,12 +371,12 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *trie.Database, gen
} }
// Check config compatibility and write the config. Compatibility errors // Check config compatibility and write the config. Compatibility errors
// are returned to the caller unless we're already at block zero. // are returned to the caller unless we're already at block zero.
height := rawdb.ReadHeaderNumber(db, rawdb.ReadHeadHeaderHash(db)) head := rawdb.ReadHeadHeader(db)
if height == nil { if head == nil {
return newcfg, stored, fmt.Errorf("missing block number for head header hash") return newcfg, stored, fmt.Errorf("missing head header")
} }
compatErr := storedcfg.CheckCompatible(newcfg, *height) compatErr := storedcfg.CheckCompatible(newcfg, head.Number.Uint64(), head.Time)
if compatErr != nil && *height != 0 && compatErr.RewindTo != 0 { if compatErr != nil && ((head.Number.Uint64() != 0 && compatErr.RewindToBlock != 0) || (head.Time != 0 && compatErr.RewindToTime != 0)) {
return newcfg, stored, compatErr return newcfg, stored, compatErr
} }
// Don't overwrite if the old is identical to the new // Don't overwrite if the old is identical to the new

@ -132,10 +132,10 @@ func TestSetupGenesis(t *testing.T) {
wantHash: customghash, wantHash: customghash,
wantConfig: customg.Config, wantConfig: customg.Config,
wantErr: &params.ConfigCompatError{ wantErr: &params.ConfigCompatError{
What: "Homestead fork block", What: "Homestead fork block",
StoredConfig: big.NewInt(2), StoredBlock: big.NewInt(2),
NewConfig: big.NewInt(3), NewBlock: big.NewInt(3),
RewindTo: 1, RewindToBlock: 1,
}, },
}, },
} }

@ -179,7 +179,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
// Rewind the chain in case of an incompatible config upgrade. // Rewind the chain in case of an incompatible config upgrade.
if compat, ok := genesisErr.(*params.ConfigCompatError); ok { if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
log.Warn("Rewinding chain to upgrade configuration", "err", compat) log.Warn("Rewinding chain to upgrade configuration", "err", compat)
leth.blockchain.SetHead(compat.RewindTo) if compat.RewindToTime > 0 {
log.Crit("Timestamp based rewinds not implemented yet /sad")
} else {
leth.blockchain.SetHead(compat.RewindToBlock)
}
rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
} }

@ -371,9 +371,12 @@ type ChainConfig struct {
ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (bomb delay) switch block (nil = no fork, 0 = already activated) ArrowGlacierBlock *big.Int `json:"arrowGlacierBlock,omitempty"` // Eip-4345 (bomb delay) switch block (nil = no fork, 0 = already activated)
GrayGlacierBlock *big.Int `json:"grayGlacierBlock,omitempty"` // Eip-5133 (bomb delay) switch block (nil = no fork, 0 = already activated) GrayGlacierBlock *big.Int `json:"grayGlacierBlock,omitempty"` // Eip-5133 (bomb delay) switch block (nil = no fork, 0 = already activated)
MergeNetsplitBlock *big.Int `json:"mergeNetsplitBlock,omitempty"` // Virtual fork after The Merge to use as a network splitter MergeNetsplitBlock *big.Int `json:"mergeNetsplitBlock,omitempty"` // Virtual fork after The Merge to use as a network splitter
ShanghaiTime *big.Int `json:"shanghaiBlock,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai)
CancunBlock *big.Int `json:"cancunBlock,omitempty"` // Cancun switch block (nil = no fork, 0 = already on cancun) CancunBlock *big.Int `json:"cancunBlock,omitempty"` // Cancun switch block (nil = no fork, 0 = already on cancun)
// Fork scheduling was switched from blocks to timestamps here
ShanghaiTime *big.Int `json:"shanghaiTime,omitempty"` // Shanghai switch time (nil = no fork, 0 = already on shanghai)
// TerminalTotalDifficulty is the amount of total difficulty reached by // TerminalTotalDifficulty is the amount of total difficulty reached by
// the network that triggers the consensus upgrade. // the network that triggers the consensus upgrade.
TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"` TerminalTotalDifficulty *big.Int `json:"terminalTotalDifficulty,omitempty"`
@ -465,9 +468,7 @@ func (c *ChainConfig) Description() string {
if c.GrayGlacierBlock != nil { if c.GrayGlacierBlock != nil {
banner += fmt.Sprintf(" - Gray Glacier: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md)\n", c.GrayGlacierBlock) banner += fmt.Sprintf(" - Gray Glacier: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/gray-glacier.md)\n", c.GrayGlacierBlock)
} }
if c.ShanghaiTime != nil { banner += fmt.Sprintf(" - Shanghai: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md)\n", c.ShanghaiTime)
banner += fmt.Sprintf(" - Shanghai: %-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md)\n", c.ShanghaiTime)
}
if c.CancunBlock != nil { if c.CancunBlock != nil {
banner += fmt.Sprintf(" - Cancun: %-8v\n", c.CancunBlock) banner += fmt.Sprintf(" - Cancun: %-8v\n", c.CancunBlock)
} }
@ -489,74 +490,74 @@ func (c *ChainConfig) Description() string {
// IsHomestead returns whether num is either equal to the homestead block or greater. // IsHomestead returns whether num is either equal to the homestead block or greater.
func (c *ChainConfig) IsHomestead(num *big.Int) bool { func (c *ChainConfig) IsHomestead(num *big.Int) bool {
return isForked(c.HomesteadBlock, num) return isBlockForked(c.HomesteadBlock, num)
} }
// IsDAOFork returns whether num is either equal to the DAO fork block or greater. // IsDAOFork returns whether num is either equal to the DAO fork block or greater.
func (c *ChainConfig) IsDAOFork(num *big.Int) bool { func (c *ChainConfig) IsDAOFork(num *big.Int) bool {
return isForked(c.DAOForkBlock, num) return isBlockForked(c.DAOForkBlock, num)
} }
// IsEIP150 returns whether num is either equal to the EIP150 fork block or greater. // IsEIP150 returns whether num is either equal to the EIP150 fork block or greater.
func (c *ChainConfig) IsEIP150(num *big.Int) bool { func (c *ChainConfig) IsEIP150(num *big.Int) bool {
return isForked(c.EIP150Block, num) return isBlockForked(c.EIP150Block, num)
} }
// IsEIP155 returns whether num is either equal to the EIP155 fork block or greater. // IsEIP155 returns whether num is either equal to the EIP155 fork block or greater.
func (c *ChainConfig) IsEIP155(num *big.Int) bool { func (c *ChainConfig) IsEIP155(num *big.Int) bool {
return isForked(c.EIP155Block, num) return isBlockForked(c.EIP155Block, num)
} }
// IsEIP158 returns whether num is either equal to the EIP158 fork block or greater. // IsEIP158 returns whether num is either equal to the EIP158 fork block or greater.
func (c *ChainConfig) IsEIP158(num *big.Int) bool { func (c *ChainConfig) IsEIP158(num *big.Int) bool {
return isForked(c.EIP158Block, num) return isBlockForked(c.EIP158Block, num)
} }
// IsByzantium returns whether num is either equal to the Byzantium fork block or greater. // IsByzantium returns whether num is either equal to the Byzantium fork block or greater.
func (c *ChainConfig) IsByzantium(num *big.Int) bool { func (c *ChainConfig) IsByzantium(num *big.Int) bool {
return isForked(c.ByzantiumBlock, num) return isBlockForked(c.ByzantiumBlock, num)
} }
// IsConstantinople returns whether num is either equal to the Constantinople fork block or greater. // IsConstantinople returns whether num is either equal to the Constantinople fork block or greater.
func (c *ChainConfig) IsConstantinople(num *big.Int) bool { func (c *ChainConfig) IsConstantinople(num *big.Int) bool {
return isForked(c.ConstantinopleBlock, num) return isBlockForked(c.ConstantinopleBlock, num)
} }
// IsMuirGlacier returns whether num is either equal to the Muir Glacier (EIP-2384) fork block or greater. // IsMuirGlacier returns whether num is either equal to the Muir Glacier (EIP-2384) fork block or greater.
func (c *ChainConfig) IsMuirGlacier(num *big.Int) bool { func (c *ChainConfig) IsMuirGlacier(num *big.Int) bool {
return isForked(c.MuirGlacierBlock, num) return isBlockForked(c.MuirGlacierBlock, num)
} }
// IsPetersburg returns whether num is either // IsPetersburg returns whether num is either
// - equal to or greater than the PetersburgBlock fork block, // - equal to or greater than the PetersburgBlock fork block,
// - OR is nil, and Constantinople is active // - OR is nil, and Constantinople is active
func (c *ChainConfig) IsPetersburg(num *big.Int) bool { func (c *ChainConfig) IsPetersburg(num *big.Int) bool {
return isForked(c.PetersburgBlock, num) || c.PetersburgBlock == nil && isForked(c.ConstantinopleBlock, num) return isBlockForked(c.PetersburgBlock, num) || c.PetersburgBlock == nil && isBlockForked(c.ConstantinopleBlock, num)
} }
// IsIstanbul returns whether num is either equal to the Istanbul fork block or greater. // IsIstanbul returns whether num is either equal to the Istanbul fork block or greater.
func (c *ChainConfig) IsIstanbul(num *big.Int) bool { func (c *ChainConfig) IsIstanbul(num *big.Int) bool {
return isForked(c.IstanbulBlock, num) return isBlockForked(c.IstanbulBlock, num)
} }
// IsBerlin returns whether num is either equal to the Berlin fork block or greater. // IsBerlin returns whether num is either equal to the Berlin fork block or greater.
func (c *ChainConfig) IsBerlin(num *big.Int) bool { func (c *ChainConfig) IsBerlin(num *big.Int) bool {
return isForked(c.BerlinBlock, num) return isBlockForked(c.BerlinBlock, num)
} }
// IsLondon returns whether num is either equal to the London fork block or greater. // IsLondon returns whether num is either equal to the London fork block or greater.
func (c *ChainConfig) IsLondon(num *big.Int) bool { func (c *ChainConfig) IsLondon(num *big.Int) bool {
return isForked(c.LondonBlock, num) return isBlockForked(c.LondonBlock, num)
} }
// IsArrowGlacier returns whether num is either equal to the Arrow Glacier (EIP-4345) fork block or greater. // IsArrowGlacier returns whether num is either equal to the Arrow Glacier (EIP-4345) fork block or greater.
func (c *ChainConfig) IsArrowGlacier(num *big.Int) bool { func (c *ChainConfig) IsArrowGlacier(num *big.Int) bool {
return isForked(c.ArrowGlacierBlock, num) return isBlockForked(c.ArrowGlacierBlock, num)
} }
// IsGrayGlacier returns whether num is either equal to the Gray Glacier (EIP-5133) fork block or greater. // IsGrayGlacier returns whether num is either equal to the Gray Glacier (EIP-5133) fork block or greater.
func (c *ChainConfig) IsGrayGlacier(num *big.Int) bool { func (c *ChainConfig) IsGrayGlacier(num *big.Int) bool {
return isForked(c.GrayGlacierBlock, num) return isBlockForked(c.GrayGlacierBlock, num)
} }
// IsTerminalPoWBlock returns whether the given block is the last block of PoW stage. // IsTerminalPoWBlock returns whether the given block is the last block of PoW stage.
@ -567,30 +568,37 @@ func (c *ChainConfig) IsTerminalPoWBlock(parentTotalDiff *big.Int, totalDiff *bi
return parentTotalDiff.Cmp(c.TerminalTotalDifficulty) < 0 && totalDiff.Cmp(c.TerminalTotalDifficulty) >= 0 return parentTotalDiff.Cmp(c.TerminalTotalDifficulty) < 0 && totalDiff.Cmp(c.TerminalTotalDifficulty) >= 0
} }
// IsShanghai returns whether time is either equal to the Shanghai fork time or greater.
func (c *ChainConfig) IsShanghai(time *big.Int) bool {
return isForked(c.ShanghaiTime, time)
}
// IsCancun returns whether num is either equal to the Cancun fork block or greater. // IsCancun returns whether num is either equal to the Cancun fork block or greater.
func (c *ChainConfig) IsCancun(num *big.Int) bool { func (c *ChainConfig) IsCancun(num *big.Int) bool {
return isForked(c.CancunBlock, num) return isBlockForked(c.CancunBlock, num)
}
// IsShanghai returns whether time is either equal to the Shanghai fork time or greater.
func (c *ChainConfig) IsShanghai(time *big.Int) bool {
return isTimestampForked(c.ShanghaiTime, time)
} }
// CheckCompatible checks whether scheduled fork transitions have been imported // CheckCompatible checks whether scheduled fork transitions have been imported
// with a mismatching chain configuration. // with a mismatching chain configuration.
func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *ConfigCompatError { func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError {
bhead := new(big.Int).SetUint64(height) var (
bhead = new(big.Int).SetUint64(height)
btime = new(big.Int).SetUint64(time)
)
// Iterate checkCompatible to find the lowest conflict. // Iterate checkCompatible to find the lowest conflict.
var lasterr *ConfigCompatError var lasterr *ConfigCompatError
for { for {
err := c.checkCompatible(newcfg, bhead) err := c.checkCompatible(newcfg, bhead, btime)
if err == nil || (lasterr != nil && err.RewindTo == lasterr.RewindTo) { if err == nil || (lasterr != nil && err.RewindToBlock == lasterr.RewindToBlock && err.RewindToTime == lasterr.RewindToTime) {
break break
} }
lasterr = err lasterr = err
bhead.SetUint64(err.RewindTo)
if err.RewindToTime > 0 {
btime.SetUint64(err.RewindToTime)
} else {
bhead.SetUint64(err.RewindToBlock)
}
} }
return lasterr return lasterr
} }
@ -599,9 +607,10 @@ func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *Confi
// to guarantee that forks can be implemented in a different order than on official networks // to guarantee that forks can be implemented in a different order than on official networks
func (c *ChainConfig) CheckConfigForkOrder() error { func (c *ChainConfig) CheckConfigForkOrder() error {
type fork struct { type fork struct {
name string name string
block *big.Int block *big.Int // forks up to - and including the merge - were defined with block numbers
optional bool // if true, the fork may be nil and next fork is still allowed timestamp *big.Int // forks after the merge are scheduled using timestamps
optional bool // if true, the fork may be nil and next fork is still allowed
} }
var lastFork fork var lastFork fork
for _, cur := range []fork{ for _, cur := range []fork{
@ -620,93 +629,107 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
{name: "arrowGlacierBlock", block: c.ArrowGlacierBlock, optional: true}, {name: "arrowGlacierBlock", block: c.ArrowGlacierBlock, optional: true},
{name: "grayGlacierBlock", block: c.GrayGlacierBlock, optional: true}, {name: "grayGlacierBlock", block: c.GrayGlacierBlock, optional: true},
{name: "mergeNetsplitBlock", block: c.MergeNetsplitBlock, optional: true}, {name: "mergeNetsplitBlock", block: c.MergeNetsplitBlock, optional: true},
//{name: "shanghaiBlock", block: c.ShanghaiBlock, optional: true},
{name: "cancunBlock", block: c.CancunBlock, optional: true}, {name: "cancunBlock", block: c.CancunBlock, optional: true},
{name: "shanghaiTime", timestamp: c.ShanghaiTime},
} { } {
if lastFork.name != "" { if lastFork.name != "" {
// Next one must be higher number switch {
if lastFork.block == nil && cur.block != nil { // Non-optional forks must all be present in the chain config up to the last defined fork
return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at %v", case lastFork.block == nil && lastFork.timestamp == nil && (cur.block != nil || cur.timestamp != nil):
lastFork.name, cur.name, cur.block) if cur.block != nil {
} return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at block %v",
if lastFork.block != nil && cur.block != nil { lastFork.name, cur.name, cur.block)
if lastFork.block.Cmp(cur.block) > 0 { } else {
return fmt.Errorf("unsupported fork ordering: %v enabled at %v, but %v enabled at %v", return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at timestamp %v",
lastFork.name, cur.name, cur.timestamp)
}
// Fork (whether defined by block or timestamp) must follow the fork definition sequence
case (lastFork.block != nil && cur.block != nil) || (lastFork.timestamp != nil && cur.timestamp != nil):
if lastFork.block != nil && lastFork.block.Cmp(cur.block) > 0 {
return fmt.Errorf("unsupported fork ordering: %v enabled at block %v, but %v enabled at block %v",
lastFork.name, lastFork.block, cur.name, cur.block) lastFork.name, lastFork.block, cur.name, cur.block)
} else if lastFork.timestamp != nil && lastFork.timestamp.Cmp(cur.timestamp) > 0 {
return fmt.Errorf("unsupported fork ordering: %v enabled at timestamp %v, but %v enabled at timestamp %v",
lastFork.name, lastFork.timestamp, cur.name, cur.timestamp)
}
// Timestamp based forks can follow block based ones, but not the other way around
if lastFork.timestamp != nil && cur.block != nil {
return fmt.Errorf("unsupported fork ordering: %v used timestamp ordering, but %v reverted to block ordering",
lastFork.name, cur.name)
} }
} }
} }
// If it was optional and not set, then ignore it // If it was optional and not set, then ignore it
if !cur.optional || cur.block != nil { if !cur.optional || (cur.block != nil || cur.timestamp != nil) {
lastFork = cur lastFork = cur
} }
} }
return nil return nil
} }
func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *ConfigCompatError { func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, headTimestamp *big.Int) *ConfigCompatError {
if isForkIncompatible(c.HomesteadBlock, newcfg.HomesteadBlock, head) { if isForkBlockIncompatible(c.HomesteadBlock, newcfg.HomesteadBlock, headNumber) {
return newCompatError("Homestead fork block", c.HomesteadBlock, newcfg.HomesteadBlock) return newBlockCompatError("Homestead fork block", c.HomesteadBlock, newcfg.HomesteadBlock)
} }
if isForkIncompatible(c.DAOForkBlock, newcfg.DAOForkBlock, head) { if isForkBlockIncompatible(c.DAOForkBlock, newcfg.DAOForkBlock, headNumber) {
return newCompatError("DAO fork block", c.DAOForkBlock, newcfg.DAOForkBlock) return newBlockCompatError("DAO fork block", c.DAOForkBlock, newcfg.DAOForkBlock)
} }
if c.IsDAOFork(head) && c.DAOForkSupport != newcfg.DAOForkSupport { if c.IsDAOFork(headNumber) && c.DAOForkSupport != newcfg.DAOForkSupport {
return newCompatError("DAO fork support flag", c.DAOForkBlock, newcfg.DAOForkBlock) return newBlockCompatError("DAO fork support flag", c.DAOForkBlock, newcfg.DAOForkBlock)
} }
if isForkIncompatible(c.EIP150Block, newcfg.EIP150Block, head) { if isForkBlockIncompatible(c.EIP150Block, newcfg.EIP150Block, headNumber) {
return newCompatError("EIP150 fork block", c.EIP150Block, newcfg.EIP150Block) return newBlockCompatError("EIP150 fork block", c.EIP150Block, newcfg.EIP150Block)
} }
if isForkIncompatible(c.EIP155Block, newcfg.EIP155Block, head) { if isForkBlockIncompatible(c.EIP155Block, newcfg.EIP155Block, headNumber) {
return newCompatError("EIP155 fork block", c.EIP155Block, newcfg.EIP155Block) return newBlockCompatError("EIP155 fork block", c.EIP155Block, newcfg.EIP155Block)
} }
if isForkIncompatible(c.EIP158Block, newcfg.EIP158Block, head) { if isForkBlockIncompatible(c.EIP158Block, newcfg.EIP158Block, headNumber) {
return newCompatError("EIP158 fork block", c.EIP158Block, newcfg.EIP158Block) return newBlockCompatError("EIP158 fork block", c.EIP158Block, newcfg.EIP158Block)
} }
if c.IsEIP158(head) && !configNumEqual(c.ChainID, newcfg.ChainID) { if c.IsEIP158(headNumber) && !configBlockEqual(c.ChainID, newcfg.ChainID) {
return newCompatError("EIP158 chain ID", c.EIP158Block, newcfg.EIP158Block) return newBlockCompatError("EIP158 chain ID", c.EIP158Block, newcfg.EIP158Block)
} }
if isForkIncompatible(c.ByzantiumBlock, newcfg.ByzantiumBlock, head) { if isForkBlockIncompatible(c.ByzantiumBlock, newcfg.ByzantiumBlock, headNumber) {
return newCompatError("Byzantium fork block", c.ByzantiumBlock, newcfg.ByzantiumBlock) return newBlockCompatError("Byzantium fork block", c.ByzantiumBlock, newcfg.ByzantiumBlock)
} }
if isForkIncompatible(c.ConstantinopleBlock, newcfg.ConstantinopleBlock, head) { if isForkBlockIncompatible(c.ConstantinopleBlock, newcfg.ConstantinopleBlock, headNumber) {
return newCompatError("Constantinople fork block", c.ConstantinopleBlock, newcfg.ConstantinopleBlock) return newBlockCompatError("Constantinople fork block", c.ConstantinopleBlock, newcfg.ConstantinopleBlock)
} }
if isForkIncompatible(c.PetersburgBlock, newcfg.PetersburgBlock, head) { if isForkBlockIncompatible(c.PetersburgBlock, newcfg.PetersburgBlock, headNumber) {
// the only case where we allow Petersburg to be set in the past is if it is equal to Constantinople // the only case where we allow Petersburg to be set in the past is if it is equal to Constantinople
// mainly to satisfy fork ordering requirements which state that Petersburg fork be set if Constantinople fork is set // mainly to satisfy fork ordering requirements which state that Petersburg fork be set if Constantinople fork is set
if isForkIncompatible(c.ConstantinopleBlock, newcfg.PetersburgBlock, head) { if isForkBlockIncompatible(c.ConstantinopleBlock, newcfg.PetersburgBlock, headNumber) {
return newCompatError("Petersburg fork block", c.PetersburgBlock, newcfg.PetersburgBlock) return newBlockCompatError("Petersburg fork block", c.PetersburgBlock, newcfg.PetersburgBlock)
} }
} }
if isForkIncompatible(c.IstanbulBlock, newcfg.IstanbulBlock, head) { if isForkBlockIncompatible(c.IstanbulBlock, newcfg.IstanbulBlock, headNumber) {
return newCompatError("Istanbul fork block", c.IstanbulBlock, newcfg.IstanbulBlock) return newBlockCompatError("Istanbul fork block", c.IstanbulBlock, newcfg.IstanbulBlock)
} }
if isForkIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, head) { if isForkBlockIncompatible(c.MuirGlacierBlock, newcfg.MuirGlacierBlock, headNumber) {
return newCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock) return newBlockCompatError("Muir Glacier fork block", c.MuirGlacierBlock, newcfg.MuirGlacierBlock)
} }
if isForkIncompatible(c.BerlinBlock, newcfg.BerlinBlock, head) { if isForkBlockIncompatible(c.BerlinBlock, newcfg.BerlinBlock, headNumber) {
return newCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock) return newBlockCompatError("Berlin fork block", c.BerlinBlock, newcfg.BerlinBlock)
} }
if isForkIncompatible(c.LondonBlock, newcfg.LondonBlock, head) { if isForkBlockIncompatible(c.LondonBlock, newcfg.LondonBlock, headNumber) {
return newCompatError("London fork block", c.LondonBlock, newcfg.LondonBlock) return newBlockCompatError("London fork block", c.LondonBlock, newcfg.LondonBlock)
} }
if isForkIncompatible(c.ArrowGlacierBlock, newcfg.ArrowGlacierBlock, head) { if isForkBlockIncompatible(c.ArrowGlacierBlock, newcfg.ArrowGlacierBlock, headNumber) {
return newCompatError("Arrow Glacier fork block", c.ArrowGlacierBlock, newcfg.ArrowGlacierBlock) return newBlockCompatError("Arrow Glacier fork block", c.ArrowGlacierBlock, newcfg.ArrowGlacierBlock)
} }
if isForkIncompatible(c.GrayGlacierBlock, newcfg.GrayGlacierBlock, head) { if isForkBlockIncompatible(c.GrayGlacierBlock, newcfg.GrayGlacierBlock, headNumber) {
return newCompatError("Gray Glacier fork block", c.GrayGlacierBlock, newcfg.GrayGlacierBlock) return newBlockCompatError("Gray Glacier fork block", c.GrayGlacierBlock, newcfg.GrayGlacierBlock)
} }
if isForkIncompatible(c.MergeNetsplitBlock, newcfg.MergeNetsplitBlock, head) { if isForkBlockIncompatible(c.MergeNetsplitBlock, newcfg.MergeNetsplitBlock, headNumber) {
return newCompatError("Merge netsplit fork block", c.MergeNetsplitBlock, newcfg.MergeNetsplitBlock) return newBlockCompatError("Merge netsplit fork block", c.MergeNetsplitBlock, newcfg.MergeNetsplitBlock)
} }
/* if isForkBlockIncompatible(c.CancunBlock, newcfg.CancunBlock, headNumber) {
if isForkIncompatible(c.ShanghaiBlock, newcfg.ShanghaiBlock, head) { return newBlockCompatError("Cancun fork block", c.CancunBlock, newcfg.CancunBlock)
return newCompatError("Shanghai fork block", c.ShanghaiBlock, newcfg.ShanghaiBlock) }
} if isForkTimestampIncompatible(c.ShanghaiTime, newcfg.ShanghaiTime, headTimestamp) {
*/ return newTimestampCompatError("Shanghai fork timestamp", c.ShanghaiTime, newcfg.ShanghaiTime)
if isForkIncompatible(c.CancunBlock, newcfg.CancunBlock, head) {
return newCompatError("Cancun fork block", c.CancunBlock, newcfg.CancunBlock)
} }
return nil return nil
} }
@ -721,21 +744,49 @@ func (c *ChainConfig) ElasticityMultiplier() uint64 {
return DefaultElasticityMultiplier return DefaultElasticityMultiplier
} }
// isForkIncompatible returns true if a fork scheduled at s1 cannot be rescheduled to // isForkBlockIncompatible returns true if a fork scheduled at block s1 cannot be
// block s2 because head is already past the fork. // rescheduled to block s2 because head is already past the fork.
func isForkIncompatible(s1, s2, head *big.Int) bool { func isForkBlockIncompatible(s1, s2, head *big.Int) bool {
return (isForked(s1, head) || isForked(s2, head)) && !configNumEqual(s1, s2) return (isBlockForked(s1, head) || isBlockForked(s2, head)) && !configBlockEqual(s1, s2)
} }
// isForked returns whether a fork scheduled at block s is active at the given head block. // isBlockForked returns whether a fork scheduled at block s is active at the
func isForked(s, head *big.Int) bool { // given head block. Whilst this method is the same as isTimestampForked, they
// are explicitly separate for clearer reading.
func isBlockForked(s, head *big.Int) bool {
if s == nil || head == nil { if s == nil || head == nil {
return false return false
} }
return s.Cmp(head) <= 0 return s.Cmp(head) <= 0
} }
func configNumEqual(x, y *big.Int) bool { func configBlockEqual(x, y *big.Int) bool {
if x == nil {
return y == nil
}
if y == nil {
return x == nil
}
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, head *big.Int) 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, head *big.Int) bool {
if s == nil || head == nil {
return false
}
return s.Cmp(head) <= 0
}
func configTimestampEqual(x, y *big.Int) bool {
if x == nil { if x == nil {
return y == nil return y == nil
} }
@ -749,13 +800,21 @@ func configNumEqual(x, y *big.Int) bool {
// ChainConfig that would alter the past. // ChainConfig that would alter the past.
type ConfigCompatError struct { type ConfigCompatError struct {
What string What string
// block numbers of the stored and new configurations
StoredConfig, NewConfig *big.Int // block numbers of the stored and new configurations if block based forking
StoredBlock, NewBlock *big.Int
// timestamps of the stored and new configurations if time based forking
StoredTime, NewTime *big.Int
// the block number to which the local chain must be rewound to correct the error // 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 { func newBlockCompatError(what string, storedblock, newblock *big.Int) *ConfigCompatError {
var rew *big.Int var rew *big.Int
switch { switch {
case storedblock == nil: case storedblock == nil:
@ -765,15 +824,45 @@ func newCompatError(what string, storedblock, newblock *big.Int) *ConfigCompatEr
default: default:
rew = newblock rew = newblock
} }
err := &ConfigCompatError{what, storedblock, newblock, 0} err := &ConfigCompatError{
What: what,
StoredBlock: storedblock,
NewBlock: newblock,
RewindToBlock: 0,
}
if rew != nil && rew.Sign() > 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 *big.Int) *ConfigCompatError {
var rew *big.Int
switch {
case storedtime == nil:
rew = newtime
case newtime == nil || storedtime.Cmp(newtime) < 0:
rew = storedtime
default:
rew = newtime
}
err := &ConfigCompatError{
What: what,
StoredTime: storedtime,
NewTime: newtime,
RewindToTime: 0,
}
if rew != nil && rew.Sign() > 0 {
err.RewindToTime = rew.Uint64() - 1
} }
return err return err
} }
func (err *ConfigCompatError) Error() string { 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)
}
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 // Rules wraps ChainConfig and is merely syntactic sugar or can be used for functions

@ -20,79 +20,99 @@ import (
"math/big" "math/big"
"reflect" "reflect"
"testing" "testing"
"time"
) )
func TestCheckCompatible(t *testing.T) { func TestCheckCompatible(t *testing.T) {
type test struct { type test struct {
stored, new *ChainConfig stored, new *ChainConfig
head uint64 headBlock uint64
wantErr *ConfigCompatError headTimestamp uint64
wantErr *ConfigCompatError
} }
tests := []test{ tests := []test{
{stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, head: 0, wantErr: nil}, {stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, headBlock: 0, headTimestamp: 0, wantErr: nil},
{stored: AllEthashProtocolChanges, new: AllEthashProtocolChanges, head: 100, 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)}, stored: &ChainConfig{EIP150Block: big.NewInt(10)},
new: &ChainConfig{EIP150Block: big.NewInt(20)}, new: &ChainConfig{EIP150Block: big.NewInt(20)},
head: 9, headBlock: 9,
wantErr: nil, wantErr: nil,
}, },
{ {
stored: AllEthashProtocolChanges, stored: AllEthashProtocolChanges,
new: &ChainConfig{HomesteadBlock: nil}, new: &ChainConfig{HomesteadBlock: nil},
head: 3, headBlock: 3,
wantErr: &ConfigCompatError{ wantErr: &ConfigCompatError{
What: "Homestead fork block", What: "Homestead fork block",
StoredConfig: big.NewInt(0), StoredBlock: big.NewInt(0),
NewConfig: nil, NewBlock: nil,
RewindTo: 0, RewindToBlock: 0,
}, },
}, },
{ {
stored: AllEthashProtocolChanges, stored: AllEthashProtocolChanges,
new: &ChainConfig{HomesteadBlock: big.NewInt(1)}, new: &ChainConfig{HomesteadBlock: big.NewInt(1)},
head: 3, headBlock: 3,
wantErr: &ConfigCompatError{ wantErr: &ConfigCompatError{
What: "Homestead fork block", What: "Homestead fork block",
StoredConfig: big.NewInt(0), StoredBlock: big.NewInt(0),
NewConfig: big.NewInt(1), NewBlock: big.NewInt(1),
RewindTo: 0, RewindToBlock: 0,
}, },
}, },
{ {
stored: &ChainConfig{HomesteadBlock: big.NewInt(30), EIP150Block: big.NewInt(10)}, stored: &ChainConfig{HomesteadBlock: big.NewInt(30), EIP150Block: big.NewInt(10)},
new: &ChainConfig{HomesteadBlock: big.NewInt(25), EIP150Block: big.NewInt(20)}, new: &ChainConfig{HomesteadBlock: big.NewInt(25), EIP150Block: big.NewInt(20)},
head: 25, headBlock: 25,
wantErr: &ConfigCompatError{ wantErr: &ConfigCompatError{
What: "EIP150 fork block", What: "EIP150 fork block",
StoredConfig: big.NewInt(10), StoredBlock: big.NewInt(10),
NewConfig: big.NewInt(20), NewBlock: big.NewInt(20),
RewindTo: 9, RewindToBlock: 9,
}, },
}, },
{ {
stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)},
new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(30)}, new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(30)},
head: 40, headBlock: 40,
wantErr: nil, wantErr: nil,
}, },
{ {
stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)}, stored: &ChainConfig{ConstantinopleBlock: big.NewInt(30)},
new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(31)}, new: &ChainConfig{ConstantinopleBlock: big.NewInt(30), PetersburgBlock: big.NewInt(31)},
head: 40, headBlock: 40,
wantErr: &ConfigCompatError{ wantErr: &ConfigCompatError{
What: "Petersburg fork block", What: "Petersburg fork block",
StoredConfig: nil, StoredBlock: nil,
NewConfig: big.NewInt(31), NewBlock: big.NewInt(31),
RewindTo: 30, RewindToBlock: 30,
},
},
{
stored: &ChainConfig{ShanghaiTime: big.NewInt(10)},
new: &ChainConfig{ShanghaiTime: big.NewInt(20)},
headTimestamp: 9,
wantErr: nil,
},
{
stored: &ChainConfig{ShanghaiTime: big.NewInt(10)},
new: &ChainConfig{ShanghaiTime: big.NewInt(20)},
headTimestamp: 25,
wantErr: &ConfigCompatError{
What: "Shanghai fork timestamp",
StoredTime: big.NewInt(10),
NewTime: big.NewInt(20),
RewindToTime: 9,
}, },
}, },
} }
for _, test := range tests { 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) { 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)
} }
} }
} }