From 40db66c21c87080cd7ab1ab88946582f629c7407 Mon Sep 17 00:00:00 2001 From: Shane Bammel Date: Thu, 19 Jan 2023 19:24:08 -0600 Subject: [PATCH] Add support for syncing pre-fork blocks --- consensus/beacon/consensus.go | 97 ++++++++++++++++++++++--------- consensus/clique/clique.go | 8 ++- consensus/consensus.go | 2 +- consensus/ethash/consensus.go | 8 ++- core/blockchain.go | 2 +- core/blockchain_test.go | 4 +- core/forkchoice.go | 4 ++ core/types/transaction_signing.go | 13 ++++- core/vm/evm.go | 16 ++++- eth/handler.go | 2 +- params/config.go | 5 ++ 11 files changed, 119 insertions(+), 42 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index a350e383a..f20b900a2 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -80,18 +80,20 @@ func (beacon *Beacon) Author(header *types.Header) (common.Address, error) { // VerifyHeader checks whether a header conforms to the consensus rules of the // stock Ethereum consensus engine. -func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { +func (beacon *Beacon) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header) error { reached, err := IsTTDReached(chain, header.ParentHash, header.Number.Uint64()-1) if err != nil { return err } if !reached { - return beacon.ethone.VerifyHeader(chain, header) + return beacon.ethone.VerifyHeader(chain, header, parent) } // Short circuit if the parent is not known - parent := chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) if parent == nil { - return consensus.ErrUnknownAncestor + parent = chain.GetHeader(header.ParentHash, header.Number.Uint64()-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } } // Sanity checks passed, do a proper verification return beacon.verifyHeader(chain, header, parent) @@ -128,21 +130,16 @@ func (beacon *Beacon) splitHeaders(chain consensus.ChainHeaderReader, headers [] var ( preHeaders = headers postHeaders []*types.Header - td = new(big.Int).Set(ptd) - tdPassed bool ) for i, header := range headers { - if tdPassed { + // PulseChain Special Case: + // Partition based on header difficulty (IsPoSHeader) instead of TTD being reached + // so we can properly handle ETH Beacon blocks below the increased PulseChain TTD. + if beacon.IsPoSHeader(header) { preHeaders = headers[:i] postHeaders = headers[i:] break } - td = td.Add(td, header.Difficulty) - if td.Cmp(ttd) >= 0 { - // This is the last PoW header, it still belongs to - // the preHeaders, so we cannot split+break yet. - tdPassed = true - } } return preHeaders, postHeaders, nil } @@ -151,31 +148,73 @@ func (beacon *Beacon) splitHeaders(chain consensus.ChainHeaderReader, headers [] // concurrently. The method returns a quit channel to abort the operations and // a results channel to retrieve the async verifications. // VerifyHeaders expect the headers to be ordered and continuous. +// +// Normal Cases: +// 1. x * POW blocks +// 2. x * POS blocks +// 3. x * POW blocks => y * POS blocks +// +// Special Cases for PulseChain: +// 4. x * POS blocks[eth] => POW fork block[pls] +// 5. x * POS blocks[eth] => POW fork block[pls] => y * POS blocks[pls] func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { preHeaders, postHeaders, err := beacon.splitHeaders(chain, headers) if err != nil { return make(chan struct{}), errOut(len(headers), err) } + // Case 1 if len(postHeaders) == 0 { return beacon.ethone.VerifyHeaders(chain, headers) } - if len(preHeaders) == 0 { + chainCfg := chain.Config() + primordialPulseIndex := 0 + if chainCfg.PrimordialPulseAhead(postHeaders[0].Number) && !chainCfg.PrimordialPulseAhead(postHeaders[len(postHeaders)-1].Number) { + primordialPulseIndex = int(new(big.Int).Sub(chainCfg.PrimordialPulseBlock, postHeaders[0].Number).Uint64()) + } + // Case 2 + if len(preHeaders) == 0 && primordialPulseIndex == 0 { return beacon.verifyHeaders(chain, headers, nil) } // The transition point exists in the middle, separate the headers - // into two batches and apply different verification rules for them. + // into batches and apply different verification rules for them. var ( abort = make(chan struct{}) results = make(chan error, len(headers)) ) go func() { var ( - old, new, out = 0, len(preHeaders), 0 - errors = make([]error, len(headers)) - done = make([]bool, len(headers)) - oldDone, oldResult = beacon.ethone.VerifyHeaders(chain, preHeaders) - newDone, newResult = beacon.verifyHeaders(chain, postHeaders, preHeaders[len(preHeaders)-1]) + oldIdx, out = 0, 0 + errors = make([]error, len(headers)) + done = make([]bool, len(headers)) + oldDone, oldResult = beacon.ethone.VerifyHeaders(chain, preHeaders) + lastPowHeader *types.Header + pulseChainForkHeader *types.Header + preforkPosIdx = len(preHeaders) + preforkPosHeaders = postHeaders + postforkPosIdx = len(headers) + postforkPosHeaders = []*types.Header{} ) + // Case 3 + if len(preHeaders) > 0 { + lastPowHeader = preHeaders[len(preHeaders)-1] + } + // Handle fork partitioning and verification for cases 4 and 5 + if primordialPulseIndex > 0 { + preforkPosHeaders = postHeaders[:primordialPulseIndex] + pulseChainForkHeader = postHeaders[primordialPulseIndex] + + // Verify the fork block + forkBlockResult := beacon.ethone.VerifyHeader(chain, pulseChainForkHeader, postHeaders[primordialPulseIndex-1]) + forkBlockIdx := preforkPosIdx + len(preforkPosHeaders) + errors[forkBlockIdx], done[forkBlockIdx] = forkBlockResult, true + + // Can be empty in case 4 + postforkPosHeaders = postHeaders[primordialPulseIndex+1:] + postforkPosIdx = forkBlockIdx + 1 + } + preforkPosDone, preforkPosResult := beacon.verifyHeaders(chain, preforkPosHeaders, lastPowHeader) + postforkPosDone, postforkPosResult := beacon.verifyHeaders(chain, postforkPosHeaders, pulseChainForkHeader) + // Collect the results for { for ; done[out]; out++ { @@ -186,16 +225,20 @@ func (beacon *Beacon) VerifyHeaders(chain consensus.ChainHeaderReader, headers [ } select { case err := <-oldResult: - if !done[old] { // skip TTD-verified failures - errors[old], done[old] = err, true + if !done[oldIdx] { // skip TTD-verified failures + errors[oldIdx], done[oldIdx] = err, true } - old++ - case err := <-newResult: - errors[new], done[new] = err, true - new++ + oldIdx++ + case err := <-preforkPosResult: + errors[preforkPosIdx], done[preforkPosIdx] = err, true + preforkPosIdx++ + case err := <-postforkPosResult: + errors[postforkPosIdx], done[postforkPosIdx] = err, true + postforkPosIdx++ case <-abort: close(oldDone) - close(newDone) + close(preforkPosDone) + close(postforkPosDone) return } } diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 367d4e170..59ef10aa8 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -216,8 +216,12 @@ func (c *Clique) Author(header *types.Header) (common.Address, error) { } // VerifyHeader checks whether a header conforms to the consensus rules. -func (c *Clique) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { - return c.verifyHeader(chain, header, nil) +func (c *Clique) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header) error { + var parentHeaders []*types.Header + if parent != nil { + parentHeaders = []*types.Header{parent} + } + return c.verifyHeader(chain, header, parentHeaders) } // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The diff --git a/consensus/consensus.go b/consensus/consensus.go index 3a2c2d222..689c74bae 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -67,7 +67,7 @@ type Engine interface { // VerifyHeader checks whether a header conforms to the consensus rules of a // given engine. - VerifyHeader(chain ChainHeaderReader, header *types.Header) error + VerifyHeader(chain ChainHeaderReader, header *types.Header, parent *types.Header) error // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers // concurrently. The method returns a quit channel to abort the operations and diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 8a395ae74..feff39c34 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -100,15 +100,17 @@ func (ethash *Ethash) Author(header *types.Header) (common.Address, error) { // VerifyHeader checks whether a header conforms to the consensus rules of the // stock Ethereum ethash engine. -func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header) error { +func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header) error { // Short circuit if the header is known, or its parent not number := header.Number.Uint64() if chain.GetHeader(header.Hash(), number) != nil { return nil } - parent := chain.GetHeader(header.ParentHash, number-1) if parent == nil { - return consensus.ErrUnknownAncestor + parent = chain.GetHeader(header.ParentHash, number-1) + if parent == nil { + return consensus.ErrUnknownAncestor + } } // Sanity checks passed, do a proper verification return ethash.verifyHeader(chain, header, parent, false, time.Now().Unix()) diff --git a/core/blockchain.go b/core/blockchain.go index b1bbc3d59..94336a435 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -410,7 +410,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis // The first thing the node will do is reconstruct the verification data for // the head block (ethash cache or clique voting snapshot). Might as well do // it in advance. - bc.engine.VerifyHeader(bc, bc.CurrentHeader()) + bc.engine.VerifyHeader(bc, bc.CurrentHeader(), nil) // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain for hash := range BadHashes { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 876d662f7..d25db982b 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -149,7 +149,7 @@ func testFork(t *testing.T, blockchain *BlockChain, i, n int, full bool, compara func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { for _, block := range chain { // Try and process the block - err := blockchain.engine.VerifyHeader(blockchain, block.Header()) + err := blockchain.engine.VerifyHeader(blockchain, block.Header(), nil) if err == nil { err = blockchain.validator.ValidateBody(block) } @@ -188,7 +188,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { func testHeaderChainImport(chain []*types.Header, blockchain *BlockChain) error { for _, header := range chain { // Try and validate the header - if err := blockchain.engine.VerifyHeader(blockchain, header); err != nil { + if err := blockchain.engine.VerifyHeader(blockchain, header, nil); err != nil { return err } // Manually insert the header into the database, but don't reorganise (allows subsequent testing) diff --git a/core/forkchoice.go b/core/forkchoice.go index b293c851b..dfc13b88b 100644 --- a/core/forkchoice.go +++ b/core/forkchoice.go @@ -108,6 +108,10 @@ func (f *ForkChoice) ReorgNeeded(current *types.Header, extern *types.Header) (b currentPreserve, externPreserve = f.preserve(current), f.preserve(extern) } reorg = !currentPreserve && (externPreserve || f.rand.Float64() < 0.5) + } else if f.chain.Config().PrimordialPulseAhead(extern.Number) { + // Pre-fork ethereum mainnet pos blocks have a difficulty of 0. + // These blocks should be accepted onto the chain head. + reorg = true } return reorg, nil } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 9e26642f7..fe72f9b5e 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -39,15 +39,22 @@ type sigCache struct { // MakeSigner returns a Signer based on the given chain config and block number. func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint64) Signer { var signer Signer + + chainID := config.ChainID + if config.PrimordialPulseAhead(blockNumber) { + // Use ethereum mainnet chainid for pre-fork transactions + chainID = big.NewInt(1) + } + switch { case config.IsCancun(blockNumber, blockTime): signer = NewCancunSigner(config.ChainID) case config.IsLondon(blockNumber): - signer = NewLondonSigner(config.ChainID) + signer = NewLondonSigner(chainID) case config.IsBerlin(blockNumber): - signer = NewEIP2930Signer(config.ChainID) + signer = NewEIP2930Signer(chainID) case config.IsEIP155(blockNumber): - signer = NewEIP155Signer(config.ChainID) + signer = NewEIP155Signer(chainID) case config.IsHomestead(blockNumber): signer = HomesteadSigner{} default: diff --git a/core/vm/evm.go b/core/vm/evm.go index 16cc85490..6ba050ee6 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -137,13 +137,25 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig blockCtx.BlobBaseFee = new(big.Int) } } + + // FlexChainConfig allows for processing prefork transactions on PulseChain + flexChainConfig := chainConfig + if chainConfig.PrimordialPulseAhead(blockCtx.BlockNumber) { + // Create a shallow of chainConfig struct and set to ethereum mainnet + chainCfgCpy := *chainConfig + chainCfgCpy.ChainID = big.NewInt(1) + + // Use the new chainCfgCpy + flexChainConfig = &chainCfgCpy + } + evm := &EVM{ Context: blockCtx, TxContext: txCtx, StateDB: statedb, Config: config, - chainConfig: chainConfig, - chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), + chainConfig: flexChainConfig, + chainRules: flexChainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } evm.interpreter = NewEVMInterpreter(evm) return evm diff --git a/eth/handler.go b/eth/handler.go index 0343a5787..cd64c8edb 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -215,7 +215,7 @@ func newHandler(config *handlerConfig) (*handler, error) { return errors.New("unexpected post-merge header") } } - return h.chain.Engine().VerifyHeader(h.chain, header) + return h.chain.Engine().VerifyHeader(h.chain, header, nil) } heighter := func() uint64 { return h.chain.CurrentBlock().Number.Uint64() diff --git a/params/config.go b/params/config.go index f2a82b193..e89fb17a1 100644 --- a/params/config.go +++ b/params/config.go @@ -591,6 +591,11 @@ func (c *ChainConfig) IsPrimordialPulseBlock(num *big.Int) bool { return c.PrimordialPulseBlock != nil && c.PrimordialPulseBlock.Cmp(num) == 0 } +// Returns true if there is a PrimordialPulse block in the future. +func (c *ChainConfig) PrimordialPulseAhead(num *big.Int) bool { + return c.PrimordialPulseBlock != nil && c.PrimordialPulseBlock.Cmp(num) > 0 +} + // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError {