fix: provide parent header during EuclidV2 transition verification (#1187)

* fix: provide parent header during EuclidV2 transition verification

* add test
This commit is contained in:
Péter Garamvölgyi 2025-05-16 09:27:46 +02:00 committed by GitHub
parent aba2ddda86
commit ad6cced99d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 199 additions and 108 deletions

View file

@ -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 // 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 // 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). // 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{}) abort := make(chan struct{})
results := make(chan error, len(headers)) results := make(chan error, len(headers))

View file

@ -71,7 +71,7 @@ type Engine interface {
// concurrently. The method returns a quit channel to abort the operations and // 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 // a results channel to retrieve the async verifications (the order is that of
// the input slice). // 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 // VerifyUncles verifies that the given block's uncles conform to the consensus
// rules of a given engine. // rules of a given engine.

View file

@ -119,7 +119,7 @@ func (ethash *Ethash) VerifyHeader(chain consensus.ChainHeaderReader, header *ty
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers // VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers
// concurrently. The method returns a quit channel to abort the operations and // concurrently. The method returns a quit channel to abort the operations and
// a results channel to retrieve the async verifications. // 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 we're running a full engine faking, accept any input as valid
if ethash.config.PowMode == ModeFullFake || len(headers) == 0 { if ethash.config.PowMode == ModeFullFake || len(headers) == 0 {
abort, results := make(chan struct{}), make(chan error, len(headers)) abort, results := make(chan struct{}), make(chan error, len(headers))

View file

@ -86,13 +86,18 @@ func (s *SystemContract) VerifyHeader(chain consensus.ChainHeaderReader, header
// concurrently. The method returns a quit channel to abort the operations and // 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 // a results channel to retrieve the async verifications (the order is that of
// the input slice). // 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{}) abort := make(chan struct{})
results := make(chan error, len(headers)) results := make(chan error, len(headers))
go func() { go func() {
for i, header := range headers { 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 { if err != nil {
log.Error("Error verifying headers", "err", err) log.Error("Error verifying headers", "err", err)
} }

View file

@ -3,10 +3,13 @@ package system_contract
import ( import (
"context" "context"
"fmt" "fmt"
"math/big"
"sync" "sync"
"time" "time"
"github.com/scroll-tech/go-ethereum"
"github.com/scroll-tech/go-ethereum/common" "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/log"
"github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/params"
"github.com/scroll-tech/go-ethereum/rollup/sync_service" "github.com/scroll-tech/go-ethereum/rollup/sync_service"
@ -140,3 +143,53 @@ func (s *SystemContract) localSignerAddress() common.Address {
return s.signer 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
}

View file

@ -3,13 +3,11 @@ package system_contract
import ( import (
"context" "context"
"math/big" "math/big"
"sync"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/scroll-tech/go-ethereum"
"github.com/scroll-tech/go-ethereum/accounts" "github.com/scroll-tech/go-ethereum/accounts"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
@ -19,14 +17,14 @@ import (
"github.com/scroll-tech/go-ethereum/trie" "github.com/scroll-tech/go-ethereum/trie"
) )
var _ sync_service.EthClient = &fakeEthClient{} var _ sync_service.EthClient = &FakeEthClient{}
func TestSystemContract_FetchSigner(t *testing.T) { func TestSystemContract_FetchSigner(t *testing.T) {
log.Root().SetHandler(log.DiscardHandler()) log.Root().SetHandler(log.DiscardHandler())
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
fakeClient := &fakeEthClient{value: expectedSigner} fakeClient := &FakeEthClient{Value: expectedSigner}
config := &params.SystemContractConfig{ config := &params.SystemContractConfig{
SystemContractAddress: common.HexToAddress("0xFAKE"), SystemContractAddress: common.HexToAddress("0xFAKE"),
@ -55,7 +53,7 @@ func TestSystemContract_AuthorizeCheck(t *testing.T) {
expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678") expectedSigner := common.HexToAddress("0x1234567890abcdef1234567890abcdef12345678")
fakeClient := &fakeEthClient{value: expectedSigner} fakeClient := &FakeEthClient{Value: expectedSigner}
config := &params.SystemContractConfig{ config := &params.SystemContractConfig{
SystemContractAddress: common.HexToAddress("0xFAKE"), SystemContractAddress: common.HexToAddress("0xFAKE"),
Period: 10, Period: 10,
@ -106,8 +104,8 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) {
updatedSigner := common.HexToAddress("0x2222222222222222222222222222222222222222") updatedSigner := common.HexToAddress("0x2222222222222222222222222222222222222222")
// Create a fake client that starts by returning the wrong signer. // Create a fake client that starts by returning the wrong signer.
fakeClient := &fakeEthClient{ fakeClient := &FakeEthClient{
value: oldSigner, Value: oldSigner,
} }
config := &params.SystemContractConfig{ config := &params.SystemContractConfig{
@ -130,7 +128,7 @@ func TestSystemContract_SignsAfterUpdate(t *testing.T) {
// Now, simulate an update: change the fake client's returned value to updatedSigner. // Now, simulate an update: change the fake client's returned value to updatedSigner.
fakeClient.mu.Lock() fakeClient.mu.Lock()
fakeClient.value = updatedSigner fakeClient.Value = updatedSigner
fakeClient.mu.Unlock() fakeClient.mu.Unlock()
// fetch new value from L1 (simulating a background poll) // 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") 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
}

View file

@ -2,7 +2,6 @@ package wrapper
import ( import (
"math/big" "math/big"
"time"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/consensus" "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) 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, // 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. // 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{}) abort := make(chan struct{})
results := make(chan error, len(headers)) 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 the first header is system, then all headers must be system.
if firstEngine == ue.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 first and last headers are both clique, then all headers are clique.
if firstEngine == lastEngine { 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 // 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. // Verify clique headers.
log.Info("Start EuclidV2 transition verification in Clique section", "startBlockNumber", cliqueHeaders[0].Number, "endBlockNumber", cliqueHeaders[len(cliqueHeaders)-1].Number) 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 // Note: cliqueResults is not closed so we cannot directly iterate over it
for i := 0; i < len(cliqueHeaders); i++ { 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`. // Since the Clique part of the header chain might not yet be stored in the local chain,
// This might result in "unknown ancestor" header validation error. // provide a hint to the SystemContract consensus engine.
// This should be temporary, so we solve this by waiting here. lastCliqueHeader := cliqueHeaders[len(cliqueHeaders)-1]
waitForHeader(chain, cliqueHeaders[len(cliqueHeaders)-1])
// Verify system contract headers. // Verify system contract headers.
log.Info("Start EuclidV2 transition verification in SystemContract section", "startBlockNumber", systemHeaders[0].Number, "endBlockNumber", systemHeaders[len(systemHeaders)-1].Number) 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 // Note: systemResults is not closed so we cannot directly iterate over it
for i := 0; i < len(systemHeaders); i++ { for i := 0; i < len(systemHeaders); i++ {

View file

@ -51,10 +51,10 @@ func TestHeaderVerification(t *testing.T) {
if valid { if valid {
engine := ethash.NewFaker() 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 { } else {
engine := ethash.NewFakeFailer(headers[i].Number.Uint64()) 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 // Wait for the verification result
select { select {
@ -107,11 +107,11 @@ func testHeaderConcurrentVerification(t *testing.T, threads int) {
if valid { if valid {
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil) 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() chain.Stop()
} else { } else {
chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeFailer(uint64(len(headers)-1)), vm.Config{}, nil, nil) 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() chain.Stop()
} }
// Wait for all the verification results // 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) chain, _ := NewBlockChain(testdb, nil, params.TestChainConfig, ethash.NewFakeDelayer(time.Millisecond), vm.Config{}, nil, nil)
defer chain.Stop() defer chain.Stop()
abort, results := chain.engine.VerifyHeaders(chain, headers, seals) abort, results := chain.engine.VerifyHeaders(chain, headers, seals, nil)
close(abort) close(abort)
// Deplete the results channel // Deplete the results channel

View file

@ -1546,7 +1546,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
headers[i] = block.Header() headers[i] = block.Header()
seals[i] = verifySeals seals[i] = verifySeals
} }
abort, results := bc.engine.VerifyHeaders(bc, headers, seals) abort, results := bc.engine.VerifyHeaders(bc, headers, seals, nil)
defer close(abort) defer close(abort)
// Peek the error for the first block to decide the directing import logic // Peek the error for the first block to decide the directing import logic

View file

@ -339,7 +339,7 @@ func (hc *HeaderChain) ValidateHeaderChain(chain []*types.Header, checkFreq int)
seals[len(seals)-1] = true 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) defer close(abort)
// Iterate over the headers and ensure they all check out // Iterate over the headers and ensure they all check out

View file

@ -17,6 +17,7 @@
package miner package miner
import ( import (
"context"
"fmt" "fmt"
"math" "math"
"math/big" "math/big"
@ -33,6 +34,8 @@ import (
"github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus"
"github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/clique"
"github.com/scroll-tech/go-ethereum/consensus/ethash" "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"
"github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
@ -77,6 +80,14 @@ var (
MaxAccountsNum: math.MaxInt, MaxAccountsNum: math.MaxInt,
CCCMaxWorkers: 2, CCCMaxWorkers: 2,
} }
testConfigAllowEmpty = &Config{
Recommit: time.Second,
GasCeil: params.GenesisGasLimit,
MaxAccountsNum: math.MaxInt,
CCCMaxWorkers: 2,
AllowEmpty: true,
}
) )
func init() { 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) { e.Authorize(testBankAddress, func(account accounts.Account, s string, data []byte) ([]byte, error) {
return crypto.Sign(crypto.Keccak256(data), testBankKey) 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: case *ethash.Ethash:
default: default:
t.Fatalf("unexpected consensus engine type: %T", engine) t.Fatalf("unexpected consensus engine type: %T", engine)
@ -207,14 +227,26 @@ func (b *testWorkerBackend) newRandomTx(creation bool) *types.Transaction {
return tx 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 := newTestWorkerBackend(t, chainConfig, engine, db, blocks)
backend.txPool.AddLocals(pendingTxs) 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) w.setEtherbase(testBankAddress)
return w, backend 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) { func TestGenerateBlockAndImportClique(t *testing.T) {
testGenerateBlockAndImport(t, true) 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 = &params.CliqueConfig{Period: 1, Epoch: 30000}
chainConfig.SystemContract = &params.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)
}

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 8 // Minor 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 VersionMeta = "mainnet" // Version metadata to append to the version string
) )