From ad6cced99df72e5863171193fd0c09bc8f78abb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 16 May 2025 09:27:46 +0200 Subject: [PATCH] fix: provide parent header during EuclidV2 transition verification (#1187) * fix: provide parent header during EuclidV2 transition verification * add test --- consensus/clique/clique.go | 2 +- consensus/consensus.go | 2 +- consensus/ethash/consensus.go | 2 +- consensus/system_contract/consensus.go | 9 +- consensus/system_contract/system_contract.go | 53 ++++++++ .../system_contract/system_contract_test.go | 64 +--------- consensus/wrapper/consensus.go | 43 ++----- core/block_validator_test.go | 10 +- core/blockchain.go | 2 +- core/headerchain.go | 2 +- miner/scroll_worker_test.go | 116 +++++++++++++++++- params/version.go | 2 +- 12 files changed, 199 insertions(+), 108 deletions(-) diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 68fadec813..8a4c8bda4a 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -226,7 +226,7 @@ func (c *Clique) VerifyHeader(chain consensus.ChainHeaderReader, header *types.H // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The // method returns a quit channel to abort the operations and a results channel to // retrieve the async verifications (the order is that of the input slice). -func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (c *Clique) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) { abort := make(chan struct{}) results := make(chan error, len(headers)) diff --git a/consensus/consensus.go b/consensus/consensus.go index 6416460684..5ed80dff4d 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -71,7 +71,7 @@ type Engine interface { // concurrently. The method returns a quit channel to abort the operations and // a results channel to retrieve the async verifications (the order is that of // the input slice). - VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) + VerifyHeaders(chain ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) // VerifyUncles verifies that the given block's uncles conform to the consensus // rules of a given engine. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index a24ec37f67..d90cf613f3 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -119,7 +119,7 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *ty // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers // concurrently. The method returns a quit channel to abort the operations and // a results channel to retrieve the async verifications. -func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (ethash *Ethash) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) { // If we're running a full engine faking, accept any input as valid if ethash.config.PowMode == ModeFullFake || len(headers) == 0 { abort, results := make(chan struct{}), make(chan error, len(headers)) diff --git a/consensus/system_contract/consensus.go b/consensus/system_contract/consensus.go index c01bec805a..01d7be1850 100644 --- a/consensus/system_contract/consensus.go +++ b/consensus/system_contract/consensus.go @@ -86,13 +86,18 @@ func (s *SystemContract) VerifyHeader(chain consensus.ChainHeaderReader, header // concurrently. The method returns a quit channel to abort the operations and // a results channel to retrieve the async verifications (the order is that of // the input slice). -func (s *SystemContract) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (s *SystemContract) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) { abort := make(chan struct{}) results := make(chan error, len(headers)) go func() { for i, header := range headers { - err := s.verifyHeader(chain, header, headers[:i]) + parents := headers[:i] + if len(parents) == 0 && parent != nil { + parents = []*types.Header{parent} + } + + err := s.verifyHeader(chain, header, parents) if err != nil { log.Error("Error verifying headers", "err", err) } diff --git a/consensus/system_contract/system_contract.go b/consensus/system_contract/system_contract.go index a93968e873..aef24a00ba 100644 --- a/consensus/system_contract/system_contract.go +++ b/consensus/system_contract/system_contract.go @@ -3,10 +3,13 @@ package system_contract import ( "context" "fmt" + "math/big" "sync" "time" + "github.com/scroll-tech/go-ethereum" "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/sync_service" @@ -140,3 +143,53 @@ func (s *SystemContract) localSignerAddress() common.Address { return s.signer } + +// FakeEthClient implements a minimal version of sync_service.EthClient for testing purposes. +type FakeEthClient struct { + mu sync.Mutex + // Value is the fixed Value to return from StorageAt. + // We'll assume StorageAt returns a 32-byte Value representing an Ethereum address. + Value common.Address +} + +// BlockNumber returns 0. +func (f *FakeEthClient) BlockNumber(ctx context.Context) (uint64, error) { + return 0, nil +} + +// ChainID returns a zero-value chain ID. +func (f *FakeEthClient) ChainID(ctx context.Context) (*big.Int, error) { + return big.NewInt(0), nil +} + +// FilterLogs returns an empty slice of logs. +func (f *FakeEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { + return []types.Log{}, nil +} + +// HeaderByNumber returns nil. +func (f *FakeEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return nil, nil +} + +// SubscribeFilterLogs returns a nil subscription. +func (f *FakeEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + return nil, nil +} + +// TransactionByHash returns (nil, false, nil). +func (f *FakeEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return nil, false, nil +} + +// BlockByHash returns nil. +func (f *FakeEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return nil, nil +} + +// StorageAt returns the byte representation of f.value. +func (f *FakeEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + f.mu.Lock() + defer f.mu.Unlock() + return f.Value.Bytes(), nil +} diff --git a/consensus/system_contract/system_contract_test.go b/consensus/system_contract/system_contract_test.go index ddd6a0fc8d..fa344f5c9e 100644 --- a/consensus/system_contract/system_contract_test.go +++ b/consensus/system_contract/system_contract_test.go @@ -3,13 +3,11 @@ package system_contract import ( "context" "math/big" - "sync" "testing" "time" "github.com/stretchr/testify/require" - "github.com/scroll-tech/go-ethereum" "github.com/scroll-tech/go-ethereum/accounts" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" @@ -19,14 +17,14 @@ import ( "github.com/scroll-tech/go-ethereum/trie" ) -var _ sync_service.EthClient = &fakeEthClient{} +var _ sync_service.EthClient = &FakeEthClient{} func TestSystemContract_FetchSigner(t *testing.T) { log.Root().SetHandler(log.DiscardHandler()) expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") - fakeClient := &fakeEthClient{value: expectedSigner} + fakeClient := &FakeEthClient{Value: expectedSigner} config := ¶ms.SystemContractConfig{ SystemContractAddress: common.HexToAddress("0xFAKE"), @@ -55,7 +53,7 @@ func TestSystemContract_AuthorizeCheck(t *testing.T) { expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") - fakeClient := &fakeEthClient{value: expectedSigner} + fakeClient := &FakeEthClient{Value: expectedSigner} config := ¶ms.SystemContractConfig{ SystemContractAddress: common.HexToAddress("0xFAKE"), Period: 10, @@ -106,8 +104,8 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { updatedSigner := common.HexToAddress("0x2222222222222222222222222222222222222222") // Create a fake client that starts by returning the wrong signer. - fakeClient := &fakeEthClient{ - value: oldSigner, + fakeClient := &FakeEthClient{ + Value: oldSigner, } config := ¶ms.SystemContractConfig{ @@ -130,7 +128,7 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { // Now, simulate an update: change the fake client's returned value to updatedSigner. fakeClient.mu.Lock() - fakeClient.value = updatedSigner + fakeClient.Value = updatedSigner fakeClient.mu.Unlock() // fetch new value from L1 (simulating a background poll) @@ -171,53 +169,3 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) { t.Fatal("Timed out waiting for Seal to return a sealed block") } } - -// fakeEthClient implements a minimal version of sync_service.EthClient for testing purposes. -type fakeEthClient struct { - mu sync.Mutex - // value is the fixed value to return from StorageAt. - // We'll assume StorageAt returns a 32-byte value representing an Ethereum address. - value common.Address -} - -// BlockNumber returns 0. -func (f *fakeEthClient) BlockNumber(ctx context.Context) (uint64, error) { - return 0, nil -} - -// ChainID returns a zero-value chain ID. -func (f *fakeEthClient) ChainID(ctx context.Context) (*big.Int, error) { - return big.NewInt(0), nil -} - -// FilterLogs returns an empty slice of logs. -func (f *fakeEthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) { - return []types.Log{}, nil -} - -// HeaderByNumber returns nil. -func (f *fakeEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { - return nil, nil -} - -// SubscribeFilterLogs returns a nil subscription. -func (f *fakeEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { - return nil, nil -} - -// TransactionByHash returns (nil, false, nil). -func (f *fakeEthClient) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { - return nil, false, nil -} - -// BlockByHash returns nil. -func (f *fakeEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - return nil, nil -} - -// StorageAt returns the byte representation of f.value. -func (f *fakeEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { - f.mu.Lock() - defer f.mu.Unlock() - return f.value.Bytes(), nil -} diff --git a/consensus/wrapper/consensus.go b/consensus/wrapper/consensus.go index bfabd0ffcc..16ea46f0d8 100644 --- a/consensus/wrapper/consensus.go +++ b/consensus/wrapper/consensus.go @@ -2,7 +2,6 @@ package wrapper import ( "math/big" - "time" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus" @@ -59,34 +58,9 @@ func (ue *UpgradableEngine) VerifyHeader(chain consensus.ChainHeaderReader, head return ue.chooseEngine(header.Time).VerifyHeader(chain, header, seal) } -func waitForHeader(chain consensus.ChainHeaderReader, header *types.Header) { - hash, number := header.Hash(), header.Number.Uint64() - - // poll every 2 seconds, should succeed after a few tries - ticker := time.NewTicker(2 * time.Second) - defer ticker.Stop() - - // we give up after 2 minutes - timeout := time.After(120 * time.Second) - - for { - select { - case <-ticker.C: - // try reading from chain, if the header is present then we are ready - if h := chain.GetHeader(hash, number); h != nil { - return - } - - case <-timeout: - log.Warn("Unable to find last pre-EuclidV2 header in chain", "hash", hash.Hex(), "number", number) - return - } - } -} - // VerifyHeaders verifies a batch of headers concurrently. In our use-case, // headers can only be all system, all clique, or start with clique and then switch once to system. -func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { +func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header, seals []bool, parent *types.Header) (chan<- struct{}, <-chan error) { abort := make(chan struct{}) results := make(chan error, len(headers)) @@ -102,12 +76,12 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea // If the first header is system, then all headers must be system. if firstEngine == ue.system { - return firstEngine.VerifyHeaders(chain, headers, seals) + return firstEngine.VerifyHeaders(chain, headers, seals, nil) } // If first and last headers are both clique, then all headers are clique. if firstEngine == lastEngine { - return firstEngine.VerifyHeaders(chain, headers, seals) + return firstEngine.VerifyHeaders(chain, headers, seals, nil) } // Otherwise, headers start as clique then switch to system. Since we assume @@ -135,7 +109,7 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea // Verify clique headers. log.Info("Start EuclidV2 transition verification in Clique section", "startBlockNumber", cliqueHeaders[0].Number, "endBlockNumber", cliqueHeaders[len(cliqueHeaders)-1].Number) - abortClique, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals) + abortClique, cliqueResults := ue.clique.VerifyHeaders(chain, cliqueHeaders, cliqueSeals, nil) // Note: cliqueResults is not closed so we cannot directly iterate over it for i := 0; i < len(cliqueHeaders); i++ { @@ -149,14 +123,13 @@ func (ue *UpgradableEngine) VerifyHeaders(chain consensus.ChainHeaderReader, hea } } - // `VerifyHeader` will try to call `chain.GetHeader`, which will race with `InsertHeaderChain`. - // This might result in "unknown ancestor" header validation error. - // This should be temporary, so we solve this by waiting here. - waitForHeader(chain, cliqueHeaders[len(cliqueHeaders)-1]) + // Since the Clique part of the header chain might not yet be stored in the local chain, + // provide a hint to the SystemContract consensus engine. + lastCliqueHeader := cliqueHeaders[len(cliqueHeaders)-1] // Verify system contract headers. log.Info("Start EuclidV2 transition verification in SystemContract section", "startBlockNumber", systemHeaders[0].Number, "endBlockNumber", systemHeaders[len(systemHeaders)-1].Number) - abortSystem, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals) + abortSystem, systemResults := ue.system.VerifyHeaders(chain, systemHeaders, systemSeals, lastCliqueHeader) // Note: systemResults is not closed so we cannot directly iterate over it for i := 0; i < len(systemHeaders); i++ { diff --git a/core/block_validator_test.go b/core/block_validator_test.go index c94132d001..12e599921c 100644 --- a/core/block_validator_test.go +++ b/core/block_validator_test.go @@ -51,10 +51,10 @@ func TestHeaderVerification(t *testing.T) { if valid { engine := ethash.NewFaker() - _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}) + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}, nil) } else { engine := ethash.NewFakeFailer(headers[i].Number.Uint64()) - _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}) + _, results = engine.VerifyHeaders(chain, []*types.Header{headers[i]}, []bool{true}, nil) } // Wait for the verification result select { @@ -107,11 +107,11 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) { if valid { chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) - _, results = chain.engine.VerifyHeaders(chain, headers, seals) + _, results = chain.engine.VerifyHeaders(chain, headers, seals, nil) chain.Stop() } else { chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil) - _, results = chain.engine.VerifyHeaders(chain, headers, seals) + _, results = chain.engine.VerifyHeaders(chain, headers, seals, nil) chain.Stop() } // Wait for all the verification results @@ -176,7 +176,7 @@ func testHeaderConcurrentAbortion(t *testing.T, threads int) { chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil) defer chain.Stop() - abort, results := chain.engine.VerifyHeaders(chain, headers, seals) + abort, results := chain.engine.VerifyHeaders(chain, headers, seals, nil) close(abort) // Deplete the results channel diff --git a/core/blockchain.go b/core/blockchain.go index 1a46a315c2..821564769e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1546,7 +1546,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er headers[i] = block.Header() seals[i] = verifySeals } - abort, results := bc.engine.VerifyHeaders(bc, headers, seals) + abort, results := bc.engine.VerifyHeaders(bc, headers, seals, nil) defer close(abort) // Peek the error for the first block to decide the directing import logic diff --git a/core/headerchain.go b/core/headerchain.go index f047d654d9..c01eb7ba8c 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -339,7 +339,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int) seals[len(seals)-1] = true } - abort, results := hc.engine.VerifyHeaders(hc, chain, seals) + abort, results := hc.engine.VerifyHeaders(hc, chain, seals, nil) defer close(abort) // Iterate over the headers and ensure they all check out diff --git a/miner/scroll_worker_test.go b/miner/scroll_worker_test.go index db18e86738..cbbf82f32a 100644 --- a/miner/scroll_worker_test.go +++ b/miner/scroll_worker_test.go @@ -17,6 +17,7 @@ package miner import ( + "context" "fmt" "math" "math/big" @@ -33,6 +34,8 @@ import ( "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/ethash" + "github.com/scroll-tech/go-ethereum/consensus/system_contract" + "github.com/scroll-tech/go-ethereum/consensus/wrapper" "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/types" @@ -77,6 +80,14 @@ var ( MaxAccountsNum: math.MaxInt, CCCMaxWorkers: 2, } + + testConfigAllowEmpty = &Config{ + Recommit: time.Second, + GasCeil: params.GenesisGasLimit, + MaxAccountsNum: math.MaxInt, + CCCMaxWorkers: 2, + AllowEmpty: true, + } ) func init() { @@ -138,6 +149,15 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { return crypto.Sign(crypto.Keccak256(data), testBankKey) }) + case *wrapper.UpgradableEngine: + gspec.ExtraData = make([]byte, 32+common.AddressLength+crypto.SignatureLength) + gspec.Timestamp = uint64(time.Now().Unix()) + copy(gspec.ExtraData[32:32+common.AddressLength], testBankAddress.Bytes()) + e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), testBankKey) + }, func(account accounts.Account, s string, data []byte) ([]byte, error) { + return crypto.Sign(crypto.Keccak256(data), testBankKey) + }) case *ethash.Ethash: default: t.Fatalf("unexpected consensus engine type: %T", engine) @@ -207,14 +227,26 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction { return tx } -func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { +func testWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int, allowEmpty bool) (*worker, *testWorkerBackend) { backend := newTestWorkerBackend(t, chainConfig, engine, db, blocks) backend.txPool.AddLocals(pendingTxs) - w := newWorker(testConfig, chainConfig, engine, backend, new(event.TypeMux), nil, false, false) + config := testConfig + if allowEmpty { + config = testConfigAllowEmpty + } + w := newWorker(config, chainConfig, engine, backend, new(event.TypeMux), nil, false, false) w.setEtherbase(testBankAddress) return w, backend } +func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { + return testWorker(t, chainConfig, engine, db, blocks, false) +} + +func newTestWorkerWithEmptyBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine, db ethdb.Database, blocks int) (*worker, *testWorkerBackend) { + return testWorker(t, chainConfig, engine, db, blocks, true) +} + func TestGenerateBlockAndImportClique(t *testing.T) { testGenerateBlockAndImport(t, true) } @@ -1355,3 +1387,83 @@ func TestEuclidV2HardForkMessageQueue(t *testing.T) { } } } + +// TestEuclidV2TransitionVerification tests that the upgradable consensus engine +// can successfully verify the EuclidV2 transition chain. +func TestEuclidV2TransitionVerification(t *testing.T) { + // patch time.Now() to be able to simulate hard fork time + patches := gomonkey.NewPatches() + defer patches.Reset() + var timeCount int64 + patches.ApplyFunc(time.Now, func() time.Time { + timeCount++ + return time.Unix(timeCount, 0) + }) + + // init chain config + chainConfig := params.AllCliqueProtocolChanges.Clone() + chainConfig.EuclidTime = newUint64(0) + chainConfig.EuclidV2Time = newUint64(10000) + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + chainConfig.SystemContract = ¶ms.SystemContractConfig{Period: 1} + + // init worker + db := rawdb.NewMemoryDatabase() + cliqueEngine := clique.New(chainConfig.Clique, db) + sysEngine := system_contract.New(context.Background(), chainConfig.SystemContract, &system_contract.FakeEthClient{Value: testBankAddress}) + engine := wrapper.NewUpgradableEngine(chainConfig.IsEuclidV2, cliqueEngine, sysEngine) + w, b := newTestWorkerWithEmptyBlock(t, chainConfig, engine, db, 0) + defer w.close() + b.genesis.MustCommit(db) + + // collect mined blocks + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + w.start() + + blocks := []*types.Block{} + headers := []*types.Header{} + + for i := 0; i < 6; i++ { + select { + case ev := <-sub.Chan(): + // activate EuclidV2 at next block + if i == 2 { + timeCount = int64(*chainConfig.EuclidV2Time) + } + + block := ev.Data.(core.NewMinedBlockEvent).Block + blocks = append(blocks, block) + headers = append(headers, block.Header()) + + case <-time.After(3 * time.Second): + t.Fatalf("timeout") + } + } + + // sanity check: we generated the EuclidV2 transition block + assert.False(t, chainConfig.IsEuclidV2(headers[0].Time)) + assert.True(t, chainConfig.IsEuclidV2(headers[len(headers)-1].Time)) + + // import headers into new chain + chainDb := rawdb.NewMemoryDatabase() + b.genesis.MustCommit(chainDb) + chain, err := core.NewBlockChain(chainDb, nil, b.chain.Config(), engine, vm.Config{}, nil, nil) + assert.NoError(t, err) + defer chain.Stop() + + // previously this would fail with `unknown ancestor` + _, err = chain.InsertHeaderChain(headers, 0) + assert.NoError(t, err) + + // import headers into new chain + chainDb = rawdb.NewMemoryDatabase() + b.genesis.MustCommit(chainDb) + chain, err = core.NewBlockChain(chainDb, nil, b.chain.Config(), engine, vm.Config{}, nil, nil) + assert.NoError(t, err) + defer chain.Stop() + + // previously this would fail with `unknown ancestor` + _, err = chain.InsertChain(blocks) + assert.NoError(t, err) +} diff --git a/params/version.go b/params/version.go index 2522b38243..dfae47a034 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 5 // Major version component of the current release VersionMinor = 8 // Minor version component of the current release - VersionPatch = 46 // Patch version component of the current release + VersionPatch = 47 // Patch version component of the current release VersionMeta = "mainnet" // Version metadata to append to the version string )