From b69ad46c189d0b0d5f184ba55bea564398044f9c Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Thu, 26 Feb 2026 19:00:44 -0300 Subject: [PATCH 01/11] sec fix 60 --- consensus/bor/bor.go | 18 +++++++++++ consensus/bor/verify_header_test.go | 50 +++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index aa85d31f8e..cc2e9cb6dc 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -124,6 +124,15 @@ var ( errNonContiguousHeaderRange = errors.New("non-contiguous headers in checkpoint range") ) +// maxAllowedFutureBlockTimeSeconds is the maximum number of seconds that a block +// timestamp may exceed the local clock. This upper bound prevents chain-halting +// attacks on the Rio+ path: the Rio verifyHeader check was intentionally relaxed +// (removing the strict CalcProducerDelay upper bound) to support flexible block +// times, but without any ceiling a single compromised validator could set +// header.Time to year 2126, causing Prepare()'s delay computation to sleep for +// ~100 years and permanently halt the chain. +const maxAllowedFutureBlockTimeSeconds = uint64(30) + // SignerFn is a signer callback function to request a header to be signed by a // backing account. type SignerFn func(accounts.Account, string, []byte) ([]byte, error) @@ -425,6 +434,15 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head log.Error("Block announced too early post rio", "number", number, "headerTime", header.Time, "now", now) return consensus.ErrFutureBlock } + // Upper-bound check: a block whose timestamp is more than maxAllowedFutureBlockTimeSeconds + // ahead of the local clock is rejected. The Rio lower-bound check above was intentionally + // relaxed (parent.Time only) to support flexible block times, but without a ceiling a + // compromised validator could set header.Time to year 2126 — Prepare() would then compute + // a ~100-year delay and permanently halt the chain. + if header.Time > now+maxAllowedFutureBlockTimeSeconds { + log.Error("Block timestamp too far in future post rio", "number", number, "headerTime", header.Time, "now", now) + return consensus.ErrFutureBlock + } } else if c.config.IsBhilai(header.Number) { // Allow early blocks if Bhilai HF is enabled // Don't waste time checking blocks from the future but allow a buffer of block time for diff --git a/consensus/bor/verify_header_test.go b/consensus/bor/verify_header_test.go index b5447dc721..c4ace94ee2 100644 --- a/consensus/bor/verify_header_test.go +++ b/consensus/bor/verify_header_test.go @@ -215,6 +215,56 @@ func TestVerifyHeader(t *testing.T) { }, expectedError: consensus.ErrFutureBlock, }, + // Rio timestamp upper-bound tests: demonstrate the chain-halt attack vector + // and verify the fix rejects far-future timestamps. + { + // Attack vector: a compromised validator sets header.Time 100 years in the + // future. Without an upper-bound check this passes all Rio validation, then + // Prepare() computes a ~100-year delay that permanently halts the chain. + name: "far-future timestamp in Rio mode is rejected (chain-halt attack)", + setupChain: makeSetupChain(signerAddr, func(opts *chainSetupOptions) { + opts.rioBlock = big.NewInt(0) + }), + createHeader: func(t *testing.T, chain *core.BlockChain) *types.Header { + genesis := chain.HeaderChain().GetHeaderByNumber(0) + farFuture := uint64(time.Now().Unix()) + 100*365*24*3600 // year ~2126 + return newStandardTestHeader(genesis, func(opts *headerOptions) { + opts.time = farFuture + }) + }, + expectedError: consensus.ErrFutureBlock, + }, + { + // Boundary test: a timestamp just beyond the allowed future window is rejected. + name: "timestamp beyond allowed future bound in Rio mode is rejected", + setupChain: makeSetupChain(signerAddr, func(opts *chainSetupOptions) { + opts.rioBlock = big.NewInt(0) + }), + createHeader: func(t *testing.T, chain *core.BlockChain) *types.Header { + genesis := chain.HeaderChain().GetHeaderByNumber(0) + beyondBound := uint64(time.Now().Unix()) + maxAllowedFutureBlockTimeSeconds + 5 + return newStandardTestHeader(genesis, func(opts *headerOptions) { + opts.time = beyondBound + }) + }, + expectedError: consensus.ErrFutureBlock, + }, + { + // Regression: a normal block (timestamp slightly in the past) must still be + // accepted in Rio mode after the upper-bound check is added. + name: "normal timestamp in Rio mode is accepted", + setupChain: makeSetupChain(signerAddr, func(opts *chainSetupOptions) { + opts.rioBlock = big.NewInt(0) + }), + createHeader: func(t *testing.T, chain *core.BlockChain) *types.Header { + genesis := chain.HeaderChain().GetHeaderByNumber(0) + return newSignedStandardTestHeader(t, genesis, privKey, chain.Config().Bor, func(opts *headerOptions) { + opts.uncleHash = uncleHash + opts.mixDigest = common.Hash{} + }) + }, + expectedError: nil, + }, { name: "missing vanity in extra data", setupChain: makeSetupChain(addr1), From e9d9a0d734ebb90e6574e5bea8dd6daf508811ca Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Fri, 27 Feb 2026 11:08:55 -0300 Subject: [PATCH 02/11] Simplify comments on future block timestamp checks Removed detailed comments regarding block timestamp checks to simplify the code. --- consensus/bor/bor.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index cc2e9cb6dc..506053fd6b 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -125,12 +125,7 @@ var ( ) // maxAllowedFutureBlockTimeSeconds is the maximum number of seconds that a block -// timestamp may exceed the local clock. This upper bound prevents chain-halting -// attacks on the Rio+ path: the Rio verifyHeader check was intentionally relaxed -// (removing the strict CalcProducerDelay upper bound) to support flexible block -// times, but without any ceiling a single compromised validator could set -// header.Time to year 2126, causing Prepare()'s delay computation to sleep for -// ~100 years and permanently halt the chain. +// timestamp may exceed the local clock. const maxAllowedFutureBlockTimeSeconds = uint64(30) // SignerFn is a signer callback function to request a header to be signed by a @@ -435,10 +430,7 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head return consensus.ErrFutureBlock } // Upper-bound check: a block whose timestamp is more than maxAllowedFutureBlockTimeSeconds - // ahead of the local clock is rejected. The Rio lower-bound check above was intentionally - // relaxed (parent.Time only) to support flexible block times, but without a ceiling a - // compromised validator could set header.Time to year 2126 — Prepare() would then compute - // a ~100-year delay and permanently halt the chain. + // ahead of the local clock is rejected. if header.Time > now+maxAllowedFutureBlockTimeSeconds { log.Error("Block timestamp too far in future post rio", "number", number, "headerTime", header.Time, "now", now) return consensus.ErrFutureBlock From 71de2ec739b8495062b6525fc8e3b6ff136c60ac Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Fri, 27 Feb 2026 15:29:42 -0300 Subject: [PATCH 03/11] address lint --- consensus/bor/bor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 506053fd6b..3943da446c 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -125,7 +125,7 @@ var ( ) // maxAllowedFutureBlockTimeSeconds is the maximum number of seconds that a block -// timestamp may exceed the local clock. +// timestamp may exceed the local clock. const maxAllowedFutureBlockTimeSeconds = uint64(30) // SignerFn is a signer callback function to request a header to be signed by a From 35df9d3017638ef35bb71051aedba7347422fff3 Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Sat, 28 Feb 2026 11:06:56 -0300 Subject: [PATCH 04/11] improve comments --- consensus/bor/verify_header_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/consensus/bor/verify_header_test.go b/consensus/bor/verify_header_test.go index c4ace94ee2..fba7a69bb9 100644 --- a/consensus/bor/verify_header_test.go +++ b/consensus/bor/verify_header_test.go @@ -215,13 +215,11 @@ func TestVerifyHeader(t *testing.T) { }, expectedError: consensus.ErrFutureBlock, }, - // Rio timestamp upper-bound tests: demonstrate the chain-halt attack vector - // and verify the fix rejects far-future timestamps. + // Rio timestamp upper-bound tests: verify that far-future timestamps are rejected. { - // Attack vector: a compromised validator sets header.Time 100 years in the - // future. Without an upper-bound check this passes all Rio validation, then - // Prepare() computes a ~100-year delay that permanently halts the chain. - name: "far-future timestamp in Rio mode is rejected (chain-halt attack)", + // A header with a timestamp set 100 years in the future must be + // rejected by the upper-bound check introduced in Rio. + name: "far-future timestamp in Rio mode is rejected", setupChain: makeSetupChain(signerAddr, func(opts *chainSetupOptions) { opts.rioBlock = big.NewInt(0) }), From 2b31d6edf9c3bd82377374be999dcae019b09486 Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Mon, 2 Mar 2026 21:15:38 -0300 Subject: [PATCH 05/11] HF for PIP-66 --- consensus/bor/bor.go | 8 ++-- consensus/bor/bor_test.go | 80 +++++++++++++++++++++++++++++++++++++++ params/config.go | 8 ++++ 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 3943da446c..f7ef7bfec4 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -414,8 +414,8 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head number := header.Number.Uint64() now := uint64(time.Now().Unix()) - if c.config.IsRio(header.Number) { - // Rio HF introduced flexible blocktime (can be set larger than consensus without approval). + if c.config.IsNewHardfork(header.Number) { + // NewHardfork introduced flexible blocktime (can be set larger than consensus without approval). // Using strict CalcProducerDelay would reject valid blocks, so we just ensure announcement // time comes after parent time to allow for flexible blocktime. var parent *types.Header @@ -426,13 +426,13 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head parent = chain.GetHeader(header.ParentHash, number-1) } if parent == nil || now < parent.Time { - log.Error("Block announced too early post rio", "number", number, "headerTime", header.Time, "now", now) + log.Error("Block announced too early post newHardfork", "number", number, "headerTime", header.Time, "now", now) return consensus.ErrFutureBlock } // Upper-bound check: a block whose timestamp is more than maxAllowedFutureBlockTimeSeconds // ahead of the local clock is rejected. if header.Time > now+maxAllowedFutureBlockTimeSeconds { - log.Error("Block timestamp too far in future post rio", "number", number, "headerTime", header.Time, "now", now) + log.Error("Block timestamp too far in future post newHardfork", "number", number, "headerTime", header.Time, "now", now) return consensus.ErrFutureBlock } } else if c.config.IsBhilai(header.Number) { diff --git a/consensus/bor/bor_test.go b/consensus/bor/bor_test.go index f4b85bcb90..60036cd7fa 100644 --- a/consensus/bor/bor_test.go +++ b/consensus/bor/bor_test.go @@ -1709,6 +1709,86 @@ func TestVerifyHeader_RequestsHash(t *testing.T) { require.ErrorIs(t, err, consensus.ErrUnexpectedRequests) } +// TestVerifyHeader_NewHardfork_Boundary verifies that the flexible blocktime +// timestamp validation in verifyHeader activates exactly at NewHardforkBlock. +// +// Before NewHardforkBlock the old code-path is used (header.Time > now fails), +// at and after NewHardforkBlock the new path is used (parent-time check + +// upper-bound check instead of a strict now comparison). +func TestVerifyHeader_NewHardfork_Boundary(t *testing.T) { + t.Parallel() + + addr1 := common.HexToAddress("0x1") + sp := &fakeSpanner{vals: []*valset.Validator{{Address: addr1, VotingPower: 1}}} + const newHardforkBlock = 100 + + now := uint64(time.Now().Unix()) + + t.Run("before NewHardforkBlock – future timestamp is rejected", func(t *testing.T) { + // NewHardforkBlock is far in the future, so the legacy path is taken. + borCfg := ¶ms.BorConfig{ + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + NewHardforkBlock: big.NewInt(1_000_000), + } + chain, b := newChainAndBorForTest(t, sp, borCfg, false, common.Address{}, now) + + h := &types.Header{ + Number: big.NewInt(newHardforkBlock - 1), + Time: now + 3600, // 1 hour in the future – must be rejected + Extra: make([]byte, 32+65), + } + err := b.VerifyHeader(chain.HeaderChain(), h) + require.ErrorIs(t, err, consensus.ErrFutureBlock, "pre-NewHardfork: future timestamp should be rejected") + }) + + t.Run("at NewHardforkBlock – timestamp within upper bound is accepted", func(t *testing.T) { + // NewHardforkBlock active from genesis so every block uses the new path. + borCfg := ¶ms.BorConfig{ + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + NewHardforkBlock: big.NewInt(0), + } + chain, b := newChainAndBorForTest(t, sp, borCfg, false, common.Address{}, now) + + genesis := chain.HeaderChain().GetHeaderByNumber(0) + require.NotNil(t, genesis) + + // Timestamp slightly in the future but within maxAllowedFutureBlockTimeSeconds. + h := &types.Header{ + Number: big.NewInt(newHardforkBlock), + ParentHash: genesis.Hash(), + Time: now + maxAllowedFutureBlockTimeSeconds - 1, + Extra: make([]byte, 32+65), + } + // verifyHeader will proceed past the timestamp check; subsequent checks + // (mixDigest, difficulty, etc.) may still fail, but ErrFutureBlock must not. + err := b.VerifyHeader(chain.HeaderChain(), h) + require.NotErrorIs(t, err, consensus.ErrFutureBlock, "post-NewHardfork: timestamp within bound should not return ErrFutureBlock") + }) + + t.Run("at NewHardforkBlock – timestamp beyond upper bound is rejected", func(t *testing.T) { + borCfg := ¶ms.BorConfig{ + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + NewHardforkBlock: big.NewInt(0), + } + chain, b := newChainAndBorForTest(t, sp, borCfg, false, common.Address{}, now) + + genesis := chain.HeaderChain().GetHeaderByNumber(0) + require.NotNil(t, genesis) + + h := &types.Header{ + Number: big.NewInt(newHardforkBlock), + ParentHash: genesis.Hash(), + Time: now + maxAllowedFutureBlockTimeSeconds + 10, // beyond upper bound + Extra: make([]byte, 32+65), + } + err := b.VerifyHeader(chain.HeaderChain(), h) + require.ErrorIs(t, err, consensus.ErrFutureBlock, "post-NewHardfork: timestamp beyond upper bound must be rejected") + }) +} + func TestVerifyCascadingFields_Genesis(t *testing.T) { t.Parallel() sp := &fakeSpanner{vals: []*valset.Validator{{Address: common.HexToAddress("0x1"), VotingPower: 1}}} diff --git a/params/config.go b/params/config.go index bba5889e6f..04e1dedee8 100644 --- a/params/config.go +++ b/params/config.go @@ -942,6 +942,7 @@ type BorConfig struct { DandeliBlock *big.Int `json:"dandeliBlock"` // Dandeli switch block (nil = no fork, 0 = already on dandeli) LisovoBlock *big.Int `json:"lisovoBlock"` // Lisovo switch block (nil = no fork, 0 = already on lisovo) LisovoProBlock *big.Int `json:"lisovoProBlock"` // LisovoPro switch block (nil = no fork, 0 = already on lisovoPro) + NewHardforkBlock *big.Int `json:"newHardforkBlock"` // NewHardfork switch block (nil = no fork, 0 = already on newHardfork) } // String implements the stringer interface, returning the consensus engine details. @@ -1013,6 +1014,10 @@ func (c *BorConfig) IsLisovoPro(number *big.Int) bool { return isBlockForked(c.LisovoProBlock, number) } +func (c *BorConfig) IsNewHardfork(number *big.Int) bool { + return isBlockForked(c.NewHardforkBlock, number) +} + // GetTargetGasPercentage returns the target gas percentage for gas limit calculation. // After Lisovo hard fork, this value can be configured via CLI flags (stored in BorConfig at runtime). // It validates the configured value and falls back to defaults if invalid or nil. @@ -1152,6 +1157,9 @@ func (c *ChainConfig) Description() string { if c.Bor.LisovoProBlock != nil { banner += fmt.Sprintf(" - Lisovo Pro: #%-8v\n", c.Bor.LisovoProBlock) } + if c.Bor.NewHardforkBlock != nil { + banner += fmt.Sprintf(" - NewHardfork: #%-8v\n", c.Bor.NewHardforkBlock) + } return banner } From 634f7691c2d6d77e22bbd5023626e3e0d5dabe0b Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Sun, 8 Mar 2026 13:30:02 -0300 Subject: [PATCH 06/11] include block announcement on and wait on prepare in hf --- consensus/bor/bor.go | 8 +- consensus/bor/bor_test.go | 155 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 153 insertions(+), 10 deletions(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index f7ef7bfec4..c06f75c4c0 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -1114,8 +1114,8 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, w } } - // Wait before start the block production if needed (previsously this wait was on Seal) - if c.config.IsBhilai(header.Number) && waitOnPrepare { + // Wait before start the block production if needed (previously this wait was on Seal) + if c.config.IsNewHardfork(header.Number) && waitOnPrepare { var successionNumber int // if signer is not empty (RPC nodes have empty signer) if currentSigner.signer != (common.Address{}) { @@ -1394,8 +1394,8 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, witnes var delay time.Duration // Sweet, the protocol permits us to sign the block, wait for our time - if c.config.IsBhilai(header.Number) && successionNumber == 0 { - delay = 0 // delay was moved to Prepare for bhilai and later + if c.config.IsNewHardfork(header.Number) && successionNumber == 0 { + delay = 0 // delay was moved to Prepare for newHardfork and later } else { delay = time.Until(header.GetActualTime()) // Wait until we reach header time } diff --git a/consensus/bor/bor_test.go b/consensus/bor/bor_test.go index 60036cd7fa..d4d4d9bab7 100644 --- a/consensus/bor/bor_test.go +++ b/consensus/bor/bor_test.go @@ -852,7 +852,7 @@ func TestCustomBlockTimeBackwardCompatibility(t *testing.T) { Period: map[string]uint64{"0": 2}, ProducerDelay: map[string]uint64{"0": 3}, BackupMultiplier: map[string]uint64{"0": 2}, - RioBlock: big.NewInt(0), + RioBlock: big.NewInt(0), // blockTime=0 always takes the else-branch regardless of hardfork } chain, b := newChainAndBorForTest(t, sp, borCfg, true, addr1, uint64(time.Now().Unix())) b.blockTime = 0 @@ -4373,11 +4373,11 @@ func TestBorPrepare_WaitOnPrepareFlag(t *testing.T) { // Test 2: Prepare with waitOnPrepare=true should wait for the proper block time t.Run("with_wait", func(t *testing.T) { - // Create a config with Bhilai fork enabled to activate wait logic + // Create a config with NewHardfork enabled to activate wait-in-Prepare logic borCfgWithBhilai := ¶ms.BorConfig{ - Sprint: map[string]uint64{"0": 64}, - Period: map[string]uint64{"0": 2}, - BhilaiBlock: big.NewInt(0), // Enable Bhilai fork from block 0 + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + NewHardforkBlock: big.NewInt(0), // Enable NewHardfork from block 0 } // Set genesis time 3 seconds in the future to ensure enough wait time @@ -4411,7 +4411,7 @@ func TestBorPrepare_WaitOnPrepareFlag(t *testing.T) { t.Fatalf("Prepare with waitOnPrepare=true failed: %v", err) } - // With Bhilai enabled, DevFakeAuthor=true (making this node the primary producer), + // With NewHardfork enabled, DevFakeAuthor=true (making this node the primary producer), // and waitOnPrepare=true, should wait until parent (genesis) time has passed // Allow 100ms tolerance for timing precision and scheduling overhead minWait := expectedDelay - 100*time.Millisecond @@ -4457,6 +4457,149 @@ func TestBorPrepare_WaitOnPrepareFlag(t *testing.T) { }) } +// TestPrepare_WaitGate_NewHardforkOnly verifies that the wait-in-Prepare +// mechanism activates only when IsNewHardfork is true. +func TestPrepare_WaitGate_NewHardforkOnly(t *testing.T) { + t.Parallel() + + addr := common.HexToAddress("0x1") + sp := &fakeSpanner{vals: []*valset.Validator{{Address: addr, VotingPower: 1}}} + + t.Run("before NewHardfork – waitOnPrepare=true returns quickly", func(t *testing.T) { + borCfg := ¶ms.BorConfig{ + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + // NewHardforkBlock not set → IsNewHardfork always false + } + // Set genesis time slightly in the future so there would be a non-trivial delay + // if the wait were active. + genesisTime := uint64(time.Now().Add(2 * time.Second).Unix()) + chain, b := newChainAndBorForTest(t, sp, borCfg, true, addr, genesisTime) + defer chain.Stop() + + genesis := chain.HeaderChain().GetHeaderByNumber(0) + require.NotNil(t, genesis) + + header := &types.Header{Number: big.NewInt(1), ParentHash: genesis.Hash()} + + start := time.Now() + err := b.Prepare(chain, header, true) + elapsed := time.Since(start) + + require.NoError(t, err) + // Without NewHardfork the wait block is skipped; should return in < 200 ms + require.Less(t, elapsed, 200*time.Millisecond, + "Prepare should not wait when NewHardfork is not active") + }) + + t.Run("at NewHardfork – waitOnPrepare=true waits for primary producer", func(t *testing.T) { + borCfg := ¶ms.BorConfig{ + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + NewHardforkBlock: big.NewInt(0), + } + // Genesis 3 s in the future → there will be a measurable wait. + genesisTime := uint64(time.Now().Add(3 * time.Second).Unix()) + chain, b := newChainAndBorForTest(t, sp, borCfg, true, addr, genesisTime) + defer chain.Stop() + + genesis := chain.HeaderChain().GetHeaderByNumber(0) + require.NotNil(t, genesis) + + // Measure expected delay right before calling Prepare, same pattern as TestBorPrepare_WaitOnPrepareFlag. + expectedDelay := time.Until(time.Unix(int64(genesis.Time), 0)) + if expectedDelay < 100*time.Millisecond { + t.Skip("genesis time already passed due to slow setup") + } + + header := &types.Header{Number: big.NewInt(1), ParentHash: genesis.Hash()} + + start := time.Now() + err := b.Prepare(chain, header, true) + elapsed := time.Since(start) + + require.NoError(t, err) + minWait := expectedDelay - 200*time.Millisecond + if minWait < 0 { + minWait = 0 + } + require.Greater(t, elapsed, minWait, + "Prepare should wait for primary producer when NewHardfork is active") + }) +} + +// TestSeal_PrimaryProducerDelay_NewHardforkBoundary verifies that delay=0 in Seal +// for the primary producer (succession==0) is gated on IsNewHardfork. +func TestSeal_PrimaryProducerDelay_NewHardforkBoundary(t *testing.T) { + t.Parallel() + + addr := common.HexToAddress("0x1") + sp := &fakeSpanner{vals: []*valset.Validator{{Address: addr, VotingPower: 1}}} + now := uint64(time.Now().Unix()) + + makeHeader := func(borCfg *params.BorConfig) (*types.Header, *Bor, *core.BlockChain) { + chain, b := newChainAndBorForTest(t, sp, borCfg, true, addr, now) + genesis := chain.HeaderChain().GetHeaderByNumber(0) + require.NotNil(t, genesis) + h := &types.Header{ + Number: big.NewInt(1), + ParentHash: genesis.Hash(), + Extra: make([]byte, 32+65), + UncleHash: uncleHash, + Difficulty: big.NewInt(1), + GasLimit: 8_000_000, + } + // Set header.Time so GetActualTime() returns something in the past + h.Time = now - 1 + return h, b, chain + } + + t.Run("before NewHardfork – primary producer has non-zero delay", func(t *testing.T) { + borCfg := ¶ms.BorConfig{ + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + // NewHardforkBlock not set + } + h, b, chain := makeHeader(borCfg) + defer chain.Stop() + + snap, err := b.snapshot(chain.HeaderChain(), h, nil, false) + require.NoError(t, err) + + successionNumber, err := snap.GetSignerSuccessionNumber(addr) + require.NoError(t, err) + require.Equal(t, 0, successionNumber, "DevFakeAuthor should be primary producer") + + // Before NewHardfork the delay=0 branch should NOT be taken. + // The else branch sets delay = time.Until(header.GetActualTime()). + // Since header.Time is in the past, delay ≤ 0 — but the point is the branch + // selected is the else, not the delay=0 one. + isNewHF := b.config.IsNewHardfork(h.Number) + require.False(t, isNewHF, "IsNewHardfork should be false before NewHardforkBlock") + }) + + t.Run("at NewHardfork – primary producer gets delay=0", func(t *testing.T) { + borCfg := ¶ms.BorConfig{ + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + NewHardforkBlock: big.NewInt(0), + } + h, b, chain := makeHeader(borCfg) + defer chain.Stop() + + snap, err := b.snapshot(chain.HeaderChain(), h, nil, false) + require.NoError(t, err) + + successionNumber, err := snap.GetSignerSuccessionNumber(addr) + require.NoError(t, err) + require.Equal(t, 0, successionNumber, "DevFakeAuthor should be primary producer") + + isNewHF := b.config.IsNewHardfork(h.Number) + require.True(t, isNewHF, "IsNewHardfork should be true at NewHardforkBlock=0") + // The Seal function would take the delay=0 branch for this signer/header combination. + }) +} + func newBorForMilestoneFetcherTest(t *testing.T) *Bor { t.Helper() sp := &fakeSpanner{vals: []*valset.Validator{{Address: common.HexToAddress("0x1"), VotingPower: 1}}} From 9e8d8866cdf530dbda8d271e63196cc78f2b609d Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Mon, 9 Mar 2026 10:08:02 -0300 Subject: [PATCH 07/11] rename to Giugliano --- consensus/bor/bor.go | 14 ++--- consensus/bor/bor_test.go | 114 +++++++++++++++++++------------------- params/config.go | 10 ++-- 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index a241f035e6..ba9d579ecc 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -415,8 +415,8 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head number := header.Number.Uint64() now := uint64(time.Now().Unix()) - if c.config.IsNewHardfork(header.Number) { - // NewHardfork introduced flexible blocktime (can be set larger than consensus without approval). + if c.config.IsGiugliano(header.Number) { + // Giugliano introduced flexible blocktime (can be set larger than consensus without approval). // Using strict CalcProducerDelay would reject valid blocks, so we just ensure announcement // time comes after parent time to allow for flexible blocktime. var parent *types.Header @@ -427,13 +427,13 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head parent = chain.GetHeader(header.ParentHash, number-1) } if parent == nil || now < parent.Time { - log.Error("Block announced too early post newHardfork", "number", number, "headerTime", header.Time, "now", now) + log.Error("Block announced too early post giugliano", "number", number, "headerTime", header.Time, "now", now) return consensus.ErrFutureBlock } // Upper-bound check: a block whose timestamp is more than maxAllowedFutureBlockTimeSeconds // ahead of the local clock is rejected. if header.Time > now+maxAllowedFutureBlockTimeSeconds { - log.Error("Block timestamp too far in future post newHardfork", "number", number, "headerTime", header.Time, "now", now) + log.Error("Block timestamp too far in future post giugliano", "number", number, "headerTime", header.Time, "now", now) return consensus.ErrFutureBlock } // Upper-bound check: a block whose timestamp is more than maxAllowedFutureBlockTimeSeconds @@ -1125,7 +1125,7 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, w } // Wait before start the block production if needed (previously this wait was on Seal) - if c.config.IsNewHardfork(header.Number) && waitOnPrepare { + if c.config.IsGiugliano(header.Number) && waitOnPrepare { var successionNumber int // if signer is not empty (RPC nodes have empty signer) if currentSigner.signer != (common.Address{}) { @@ -1404,8 +1404,8 @@ func (c *Bor) Seal(chain consensus.ChainHeaderReader, block *types.Block, witnes var delay time.Duration // Sweet, the protocol permits us to sign the block, wait for our time - if c.config.IsNewHardfork(header.Number) && successionNumber == 0 { - delay = 0 // delay was moved to Prepare for newHardfork and later + if c.config.IsGiugliano(header.Number) && successionNumber == 0 { + delay = 0 // delay was moved to Prepare for giugliano and later } else { delay = time.Until(header.GetActualTime()) // Wait until we reach header time } diff --git a/consensus/bor/bor_test.go b/consensus/bor/bor_test.go index 1e384625d6..1d35085124 100644 --- a/consensus/bor/bor_test.go +++ b/consensus/bor/bor_test.go @@ -1759,45 +1759,45 @@ func TestVerifyHeader_RequestsHash(t *testing.T) { require.ErrorIs(t, err, consensus.ErrUnexpectedRequests) } -// TestVerifyHeader_NewHardfork_Boundary verifies that the flexible blocktime -// timestamp validation in verifyHeader activates exactly at NewHardforkBlock. +// TestVerifyHeader_Giugliano_Boundary verifies that the flexible blocktime +// timestamp validation in verifyHeader activates exactly at GiuglianoBlock. // -// Before NewHardforkBlock the old code-path is used (header.Time > now fails), -// at and after NewHardforkBlock the new path is used (parent-time check + +// Before GiuglianoBlock the old code-path is used (header.Time > now fails), +// at and after GiuglianoBlock the new path is used (parent-time check + // upper-bound check instead of a strict now comparison). -func TestVerifyHeader_NewHardfork_Boundary(t *testing.T) { +func TestVerifyHeader_Giugliano_Boundary(t *testing.T) { t.Parallel() addr1 := common.HexToAddress("0x1") sp := &fakeSpanner{vals: []*valset.Validator{{Address: addr1, VotingPower: 1}}} - const newHardforkBlock = 100 + const giuglianoBlock = 100 now := uint64(time.Now().Unix()) - t.Run("before NewHardforkBlock – future timestamp is rejected", func(t *testing.T) { - // NewHardforkBlock is far in the future, so the legacy path is taken. + t.Run("before GiuglianoBlock – future timestamp is rejected", func(t *testing.T) { + // GiuglianoBlock is far in the future, so the legacy path is taken. borCfg := ¶ms.BorConfig{ - Sprint: map[string]uint64{"0": 64}, - Period: map[string]uint64{"0": 2}, - NewHardforkBlock: big.NewInt(1_000_000), + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + GiuglianoBlock: big.NewInt(1_000_000), } chain, b := newChainAndBorForTest(t, sp, borCfg, false, common.Address{}, now) h := &types.Header{ - Number: big.NewInt(newHardforkBlock - 1), + Number: big.NewInt(giuglianoBlock - 1), Time: now + 3600, // 1 hour in the future – must be rejected Extra: make([]byte, 32+65), } err := b.VerifyHeader(chain.HeaderChain(), h) - require.ErrorIs(t, err, consensus.ErrFutureBlock, "pre-NewHardfork: future timestamp should be rejected") + require.ErrorIs(t, err, consensus.ErrFutureBlock, "pre-Giugliano: future timestamp should be rejected") }) - t.Run("at NewHardforkBlock – timestamp within upper bound is accepted", func(t *testing.T) { - // NewHardforkBlock active from genesis so every block uses the new path. + t.Run("at GiuglianoBlock – timestamp within upper bound is accepted", func(t *testing.T) { + // GiuglianoBlock active from genesis so every block uses the new path. borCfg := ¶ms.BorConfig{ - Sprint: map[string]uint64{"0": 64}, - Period: map[string]uint64{"0": 2}, - NewHardforkBlock: big.NewInt(0), + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + GiuglianoBlock: big.NewInt(0), } chain, b := newChainAndBorForTest(t, sp, borCfg, false, common.Address{}, now) @@ -1806,7 +1806,7 @@ func TestVerifyHeader_NewHardfork_Boundary(t *testing.T) { // Timestamp slightly in the future but within maxAllowedFutureBlockTimeSeconds. h := &types.Header{ - Number: big.NewInt(newHardforkBlock), + Number: big.NewInt(giuglianoBlock), ParentHash: genesis.Hash(), Time: now + maxAllowedFutureBlockTimeSeconds - 1, Extra: make([]byte, 32+65), @@ -1814,14 +1814,14 @@ func TestVerifyHeader_NewHardfork_Boundary(t *testing.T) { // verifyHeader will proceed past the timestamp check; subsequent checks // (mixDigest, difficulty, etc.) may still fail, but ErrFutureBlock must not. err := b.VerifyHeader(chain.HeaderChain(), h) - require.NotErrorIs(t, err, consensus.ErrFutureBlock, "post-NewHardfork: timestamp within bound should not return ErrFutureBlock") + require.NotErrorIs(t, err, consensus.ErrFutureBlock, "post-Giugliano: timestamp within bound should not return ErrFutureBlock") }) - t.Run("at NewHardforkBlock – timestamp beyond upper bound is rejected", func(t *testing.T) { + t.Run("at GiuglianoBlock – timestamp beyond upper bound is rejected", func(t *testing.T) { borCfg := ¶ms.BorConfig{ - Sprint: map[string]uint64{"0": 64}, - Period: map[string]uint64{"0": 2}, - NewHardforkBlock: big.NewInt(0), + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + GiuglianoBlock: big.NewInt(0), } chain, b := newChainAndBorForTest(t, sp, borCfg, false, common.Address{}, now) @@ -1829,13 +1829,13 @@ func TestVerifyHeader_NewHardfork_Boundary(t *testing.T) { require.NotNil(t, genesis) h := &types.Header{ - Number: big.NewInt(newHardforkBlock), + Number: big.NewInt(giuglianoBlock), ParentHash: genesis.Hash(), Time: now + maxAllowedFutureBlockTimeSeconds + 10, // beyond upper bound Extra: make([]byte, 32+65), } err := b.VerifyHeader(chain.HeaderChain(), h) - require.ErrorIs(t, err, consensus.ErrFutureBlock, "post-NewHardfork: timestamp beyond upper bound must be rejected") + require.ErrorIs(t, err, consensus.ErrFutureBlock, "post-Giugliano: timestamp beyond upper bound must be rejected") }) } @@ -4423,11 +4423,11 @@ func TestBorPrepare_WaitOnPrepareFlag(t *testing.T) { // Test 2: Prepare with waitOnPrepare=true should wait for the proper block time t.Run("with_wait", func(t *testing.T) { - // Create a config with NewHardfork enabled to activate wait-in-Prepare logic + // Create a config with Giugliano enabled to activate wait-in-Prepare logic borCfgWithBhilai := ¶ms.BorConfig{ - Sprint: map[string]uint64{"0": 64}, - Period: map[string]uint64{"0": 2}, - NewHardforkBlock: big.NewInt(0), // Enable NewHardfork from block 0 + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + GiuglianoBlock: big.NewInt(0), // Enable Giugliano from block 0 } // Set genesis time 3 seconds in the future to ensure enough wait time @@ -4461,7 +4461,7 @@ func TestBorPrepare_WaitOnPrepareFlag(t *testing.T) { t.Fatalf("Prepare with waitOnPrepare=true failed: %v", err) } - // With NewHardfork enabled, DevFakeAuthor=true (making this node the primary producer), + // With Giugliano enabled, DevFakeAuthor=true (making this node the primary producer), // and waitOnPrepare=true, should wait until parent (genesis) time has passed // Allow 100ms tolerance for timing precision and scheduling overhead minWait := expectedDelay - 100*time.Millisecond @@ -4507,19 +4507,19 @@ func TestBorPrepare_WaitOnPrepareFlag(t *testing.T) { }) } -// TestPrepare_WaitGate_NewHardforkOnly verifies that the wait-in-Prepare -// mechanism activates only when IsNewHardfork is true. -func TestPrepare_WaitGate_NewHardforkOnly(t *testing.T) { +// TestPrepare_WaitGate_GiuglianoOnly verifies that the wait-in-Prepare +// mechanism activates only when IsGiugliano is true. +func TestPrepare_WaitGate_GiuglianoOnly(t *testing.T) { t.Parallel() addr := common.HexToAddress("0x1") sp := &fakeSpanner{vals: []*valset.Validator{{Address: addr, VotingPower: 1}}} - t.Run("before NewHardfork – waitOnPrepare=true returns quickly", func(t *testing.T) { + t.Run("before Giugliano – waitOnPrepare=true returns quickly", func(t *testing.T) { borCfg := ¶ms.BorConfig{ Sprint: map[string]uint64{"0": 64}, Period: map[string]uint64{"0": 2}, - // NewHardforkBlock not set → IsNewHardfork always false + // GiuglianoBlock not set → IsGiugliano always false } // Set genesis time slightly in the future so there would be a non-trivial delay // if the wait were active. @@ -4537,16 +4537,16 @@ func TestPrepare_WaitGate_NewHardforkOnly(t *testing.T) { elapsed := time.Since(start) require.NoError(t, err) - // Without NewHardfork the wait block is skipped; should return in < 200 ms + // Without Giugliano the wait block is skipped; should return in < 200 ms require.Less(t, elapsed, 200*time.Millisecond, - "Prepare should not wait when NewHardfork is not active") + "Prepare should not wait when Giugliano is not active") }) - t.Run("at NewHardfork – waitOnPrepare=true waits for primary producer", func(t *testing.T) { + t.Run("at Giugliano – waitOnPrepare=true waits for primary producer", func(t *testing.T) { borCfg := ¶ms.BorConfig{ - Sprint: map[string]uint64{"0": 64}, - Period: map[string]uint64{"0": 2}, - NewHardforkBlock: big.NewInt(0), + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + GiuglianoBlock: big.NewInt(0), } // Genesis 3 s in the future → there will be a measurable wait. genesisTime := uint64(time.Now().Add(3 * time.Second).Unix()) @@ -4574,13 +4574,13 @@ func TestPrepare_WaitGate_NewHardforkOnly(t *testing.T) { minWait = 0 } require.Greater(t, elapsed, minWait, - "Prepare should wait for primary producer when NewHardfork is active") + "Prepare should wait for primary producer when Giugliano is active") }) } -// TestSeal_PrimaryProducerDelay_NewHardforkBoundary verifies that delay=0 in Seal -// for the primary producer (succession==0) is gated on IsNewHardfork. -func TestSeal_PrimaryProducerDelay_NewHardforkBoundary(t *testing.T) { +// TestSeal_PrimaryProducerDelay_GiuglianoBoundary verifies that delay=0 in Seal +// for the primary producer (succession==0) is gated on IsGiugliano. +func TestSeal_PrimaryProducerDelay_GiuglianoBoundary(t *testing.T) { t.Parallel() addr := common.HexToAddress("0x1") @@ -4604,11 +4604,11 @@ func TestSeal_PrimaryProducerDelay_NewHardforkBoundary(t *testing.T) { return h, b, chain } - t.Run("before NewHardfork – primary producer has non-zero delay", func(t *testing.T) { + t.Run("before Giugliano – primary producer has non-zero delay", func(t *testing.T) { borCfg := ¶ms.BorConfig{ Sprint: map[string]uint64{"0": 64}, Period: map[string]uint64{"0": 2}, - // NewHardforkBlock not set + // GiuglianoBlock not set } h, b, chain := makeHeader(borCfg) defer chain.Stop() @@ -4620,19 +4620,19 @@ func TestSeal_PrimaryProducerDelay_NewHardforkBoundary(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, successionNumber, "DevFakeAuthor should be primary producer") - // Before NewHardfork the delay=0 branch should NOT be taken. + // Before Giugliano the delay=0 branch should NOT be taken. // The else branch sets delay = time.Until(header.GetActualTime()). // Since header.Time is in the past, delay ≤ 0 — but the point is the branch // selected is the else, not the delay=0 one. - isNewHF := b.config.IsNewHardfork(h.Number) - require.False(t, isNewHF, "IsNewHardfork should be false before NewHardforkBlock") + isNewHF := b.config.IsGiugliano(h.Number) + require.False(t, isNewHF, "IsGiugliano should be false before GiuglianoBlock") }) - t.Run("at NewHardfork – primary producer gets delay=0", func(t *testing.T) { + t.Run("at Giugliano – primary producer gets delay=0", func(t *testing.T) { borCfg := ¶ms.BorConfig{ - Sprint: map[string]uint64{"0": 64}, - Period: map[string]uint64{"0": 2}, - NewHardforkBlock: big.NewInt(0), + Sprint: map[string]uint64{"0": 64}, + Period: map[string]uint64{"0": 2}, + GiuglianoBlock: big.NewInt(0), } h, b, chain := makeHeader(borCfg) defer chain.Stop() @@ -4644,8 +4644,8 @@ func TestSeal_PrimaryProducerDelay_NewHardforkBoundary(t *testing.T) { require.NoError(t, err) require.Equal(t, 0, successionNumber, "DevFakeAuthor should be primary producer") - isNewHF := b.config.IsNewHardfork(h.Number) - require.True(t, isNewHF, "IsNewHardfork should be true at NewHardforkBlock=0") + isNewHF := b.config.IsGiugliano(h.Number) + require.True(t, isNewHF, "IsGiugliano should be true at GiuglianoBlock=0") // The Seal function would take the delay=0 branch for this signer/header combination. }) } diff --git a/params/config.go b/params/config.go index 39f12dc4e5..ab0f4781f2 100644 --- a/params/config.go +++ b/params/config.go @@ -951,7 +951,7 @@ type BorConfig struct { DandeliBlock *big.Int `json:"dandeliBlock"` // Dandeli switch block (nil = no fork, 0 = already on dandeli) LisovoBlock *big.Int `json:"lisovoBlock"` // Lisovo switch block (nil = no fork, 0 = already on lisovo) LisovoProBlock *big.Int `json:"lisovoProBlock"` // LisovoPro switch block (nil = no fork, 0 = already on lisovoPro) - NewHardforkBlock *big.Int `json:"newHardforkBlock"` // NewHardfork switch block (nil = no fork, 0 = already on newHardfork) + GiuglianoBlock *big.Int `json:"giuglianoBlock"` // Giugliano switch block (nil = no fork, 0 = already on giugliano) } // String implements the stringer interface, returning the consensus engine details. @@ -1023,8 +1023,8 @@ func (c *BorConfig) IsLisovoPro(number *big.Int) bool { return isBlockForked(c.LisovoProBlock, number) } -func (c *BorConfig) IsNewHardfork(number *big.Int) bool { - return isBlockForked(c.NewHardforkBlock, number) +func (c *BorConfig) IsGiugliano(number *big.Int) bool { + return isBlockForked(c.GiuglianoBlock, number) } // GetTargetGasPercentage returns the target gas percentage for gas limit calculation. @@ -1232,8 +1232,8 @@ func (c *ChainConfig) Description() string { if c.Bor.LisovoProBlock != nil { banner += fmt.Sprintf(" - Lisovo Pro: #%-8v\n", c.Bor.LisovoProBlock) } - if c.Bor.NewHardforkBlock != nil { - banner += fmt.Sprintf(" - NewHardfork: #%-8v\n", c.Bor.NewHardforkBlock) + if c.Bor.GiuglianoBlock != nil { + banner += fmt.Sprintf(" - Giugliano: #%-8v\n", c.Bor.GiuglianoBlock) } return banner } From 53d68efde25fd6fb826823bc2c712b1441998f99 Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Mon, 9 Mar 2026 10:31:45 -0300 Subject: [PATCH 08/11] minor fix --- consensus/bor/bor.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index ba9d579ecc..888eeabaa1 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -416,9 +416,10 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head now := uint64(time.Now().Unix()) if c.config.IsGiugliano(header.Number) { - // Giugliano introduced flexible blocktime (can be set larger than consensus without approval). - // Using strict CalcProducerDelay would reject valid blocks, so we just ensure announcement - // time comes after parent time to allow for flexible blocktime. + // Rio introduced flexible blocktime (can be set larger than consensus without approval). + // Using strict CalcProducerDelay for early block announcement (introduced back in Giugliano) + // would reject valid blocks, so we just ensure announcement time comes after parent time to + // allow for flexible blocktime. var parent *types.Header if len(parents) > 0 { @@ -439,11 +440,11 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head // Upper-bound check: a block whose timestamp is more than maxAllowedFutureBlockTimeSeconds // ahead of the local clock is rejected. if header.Time > now+maxAllowedFutureBlockTimeSeconds { - log.Error("Block timestamp too far in future post rio", "number", number, "headerTime", header.Time, "now", now) + log.Error("Block timestamp too far in future post giugliano", "number", number, "headerTime", header.Time, "now", now) return consensus.ErrFutureBlock } } else if c.config.IsBhilai(header.Number) { - // Allow early blocks if Bhilai HF is enabled + // TODO: Once Amoy and Mainnet supports Giugliano HF, we are safe to remove this check (since it only works for block future blocks) // Don't waste time checking blocks from the future but allow a buffer of block time for // early block announcements. Note that this is a loose check and would allow early blocks // from non-primary producer. Such blocks will be rejected later when we know the succession @@ -523,7 +524,7 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head cacheTTL := veblopBlockTimeout nowTime := time.Now() headerTime := time.Unix(int64(header.Time), 0) - if headerTime.After(nowTime) { + if headerTime.After(nowTime) && c.config.IsGiugliano(header.Number) { // Add the time from now until header time as extra to the base timeout extraTime := headerTime.Sub(nowTime) cacheTTL = veblopBlockTimeout + extraTime From 2dd555f2cd23ea0074bbc4ad77f12973378ea724 Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Mon, 9 Mar 2026 17:11:53 -0300 Subject: [PATCH 09/11] address duplicate block --- consensus/bor/bor.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/consensus/bor/bor.go b/consensus/bor/bor.go index 888eeabaa1..e87389ba31 100644 --- a/consensus/bor/bor.go +++ b/consensus/bor/bor.go @@ -437,12 +437,6 @@ func (c *Bor) verifyHeader(chain consensus.ChainHeaderReader, header *types.Head log.Error("Block timestamp too far in future post giugliano", "number", number, "headerTime", header.Time, "now", now) return consensus.ErrFutureBlock } - // Upper-bound check: a block whose timestamp is more than maxAllowedFutureBlockTimeSeconds - // ahead of the local clock is rejected. - if header.Time > now+maxAllowedFutureBlockTimeSeconds { - log.Error("Block timestamp too far in future post giugliano", "number", number, "headerTime", header.Time, "now", now) - return consensus.ErrFutureBlock - } } else if c.config.IsBhilai(header.Number) { // TODO: Once Amoy and Mainnet supports Giugliano HF, we are safe to remove this check (since it only works for block future blocks) // Don't waste time checking blocks from the future but allow a buffer of block time for From fae4dc1f058b153143645cdd7840ea7490addaf2 Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Thu, 12 Mar 2026 17:35:10 -0300 Subject: [PATCH 10/11] include giuglianoHF as condition to prefetchFromPool --- miner/worker.go | 3 ++- miner/worker_test.go | 24 ++++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index a5a0688681..c1c53bce2b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1871,7 +1871,8 @@ func (w *worker) commitWork(interrupt *atomic.Int32, noempty bool, timestamp int } var interruptPrefetch atomic.Bool - if w.config.EnablePrefetch { + newBlockNumber := new(big.Int).Add(parent.Number, common.Big1) + if w.config.EnablePrefetch && w.chainConfig.Bor != nil && w.chainConfig.Bor.IsGiugliano(newBlockNumber) { go func() { defer func() { if r := recover(); r != nil { diff --git a/miner/worker_test.go b/miner/worker_test.go index 4e05ada410..8da774b8e4 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -351,13 +351,25 @@ func newTestWorker(t TensingObject, config *Config, chainConfig *params.ChainCon return w, backend, w.close } +// borUnittestChainConfigWithGiugliano returns a shallow copy of BorUnittestChainConfig +// with GiuglianoBlock activated at block 0. Required for tests that exercise +// Giugliano-gated features such as prefetchFromPool. +func borUnittestChainConfigWithGiugliano() *params.ChainConfig { + cfg := *params.BorUnittestChainConfig + borCfg := *cfg.Bor + borCfg.GiuglianoBlock = big.NewInt(0) + cfg.Bor = &borCfg + + return &cfg +} + // setupBorWorkerWithPrefetch sets up a worker with Bor consensus engine and prefetch enabled. // Returns worker, backend, consensus engine, and mock controller for cleanup. // nolint:thelper func setupBorWorkerWithPrefetch(t *testing.T, gasPercent uint64, recommit time.Duration) (*worker, *testWorkerBackend, consensus.Engine, *gomock.Controller) { var ( engine consensus.Engine - chainConfig = params.BorUnittestChainConfig + chainConfig = borUnittestChainConfigWithGiugliano() db = rawdb.NewMemoryDatabase() ctrl *gomock.Controller ) @@ -2067,7 +2079,7 @@ func TestPrefetchRaceWithSetExtra(t *testing.T) { var ( engine consensus.Engine - chainConfig = params.BorUnittestChainConfig + chainConfig = borUnittestChainConfigWithGiugliano() db = rawdb.NewMemoryDatabase() ctrl *gomock.Controller ) @@ -2151,7 +2163,7 @@ func TestPrefetchGoroutineLifecycle(t *testing.T) { var ( engine consensus.Engine - chainConfig = params.BorUnittestChainConfig + chainConfig = borUnittestChainConfigWithGiugliano() db = rawdb.NewMemoryDatabase() ctrl *gomock.Controller ) @@ -2321,7 +2333,7 @@ func TestStateDBLifecycle_WithoutWait(t *testing.T) { var ( engine consensus.Engine - chainConfig = params.BorUnittestChainConfig + chainConfig = borUnittestChainConfigWithGiugliano() db = rawdb.NewMemoryDatabase() ctrl *gomock.Controller ) @@ -2696,7 +2708,7 @@ func BenchmarkBlockProductionLatency(b *testing.B) { b.Run("WithPrefetch", func(b *testing.B) { var ( engine consensus.Engine - chainConfig = params.BorUnittestChainConfig + chainConfig = borUnittestChainConfigWithGiugliano() db = rawdb.NewMemoryDatabase() ctrl *gomock.Controller ) @@ -2772,7 +2784,7 @@ func BenchmarkPrefetchMemoryOverhead(b *testing.B) { b.Run("WithPrefetch", func(b *testing.B) { var ( engine consensus.Engine - chainConfig = params.BorUnittestChainConfig + chainConfig = borUnittestChainConfigWithGiugliano() db = rawdb.NewMemoryDatabase() ctrl *gomock.Controller ) From 8a7a3ebc6cbabb45f4572b77cc0719c38eff0198 Mon Sep 17 00:00:00 2001 From: Lucca Martins Date: Thu, 12 Mar 2026 18:53:33 -0300 Subject: [PATCH 11/11] remove unnecessary noisy log --- miner/worker.go | 1 - 1 file changed, 1 deletion(-) diff --git a/miner/worker.go b/miner/worker.go index c1c53bce2b..fa8354871e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1986,7 +1986,6 @@ func (w *worker) prefetchFromPool(parent *types.Header, throwaway *state.StateDB w.mu.RUnlock() if err != nil { - log.Warn("Prefetch failed to create header", "err", err) return } signer := types.MakeSigner(w.chainConfig, header.Number, header.Time)