Skip to content
This repository has been archived by the owner on Aug 28, 2024. It is now read-only.

Commit

Permalink
feat: add estimate_fee method, a few test updates [ci skip]
Browse files Browse the repository at this point in the history
  • Loading branch information
steph-rs committed Jul 18, 2024
1 parent a579af7 commit add84f4
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 43 deletions.
21 changes: 21 additions & 0 deletions core/lib/via_btc_client/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use async_trait::async_trait;
use bitcoin::{address::NetworkUnchecked, Address, Network, Txid};
use bitcoincore_rpc::json::EstimateMode;

use crate::{
traits::{BitcoinOps, BitcoinRpc},
Expand All @@ -10,6 +11,8 @@ mod rpc_client;
#[allow(unused)]
pub use rpc_client::BitcoinRpcClient;

use crate::types::BitcoinError;

#[allow(unused)]
pub struct BitcoinClient {
rpc: Box<dyn BitcoinRpc>,
Expand Down Expand Up @@ -87,4 +90,22 @@ impl BitcoinOps for BitcoinClient {
let _block = self.rpc.get_block(block_height).await?;
todo!()
}

async fn estimate_fee(&self, conf_target: u16) -> BitcoinClientResult<u64> {
let estimation = self
.rpc
.estimate_smart_fee(conf_target, Some(EstimateMode::Economical))
.await?;

match estimation.fee_rate {
Some(fee_rate) => Ok(fee_rate.to_sat()),
None => {
let err = match estimation.errors {
Some(errors) => errors.join(", "),
None => "Unknown error during fee estimation".to_string(),
};
Err(BitcoinError::FeeEstimationFailed(err))
}
}
}
}
58 changes: 20 additions & 38 deletions core/lib/via_btc_client/src/client/rpc_client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use async_trait::async_trait;
use bitcoin::{Address, Block, OutPoint, Transaction, Txid};
use bitcoincore_rpc::{Auth, Client, RpcApi};
use bitcoincore_rpc::{
bitcoincore_rpc_json::EstimateMode, json::EstimateSmartFeeResult, Auth, Client, RpcApi,
};

use crate::{traits::BitcoinRpc, types::BitcoinRpcResult};

Expand Down Expand Up @@ -79,50 +81,30 @@ impl BitcoinRpc for BitcoinRpcClient {
.get_raw_transaction_info(txid, block_hash)
.map_err(|e| e.into())
}

async fn estimate_smart_fee(
&self,
conf_target: u16,
estimate_mode: Option<EstimateMode>,
) -> BitcoinRpcResult<EstimateSmartFeeResult> {
self.client
.estimate_smart_fee(conf_target, estimate_mode)
.map_err(|e| e.into())
}
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use bitcoin::Network;
use tokio;

use super::*;
use crate::{regtest::BitcoinRegtest, traits::BitcoinRpc};

struct TestContext {
_regtest: BitcoinRegtest,
client: BitcoinRpcClient,
test_address: Address,
}

impl TestContext {
async fn setup() -> Self {
let regtest = BitcoinRegtest::new().expect("Failed to create BitcoinRegtest");
regtest.run().expect("Failed to run Bitcoin regtest");

let url = regtest.get_url();
let client = BitcoinRpcClient::new(&url, "rpcuser", "rpcpassword")
.expect("Failed to create BitcoinRpcClient");

// some random address
let test_address = Address::from_str("bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080")
.unwrap()
.require_network(Network::Regtest)
.unwrap();

Self {
_regtest: regtest,
client,
test_address,
}
}
}
use crate::{regtest::TestContext, traits::BitcoinRpc};

#[tokio::test]
async fn test_get_balance() {
let context = TestContext::setup().await;
let context = TestContext::setup(None).await;
let balance = context
.client
.get_balance(&context.test_address)
Expand All @@ -133,21 +115,21 @@ mod tests {

#[tokio::test]
async fn test_get_block_count() {
let context = TestContext::setup().await;
let context = TestContext::setup(None).await;
let block_count = context.client.get_block_count().await.unwrap();
assert!(block_count > 100, "Block count should be greater than 100");
}

#[tokio::test]
async fn test_get_block_size() {
let context = TestContext::setup().await;
let context = TestContext::setup(None).await;
let block = context.client.get_block(1).await.unwrap();
assert_eq!(block.total_size(), 248usize, "Block version should be 1");
}

#[tokio::test]
async fn test_list_unspent() {
let context = TestContext::setup().await;
let context = TestContext::setup(None).await;
let unspent = context
.client
.list_unspent(&context.test_address)
Expand All @@ -161,15 +143,15 @@ mod tests {

#[tokio::test]
async fn test_send_raw_transaction() {
let context = TestContext::setup().await;
let context = TestContext::setup(None).await;
let dummy_tx_hex = "020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0502cd010101ffffffff0200f2052a01000000160014a8a01aa2b9c7f00bbd59aabfb047e8f3a181d7ed0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000";
let result = context.client.send_raw_transaction(dummy_tx_hex).await;
assert!(result.is_err(), "Sending invalid transaction should fail");
}

#[tokio::test]
async fn test_get_transaction() {
let context = TestContext::setup().await;
let context = TestContext::setup(None).await;
let dummy_txid =
Txid::from_str("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")
.unwrap();
Expand Down
44 changes: 39 additions & 5 deletions core/lib/via_btc_client/src/regtest.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
use std::{env, fs, path::PathBuf, process::Command, thread, time::Duration};

use bitcoin::{address::NetworkUnchecked, Address, Network};
use bitcoincore_rpc::{Auth, Client};
use rand::Rng;
use tempfile::TempDir;

use crate::client::BitcoinRpcClient;

const COMPOSE_TEMPLATE_PATH: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/docker-compose-btc-template.yml"
);

#[allow(unused)]
pub struct BitcoinRegtest {
pub(crate) struct BitcoinRegtest {
temp_dir: TempDir,
compose_file: PathBuf,
rpc_port: u16,
Expand All @@ -30,13 +33,13 @@ impl BitcoinRegtest {
})
}

pub fn generate_compose_file(&self) -> std::io::Result<()> {
fn generate_compose_file(&self) -> std::io::Result<()> {
let template = fs::read_to_string(COMPOSE_TEMPLATE_PATH)?;
let compose_content = template.replace("{RPC_PORT}", &self.rpc_port.to_string());
fs::write(&self.compose_file, compose_content)
}

pub fn run(&self) -> std::io::Result<()> {
fn run(&self) -> std::io::Result<()> {
self.generate_compose_file()?;

Command::new("docker")
Expand All @@ -56,7 +59,7 @@ impl BitcoinRegtest {
Ok(())
}

pub fn stop(&self) -> std::io::Result<()> {
fn stop(&self) -> std::io::Result<()> {
Command::new("docker")
.args(&[
"compose",
Expand All @@ -72,7 +75,7 @@ impl BitcoinRegtest {
Ok(())
}

pub fn get_url(&self) -> String {
fn get_url(&self) -> String {
format!("http://127.0.0.1:{}", self.rpc_port)
}
}
Expand All @@ -85,6 +88,37 @@ impl Drop for BitcoinRegtest {
}
}

pub(crate) struct TestContext {
pub(crate) _regtest: BitcoinRegtest,
pub(crate) client: BitcoinRpcClient,
pub(crate) test_address: Address,
}

impl TestContext {
pub(crate) async fn setup(address: Option<&str>) -> Self {
let regtest = BitcoinRegtest::new().expect("Failed to create BitcoinRegtest");
regtest.run().expect("Failed to run Bitcoin regtest");

let url = regtest.get_url();
let client = BitcoinRpcClient::new(&url, "rpcuser", "rpcpassword")
.expect("Failed to create BitcoinRpcClient");

// some random address
let test_address = address
.unwrap_or("bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080")
.parse::<Address<NetworkUnchecked>>()
.unwrap()
.require_network(Network::Regtest)
.unwrap();

Self {
_regtest: regtest,
client,
test_address,
}
}
}

#[cfg(test)]
mod tests {
use bitcoincore_rpc::RpcApi;
Expand Down
6 changes: 6 additions & 0 deletions core/lib/via_btc_client/src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub trait BitcoinOps: Send + Sync {
async fn check_tx_confirmation(&self, txid: &str) -> types::BitcoinClientResult<bool>;
async fn fetch_block_height(&self) -> types::BitcoinClientResult<u128>;
async fn fetch_and_parse_block(&self, block_height: u128) -> types::BitcoinClientResult<&str>;
async fn estimate_fee(&self, conf_target: u16) -> types::BitcoinClientResult<u64>;
}

#[allow(dead_code)]
Expand All @@ -42,6 +43,11 @@ pub trait BitcoinRpc: Send + Sync {
txid: &Txid,
block_hash: Option<&bitcoin::BlockHash>,
) -> types::BitcoinRpcResult<bitcoincore_rpc::json::GetRawTransactionResult>;
async fn estimate_smart_fee(
&self,
conf_target: u16,
estimate_mode: Option<bitcoincore_rpc::json::EstimateMode>,
) -> types::BitcoinRpcResult<bitcoincore_rpc::json::EstimateSmartFeeResult>;
}

#[allow(dead_code)]
Expand Down
3 changes: 3 additions & 0 deletions core/lib/via_btc_client/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ pub enum BitcoinError {
#[error("Transaction building error: {0}")]
TransactionBuildingError(String),

#[error("Fee estimation error: {0}")]
FeeEstimationFailed(String),

#[error("Other error: {0}")]
Other(String),
}
Expand Down

0 comments on commit add84f4

Please sign in to comment.