feat: wire bitcoin.getinfo and lnd.getinfo RPC routes + AIUI bitcoin context (Task 3)
Register bitcoin.rs and lnd.rs modules in mod.rs and add route entries for bitcoin.getinfo and lnd.getinfo. Add bitcoinInfo ref and context display to AIUI useArchy.ts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
75c424874e
commit
ddf3f73df5
@ -45,7 +45,7 @@ Add `tail-logs` action handler:
|
||||
|
||||
---
|
||||
|
||||
## 3. Bitcoin Deep Data (backend + frontend)
|
||||
## 3. Bitcoin Deep Data (backend + frontend) [DONE]
|
||||
|
||||
### `core/archipelago/src/api/rpc/mod.rs`
|
||||
Add routing: `"bitcoin.getinfo" => self.handle_bitcoin_getinfo().await`
|
||||
|
||||
108
core/archipelago/src/api/rpc/bitcoin.rs
Normal file
108
core/archipelago/src/api/rpc/bitcoin.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use super::RpcHandler;
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct BitcoinInfo {
|
||||
block_height: u64,
|
||||
sync_progress: f64,
|
||||
chain: String,
|
||||
difficulty: f64,
|
||||
mempool_size: u64,
|
||||
mempool_tx_count: u64,
|
||||
verification_progress: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct BitcoinRpcResponse<T> {
|
||||
result: Option<T>,
|
||||
error: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct BlockchainInfo {
|
||||
chain: Option<String>,
|
||||
blocks: Option<u64>,
|
||||
difficulty: Option<f64>,
|
||||
#[serde(rename = "verificationprogress")]
|
||||
verification_progress: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MempoolInfo {
|
||||
size: Option<u64>,
|
||||
bytes: Option<u64>,
|
||||
}
|
||||
|
||||
impl RpcHandler {
|
||||
pub(super) async fn handle_bitcoin_getinfo(&self) -> Result<serde_json::Value> {
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()
|
||||
.context("Failed to create HTTP client")?;
|
||||
|
||||
let blockchain_info = self
|
||||
.bitcoin_rpc_call::<BlockchainInfo>(&client, "getblockchaininfo", &[])
|
||||
.await
|
||||
.context("Failed to query getblockchaininfo")?;
|
||||
|
||||
let mempool_info = self
|
||||
.bitcoin_rpc_call::<MempoolInfo>(&client, "getmempoolinfo", &[])
|
||||
.await
|
||||
.unwrap_or(MempoolInfo {
|
||||
size: Some(0),
|
||||
bytes: Some(0),
|
||||
});
|
||||
|
||||
let info = BitcoinInfo {
|
||||
block_height: blockchain_info.blocks.unwrap_or(0),
|
||||
sync_progress: blockchain_info
|
||||
.verification_progress
|
||||
.unwrap_or(0.0),
|
||||
chain: blockchain_info.chain.unwrap_or_else(|| "unknown".into()),
|
||||
difficulty: blockchain_info.difficulty.unwrap_or(0.0),
|
||||
mempool_size: mempool_info.bytes.unwrap_or(0),
|
||||
mempool_tx_count: mempool_info.size.unwrap_or(0),
|
||||
verification_progress: blockchain_info
|
||||
.verification_progress
|
||||
.unwrap_or(0.0),
|
||||
};
|
||||
|
||||
Ok(serde_json::to_value(info)?)
|
||||
}
|
||||
|
||||
async fn bitcoin_rpc_call<T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
client: &reqwest::Client,
|
||||
method: &str,
|
||||
params: &[serde_json::Value],
|
||||
) -> Result<T> {
|
||||
let body = serde_json::json!({
|
||||
"jsonrpc": "1.0",
|
||||
"id": "archy",
|
||||
"method": method,
|
||||
"params": params,
|
||||
});
|
||||
|
||||
let resp = client
|
||||
.post("http://127.0.0.1:8332/")
|
||||
.basic_auth("archipelago", Some("archipelago123"))
|
||||
.json(&body)
|
||||
.send()
|
||||
.await
|
||||
.context("Bitcoin RPC connection failed")?;
|
||||
|
||||
let rpc_resp: BitcoinRpcResponse<T> = resp
|
||||
.json()
|
||||
.await
|
||||
.context("Failed to parse Bitcoin RPC response")?;
|
||||
|
||||
if let Some(err) = rpc_resp.error {
|
||||
anyhow::bail!("Bitcoin RPC error: {}", err);
|
||||
}
|
||||
|
||||
rpc_resp
|
||||
.result
|
||||
.ok_or_else(|| anyhow::anyhow!("Bitcoin RPC returned null result"))
|
||||
}
|
||||
}
|
||||
124
core/archipelago/src/api/rpc/lnd.rs
Normal file
124
core/archipelago/src/api/rpc/lnd.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use super::RpcHandler;
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct LndInfo {
|
||||
alias: String,
|
||||
num_active_channels: u32,
|
||||
num_peers: u32,
|
||||
synced_to_chain: bool,
|
||||
block_height: u64,
|
||||
balance_sats: i64,
|
||||
channel_balance_sats: i64,
|
||||
pending_open_balance: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LndGetInfoResponse {
|
||||
alias: Option<String>,
|
||||
num_active_channels: Option<u32>,
|
||||
num_peers: Option<u32>,
|
||||
synced_to_chain: Option<bool>,
|
||||
block_height: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LndChannelBalanceResponse {
|
||||
local_balance: Option<LndAmount>,
|
||||
pending_open_local_balance: Option<LndAmount>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LndBalanceResponse {
|
||||
total_balance: Option<String>,
|
||||
#[allow(dead_code)]
|
||||
confirmed_balance: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct LndAmount {
|
||||
sat: Option<String>,
|
||||
}
|
||||
|
||||
impl RpcHandler {
|
||||
pub(super) async fn handle_lnd_getinfo(&self) -> Result<serde_json::Value> {
|
||||
let macaroon_path =
|
||||
"/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon";
|
||||
|
||||
let macaroon_bytes = tokio::fs::read(macaroon_path)
|
||||
.await
|
||||
.context("Failed to read LND admin macaroon — is LND installed?")?;
|
||||
let macaroon_hex = hex::encode(&macaroon_bytes);
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.danger_accept_invalid_certs(true)
|
||||
.build()
|
||||
.context("Failed to create HTTP client")?;
|
||||
|
||||
let get_info: LndGetInfoResponse = client
|
||||
.get("https://127.0.0.1:8080/v1/getinfo")
|
||||
.header("Grpc-Metadata-macaroon", &macaroon_hex)
|
||||
.send()
|
||||
.await
|
||||
.context("LND REST connection failed")?
|
||||
.json()
|
||||
.await
|
||||
.context("Failed to parse LND getinfo response")?;
|
||||
|
||||
let channel_balance: LndChannelBalanceResponse = match client
|
||||
.get("https://127.0.0.1:8080/v1/balance/channels")
|
||||
.header("Grpc-Metadata-macaroon", &macaroon_hex)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(resp) => resp.json().await.unwrap_or(LndChannelBalanceResponse {
|
||||
local_balance: None,
|
||||
pending_open_local_balance: None,
|
||||
}),
|
||||
Err(_) => LndChannelBalanceResponse {
|
||||
local_balance: None,
|
||||
pending_open_local_balance: None,
|
||||
},
|
||||
};
|
||||
|
||||
let wallet_balance: LndBalanceResponse = match client
|
||||
.get("https://127.0.0.1:8080/v1/balance/blockchain")
|
||||
.header("Grpc-Metadata-macaroon", &macaroon_hex)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(resp) => resp.json().await.unwrap_or(LndBalanceResponse {
|
||||
total_balance: None,
|
||||
confirmed_balance: None,
|
||||
}),
|
||||
Err(_) => LndBalanceResponse {
|
||||
total_balance: None,
|
||||
confirmed_balance: None,
|
||||
},
|
||||
};
|
||||
|
||||
let info = LndInfo {
|
||||
alias: get_info.alias.unwrap_or_default(),
|
||||
num_active_channels: get_info.num_active_channels.unwrap_or(0),
|
||||
num_peers: get_info.num_peers.unwrap_or(0),
|
||||
synced_to_chain: get_info.synced_to_chain.unwrap_or(false),
|
||||
block_height: get_info.block_height.unwrap_or(0),
|
||||
balance_sats: wallet_balance
|
||||
.total_balance
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(0),
|
||||
channel_balance_sats: channel_balance
|
||||
.local_balance
|
||||
.and_then(|a| a.sat.and_then(|s| s.parse().ok()))
|
||||
.unwrap_or(0),
|
||||
pending_open_balance: channel_balance
|
||||
.pending_open_local_balance
|
||||
.and_then(|a| a.sat.and_then(|s| s.parse().ok()))
|
||||
.unwrap_or(0),
|
||||
};
|
||||
|
||||
Ok(serde_json::to_value(info)?)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
mod auth;
|
||||
mod bitcoin;
|
||||
mod container;
|
||||
mod lnd;
|
||||
mod node;
|
||||
mod package;
|
||||
mod peers;
|
||||
@ -128,6 +130,10 @@ impl RpcHandler {
|
||||
"node.nostr-pubkey" => self.handle_node_nostr_pubkey().await,
|
||||
"node-nostr-verify-revoked" => self.handle_node_nostr_verify_revoked().await,
|
||||
|
||||
// Bitcoin & Lightning deep data
|
||||
"bitcoin.getinfo" => self.handle_bitcoin_getinfo().await,
|
||||
"lnd.getinfo" => self.handle_lnd_getinfo().await,
|
||||
|
||||
_ => {
|
||||
Err(anyhow::anyhow!("Unknown method: {}", rpc_req.method))
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user