Document Bitcoin RPC and make it more robust

This commit is contained in:
Luke Parker
2023-03-17 21:25:38 -04:00
parent 9b47ad56bb
commit 0525ba2f62
8 changed files with 64 additions and 13 deletions

View File

@@ -16,11 +16,12 @@ use bitcoin::{
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub(crate) enum RpcResponse<T> {
enum RpcResponse<T> {
Ok { result: T },
Err { error: String },
}
/// A minimal asynchronous Bitcoin RPC client.
#[derive(Clone, Debug)]
pub struct Rpc(String);
@@ -35,10 +36,14 @@ pub enum RpcError {
}
impl Rpc {
pub fn new(url: String) -> Rpc {
Rpc(url)
pub async fn new(url: String) -> Result<Rpc, RpcError> {
let rpc = Rpc(url);
// Make an RPC request to verify the node is reachable and sane
rpc.get_latest_block_number().await?;
Ok(rpc)
}
/// Perform an arbitrary RPC call.
pub async fn rpc_call<Response: DeserializeOwned + Debug>(
&self,
method: &str,
@@ -63,17 +68,21 @@ impl Rpc {
}
}
/// Get the latest block's number.
pub async fn get_latest_block_number(&self) -> Result<usize, RpcError> {
self.rpc_call("getblockcount", json!([])).await
}
/// Get the hash of a block by the block's number.
pub async fn get_block_hash(&self, number: usize) -> Result<[u8; 32], RpcError> {
let mut hash =
self.rpc_call::<BlockHash>("getblockhash", json!([number])).await?.as_hash().into_inner();
// bitcoin stores the inner bytes in reverse order.
hash.reverse();
Ok(hash)
}
/// Get a block's number by its hash.
pub async fn get_block_number(&self, hash: &[u8; 32]) -> Result<usize, RpcError> {
#[derive(Deserialize, Debug)]
struct Number {
@@ -82,19 +91,42 @@ impl Rpc {
Ok(self.rpc_call::<Number>("getblockheader", json!([hash.to_hex()])).await?.height)
}
/// Get a block by its hash.
pub async fn get_block(&self, hash: &[u8; 32]) -> Result<Block, RpcError> {
let hex = self.rpc_call::<String>("getblock", json!([hash.to_hex(), 0])).await?;
let bytes: Vec<u8> = FromHex::from_hex(&hex).map_err(|_| RpcError::InvalidResponse)?;
encode::deserialize(&bytes).map_err(|_| RpcError::InvalidResponse)
let block: Block = encode::deserialize(&bytes).map_err(|_| RpcError::InvalidResponse)?;
let mut block_hash = block.block_hash().as_hash().into_inner();
block_hash.reverse();
if hash != &block_hash {
Err(RpcError::InvalidResponse)?;
}
Ok(block)
}
/// Publish a transaction.
pub async fn send_raw_transaction(&self, tx: &Transaction) -> Result<Txid, RpcError> {
self.rpc_call("sendrawtransaction", json!([encode::serialize_hex(tx)])).await
let txid = self.rpc_call("sendrawtransaction", json!([encode::serialize_hex(tx)])).await?;
if txid != tx.txid() {
Err(RpcError::InvalidResponse)?;
}
Ok(txid)
}
/// Get a transaction by its hash.
pub async fn get_transaction(&self, hash: &[u8; 32]) -> Result<Transaction, RpcError> {
let hex = self.rpc_call::<String>("getrawtransaction", json!([hash.to_hex()])).await?;
let bytes: Vec<u8> = FromHex::from_hex(&hex).map_err(|_| RpcError::InvalidResponse)?;
encode::deserialize(&bytes).map_err(|_| RpcError::InvalidResponse)
let tx: Transaction = encode::deserialize(&bytes).map_err(|_| RpcError::InvalidResponse)?;
let mut tx_hash = tx.txid().as_hash().into_inner();
tx_hash.reverse();
if hash != &tx_hash {
Err(RpcError::InvalidResponse)?;
}
Ok(tx)
}
}