@@ -230,51 +230,107 @@ pub(crate) fn getblockheader(ctx: &Arc<Context>, params: &Value) -> Result<Value
230230}
231231
232232pub ( crate ) fn getblockstats ( ctx : & Arc < Context > , params : & Value ) -> Result < Value , RpcError > {
233+ use bitcoin:: consensus:: encode:: deserialize;
234+ use bitcoin:: hex:: FromHex as _;
235+
233236 let target = params_array ( params) ?
234237 . first ( )
235238 . ok_or ( RpcError :: InvalidParams ( "hash_or_height is required" ) ) ?;
236239 let height = if let Some ( height) = target. as_u64 ( ) {
237240 u32:: try_from ( height) . map_err ( |_| RpcError :: InvalidParams ( "height exceeds u32" ) ) ?
238241 } else if let Some ( hash) = target. as_str ( ) {
239- parse_hash ( hash) ?;
240- ctx. height ( )
242+ let block_hash = parse_hash ( hash) ?;
243+ ctx. height_for_hash ( block_hash)
244+ . unwrap_or_else ( || ctx. height ( ) )
241245 } else {
242246 return Err ( RpcError :: InvalidType (
243247 "hash_or_height must be string or number" ,
244248 ) ) ;
245249 } ;
250+
251+ let block_hash = ctx. block_hash_at_height ( height) . unwrap_or_default ( ) ;
252+ let subsidy_sat = subsidy_at_height ( height) ;
253+ let record = ctx. block_by_hash ( block_hash) ;
254+ let time = record. as_ref ( ) . map_or ( 0 , |r| r. time ) ;
255+ let mediantime = ctx. median_time_past_for_hash ( block_hash) . unwrap_or ( 0 ) ;
256+
257+ let mut total_size: u64 = 0 ;
258+ let mut total_weight: u64 = 0 ;
259+ let mut total_out: u64 = 0 ;
260+ let mut ins: u64 = 0 ;
261+ let mut outs: u64 = 0 ;
262+ let mut txs: u64 = 0 ;
263+ let mut swtxs: u64 = 0 ;
264+ let mut swtotal_size: u64 = 0 ;
265+ let mut swtotal_weight: u64 = 0 ;
266+ if let Some ( record) = record. as_ref ( ) {
267+ if let Ok ( bytes) = Vec :: < u8 > :: from_hex ( & record. block_hex ) {
268+ total_size = u64:: try_from ( bytes. len ( ) ) . unwrap_or ( u64:: MAX ) ;
269+ if let Ok ( block) = deserialize :: < bitcoin:: Block > ( & bytes) {
270+ total_weight = block. weight ( ) . to_wu ( ) ;
271+ txs = u64:: try_from ( block. txdata . len ( ) ) . unwrap_or ( u64:: MAX ) ;
272+ for tx in & block. txdata {
273+ ins = ins. saturating_add ( u64:: try_from ( tx. input . len ( ) ) . unwrap_or ( u64:: MAX ) ) ;
274+ outs = outs. saturating_add ( u64:: try_from ( tx. output . len ( ) ) . unwrap_or ( u64:: MAX ) ) ;
275+ for output in & tx. output {
276+ total_out = total_out. saturating_add ( output. value . to_sat ( ) ) ;
277+ }
278+ if tx. input . iter ( ) . any ( |i| !i. witness . is_empty ( ) ) {
279+ swtxs = swtxs. saturating_add ( 1 ) ;
280+ let tx_size = bitcoin:: consensus:: encode:: serialize ( tx) . len ( ) ;
281+ swtotal_size =
282+ swtotal_size. saturating_add ( u64:: try_from ( tx_size) . unwrap_or ( u64:: MAX ) ) ;
283+ swtotal_weight = swtotal_weight. saturating_add ( tx. weight ( ) . to_wu ( ) ) ;
284+ }
285+ }
286+ }
287+ }
288+ }
289+
246290 Ok ( json ! ( {
247291 "avgfee" : 0 ,
248292 "avgfeerate" : 0 ,
249293 "avgtxsize" : 0 ,
250- "blockhash" : ctx . block_hash_at_height ( height ) . unwrap_or_default ( ) . to_string_be( ) ,
294+ "blockhash" : block_hash . to_string_be( ) ,
251295 "feerate_percentiles" : [ 0 , 0 , 0 , 0 , 0 ] ,
252296 "height" : height,
253- "ins" : 0 ,
297+ "ins" : ins ,
254298 "maxfee" : 0 ,
255299 "maxfeerate" : 0 ,
256300 "maxtxsize" : 0 ,
257301 "medianfee" : 0 ,
258- "mediantime" : 0 ,
302+ "mediantime" : mediantime ,
259303 "mediantxsize" : 0 ,
260304 "minfee" : 0 ,
261305 "minfeerate" : 0 ,
262306 "mintxsize" : 0 ,
263- "outs" : 0 ,
264- "subsidy" : 0 ,
265- "swtotal_size" : 0 ,
266- "swtotal_weight" : 0 ,
267- "swtxs" : 0 ,
268- "time" : 0 ,
269- "total_out" : 0 ,
270- "total_size" : 0 ,
271- "total_weight" : 0 ,
307+ "outs" : outs ,
308+ "subsidy" : subsidy_sat ,
309+ "swtotal_size" : swtotal_size ,
310+ "swtotal_weight" : swtotal_weight ,
311+ "swtxs" : swtxs ,
312+ "time" : time ,
313+ "total_out" : total_out ,
314+ "total_size" : total_size ,
315+ "total_weight" : total_weight ,
272316 "totalfee" : 0 ,
273- "txs" : 0 ,
317+ "txs" : txs ,
274318 "utxo_increase" : 0 ,
275319 "utxo_size_inc" : 0
276320 } ) )
277321}
322+
323+ /// Bitcoin block subsidy at `height` in satoshis. 50 BTC initially, halving
324+ /// every 210,000 blocks, saturating to zero after ~64 halvings.
325+ fn subsidy_at_height ( height : u32 ) -> u64 {
326+ const INITIAL_SUBSIDY_SAT : u64 = 5_000_000_000 ;
327+ const HALVING_INTERVAL : u32 = 210_000 ;
328+ let halvings = height / HALVING_INTERVAL ;
329+ if halvings >= 64 {
330+ return 0 ;
331+ }
332+ INITIAL_SUBSIDY_SAT >> halvings
333+ }
278334pub ( crate ) fn pruneblockchain ( ctx : & Arc < Context > , params : & Value ) -> Result < Value , RpcError > {
279335 let height = required_u64 ( params, 0 , "height is required" ) ?;
280336 let applied = u64:: from ( ctx. applied_height ( ) ) ;
@@ -668,6 +724,22 @@ mod tests {
668724 use super :: * ;
669725 use bitcoin_rs_chain:: { ChainWork , NodeId , TipSnapshot } ;
670726
727+ #[ test]
728+ fn subsidy_at_height_genesis_is_50_btc ( ) {
729+ assert_eq ! ( subsidy_at_height( 0 ) , 5_000_000_000 ) ;
730+ }
731+
732+ #[ test]
733+ fn subsidy_at_height_first_halving_is_25_btc ( ) {
734+ assert_eq ! ( subsidy_at_height( 210_000 ) , 2_500_000_000 ) ;
735+ }
736+
737+ #[ test]
738+ fn subsidy_at_height_after_64_halvings_is_zero ( ) {
739+ assert_eq ! ( subsidy_at_height( 64 * 210_000 ) , 0 ) ;
740+ assert_eq ! ( subsidy_at_height( u32 :: MAX ) , 0 ) ;
741+ }
742+
671743 #[ test]
672744 fn getblock_populates_real_header_fields_from_stored_record ( )
673745 -> Result < ( ) , Box < dyn std:: error:: Error > > {
0 commit comments