@@ -696,3 +696,71 @@ func TestAPI(t *testing.T) {
696696 t .Run (subtest .name , subtest .test )
697697 }
698698}
699+
700+ func TestDifficultyMetricsOffByOneRegression (t * testing.T ) {
701+ network , genesisBlock := ctestutil .Network ()
702+ pk := types .GeneratePrivateKey ()
703+ addr := types .StandardUnlockHash (pk .PublicKey ())
704+ genesisBlock .Transactions [0 ].SiacoinOutputs [0 ].Address = addr
705+
706+ scanCfg := config.Scanner {
707+ NumThreads : 100 ,
708+ ScanTimeout : 30 * time .Second ,
709+ ScanFrequency : 100 * time .Millisecond ,
710+ ScanInterval : 3 * time .Hour ,
711+ MinLastAnnouncement : 90 * 24 * time .Hour ,
712+ }
713+ e , cm , err := newExplorer (t , network , genesisBlock , scanCfg )
714+ if err != nil {
715+ t .Fatal (err )
716+ }
717+
718+ listenAddr := "127.0.0.1:9998"
719+ _ , err = newServer (t , cm , e , listenAddr )
720+ if err != nil {
721+ t .Fatal (err )
722+ }
723+
724+ // Mine 450 blocks to ensure we have enough data for our test range (150-450)
725+ for range 450 {
726+ cs := cm .TipState ()
727+ b := types.Block {
728+ ParentID : cs .Index .ID ,
729+ Timestamp : genesisBlock .Timestamp .Add (time .Duration (cs .Index .Height + 1 ) * network .BlockInterval ),
730+ MinerPayouts : []types.SiacoinOutput {{Value : cs .BlockReward ()}},
731+ }
732+ if ! coreutils .FindBlockNonce (cs , & b , time .Second ) {
733+ panic ("failed to mine test block quickly enough" )
734+ }
735+ if err := cm .AddBlocks ([]types.Block {b }); err != nil {
736+ t .Fatal (err )
737+ }
738+ }
739+ for {
740+ tip , _ := e .Tip ()
741+ if tip .Height == 450 {
742+ break
743+ }
744+ time .Sleep (100 * time .Millisecond )
745+ }
746+
747+ client := api .NewClient ("http://" + listenAddr + "/api" , testPassword )
748+
749+ resp , err := client .DifficultyMetrics (150 , 450 )
750+ if err != nil {
751+ t .Fatal (err )
752+ }
753+
754+ testutil .Equal (t , "blocks per step" , 2 , resp .BlocksPerStep )
755+ testutil .Equal (t , "difficulties length" , 150 , len (resp .Difficulties ))
756+ testutil .Equal (t , "block times length" , 150 , len (resp .BlockTimes ))
757+ testutil .Equal (t , "drifts length" , 150 , len (resp .Drifts ))
758+
759+ // Verify response starts at height 150
760+ index , _ := cm .BestIndex (150 )
761+ cs , _ := cm .State (index .ID )
762+ testutil .Equal (t , "first difficulty is for height 150" , cs .Difficulty , resp .Difficulties [0 ])
763+
764+ // Verify first block time is calculated from height 148 to 150
765+ testutil .Equal (t , "first block time" , cs .PrevTimestamps [0 ].Sub (cs .PrevTimestamps [2 ])/ 2 , resp .BlockTimes [0 ])
766+ }
0 commit comments