Quellcode durchsuchen

enforce backoff time for out-turn validator (#23)

zjubfd vor 5 Jahren
Ursprung
Commit
a8c9e53eb0

+ 6 - 0
cmd/geth/retesteth.go

@@ -133,6 +133,7 @@ type CParamsParams struct {
 	ConstantinopleForkBlock    *math.HexOrDecimal64  `json:"constantinopleForkBlock"`
 	ConstantinopleFixForkBlock *math.HexOrDecimal64  `json:"constantinopleFixForkBlock"`
 	IstanbulBlock              *math.HexOrDecimal64  `json:"istanbulForkBlock"`
+	RamanujanForkBlock         *math.HexOrDecimal64  `json:"ramanujanForkBlock"`
 	ChainID                    *math.HexOrDecimal256 `json:"chainID"`
 	MaximumExtraDataSize       math.HexOrDecimal64   `json:"maximumExtraDataSize"`
 	TieBreakingGas             bool                  `json:"tieBreakingGas"`
@@ -322,6 +323,7 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa
 		constantinopleBlock *big.Int
 		petersburgBlock     *big.Int
 		istanbulBlock       *big.Int
+		ramanujanBlock      *big.Int
 	)
 	if chainParams.Params.HomesteadForkBlock != nil {
 		homesteadBlock = big.NewInt(int64(*chainParams.Params.HomesteadForkBlock))
@@ -351,6 +353,9 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa
 	if chainParams.Params.IstanbulBlock != nil {
 		istanbulBlock = big.NewInt(int64(*chainParams.Params.IstanbulBlock))
 	}
+	if chainParams.Params.RamanujanForkBlock != nil {
+		ramanujanBlock = big.NewInt(int64(*chainParams.Params.RamanujanForkBlock))
+	}
 
 	genesis := &core.Genesis{
 		Config: &params.ChainConfig{
@@ -365,6 +370,7 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa
 			ConstantinopleBlock: constantinopleBlock,
 			PetersburgBlock:     petersburgBlock,
 			IstanbulBlock:       istanbulBlock,
+			RamanujanBlock:      ramanujanBlock,
 		},
 		Nonce:      uint64(chainParams.Genesis.Nonce),
 		Timestamp:  uint64(chainParams.Genesis.Timestamp),

+ 37 - 16
consensus/parlia/parlia.go

@@ -48,8 +48,8 @@ const (
 	extraSeal   = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
 
 	validatorBytesLength = common.AddressLength
-	wiggleTime           = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers
-	fixedBackOffTime     = 200 * time.Millisecond
+	wiggleTime           = uint64(1) // second, Random delay (per signer) to allow concurrent signers
+	initialBackOffTime   = uint64(1) // second
 
 	systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system
 
@@ -81,7 +81,7 @@ var (
 		common.HexToAddress(GovHubContract):             true,
 		common.HexToAddress(TokenHubContract):           true,
 		common.HexToAddress(RelayerIncentivizeContract): true,
-		common.HexToAddress(CrossChainContract): true,
+		common.HexToAddress(CrossChainContract):         true,
 	}
 )
 
@@ -395,6 +395,16 @@ func (p *Parlia) verifyCascadingFields(chain consensus.ChainReader, header *type
 		return consensus.ErrUnknownAncestor
 	}
 
+	snap, err := p.snapshot(chain, number-1, header.ParentHash, parents)
+	if err != nil {
+		return err
+	}
+
+	err = p.blockTimeVerifyForRamanujanFork(snap, header, parent)
+	if err != nil {
+		return nil
+	}
+
 	// Verify that the gas limit is <= 2^63-1
 	capacity := uint64(0x7fffffffffffffff)
 	if header.GasLimit > capacity {
@@ -570,7 +580,7 @@ func (p *Parlia) verifySeal(chain consensus.ChainReader, header *types.Header, p
 
 	// Ensure that the difficulty corresponds to the turn-ness of the signer
 	if !p.fakeDiff {
-		inturn := snap.inturn(header.Number.Uint64(), signer)
+		inturn := snap.inturn(signer)
 		if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
 			return errWrongDifficulty
 		}
@@ -626,8 +636,7 @@ func (p *Parlia) Prepare(chain consensus.ChainReader, header *types.Header) erro
 	if parent == nil {
 		return consensus.ErrUnknownAncestor
 	}
-
-	header.Time = parent.Time + p.config.Period
+	header.Time = p.blockTimeForRamanujanFork(snap, header, parent)
 	if header.Time < uint64(time.Now().Unix()) {
 		header.Time = uint64(time.Now().Unix())
 	}
@@ -809,14 +818,7 @@ func (p *Parlia) Seal(chain consensus.ChainReader, block *types.Block, results c
 	}
 
 	// Sweet, the protocol permits us to sign the block, wait for our time
-	delay := time.Until(time.Unix(int64(header.Time), 0)) // nolint: gosimple
-	if header.Difficulty.Cmp(diffNoTurn) == 0 {
-		// It's not our turn explicitly to sign, delay it a bit
-		wiggle := time.Duration(len(snap.Validators)/2+1) * wiggleTime
-		delay += time.Duration(fixedBackOffTime) + time.Duration(rand.Int63n(int64(wiggle)))
-
-		log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
-	}
+	delay := p.delayForRamanujanFork(snap, header)
 
 	log.Info("Sealing block with", "number", number, "delay", delay, "headerDifficulty", header.Difficulty, "val", val.Hex())
 
@@ -861,7 +863,7 @@ func (p *Parlia) CalcDifficulty(chain consensus.ChainReader, time uint64, parent
 // that a new block should have based on the previous blocks in the chain and the
 // current signer.
 func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
-	if snap.inturn(snap.Number+1, signer) {
+	if snap.inturn(signer) {
 		return new(big.Int).Set(diffInTurn)
 	}
 	return new(big.Int).Set(diffNoTurn)
@@ -1140,6 +1142,26 @@ func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) {
 	}
 }
 
+func backOffTime(snap *Snapshot, val common.Address) uint64 {
+	if snap.inturn(val) {
+		return 0
+	} else {
+		dis := snap.distanceToInTurn(val)
+		s := rand.NewSource(int64(snap.Number))
+		r := rand.New(s)
+		n := len(snap.Validators)
+		backOffSteps := make([]uint64, 0, n)
+		for idx := uint64(0); idx < uint64(n); idx++ {
+			backOffSteps = append(backOffSteps, idx)
+		}
+		r.Shuffle(n, func(i, j int) {
+			backOffSteps[i], backOffSteps[j] = backOffSteps[j], backOffSteps[i]
+		})
+		delay := initialBackOffTime + backOffSteps[dis]*wiggleTime
+		return delay
+	}
+}
+
 // chain context
 type chainContext struct {
 	Chain  consensus.ChainReader
@@ -1194,4 +1216,3 @@ func applyMessage(
 	}
 	return msg.Gas() - returnGas, err
 }
-

+ 25 - 16
consensus/parlia/parlia_test.go

@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"math/rand"
 	"testing"
-	"time"
 
 	"github.com/ethereum/go-ethereum/common"
 )
@@ -57,7 +56,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
 		return validators[idx]
 	}
 
-	downDelay := time.Duration(0)
+	downDelay := uint64(0)
 	for h := 1; h <= downBlocks; h++ {
 		if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit {
 			delete(recents, uint64(h)-limit)
@@ -73,7 +72,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
 			if len(candidates) == 0 {
 				panic("can not test such case")
 			}
-			idx, delay := producerBlockDelay(candidates, totalValidators)
+			idx, delay := producerBlockDelay(candidates, h, totalValidators)
 			downDelay = downDelay + delay
 			recents[uint64(h)] = idx
 		} else {
@@ -81,13 +80,13 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
 		}
 	}
 	fmt.Printf("average delay is %v  when there is %d validators and %d is down \n",
-		downDelay/time.Duration(downBlocks), totalValidators, downValidators)
+		downDelay/uint64(downBlocks), totalValidators, downValidators)
 
 	for i := 0; i < downValidators; i++ {
 		validators[down[i]] = true
 	}
 
-	recoverDelay := time.Duration(0)
+	recoverDelay := uint64(0)
 	lastseen := downBlocks
 	for h := downBlocks + 1; h <= downBlocks+recoverBlocks; h++ {
 		if limit := uint64(totalValidators/2 + 1); uint64(h) >= limit {
@@ -105,7 +104,7 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
 			if len(candidates) == 0 {
 				panic("can not test such case")
 			}
-			idx, delay := producerBlockDelay(candidates, totalValidators)
+			idx, delay := producerBlockDelay(candidates, h, totalValidators)
 			recoverDelay = recoverDelay + delay
 			recents[uint64(h)] = idx
 		} else {
@@ -116,18 +115,28 @@ func simulateValidatorOutOfService(totalValidators int, downValidators int) {
 		recoverDelay, downValidators, lastseen)
 }
 
-func producerBlockDelay(candidates map[int]bool, numOfValidators int) (int, time.Duration) {
-	minDur := time.Duration(0)
-	minIdx := 0
-	wiggle := time.Duration(numOfValidators/2+1) * wiggleTime
-	for idx := range candidates {
-		sleepTime := rand.Int63n(int64(wiggle))
-		if int64(minDur) < sleepTime {
-			minDur = time.Duration(rand.Int63n(int64(wiggle)))
-			minIdx = idx
+func producerBlockDelay(candidates map[int]bool, height, numOfValidators int) (int, uint64) {
+
+	s := rand.NewSource(int64(height))
+	r := rand.New(s)
+	n := numOfValidators
+	backOffSteps := make([]int, 0, n)
+	for idx := 0; idx < n; idx++ {
+		backOffSteps = append(backOffSteps, idx)
+	}
+	r.Shuffle(n, func(i, j int) {
+		backOffSteps[i], backOffSteps[j] = backOffSteps[j], backOffSteps[i]
+	})
+	minDelay := numOfValidators
+	minCandidate := 0
+	for c := range candidates {
+		if minDelay > backOffSteps[c] {
+			minDelay = backOffSteps[c]
+			minCandidate = c
 		}
 	}
-	return minIdx, minDur
+	delay := initialBackOffTime + uint64(minDelay)*wiggleTime
+	return minCandidate, delay
 }
 
 func randomAddress() common.Address {

+ 40 - 0
consensus/parlia/ramanujanfork.go

@@ -0,0 +1,40 @@
+package parlia
+
+import (
+	"math/rand"
+	"time"
+
+	"github.com/ethereum/go-ethereum/consensus"
+	"github.com/ethereum/go-ethereum/core/types"
+)
+
+const (
+	wiggleTimeBeforeFork       = 500 * time.Millisecond // Random delay (per signer) to allow concurrent signers
+	fixedBackOffTimeBeforeFork = 200 * time.Millisecond
+)
+
+func (p *Parlia) delayForRamanujanFork(snap *Snapshot, header *types.Header) time.Duration {
+	delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple
+	if p.chainConfig.IsRamanujan(header.Number) {
+		return delay
+	}
+	wiggle := time.Duration(len(snap.Validators)/2+1) * wiggleTimeBeforeFork
+	return delay + time.Duration(fixedBackOffTimeBeforeFork) + time.Duration(rand.Int63n(int64(wiggle)))
+}
+
+func (p *Parlia) blockTimeForRamanujanFork(snap *Snapshot, header, parent *types.Header) uint64 {
+	blockTime := parent.Time + p.config.Period
+	if p.chainConfig.IsRamanujan(header.Number) {
+		blockTime = blockTime + backOffTime(snap, p.val)
+	}
+	return blockTime
+}
+
+func (p *Parlia) blockTimeVerifyForRamanujanFork(snap *Snapshot, header, parent *types.Header) error {
+	if p.chainConfig.IsRamanujan(header.Number) {
+		if header.Time < parent.Time+p.config.Period+backOffTime(snap, header.Coinbase) {
+			return consensus.ErrFutureBlock
+		}
+	}
+	return nil
+}

+ 16 - 2
consensus/parlia/snapshot.go

@@ -210,12 +210,26 @@ func (s *Snapshot) validators() []common.Address {
 }
 
 // inturn returns if a validator at a given block height is in-turn or not.
-func (s *Snapshot) inturn(number uint64, validator common.Address) bool {
+func (s *Snapshot) inturn(validator common.Address) bool {
 	validators := s.validators()
-	offset := number % uint64(len(validators))
+	offset := (s.Number + 1) % uint64(len(validators))
 	return validators[offset] == validator
 }
 
+func (s *Snapshot) distanceToInTurn(validator common.Address) uint64 {
+	validators := s.validators()
+	offset := (s.Number + 1) % uint64(len(validators))
+	idx := uint64(0)
+	for idx < uint64(len(validator)) && validators[idx] != validator {
+		idx++
+	}
+	if offset > idx {
+		return uint64(len(validators)) + idx - offset
+	} else {
+		return idx - offset
+	}
+}
+
 func (s *Snapshot) supposeValidator() common.Address {
 	validators := s.validators()
 	index := (s.Number + 1) % uint64(len(validators))

+ 7 - 1
core/genesis.go

@@ -222,7 +222,9 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override
 	// Special case: don't change the existing config of a non-mainnet chain if no new
 	// config is supplied. These chains would get AllProtocolChanges (and a compat error)
 	// if we just continued here.
-	if genesis == nil && stored != params.MainnetGenesisHash {
+	// The full node of two BSC testnets may run without genesis file after been inited.
+	if genesis == nil && stored != params.MainnetGenesisHash &&
+		stored != params.ChapelGenesisHash && stored != params.RialtoGenesisHash {
 		return storedcfg, stored, nil
 	}
 
@@ -252,6 +254,10 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
 		return params.RinkebyChainConfig
 	case ghash == params.GoerliGenesisHash:
 		return params.GoerliChainConfig
+	case ghash == params.ChapelGenesisHash:
+		return params.ChapelChainConfig
+	case ghash == params.RialtoGenesisHash:
+		return params.RialtoChainConfig
 	default:
 		return params.AllEthashProtocolChanges
 	}

+ 3 - 2
eth/fetcher/block_fetcher.go

@@ -681,11 +681,12 @@ func (f *BlockFetcher) insert(peer string, block *types.Block) {
 			go f.broadcastBlock(block, true)
 
 		case consensus.ErrFutureBlock:
-			// Weird future block, don't fail, but neither propagate
+			log.Error("Received future block", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
+			f.dropPeer(peer)
 
 		default:
 			// Something went very wrong, drop the peer
-			log.Debug("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
+			log.Error("Propagated block verification failed", "peer", peer, "number", block.Number(), "hash", hash, "err", err)
 			f.dropPeer(peer)
 			return
 		}

+ 54 - 4
params/config.go

@@ -31,6 +31,9 @@ var (
 	RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
 	RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
 	GoerliGenesisHash  = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a")
+
+	ChapelGenesisHash = common.HexToHash("0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34")
+	RialtoGenesisHash = common.HexToHash("0xaa1c1e0af675e846942719466ab72822eff51ebf8462ead0897ae1240e3c0da1")
 )
 
 // TrustedCheckpoints associates each known checkpoint with the genesis hash of
@@ -210,21 +213,57 @@ var (
 		Threshold: 2,
 	}
 
+	ChapelChainConfig = &ChainConfig{
+		ChainID:             big.NewInt(97),
+		HomesteadBlock:      big.NewInt(0),
+		EIP150Block:         big.NewInt(0),
+		EIP155Block:         big.NewInt(0),
+		EIP158Block:         big.NewInt(0),
+		ByzantiumBlock:      big.NewInt(0),
+		ConstantinopleBlock: big.NewInt(0),
+		PetersburgBlock:     big.NewInt(0),
+		IstanbulBlock:       big.NewInt(0),
+		MuirGlacierBlock:    big.NewInt(0),
+		RamanujanBlock:      big.NewInt(1066095),
+		Parlia: &ParliaConfig{
+			Period: 3,
+			Epoch:  200,
+		},
+	}
+
+	RialtoChainConfig = &ChainConfig{
+		ChainID:             big.NewInt(1417),
+		HomesteadBlock:      big.NewInt(0),
+		EIP150Block:         big.NewInt(0),
+		EIP155Block:         big.NewInt(0),
+		EIP158Block:         big.NewInt(0),
+		ByzantiumBlock:      big.NewInt(0),
+		ConstantinopleBlock: big.NewInt(0),
+		PetersburgBlock:     big.NewInt(0),
+		IstanbulBlock:       big.NewInt(0),
+		MuirGlacierBlock:    big.NewInt(0),
+		RamanujanBlock:      big.NewInt(200987),
+		Parlia: &ParliaConfig{
+			Period: 3,
+			Epoch:  200,
+		},
+	}
+
 	// AllEthashProtocolChanges contains every protocol change (EIPs) introduced
 	// and accepted by the Ethereum core developers into the Ethash consensus.
 	//
 	// This configuration is intentionally not using keyed fields to force anyone
 	// adding flags to the config to also have to set these fields.
-	AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil, nil}
+	AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil}
 
 	// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced
 	// and accepted by the Ethereum core developers into the Clique consensus.
 	//
 	// This configuration is intentionally not using keyed fields to force anyone
 	// adding flags to the config to also have to set these fields.
-	AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil}
+	AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil}
 
-	TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, new(EthashConfig), nil, nil}
+	TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil, nil}
 	TestRules       = TestChainConfig.Rules(new(big.Int))
 )
 
@@ -296,6 +335,7 @@ type ChainConfig struct {
 	IstanbulBlock       *big.Int `json:"istanbulBlock,omitempty" toml:",omitempty"`       // Istanbul switch block (nil = no fork, 0 = already on istanbul)
 	MuirGlacierBlock    *big.Int `json:"muirGlacierBlock,omitempty" toml:",omitempty"`    // Eip-2384 (bomb delay) switch block (nil = no fork, 0 = already activated)
 	EWASMBlock          *big.Int `json:"ewasmBlock,omitempty" toml:",omitempty"`          // EWASM switch block (nil = no fork, 0 = already activated)
+	RamanujanBlock      *big.Int `json:"ramanujanBlock,omitempty" toml:",omitempty"`      // ramanujanBlock switch block (nil = no fork, 0 = already activated)
 
 	// Various consensus engines
 	Ethash *EthashConfig `json:"ethash,omitempty" toml:",omitempty"`
@@ -346,7 +386,7 @@ func (c *ChainConfig) String() string {
 	default:
 		engine = "unknown"
 	}
-	return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Engine: %v}",
+	return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Ramanujan: %v, Engine: %v}",
 		c.ChainID,
 		c.HomesteadBlock,
 		c.DAOForkBlock,
@@ -359,6 +399,7 @@ func (c *ChainConfig) String() string {
 		c.PetersburgBlock,
 		c.IstanbulBlock,
 		c.MuirGlacierBlock,
+		c.RamanujanBlock,
 		engine,
 	)
 }
@@ -398,6 +439,11 @@ func (c *ChainConfig) IsConstantinople(num *big.Int) bool {
 	return isForked(c.ConstantinopleBlock, num)
 }
 
+// IsRamanujan returns whether num is either equal to the IsRamanujan fork block or greater.
+func (c *ChainConfig) IsRamanujan(num *big.Int) bool {
+	return isForked(c.RamanujanBlock, num)
+}
+
 // 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 {
 	return isForked(c.MuirGlacierBlock, num)
@@ -456,6 +502,7 @@ func (c *ChainConfig) CheckConfigForkOrder() error {
 		{"petersburgBlock", c.PetersburgBlock},
 		{"istanbulBlock", c.IstanbulBlock},
 		{"muirGlacierBlock", c.MuirGlacierBlock},
+		{"ramanujanBlock", c.RamanujanBlock},
 	} {
 		if lastFork.name != "" {
 			// Next one must be higher number
@@ -515,6 +562,9 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, head *big.Int) *Confi
 	if isForkIncompatible(c.EWASMBlock, newcfg.EWASMBlock, head) {
 		return newCompatError("ewasm fork block", c.EWASMBlock, newcfg.EWASMBlock)
 	}
+	if isForkIncompatible(c.RamanujanBlock, newcfg.RamanujanBlock, head) {
+		return newCompatError("ramanujan fork block", c.RamanujanBlock, newcfg.RamanujanBlock)
+	}
 	return nil
 }