@@ -48,6 +48,9 @@ pub enum ApplyError {
4848 /// Consensus validation rejected the block.
4949 #[ error( "consensus: {0}" ) ]
5050 Consensus ( #[ from] bitcoin_rs_consensus:: ConsensusError ) ,
51+ /// Block-tree insertion rejected the header.
52+ #[ error( "chain: {0}" ) ]
53+ Chain ( #[ from] bitcoin_rs_chain:: ChainError ) ,
5154 /// UTXO commit failed during block apply.
5255 #[ error( "utxo commit: {0}" ) ]
5356 UtxoCommit ( #[ from] bitcoin_rs_utxo:: UtxoError ) ,
@@ -157,6 +160,7 @@ pub struct NodeState {
157160 utxo : Arc < UtxoSet > ,
158161 mempool : Arc < RwLock < Mempool > > ,
159162 chain_tip : Arc < ArcSwapOption < TipSnapshot > > ,
163+ block_tree : Arc < RwLock < bitcoin_rs_chain:: BlockTree > > ,
160164 blocks : Arc < RwLock < Vec < BlockRecord > > > ,
161165 transactions : Arc < RwLock < HashMap < Txid , Transaction > > > ,
162166 network : Arc < RwLock < NetworkState > > ,
@@ -175,6 +179,7 @@ impl NodeState {
175179 let utxo = Arc :: new ( UtxoSet :: new ( ) ) ;
176180 let mempool = Arc :: new ( RwLock :: new ( Mempool :: new ( MempoolLimits :: default ( ) ) ) ) ;
177181 let chain_tip = Arc :: new ( ArcSwapOption :: empty ( ) ) ;
182+ let block_tree = Arc :: new ( RwLock :: new ( bitcoin_rs_chain:: BlockTree :: new ( ) ) ) ;
178183 let blocks = Arc :: new ( RwLock :: new ( Vec :: new ( ) ) ) ;
179184 let transactions = Arc :: new ( RwLock :: new ( HashMap :: new ( ) ) ) ;
180185 let network = Arc :: new ( RwLock :: new ( NetworkState :: default ( ) ) ) ;
@@ -192,6 +197,7 @@ impl NodeState {
192197 utxo,
193198 mempool,
194199 chain_tip,
200+ block_tree,
195201 blocks,
196202 transactions,
197203 network,
@@ -236,6 +242,12 @@ impl NodeState {
236242 Arc :: clone ( & self . chain_tip )
237243 }
238244
245+ /// Returns the shared block-tree handle.
246+ #[ must_use]
247+ pub fn block_tree ( & self ) -> Arc < RwLock < bitcoin_rs_chain:: BlockTree > > {
248+ Arc :: clone ( & self . block_tree )
249+ }
250+
239251 /// Returns the shared block-records handle exposed to RPC handlers.
240252 #[ must_use]
241253 pub fn blocks ( & self ) -> Arc < RwLock < Vec < BlockRecord > > > {
@@ -352,6 +364,11 @@ impl NodeState {
352364 // Contextual consensus checks (BIP30 + BIP34) using the resolved height.
353365 self . check_bip30_and_bip34 ( block, height) ?;
354366
367+ // Persist the header into the in-memory block tree; it is the source of
368+ // truth for header height, chainwork, and parent linkage. We dual-write
369+ // `chain_tip` below until readers migrate to `block_tree()`.
370+ self . insert_active_header ( block) ?;
371+
355372 let mut changes = BlockChanges :: default ( ) ;
356373 for tx in & block. txdata {
357374 let txid = tx. compute_txid ( ) ;
@@ -411,6 +428,13 @@ impl NodeState {
411428 Ok ( tip)
412429 }
413430
431+ fn insert_active_header ( & self , block : & bitcoin:: Block ) -> core:: result:: Result < ( ) , ApplyError > {
432+ self . block_tree
433+ . write ( )
434+ . insert_header ( block. header , bitcoin_rs_chain:: node:: NodeStatus :: Active ) ?;
435+ Ok ( ( ) )
436+ }
437+
414438 fn check_bip30_and_bip34 (
415439 & self ,
416440 block : & bitcoin:: Block ,
@@ -491,6 +515,24 @@ mod tests {
491515 Ok ( ( ) )
492516 }
493517
518+ #[ test]
519+ fn open_constructs_empty_block_tree ( ) -> anyhow:: Result < ( ) > {
520+ use tempfile:: tempdir;
521+
522+ let dir = tempdir ( ) ?;
523+ let mut config = crate :: Config :: default_for_network ( crate :: Network :: Regtest ) ;
524+ config. data_dir = dir. path ( ) . join ( "node" ) ;
525+ config. p2p_listen . clear ( ) ;
526+ let state = NodeState :: open ( config) ?;
527+ let tree = state. block_tree ( ) ;
528+
529+ assert ! (
530+ tree. read( ) . is_empty( ) ,
531+ "freshly opened tree has zero headers"
532+ ) ;
533+ Ok ( ( ) )
534+ }
535+
494536 #[ test]
495537 fn open_constructs_full_rpc_handle_set ( ) -> anyhow:: Result < ( ) > {
496538 use tempfile:: tempdir;
0 commit comments