@@ -2,8 +2,9 @@ use alloc::sync::Arc;
22use bitcoin:: consensus:: encode:: deserialize;
33use bitcoin:: hex:: { DisplayHex as _, FromHex as _} ;
44use core:: str:: FromStr as _;
5+ use core:: { fmt, fmt:: Write as _} ;
56
6- use bitcoin_rs_chain:: NodeStatus ;
7+ use bitcoin_rs_chain:: { NodeStatus , TipSnapshot } ;
78use bitcoin_rs_primitives:: Hash256 ;
89use bitcoin_rs_pruning:: policy:: CORE_REORG_SAFETY_MARGIN ;
910use sonic_rs:: { JsonContainerTrait as _, JsonValueTrait , Value , json} ;
@@ -14,13 +15,18 @@ use crate::handlers::{ensure_no_params, optional_bool, params_array, required_st
1415
1516pub ( crate ) fn getblockchaininfo ( ctx : & Arc < Context > , params : & Value ) -> Result < Value , RpcError > {
1617 ensure_no_params ( params) ?;
17- let applied = ctx. applied_height ( ) ;
18+ let applied_tip = ctx. applied_tip . load_full ( ) ;
19+ let applied = applied_tip. as_ref ( ) . map_or ( 0 , |tip| tip. height ) ;
1820 let headers = ctx. height ( ) ;
19- let difficulty = ctx . applied_tip . load_full ( ) . map_or ( 0.0 , |tip| {
21+ let ( difficulty, time , mediantime ) = applied_tip. as_ref ( ) . map_or ( ( 0.0 , 0_u64 , 0_u64 ) , |tip| {
2022 let tree = ctx. block_tree . read ( ) ;
21- tree. node ( tip. tip_id )
22- . ok ( )
23- . map_or ( 0.0 , |node| ctx. difficulty_for_bits ( node. header . bits ) )
23+ tree. node ( tip. tip_id ) . map_or ( ( 0.0 , 0 , 0 ) , |node| {
24+ (
25+ ctx. difficulty_for_bits ( node. header . bits ) ,
26+ u64:: from ( node. header . time ) ,
27+ u64:: from ( tree. median_time_past_at ( tip. tip_id , 11 ) . unwrap_or ( 0 ) ) ,
28+ )
29+ } )
2430 } ) ;
2531 let verification_progress = if headers > 0 {
2632 f64:: from ( applied) / f64:: from ( headers)
@@ -40,16 +46,21 @@ pub(crate) fn getblockchaininfo(ctx: &Arc<Context>, params: &Value) -> Result<Va
4046 fold_block_records ( & blocks, applied, None )
4147 } ;
4248 let prune_status = ctx. prune_status ( ) ;
43- let bestblockhash = ctx. applied_hash ( ) . to_string_be ( ) ;
44- let chainwork = ctx. chainwork_hex ( ) ;
49+ let bestblockhash = applied_tip
50+ . as_ref ( )
51+ . map_or_else ( Hash256 :: default, |tip| tip. hash )
52+ . to_string_be ( ) ;
53+ let chainwork = applied_tip
54+ . as_deref ( )
55+ . map_or_else ( || ctx. chainwork_hex ( ) , chainwork_hex) ;
4556 let mut response = sonic_rs:: Object :: new ( ) ;
4657 let _ = response. insert ( & "chain" , chain) ;
4758 let _ = response. insert ( & "blocks" , applied) ;
4859 let _ = response. insert ( & "headers" , headers) ;
4960 let _ = response. insert ( & "bestblockhash" , bestblockhash. as_str ( ) ) ;
5061 let _ = response. insert ( & "difficulty" , json ! ( difficulty) ) ;
51- let _ = response. insert ( & "time" , 0_u64 ) ;
52- let _ = response. insert ( & "mediantime" , 0_u64 ) ;
62+ let _ = response. insert ( & "time" , time ) ;
63+ let _ = response. insert ( & "mediantime" , mediantime ) ;
5364 let _ = response. insert ( & "verificationprogress" , json ! ( verification_progress) ) ;
5465 let _ = response. insert ( & "initialblockdownload" , applied < headers) ;
5566 let _ = response. insert ( & "chainwork" , chainwork. as_str ( ) ) ;
@@ -61,6 +72,15 @@ pub(crate) fn getblockchaininfo(ctx: &Arc<Context>, params: &Value) -> Result<Va
6172 let _ = response. insert ( & "warnings" , "" ) ;
6273 Ok ( Value :: from ( response) )
6374}
75+
76+ fn chainwork_hex ( tip : & TipSnapshot ) -> String {
77+ let bytes: [ u8 ; 32 ] = tip. chainwork . to_be_bytes ( ) ;
78+ let mut out = String :: with_capacity ( bytes. len ( ) * 2 ) ;
79+ for byte in bytes {
80+ let _: fmt:: Result = write ! ( & mut out, "{byte:02x}" ) ;
81+ }
82+ out
83+ }
6484pub ( crate ) fn getdifficulty ( ctx : & Arc < Context > , params : & Value ) -> Result < Value , RpcError > {
6585 ensure_no_params ( params) ?;
6686 let difficulty = {
@@ -941,10 +961,55 @@ mod tests {
941961 use core:: sync:: atomic:: { AtomicUsize , Ordering } ;
942962
943963 use bitcoin:: blockdata:: constants:: genesis_block;
964+ use bitcoin:: hashes:: Hash as _;
965+ use bitcoin:: { BlockHash , CompactTarget , TxMerkleNode , block:: Header , block:: Version } ;
944966
945967 use super :: * ;
946968 use bitcoin_rs_chain:: { ChainWork , NodeId , TipSnapshot } ;
947969
970+ fn context_with_tip (
971+ network : bitcoin_rs_primitives:: Network ,
972+ bits : u32 ,
973+ times : & [ u32 ] ,
974+ ) -> Arc < Context > {
975+ let mut context = Context :: new ( ) ;
976+ context. chain_network = network;
977+ let ctx = Arc :: new ( context) ;
978+ let ( tip_id, tip_hash) = {
979+ let mut tree = ctx. block_tree . write ( ) ;
980+ let mut parent = None ;
981+ let mut previous_hash = BlockHash :: all_zeros ( ) ;
982+ let mut tip_id = NodeId :: new ( 0 ) ;
983+ let mut tip_hash = Hash256 :: default ( ) ;
984+ for ( index, time) in times. iter ( ) . copied ( ) . enumerate ( ) {
985+ let header = Header {
986+ version : Version :: ONE ,
987+ prev_blockhash : previous_hash,
988+ merkle_root : TxMerkleNode :: all_zeros ( ) ,
989+ time,
990+ bits : CompactTarget :: from_consensus ( bits) ,
991+ nonce : u32:: try_from ( index) . unwrap_or ( u32:: MAX ) ,
992+ } ;
993+ previous_hash = header. block_hash ( ) ;
994+ tip_id = tree
995+ . insert_node ( parent, header, NodeStatus :: Active )
996+ . unwrap_or ( tip_id) ;
997+ tip_hash = Hash256 :: from_le_bytes ( previous_hash. as_byte_array ( ) ) ;
998+ parent = Some ( tip_id) ;
999+ }
1000+ ( tip_id, tip_hash)
1001+ } ;
1002+ let tip = TipSnapshot {
1003+ tip_id,
1004+ height : u32:: try_from ( times. len ( ) . saturating_sub ( 1 ) ) . unwrap_or ( u32:: MAX ) ,
1005+ chainwork : ChainWork :: ZERO ,
1006+ hash : tip_hash,
1007+ } ;
1008+ ctx. set_chain_tip ( tip. clone ( ) ) ;
1009+ ctx. set_applied_tip ( tip) ;
1010+ ctx
1011+ }
1012+
9481013 #[ test]
9491014 fn subsidy_at_height_genesis_is_50_btc ( ) {
9501015 assert_eq ! ( subsidy_at_height( 0 ) , 5_000_000_000 ) ;
@@ -1269,6 +1334,90 @@ mod tests {
12691334 ) ;
12701335 }
12711336
1337+ #[ test]
1338+ fn difficulty_matches_core_for_mainnet_and_regtest_targets ( ) {
1339+ let ctx = Context :: new ( ) ;
1340+ let mainnet = ctx. difficulty_for_bits ( CompactTarget :: from_consensus ( 0x1d00_ffff ) ) ;
1341+ assert_eq ! ( mainnet. to_bits( ) , 1.0_f64 . to_bits( ) ) ;
1342+ let regtest = ctx. difficulty_for_bits ( CompactTarget :: from_consensus ( 0x207f_ffff ) ) ;
1343+ let expected = 4.656_542_373_906_924_7e-10_f64 ;
1344+ assert_eq ! ( regtest. to_bits( ) , expected. to_bits( ) ) ;
1345+ }
1346+
1347+ #[ test]
1348+ fn getblockchaininfo_reports_tip_time_and_median_time_past ( ) {
1349+ let ctx = context_with_tip (
1350+ bitcoin_rs_primitives:: Network :: Regtest ,
1351+ 0x207f_ffff ,
1352+ & [ 100 , 300 , 200 ] ,
1353+ ) ;
1354+ let result = getblockchaininfo ( & ctx, & json ! ( [ ] ) )
1355+ . unwrap_or_else ( |err| panic ! ( "getblockchaininfo failed: {err}" ) ) ;
1356+
1357+ assert_eq ! (
1358+ result. get( "time" ) . and_then( JsonValueTrait :: as_u64) ,
1359+ Some ( 200 )
1360+ ) ;
1361+ assert_eq ! (
1362+ result. get( "mediantime" ) . and_then( JsonValueTrait :: as_u64) ,
1363+ Some ( 200 )
1364+ ) ;
1365+ let difficulty = result
1366+ . get ( "difficulty" )
1367+ . and_then ( JsonValueTrait :: as_f64)
1368+ . unwrap_or_default ( ) ;
1369+ assert_eq ! (
1370+ difficulty. to_bits( ) ,
1371+ 4.656_542_373_906_924_7e-10_f64 . to_bits( )
1372+ ) ;
1373+ }
1374+
1375+ #[ test]
1376+ fn getblockchaininfo_uses_one_applied_tip_snapshot ( ) {
1377+ use bitcoin_rs_chain:: ChainWork ;
1378+
1379+ let ctx = context_with_tip (
1380+ bitcoin_rs_primitives:: Network :: Regtest ,
1381+ 0x207f_ffff ,
1382+ & [ 100 , 300 , 200 ] ,
1383+ ) ;
1384+ let Some ( applied) = ctx. applied_tip . load_full ( ) else {
1385+ panic ! ( "applied tip missing" ) ;
1386+ } ;
1387+ let applied_chainwork = ChainWork :: from_be_bytes ( [ 1 ; 32 ] ) ;
1388+ ctx. set_applied_tip ( TipSnapshot {
1389+ chainwork : applied_chainwork,
1390+ ..( * applied) . clone ( )
1391+ } ) ;
1392+ ctx. set_chain_tip ( TipSnapshot {
1393+ tip_id : applied. tip_id ,
1394+ height : 99 ,
1395+ chainwork : ChainWork :: from_be_bytes ( [ 2 ; 32 ] ) ,
1396+ hash : Hash256 :: from_le_bytes ( & [ 9 ; 32 ] ) ,
1397+ } ) ;
1398+
1399+ let result = getblockchaininfo ( & ctx, & json ! ( [ ] ) )
1400+ . unwrap_or_else ( |err| panic ! ( "getblockchaininfo failed: {err}" ) ) ;
1401+ let expected_hash = applied. hash . to_string_be ( ) ;
1402+ let expected_chainwork = "01" . repeat ( 32 ) ;
1403+ assert_eq ! (
1404+ result. get( "blocks" ) . and_then( JsonValueTrait :: as_u64) ,
1405+ Some ( 2 )
1406+ ) ;
1407+ assert_eq ! (
1408+ result. get( "headers" ) . and_then( JsonValueTrait :: as_u64) ,
1409+ Some ( 99 )
1410+ ) ;
1411+ assert_eq ! (
1412+ result. get( "bestblockhash" ) . and_then( JsonValueTrait :: as_str) ,
1413+ Some ( expected_hash. as_str( ) )
1414+ ) ;
1415+ assert_eq ! (
1416+ result. get( "chainwork" ) . and_then( JsonValueTrait :: as_str) ,
1417+ Some ( expected_chainwork. as_str( ) )
1418+ ) ;
1419+ }
1420+
12721421 #[ test]
12731422 fn getblockchaininfo_size_on_disk_uses_metadata_body_size ( ) {
12741423 let genesis = genesis_block ( bitcoin:: Network :: Regtest ) ;
0 commit comments