diff --git a/Cargo.toml b/Cargo.toml index a3d165c..36e5b74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" [dependencies] bitcoin = { version= "0.31.0", features = ["std"], default-features = false } bitcoincore-rpc = { version = "0.18.0", default-features = false } +opentelemetry = { version = "0.23.0", features = ["trace"], default-features = false } tokio = { version = "1.12.0", features = ["rt", "macros"], default-features = false } -tracing = { version = "0.1.37", default-features = false } diff --git a/src/lib.rs b/src/lib.rs index 1931043..83a2de4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,26 +4,29 @@ use bitcoincore_rpc::{ json::{AddressType, GetBalancesResult, ListTransactionResult}, RpcApi, }; +use opentelemetry::trace::{FutureExt, TraceContextExt}; use std::{future::Future, sync::Arc}; -use tracing::Instrument; pub use bitcoincore_rpc as rpc; #[derive(Clone)] pub struct Btc { client: Arc, + tracer: Arc, } pub fn build(rpc_user: &str, rpc_password: &str, rpc_url: &str) -> bitcoincore_rpc::Result { let auth = bitcoincore_rpc::Auth::UserPass(rpc_user.into(), rpc_password.into()); bitcoincore_rpc::Client::new(rpc_url, auth).map(|client| Btc { + tracer: Arc::new(opentelemetry::global::tracer("btcore")), client: Arc::new(client), }) } impl Btc { pub fn get_block_count(&self) -> impl Future> { + let span = start_span(self.tracer.as_ref(), "get_block_count"); let client = self.client.clone(); async move { @@ -31,13 +34,14 @@ impl Btc { .await .unwrap() } - .instrument(span!("get_block_count")) + .with_context(opentelemetry::Context::current_with_span(span)) } pub fn list_transactions( &self, count: usize, ) -> impl Future>> { + let span = start_span(self.tracer.as_ref(), "listtransactions"); let client = self.client.clone(); async move { @@ -47,7 +51,7 @@ impl Btc { .await .unwrap() } - .instrument(span!("listtransactions")) + .with_context(opentelemetry::Context::current_with_span(span)) } pub fn list_since_block( @@ -56,6 +60,7 @@ impl Btc { confirmations: usize, ) -> impl Future, BlockHash)>> { + let span = start_span(self.tracer.as_ref(), "listsinceblock"); let client = self.client.clone(); async move { @@ -67,10 +72,11 @@ impl Btc { .await .unwrap() } - .instrument(span!("listsinceblock")) + .with_context(opentelemetry::Context::current_with_span(span)) } pub fn get_balances(&self) -> impl Future> { + let span = start_span(self.tracer.as_ref(), "getbalances"); let client = self.client.clone(); async move { @@ -78,13 +84,14 @@ impl Btc { .await .unwrap() } - .instrument(span!("getbalances")) + .with_context(opentelemetry::Context::current_with_span(span)) } pub fn get_balance( &self, number_of_confirmations: Option, ) -> impl Future> { + let span = start_span(self.tracer.as_ref(), "getbalance"); let client = self.client.clone(); async move { @@ -92,13 +99,14 @@ impl Btc { .await .unwrap() } - .instrument(span!("getbalance")) + .with_context(opentelemetry::Context::current_with_span(span)) } pub fn get_transaction( &self, txid: Txid, ) -> impl Future> { + let span = start_span(self.tracer.as_ref(), "gettransaction"); let client = self.client.clone(); async move { @@ -106,13 +114,14 @@ impl Btc { .await .unwrap() } - .instrument(span!("gettransaction")) + .with_context(opentelemetry::Context::current_with_span(span)) } pub fn generate_address( &self, address_type: AddressType, ) -> impl Future>> { + let span = start_span(self.tracer.as_ref(), "getnewaddress"); let client = self.client.clone(); async move { @@ -120,23 +129,23 @@ impl Btc { .await .unwrap() } - .instrument(span!("getnewaddress")) + .with_context(opentelemetry::Context::current_with_span(span)) } } -#[macro_export] -macro_rules! span { - ($method:literal) => { - tracing::info_span!( - "btcore", - service.name = "btcore", - otel.name = $method, - otel.kind = "client", - rpc.system = "jsonrpc", - rpc.service = "bitcoind", - rpc.method = $method, - ) - }; +fn start_span( + tracer: &impl opentelemetry::trace::Tracer, + method: &'static str, +) -> impl opentelemetry::trace::Span { + opentelemetry::trace::SpanBuilder::from_name(method) + .with_kind(opentelemetry::trace::SpanKind::Client) + .with_attributes([ + opentelemetry::KeyValue::new("service.name", "btcore"), + opentelemetry::KeyValue::new("rpc.service", "bitcoind"), + opentelemetry::KeyValue::new("rpc.system", "jsonrpc"), + opentelemetry::KeyValue::new("rpc.method", method), + ]) + .start(tracer) } #[cfg(test)] @@ -153,6 +162,7 @@ mod test { let auth = bitcoincore_rpc::Auth::UserPass(USERNAME.into(), PASSWORD.into()); let client = bitcoincore_rpc::Client::new(RPC_URL, auth).map(|client| Btc { + tracer: Arc::new(opentelemetry::global::tracer("btcore")), client: Arc::new(client), })?; @@ -183,6 +193,7 @@ mod test { tokio::task::spawn_blocking(move || client.unload_wallet(Some(&wallet_name))) .await .unwrap() + .map(|_| ()) } fn delete_wallet(&self, wallet_name: &str) -> Result<(), std::io::Error> { @@ -252,10 +263,11 @@ mod test { let address = if burn_coins { Address::from_str("bcrt1qs758ursh4q9z627kt3pp5yysm78ddny6txaqgw").unwrap() } else { - self.generate_address_async(AddressType::P2shSegwit) + self.generate_address(AddressType::P2shSegwit) .await .unwrap() - }; + } + .assume_checked(); tokio::task::spawn_blocking(move || client.generate_to_address(block_num, &address)) .await @@ -347,32 +359,4 @@ mod test { assert_eq!(balances.mine.trusted, Amount::ZERO); } - - #[tokio::test] - async fn test_get_transaction_fee() { - let client = build_for_test().await.unwrap(); - - client.generate_one_spendable_output().await.unwrap(); - - let fee_rate = 1 as i64; - - let txid = client - .send_to_address( - client - .generate_address_async(AddressType::P2shSegwit) - .await - .unwrap(), - 5_000_000, - Some(fee_rate as i32), - ) - .await - .unwrap(); - - client.generate_n_blocks(1, true).await.unwrap(); - - let fee = client.get_transaction_fee(txid).await.unwrap().unwrap(); - let expected_fee = -(fee.vsize as i64 * fee_rate); - - assert_eq!(fee.fee, expected_fee); - } }