Skip to content

Commit 58d5ec2

Browse files
committed
Map Bitcoin chain ID from node info
Derive the Bitcoin chain ID from the node's /api/ node info instead of reading a block hash. Add map_chain_id to state_mapper (using primitives::Asset/BitcoinChain) and update get_chain_id to call get_node_info + mapper. Remove the unused BitcoinBlock model and obsolete fields (last_block_time, backend.chain), and drop get_block_info from the RPC client. Update state_mapper tests and testdata to reflect new blockbook fields (coin, network) and include a helper to construct BitcoinNodeInfo for tests.
1 parent 59d832c commit 58d5ec2

5 files changed

Lines changed: 38 additions & 45 deletions

File tree

core/crates/gem_bitcoin/src/models/block.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,6 @@ use crate::models::Transaction;
44

55
type Int = u64;
66

7-
// Domain models
8-
#[derive(Debug, Clone, Serialize, Deserialize)]
9-
#[serde(rename_all = "camelCase")]
10-
pub struct BitcoinBlock {
11-
pub previous_block_hash: Option<String>,
12-
}
13-
147
#[derive(Debug, Clone, Serialize, Deserialize)]
158
pub struct BitcoinNodeInfo {
169
pub blockbook: BitcoinBlockbook,
@@ -20,16 +13,15 @@ pub struct BitcoinNodeInfo {
2013
#[derive(Debug, Clone, Serialize, Deserialize)]
2114
#[serde(rename_all = "camelCase")]
2215
pub struct BitcoinBlockbook {
16+
pub network: Option<String>,
2317
pub in_sync: bool,
24-
pub last_block_time: String,
2518
pub best_height: Int,
2619
}
2720

2821
#[derive(Debug, Clone, Serialize, Deserialize)]
2922
#[serde(rename_all = "camelCase")]
3023
pub struct BitcoinBackend {
3124
pub blocks: Option<Int>,
32-
pub chain: Option<String>,
3325
pub consensus: Option<Consensus>,
3426
}
3527

core/crates/gem_bitcoin/src/provider/state.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use crate::{provider::state_mapper, rpc::client::BitcoinClient};
99
#[async_trait]
1010
impl<C: Client> ChainState for BitcoinClient<C> {
1111
async fn get_chain_id(&self) -> Result<String, Box<dyn Error + Sync + Send>> {
12-
let block = self.get_block_info(1).await?;
13-
block.previous_block_hash.ok_or_else(|| "Unable to get block hash".into())
12+
let node_info = self.get_node_info().await?;
13+
Ok(state_mapper::map_chain_id(self.chain, &node_info)?)
1414
}
1515

1616
async fn get_node_status(&self) -> Result<NodeSyncStatus, Box<dyn Error + Sync + Send>> {
@@ -46,7 +46,7 @@ mod chain_integration_tests {
4646
let chain_id = client.get_chain_id().await?;
4747

4848
assert!(!chain_id.is_empty());
49-
assert!(chain_id.len() == 64); // Bitcoin block hashes are 64 characters
49+
assert_eq!(chain_id, primitives::Chain::Bitcoin.network_id());
5050
println!("Bitcoin chain ID: {}", chain_id);
5151

5252
Ok(())
Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
use crate::models::block::BitcoinNodeInfo;
2-
use primitives::NodeSyncStatus;
2+
use primitives::{Asset, BitcoinChain, NodeSyncStatus};
3+
4+
pub fn map_chain_id(chain: BitcoinChain, node_info: &BitcoinNodeInfo) -> Result<String, &'static str> {
5+
let chain = chain.get_chain();
6+
let asset = Asset::from_chain(chain);
7+
8+
if node_info.blockbook.network.as_deref() == Some(asset.symbol.as_str()) {
9+
Ok(chain.network_id().to_string())
10+
} else {
11+
Err("Invalid Bitcoin network")
12+
}
13+
}
314

415
pub fn map_node_status(node_info: &BitcoinNodeInfo) -> NodeSyncStatus {
516
let latest_block_number = node_info.backend.blocks;
@@ -16,21 +27,21 @@ pub fn map_latest_block_number(node_info: &BitcoinNodeInfo) -> u64 {
1627
mod tests {
1728
use super::*;
1829
use crate::models::block::{BitcoinBackend, BitcoinBlockbook, BitcoinNodeInfo};
30+
use primitives::{Asset, Chain};
31+
32+
#[test]
33+
fn test_map_chain_id() {
34+
let bitcoin = node_info(Some(Asset::from_chain(Chain::Bitcoin).symbol), true, 1, Some(1));
35+
assert_eq!(map_chain_id(BitcoinChain::Bitcoin, &bitcoin).unwrap(), Chain::Bitcoin.network_id());
36+
37+
let doge = node_info(Some(Asset::from_chain(Chain::Doge).symbol), true, 1, Some(1));
38+
assert_eq!(map_chain_id(BitcoinChain::Doge, &doge).unwrap(), Chain::Doge.network_id());
39+
assert_eq!(map_chain_id(BitcoinChain::Bitcoin, &doge), Err("Invalid Bitcoin network"));
40+
}
1941

2042
#[test]
2143
fn test_map_node_status_returns_flag_and_block_numbers() {
22-
let node_info = BitcoinNodeInfo {
23-
blockbook: BitcoinBlockbook {
24-
in_sync: false,
25-
last_block_time: "2024-01-01T00:00:00Z".to_string(),
26-
best_height: 123,
27-
},
28-
backend: BitcoinBackend {
29-
blocks: Some(456),
30-
chain: Some("main".to_string()),
31-
consensus: None,
32-
},
33-
};
44+
let node_info = node_info(Some(Asset::from_chain(Chain::Bitcoin).symbol), false, 123, Some(456));
3445

3546
let status = map_node_status(&node_info);
3647

@@ -41,19 +52,14 @@ mod tests {
4152

4253
#[test]
4354
fn test_map_latest_block_number_returns_best_height() {
44-
let node_info = BitcoinNodeInfo {
45-
blockbook: BitcoinBlockbook {
46-
in_sync: true,
47-
last_block_time: "2024-01-01T00:00:00Z".to_string(),
48-
best_height: 1_000,
49-
},
50-
backend: BitcoinBackend {
51-
blocks: Some(2_000),
52-
chain: Some("main".to_string()),
53-
consensus: None,
54-
},
55-
};
56-
55+
let node_info = node_info(Some(Asset::from_chain(Chain::Bitcoin).symbol), true, 1_000, Some(2_000));
5756
assert_eq!(map_latest_block_number(&node_info), 1_000);
5857
}
58+
59+
fn node_info(network: Option<String>, in_sync: bool, best_height: u64, blocks: Option<u64>) -> BitcoinNodeInfo {
60+
BitcoinNodeInfo {
61+
blockbook: BitcoinBlockbook { network, in_sync, best_height },
62+
backend: BitcoinBackend { blocks, consensus: None },
63+
}
64+
}
5965
}

core/crates/gem_bitcoin/src/rpc/client.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::error::Error;
22

33
use crate::models::account::BitcoinAccount;
4-
use crate::models::block::{BitcoinBlock, BitcoinNodeInfo, Block, Status};
4+
use crate::models::block::{BitcoinNodeInfo, Block, Status};
55
use crate::models::fee::BitcoinFeeResult;
66
use crate::models::transaction::{AddressDetails, BitcoinTransactionBroacastResult, BitcoinUTXO, Transaction};
77
use chain_traits::{ChainAddressStatus, ChainPerpetual, ChainStaking, ChainToken, ChainTraits};
@@ -44,10 +44,6 @@ impl<C: Client> BitcoinClient<C> {
4444
Ok(self.client.get(&format!("/api/v2/address/{address}")).await?)
4545
}
4646

47-
pub async fn get_block_info(&self, block_number: u64) -> Result<BitcoinBlock, Box<dyn Error + Send + Sync>> {
48-
Ok(self.client.get(&format!("/api/v2/block/{block_number}")).await?)
49-
}
50-
5147
pub async fn get_node_info(&self) -> Result<BitcoinNodeInfo, Box<dyn Error + Send + Sync>> {
5248
Ok(self.client.get("/api/").await?)
5349
}

core/crates/gem_bitcoin/testdata/node_info_no_blocks.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
{
22
"blockbook": {
3+
"network": "ZEC",
34
"inSync": true,
4-
"lastBlockTime": "2026-06-03T00:00:00Z",
55
"bestHeight": 2700000
66
},
77
"backend": {
8-
"chain": "main",
98
"consensus": {
109
"chaintip": "c2d6d0b4"
1110
}

0 commit comments

Comments
 (0)