@@ -50,7 +50,7 @@ func (s *fakeSpanner) CommitSpan(ctx context.Context, _ borTypes.Span, _ []stake
5050}
5151
5252// newChainAndBorForTest centralizes common Bor + HeaderChain initialization for tests
53- func newChainAndBorForTest (t * testing.T , sp Spanner , borCfg * params.BorConfig , devFake bool , signerAddr common.Address ) (* core.BlockChain , * Bor ) {
53+ func newChainAndBorForTest (t * testing.T , sp Spanner , borCfg * params.BorConfig , devFake bool , signerAddr common.Address , genesisTime uint64 ) (* core.BlockChain , * Bor ) {
5454 cfg := & params.ChainConfig {ChainID : big .NewInt (1 ), Bor : borCfg }
5555
5656 b := & Bor {chainConfig : cfg , config : cfg .Bor , DevFakeAuthor : devFake }
@@ -76,8 +76,9 @@ func newChainAndBorForTest(t *testing.T, sp Spanner, borCfg *params.BorConfig, d
7676 if devFake && signerAddr != (common.Address {}) {
7777 b .authorizedSigner .Store (& signer {signer : signerAddr })
7878 }
79+ b .parentActualTimeCache , _ = lru .New (10 )
7980
80- genspec := & core.Genesis {Config : cfg }
81+ genspec := & core.Genesis {Config : cfg , Timestamp : genesisTime }
8182 db := rawdb .NewMemoryDatabase ()
8283 _ = genspec .MustCommit (db , triedb .NewDatabase (db , triedb .HashDefaults ))
8384 chain , err := core .NewBlockChain (rawdb .NewMemoryDatabase (), genspec , b , core .DefaultConfig ())
@@ -392,7 +393,7 @@ func TestPerformSpanCheck(t *testing.T) {
392393 t .Run (c .name , func (t * testing.T ) {
393394 sp := & fakeSpanner {vals : []* valset.Validator {{Address : addr2 , VotingPower : 1 }}}
394395 borCfg := & params.BorConfig {Sprint : map [string ]uint64 {"0" : 64 }, Period : map [string ]uint64 {"0" : 2 }}
395- chain , b := newChainAndBorForTest (t , sp , borCfg , false , common.Address {})
396+ chain , b := newChainAndBorForTest (t , sp , borCfg , false , common.Address {}, uint64 ( time . Now (). Unix ()) )
396397
397398 var parents []* types.Header
398399 var parentHash common.Hash
@@ -469,7 +470,7 @@ func TestGetVeBlopSnapshot(t *testing.T) {
469470 t .Run (c .name , func (t * testing.T ) {
470471 sp := & fakeSpanner {vals : c .spVals }
471472 borCfg := & params.BorConfig {Sprint : map [string ]uint64 {"0" : 64 }, Period : map [string ]uint64 {"0" : 2 }, RioBlock : big .NewInt (0 )}
472- chain , b := newChainAndBorForTest (t , sp , borCfg , false , common.Address {})
473+ chain , b := newChainAndBorForTest (t , sp , borCfg , false , common.Address {}, uint64 ( time . Now (). Unix ()) )
473474 h := & types.Header {Number : big .NewInt (int64 (c .targetNum ))}
474475 snap , err := b .getVeBlopSnapshot (chain .HeaderChain (), h , nil , c .checkNewSpan )
475476 require .NoError (t , err )
@@ -516,7 +517,7 @@ func TestSnapshot(t *testing.T) {
516517 sp := & fakeSpanner {vals : c .spVals }
517518 // Configure RioBlock far in the future so IsRio(header.Number) == false
518519 borCfg := & params.BorConfig {Sprint : map [string ]uint64 {"0" : 64 }, Period : map [string ]uint64 {"0" : 2 }, RioBlock : big .NewInt (1_000_000 )}
519- chain , b := newChainAndBorForTest (t , sp , borCfg , false , common.Address {})
520+ chain , b := newChainAndBorForTest (t , sp , borCfg , false , common.Address {}, uint64 ( time . Now (). Unix ()) )
520521 gen := chain .HeaderChain ().GetHeaderByNumber (0 )
521522 require .NotNil (t , gen )
522523 target := & types.Header {Number : big .NewInt (1 ), ParentHash : gen .Hash ()}
@@ -590,7 +591,7 @@ func TestCustomBlockTimeValidation(t *testing.T) {
590591 Period : map [string ]uint64 {"0" : tc .consensusPeriod },
591592 RioBlock : big .NewInt (0 ), // Enable Rio from genesis
592593 }
593- chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 )
594+ chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 , uint64 ( time . Now (). Unix ()) )
594595 b .blockTime = tc .blockTime
595596
596597 // Get genesis block as parent
@@ -626,7 +627,7 @@ func TestCustomBlockTimeCalculation(t *testing.T) {
626627 Period : map [string ]uint64 {"0" : 2 },
627628 RioBlock : big .NewInt (0 ),
628629 }
629- chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 )
630+ chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 , uint64 ( time . Now (). Unix ()) )
630631 b .blockTime = 5 * time .Second
631632
632633 genesis := chain .HeaderChain ().GetHeaderByNumber (0 )
@@ -652,7 +653,7 @@ func TestCustomBlockTimeCalculation(t *testing.T) {
652653 Period : map [string ]uint64 {"0" : 2 },
653654 RioBlock : big .NewInt (0 ),
654655 }
655- chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 )
656+ chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 , uint64 ( time . Now (). Unix ()) )
656657 b .blockTime = 3 * time .Second
657658
658659 genesis := chain .HeaderChain ().GetHeaderByNumber (0 )
@@ -678,22 +679,23 @@ func TestCustomBlockTimeCalculation(t *testing.T) {
678679 Period : map [string ]uint64 {"0" : 2 },
679680 RioBlock : big .NewInt (0 ),
680681 }
681- chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 )
682+ chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 , uint64 ( time . Now (). Unix ()) )
682683 b .blockTime = 4 * time .Second
683684
684685 genesis := chain .HeaderChain ().GetHeaderByNumber (0 )
685686 require .NotNil (t , genesis )
686687 baseTime := genesis .Time
688+ parentHash := genesis .Hash ()
687689
688690 if baseTime > 10 {
689- b .lastMinedBlockTime = time .Unix (int64 (baseTime - 10 ), 0 )
691+ b .parentActualTimeCache . Add ( parentHash , time .Unix (int64 (baseTime - 10 ), 0 ) )
690692 } else {
691- b .lastMinedBlockTime = time .Unix (0 , 0 )
693+ b .parentActualTimeCache . Add ( parentHash , time .Unix (0 , 0 ) )
692694 }
693695
694696 header := & types.Header {
695697 Number : big .NewInt (1 ),
696- ParentHash : genesis . Hash () ,
698+ ParentHash : parentHash ,
697699 }
698700
699701 err := b .Prepare (chain .HeaderChain (), header )
@@ -718,7 +720,7 @@ func TestCustomBlockTimeBackwardCompatibility(t *testing.T) {
718720 BackupMultiplier : map [string ]uint64 {"0" : 2 },
719721 RioBlock : big .NewInt (0 ),
720722 }
721- chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 )
723+ chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 , uint64 ( time . Now (). Unix ()) )
722724 b .blockTime = 0
723725
724726 genesis := chain .HeaderChain ().GetHeaderByNumber (0 )
@@ -736,6 +738,53 @@ func TestCustomBlockTimeBackwardCompatibility(t *testing.T) {
736738 })
737739}
738740
741+ func TestCustomBlockTimeClampsToNowAlsoUpdatesActualTime (t * testing.T ) {
742+ t .Parallel ()
743+
744+ addr1 := common .HexToAddress ("0x1" )
745+ // Force parent time far in the past so that after adding blockTime, header.Time is still < now
746+ // and the "clamp to now" block triggers.
747+ pastParentTime := time .Now ().Add (- 10 * time .Minute ).Unix ()
748+
749+ sp := & fakeSpanner {vals : []* valset.Validator {{Address : addr1 , VotingPower : 1 }}}
750+ borCfg := & params.BorConfig {
751+ Sprint : map [string ]uint64 {"0" : 64 },
752+ Period : map [string ]uint64 {"0" : 2 },
753+ RioBlock : big .NewInt (0 ), // Rio enabled from genesis
754+ }
755+ chain , b := newChainAndBorForTest (t , sp , borCfg , true , addr1 , uint64 (pastParentTime ))
756+
757+ // Enable custom block time (must be >= Period to avoid validation error)
758+ b .blockTime = 5 * time .Second
759+
760+ genesis := chain .HeaderChain ().GetHeaderByNumber (0 )
761+ require .NotNil (t , genesis )
762+
763+ header := & types.Header {
764+ Number : big .NewInt (1 ),
765+ ParentHash : genesis .Hash (),
766+ }
767+
768+ before := time .Now ()
769+ err := b .Prepare (chain .HeaderChain (), header )
770+ after := time .Now ()
771+
772+ require .NoError (t , err )
773+
774+ // Validate the clamp happened: header.Time should be "now-ish", not the past-derived time.
775+ require .GreaterOrEqual (t , int64 (header .Time ), before .Unix (), "header.Time should be clamped up to now" )
776+ require .LessOrEqual (t , int64 (header .Time ), after .Unix ()+ 1 , "header.Time should be close to now" )
777+
778+ // Critical regression assertion:
779+ // When custom blockTime is enabled for Rio, clamping header.Time to now must also set ActualTime = now.
780+ require .False (t , header .ActualTime .IsZero (), "ActualTime should be set when blockTime > 0 and Rio is enabled" )
781+ require .GreaterOrEqual (t , header .ActualTime .Unix (), before .Unix (), "ActualTime should be updated to now when clamping occurs" )
782+ require .LessOrEqual (t , header .ActualTime .Unix (), after .Unix ()+ 1 , "ActualTime should be close to now when clamping occurs" )
783+
784+ // Optional: since clamping sets both from the same `now`, they should match on Unix seconds.
785+ require .Equal (t , int64 (header .Time ), header .ActualTime .Unix (), "header.Time and ActualTime should align after clamping" )
786+ }
787+
739788func TestVerifySealRejectsOversizedDifficulty (t * testing.T ) {
740789 t .Parallel ()
741790
@@ -757,7 +806,7 @@ func TestVerifySealRejectsOversizedDifficulty(t *testing.T) {
757806 }
758807
759808 // devFake=false, we need real signatures for the sake of this test
760- chain , b := newChainAndBorForTest (t , sp , borCfg , false , common.Address {})
809+ chain , b := newChainAndBorForTest (t , sp , borCfg , false , common.Address {}, uint64 ( time . Now (). Unix ()) )
761810
762811 parent := chain .HeaderChain ().GetHeaderByNumber (0 )
763812 require .NotNil (t , parent )
0 commit comments