diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 7ef3a1e2da..0ff39cdc87 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -52,7 +52,7 @@ import ( ) const ( - defaultSpanLength = 6400 // Default span length i.e. number of bor blocks in a span + defaultSpanLength = params.DefaultSpanLength zerothSpanEnd = 255 // End block of 0th span checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory diff --git a/core/blockchain.go b/core/blockchain.go index 2563e3f19a..2863519b74 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -228,7 +228,12 @@ type BlockChainConfig struct { // This defines the cutoff block for history expiry. // Blocks before this number may be unavailable in the chain database. HistoryPruningCutoff uint64 - Stateless bool // Whether the node is in stateless mode + + // Whether the node is in stateless mode or not. + Stateless bool + + // MilestoneFetcher returns the latest milestone end block from Heimdall. + MilestoneFetcher func(ctx context.Context) (uint64, error) } // DefaultConfig returns the default config. @@ -390,14 +395,14 @@ type BlockChain struct { stateSizer *state.SizeTracker // State size tracking // Bor related changes - borReceiptsCache *lru.Cache[common.Hash, *types.Receipt] // Cache for the most recent bor receipt receipts per block - stateSyncMu sync.RWMutex // Mutex to protect the stateSyncData access - borReceiptsRLPCache *lru.Cache[common.Hash, rlp.RawValue] // Cache for the most recent bor receipt RLPs per block - stateSyncData []*types.StateSyncData // State sync data - stateSyncFeed event.Feed // State sync feed - chain2HeadFeed event.Feed // Reorg/NewHead/Fork data feed - chainSideFeed event.Feed // Side chain data feed (removed from geth but needed in bor) - checker ethereum.ChainValidator + borReceiptsCache *lru.Cache[common.Hash, *types.Receipt] // Cache for the most recent bor receipt receipts per block + stateSyncMu sync.RWMutex // Mutex to protect the stateSyncData access + borReceiptsRLPCache *lru.Cache[common.Hash, rlp.RawValue] // Cache for the most recent bor receipt RLPs per block + stateSyncData []*types.StateSyncData // State sync data + stateSyncFeed event.Feed // State sync feed + chain2HeadFeed event.Feed // Reorg/NewHead/Fork data feed + chainSideFeed event.Feed // Side chain data feed (removed from geth but needed in bor) + milestoneFetcher func(ctx context.Context) (uint64, error) // Function to fetch the latest milestone end block from Heimdall. } // NewBlockChain returns a fully initialised block chain using information @@ -452,7 +457,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, borReceiptsCache: lru.NewCache[common.Hash, *types.Receipt](receiptsCacheLimit), borReceiptsRLPCache: lru.NewCache[common.Hash, rlp.RawValue](receiptsCacheLimit), logger: cfg.VmConfig.Tracer, - checker: cfg.Checker, + milestoneFetcher: cfg.MilestoneFetcher, } bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped) @@ -574,11 +579,6 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, } } } - // 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. - // BOR - commented out intentionally - // bc.engine.VerifyHeader(bc, bc.CurrentHeader()) // 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 { @@ -4205,9 +4205,9 @@ func (bc *BlockChain) ProcessBlockWithWitnesses(block *types.Block, witness *sta // verifies headers after the latest finalized block and rewinds the chain if // invalid headers are detected. func (bc *BlockChain) startHeaderVerificationLoop() { - if bc.checker == nil { - log.Warn("chain validator service is not set, skipping header verification loop") - return // No checker available + if bc.milestoneFetcher == nil { + log.Warn("milestone fetcher is not set, skipping header verification loop") + return } bc.wg.Add(1) @@ -4216,7 +4216,7 @@ func (bc *BlockChain) startHeaderVerificationLoop() { ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() - log.Info("Started header verification loop") + log.Info("Starting header verification loop") for { select { @@ -4230,51 +4230,57 @@ func (bc *BlockChain) startHeaderVerificationLoop() { }() } -// verifyPendingHeaders checks headers after the latest finalized block -// and rewinds the chain if invalid headers are found. +// verifyPendingHeaders fetches the latest milestone from Heimdall and verifies +// all headers between that milestone's end block and the current chain head. If an invalid +// header is found, the chain is rewound to the last valid block. func (bc *BlockChain) verifyPendingHeaders() { - // Get the latest finalized block - hasMilestone, milestoneNumber, _ := bc.checker.GetWhitelistedMilestone() - if !hasMilestone { - return // No finalized block yet - } - currentHead := bc.CurrentBlock() - if currentHead.Number.Uint64() <= milestoneNumber { - return // Nothing to verify - } chainConfig := bc.Config() - - // We don't need to verify headers before Rio if chainConfig.Bor == nil || !chainConfig.Bor.IsRio(currentHead.Number) { - return // Rio is not enabled yet + return } - // Collect headers from finalized block + 1 to current head - var headers []*types.Header - for i := milestoneNumber + 1; i <= currentHead.Number.Uint64(); i++ { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + + milestoneEndBlock, err := bc.milestoneFetcher(ctx) + if err != nil { + log.Error("Failed to fetch milestone end block from Heimdall for header verification", "err", err) + return + } + + headNumber := currentHead.Number.Uint64() + if milestoneEndBlock >= headNumber { + return // Still syncing or synced to the milestone end block, nothing to verify. + } + + startBlock := milestoneEndBlock + 1 + + // Collect headers from startBlock to current head. + headers := make([]*types.Header, 0, headNumber-startBlock+1) + for i := startBlock; i <= headNumber; i++ { header := bc.GetHeaderByNumber(i) if header == nil { log.Debug("Missing header during verification", "number", i) - return // Missing header, skip verification + return } headers = append(headers, header) } if len(headers) == 0 { + log.Debug("No headers to verify") return } - log.Debug("Verifying pending headers", "from", headers[0].Number.Uint64(), - "to", headers[len(headers)-1].Number.Uint64(), "count", len(headers)) + log.Debug("Verifying pending headers", + "from", headers[0].Number.Uint64(), "to", headers[len(headers)-1].Number.Uint64(), "count", len(headers)) - // Verify headers abort, results := bc.engine.VerifyHeaders(bc, headers) defer close(abort) - // Check results and find the last valid header - lastValidNumber := milestoneNumber + // Check results and find the last valid header. + lastValidNumber := milestoneEndBlock for _, header := range headers { select { case <-bc.quit: @@ -4283,12 +4289,18 @@ func (bc *BlockChain) verifyPendingHeaders() { if err != nil { log.Warn("Invalid header detected during background verification", "number", header.Number.Uint64(), "hash", header.Hash(), "err", err) - // Rewind to the last valid block - if lastValidNumber < currentHead.Number.Uint64() { - log.Warn("Rewinding chain due to invalid header", - "from", currentHead.Number.Uint64(), "to", lastValidNumber) + + if lastValidNumber < headNumber { + dropCount := int64(headNumber - lastValidNumber) + + log.Warn("Rewinding chain due to an invalid header", + "from", headNumber, "to", lastValidNumber, "drop", dropCount) + if err := bc.SetHead(lastValidNumber); err != nil { - log.Error("Failed to rewind chain", "err", err) + log.Error("Failed to rewind chain to the last valid header", "err", err) + } else { + blockReorgMeter.Mark(1) + blockReorgDropMeter.Mark(dropCount) } } return diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 7dc9a39635..4358922c42 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -4829,24 +4829,23 @@ func testHeaderVerificationLoop(t *testing.T, scheme string) { // Test case 1: Valid chain - no rewinds should happen t.Run("ValidChain", func(t *testing.T) { engine := ethash.NewFaker() + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } genesis := &Genesis{ - Config: params.TestChainConfig, + Config: &config, BaseFee: big.NewInt(params.InitialBaseFee), } - // Create a mock validator that has a finalized block at height 3 - mockValidator := &mockChainValidator{ - hasMilestone: true, - milestoneNumber: 3, - milestoneHash: common.HexToHash("0x123"), - } - // Generate blocks _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 8, nil) - // Create blockchain with mock validator cfg := DefaultConfig() - cfg.Checker = mockValidator + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 3, nil + } chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) if err != nil { t.Fatalf("failed to create blockchain: %v", err) @@ -4891,19 +4890,14 @@ func testHeaderVerificationLoop(t *testing.T, scheme string) { BaseFee: big.NewInt(params.InitialBaseFee), } - // Create a mock validator that has a finalized block at height 3 - mockValidator := &mockChainValidator{ - hasMilestone: true, - milestoneNumber: 3, - milestoneHash: common.HexToHash("0x123"), - } - // Generate blocks _, blocks, _ := GenerateChainWithGenesis(genesis, engine.Ethash, 8, nil) - // Create blockchain with mock validator and failing engine + // Create blockchain with milestone fetcher and failing engine cfg := DefaultConfig() - cfg.Checker = mockValidator + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 3, nil // milestone at block 3 + } chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) if err != nil { t.Fatalf("failed to create blockchain: %v", err) @@ -4934,25 +4928,26 @@ func testHeaderVerificationLoop(t *testing.T, scheme string) { } }) - // Test case 3: No finalized block - verification should not run + // Test case 3: Fetcher returns error - verification should not run t.Run("NoFinalizedBlock", func(t *testing.T) { engine := ethash.NewFaker() + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } genesis := &Genesis{ - Config: params.TestChainConfig, + Config: &config, BaseFee: big.NewInt(params.InitialBaseFee), } - // Create a mock validator with no finalized block - mockValidator := &mockChainValidator{ - hasMilestone: false, - } - // Generate blocks _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 5, nil) - // Create blockchain with mock validator cfg := DefaultConfig() - cfg.Checker = mockValidator + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 0, fmt.Errorf("no milestone available") + } chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) if err != nil { t.Fatalf("failed to create blockchain: %v", err) @@ -4976,27 +4971,26 @@ func testHeaderVerificationLoop(t *testing.T, scheme string) { } }) - // Test case 4: Current head at finalized block - no verification needed + // Test case 4: Milestone at head - no verification needed t.Run("HeadAtFinalizedBlock", func(t *testing.T) { engine := ethash.NewFaker() + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } genesis := &Genesis{ - Config: params.TestChainConfig, + Config: &config, BaseFee: big.NewInt(params.InitialBaseFee), } // Generate blocks _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 5, nil) - // Create a mock validator where finalized block equals current head - mockValidator := &mockChainValidator{ - hasMilestone: true, - milestoneNumber: 5, // Same as head - milestoneHash: blocks[4].Hash(), - } - - // Create blockchain with mock validator cfg := DefaultConfig() - cfg.Checker = mockValidator + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 5, nil // milestone at head + } chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) if err != nil { t.Fatalf("failed to create blockchain: %v", err) @@ -5023,23 +5017,23 @@ func testHeaderVerificationLoop(t *testing.T, scheme string) { // Test case 5: Verify proper shutdown when blockchain stops t.Run("ProperShutdown", func(t *testing.T) { engine := ethash.NewFaker() + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } genesis := &Genesis{ - Config: params.TestChainConfig, + Config: &config, BaseFee: big.NewInt(params.InitialBaseFee), } - mockValidator := &mockChainValidator{ - hasMilestone: true, - milestoneNumber: 2, - milestoneHash: common.HexToHash("0x123"), - } - // Generate blocks _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 5, nil) - // Create blockchain cfg := DefaultConfig() - cfg.Checker = mockValidator + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 2, nil + } chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) if err != nil { t.Fatalf("failed to create blockchain: %v", err) @@ -5064,24 +5058,24 @@ func TestVerifyPendingHeaders(t *testing.T) { func testVerifyPendingHeaders(t *testing.T, scheme string) { engine := ethash.NewFaker() + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } genesis := &Genesis{ - Config: params.TestChainConfig, + Config: &config, BaseFee: big.NewInt(params.InitialBaseFee), } // Generate blocks _, blocks, _ := GenerateChainWithGenesis(genesis, engine, 8, nil) - // Test with mock validator - mockValidator := &mockChainValidator{ - hasMilestone: true, - milestoneNumber: 3, - milestoneHash: common.HexToHash("0x123"), - } - - // Create blockchain + // Create blockchain with milestone fetcher cfg := DefaultConfig() - cfg.Checker = mockValidator + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 3, nil + } chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) if err != nil { t.Fatalf("failed to create blockchain: %v", err) @@ -5105,15 +5099,16 @@ func testVerifyPendingHeaders(t *testing.T, scheme string) { } } -// TestHeaderVerificationWithNilChecker tests that verification is skipped when checker is nil -func TestHeaderVerificationWithNilChecker(t *testing.T) { +// TestHeaderVerificationWithNilFetcher tests that the verification loop is skipped +// when MilestoneFetcher is nil. +func TestHeaderVerificationWithNilFetcher(t *testing.T) { engine := ethash.NewFaker() genesis := &Genesis{ Config: params.TestChainConfig, BaseFee: big.NewInt(params.InitialBaseFee), } - // Create blockchain with nil checker + // Create blockchain without MilestoneFetcher chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, DefaultConfig()) if err != nil { t.Fatalf("failed to create blockchain: %v", err) @@ -5128,13 +5123,202 @@ func TestHeaderVerificationWithNilChecker(t *testing.T) { initialHead := chain.CurrentBlock().Number.Uint64() - // Wait a bit - the verification loop should not run since checker is nil + // Wait a bit - the verification loop should not run since milestoneFetcher is nil time.Sleep(2 * time.Second) // Head should not have changed newHead := chain.CurrentBlock().Number.Uint64() if newHead != initialHead { - t.Errorf("Head should not have changed when checker is nil, got %d, want %d", newHead, initialHead) + t.Errorf("Head should not have changed when milestoneFetcher is nil, got %d, want %d", newHead, initialHead) + } +} + +// headerCountingEngine wraps ethash and records how many headers VerifyHeaders receives. +type headerCountingEngine struct { + *ethash.Ethash + headersVerified atomic.Int64 +} + +func (m *headerCountingEngine) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*types.Header) (chan<- struct{}, <-chan error) { + m.headersVerified.Store(int64(len(headers))) + return m.Ethash.VerifyHeaders(chain, headers) +} + +// TestVerifyPendingHeadersMilestoneFetcher tests that verifyPendingHeaders +// verifies only headers between the Heimdall milestone and the chain head. +func TestVerifyPendingHeadersMilestoneFetcher(t *testing.T) { + t.Run("VerifiesFromMilestoneToHead", func(t *testing.T) { + engine := &headerCountingEngine{Ethash: ethash.NewFaker()} + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } + genesis := &Genesis{ + Config: &config, + BaseFee: big.NewInt(params.InitialBaseFee), + } + + _, blocks, _ := GenerateChainWithGenesis(genesis, engine.Ethash, 20, nil) + + cfg := DefaultConfig() + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 3, nil // milestone at block 3 + } + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) + if err != nil { + t.Fatalf("failed to create blockchain: %v", err) + } + defer chain.Stop() + + if _, err := chain.InsertChain(blocks, false); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + chain.verifyPendingHeaders() + + // Should verify blocks 4-20 = 17 headers + if got := engine.headersVerified.Load(); got != 17 { + t.Errorf("expected 17 headers verified, got %d", got) + } + }) + + t.Run("SkipsWhenMilestoneAheadOfHead", func(t *testing.T) { + engine := &headerCountingEngine{Ethash: ethash.NewFaker()} + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } + genesis := &Genesis{ + Config: &config, + BaseFee: big.NewInt(params.InitialBaseFee), + } + + _, blocks, _ := GenerateChainWithGenesis(genesis, engine.Ethash, 10, nil) + + cfg := DefaultConfig() + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 100, nil // milestone ahead of head (still syncing) + } + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) + if err != nil { + t.Fatalf("failed to create blockchain: %v", err) + } + defer chain.Stop() + + if _, err := chain.InsertChain(blocks, false); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + engine.headersVerified.Store(0) // reset counter after initial insertion + + chain.verifyPendingHeaders() + + // Should not verify anything since milestone > head + if got := engine.headersVerified.Load(); got != 0 { + t.Errorf("expected 0 headers verified when milestone ahead of head, got %d", got) + } + }) + + t.Run("SkipsOnFetcherError", func(t *testing.T) { + engine := &headerCountingEngine{Ethash: ethash.NewFaker()} + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } + genesis := &Genesis{ + Config: &config, + BaseFee: big.NewInt(params.InitialBaseFee), + } + + _, blocks, _ := GenerateChainWithGenesis(genesis, engine.Ethash, 10, nil) + + cfg := DefaultConfig() + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 0, fmt.Errorf("heimdall unavailable") + } + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) + if err != nil { + t.Fatalf("failed to create blockchain: %v", err) + } + defer chain.Stop() + + if _, err := chain.InsertChain(blocks, false); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + engine.headersVerified.Store(0) // reset counter after initial insertion + + chain.verifyPendingHeaders() + + // Should not verify anything when fetcher returns error + if got := engine.headersVerified.Load(); got != 0 { + t.Errorf("expected 0 headers verified on fetcher error, got %d", got) + } + }) +} + +// TestVerifyPendingHeadersReorgMetrics tests that reorg metrics are recorded +// when verifyPendingHeaders rewinds the chain due to an invalid header. +func TestVerifyPendingHeadersReorgMetrics(t *testing.T) { + failingHeaders := map[uint64]bool{6: true} + engine := &mockFailingEngine{ + Ethash: ethash.NewFaker(), + shouldFailHeader: failingHeaders, + allowInitialInsertion: true, + } + + config := *params.TestChainConfig + config.Bor = ¶ms.BorConfig{ + RioBlock: big.NewInt(0), + } + genesis := &Genesis{ + Config: &config, + BaseFee: big.NewInt(params.InitialBaseFee), + } + + _, blocks, _ := GenerateChainWithGenesis(genesis, engine.Ethash, 8, nil) + + cfg := DefaultConfig() + cfg.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + return 3, nil // milestone at block 3 + } + chain, err := NewBlockChain(rawdb.NewMemoryDatabase(), genesis, engine, cfg) + if err != nil { + t.Fatalf("failed to create blockchain: %v", err) + } + defer chain.Stop() + + if _, err := chain.InsertChain(blocks, false); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + engine.markInsertionComplete() + + // Snapshot metrics before + reorgCountBefore := blockReorgMeter.Snapshot().Count() + reorgDropBefore := blockReorgDropMeter.Snapshot().Count() + + chain.verifyPendingHeaders() + + // Chain should have rewound to block 5 + newHead := chain.CurrentBlock().Number.Uint64() + if newHead != 5 { + t.Errorf("expected head to rewind to 5, got %d", newHead) + } + + // Reorg execute meter should have incremented by 1 + reorgCountAfter := blockReorgMeter.Snapshot().Count() + if reorgCountAfter-reorgCountBefore != 1 { + t.Errorf("expected blockReorgMeter to increment by 1, got %d", reorgCountAfter-reorgCountBefore) + } + + // Reorg drop meter should have incremented by 3 (dropped blocks 6, 7, 8) + reorgDropAfter := blockReorgDropMeter.Snapshot().Count() + if reorgDropAfter-reorgDropBefore != 3 { + t.Errorf("expected blockReorgDropMeter to increment by 3, got %d", reorgDropAfter-reorgDropBefore) } } diff --git a/eth/backend.go b/eth/backend.go index be0f82ba94..06bf4a3375 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -319,6 +319,17 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { options.Overrides = &overrides options.Checker = checker + // Wire MilestoneFetcher so verifyPendingHeaders queries Heimdall directly. + if borEngine, ok := eth.engine.(*bor.Bor); ok && borEngine.HeimdallClient != nil { + options.MilestoneFetcher = func(ctx context.Context) (uint64, error) { + m, err := borEngine.HeimdallClient.FetchMilestone(ctx) + if err != nil { + return 0, err + } + return m.EndBlock, nil + } + } + // check if Parallel EVM is enabled // if enabled, use parallel state processor if config.ParallelEVM.Enable { diff --git a/params/config.go b/params/config.go index 6e71f8dbdb..b6db170b3f 100644 --- a/params/config.go +++ b/params/config.go @@ -901,6 +901,11 @@ type BlockRangeOverrideValidatorSet struct { Validators []common.Address `json:"validators"` } +// DefaultSpanLength is the number of bor blocks in a span. This must match +// heimdall-v2's bor module Params.span_duration to ensure reorg protection +// boundaries stay consistent between the execution and consensus layers. +const DefaultSpanLength = 6400 + // BorConfig is the consensus engine configs for Matic bor based sealing. type BorConfig struct { Period map[string]uint64 `json:"period"` // Number of seconds between blocks to enforce @@ -1142,7 +1147,7 @@ func (c *ChainConfig) Description() string { banner += fmt.Sprintf(" - Lisovo: #%-8v\n", c.Bor.LisovoBlock) } if c.Bor.LisovoProBlock != nil { - banner += fmt.Sprintf(" - Lisovo Pro: #%-8v\n", c.Bor.LisovoProBlock) + banner += fmt.Sprintf(" - Lisovo Pro: #%-8v\n", c.Bor.LisovoProBlock) } return banner } diff --git a/tests/bor/bor_test.go b/tests/bor/bor_test.go index f91c1657e0..64fbc10a97 100644 --- a/tests/bor/bor_test.go +++ b/tests/bor/bor_test.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/bor" "github.com/ethereum/go-ethereum/consensus/bor/clerk" + borMilestone "github.com/ethereum/go-ethereum/consensus/bor/heimdall/milestone" borSpan "github.com/ethereum/go-ethereum/consensus/bor/heimdall/span" "github.com/ethereum/go-ethereum/consensus/bor/valset" "github.com/ethereum/go-ethereum/consensus/ethash" @@ -2786,7 +2787,7 @@ func TestVerifyPendingHeadersSpanRotationReorg(t *testing.T) { h2.EXPECT().GetSpan(gomock.Any(), uint64(1)).Return(&span1, nil).AnyTimes() h2.EXPECT().GetLatestSpan(gomock.Any()).Return(&span1, nil).AnyTimes() h2.EXPECT().FetchCheckpoint(gomock.Any(), int64(-1)).Return(nil, fmt.Errorf("no checkpoint available")).AnyTimes() - h2.EXPECT().FetchMilestone(gomock.Any()).Return(nil, fmt.Errorf("no milestone available")).AnyTimes() + h2.EXPECT().FetchMilestone(gomock.Any()).Return(&borMilestone.Milestone{EndBlock: 15}, nil).AnyTimes() h2.EXPECT().FetchStatus(gomock.Any()).Return(&ctypes.SyncInfo{CatchingUp: false}, nil).AnyTimes() h2.EXPECT().StateSyncEvents(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*clerk.EventRecordWithTime{getSampleEventRecord(t)}, nil).AnyTimes() @@ -2799,17 +2800,6 @@ func TestVerifyPendingHeadersSpanRotationReorg(t *testing.T) { borEngs[1].PurgeCache() log.Info("Purged caches on validator 2 to apply new span data") - // Set a milestone at block 15 on validator 2's chain - // This will cause verifyPendingHeaders to check blocks 16-18 - milestoneBlock := nodes[1].BlockChain().GetHeaderByNumber(15) - require.NotNil(t, milestoneBlock, "Milestone block 15 should exist") - - log.Info("Setting milestone at block 15", - "hash", milestoneBlock.Hash(), - "miner", milestoneBlock.Coinbase) - - nodes[1].Downloader().ChainValidator.ProcessMilestone(15, milestoneBlock.Hash()) - log.Info("Waiting for header verification loop to detect invalid headers...") timeout := time.After(30 * time.Second) @@ -2851,7 +2841,6 @@ func TestVerifyPendingHeadersSpanRotationReorg(t *testing.T) { block15 := nodes[1].BlockChain().GetHeaderByNumber(15) require.NotNil(t, block15, "Block 15 should still exist") - require.Equal(t, milestoneBlock.Hash(), block15.Hash(), "Block 15 hash should match milestone") for blockNum := uint64(16); blockNum <= uint64(18); blockNum++ { header := nodes[1].BlockChain().GetHeaderByNumber(blockNum)