From 87e1ccacc843e7e736a5a1b7aad2e6116b826d19 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Fri, 1 Nov 2024 16:26:25 +0800 Subject: [PATCH] feat: add `id_from_pos` support --- src/api.rs | 11 +++++ src/client.rs | 14 ++++++ src/raw_client.rs | 112 ++++++++++++++++++++++++++++++++++++++-------- src/types.rs | 11 +++++ 4 files changed, 130 insertions(+), 18 deletions(-) diff --git a/src/api.rs b/src/api.rs index e7522de..5ed5f09 100644 --- a/src/api.rs +++ b/src/api.rs @@ -200,6 +200,17 @@ pub trait ElectrumApi { /// Returns the merkle path for the transaction `txid` confirmed in the block at `height`. fn transaction_get_merkle(&self, txid: &Txid, height: usize) -> Result; + /// Returns a transaction hash, given a block `height` and a `tx_pos` in the block. + fn transaction_id_from_pos(&self, height: usize, tx_pos: usize) -> Result; + + /// Returns a transaction hash and a merkle path, given a block `height` and a `tx_pos` in the + /// block. + fn transaction_id_from_pos_with_merkle( + &self, + height: usize, + tx_pos: usize, + ) -> Result; + /// Returns the capabilities of the server. fn server_features(&self) -> Result; diff --git a/src/client.rs b/src/client.rs index 3511724..8d18070 100644 --- a/src/client.rs +++ b/src/client.rs @@ -327,6 +327,20 @@ impl ElectrumApi for Client { impl_inner_call!(self, transaction_get_merkle, txid, height) } + #[inline] + fn transaction_id_from_pos(&self, height: usize, tx_pos: usize) -> Result { + impl_inner_call!(self, transaction_id_from_pos, height, tx_pos) + } + + #[inline] + fn transaction_id_from_pos_with_merkle( + &self, + height: usize, + tx_pos: usize, + ) -> Result { + impl_inner_call!(self, transaction_id_from_pos_with_merkle, height, tx_pos) + } + #[inline] fn server_features(&self) -> Result { impl_inner_call!(self, server_features) diff --git a/src/raw_client.rs b/src/raw_client.rs index e278c84..46bf2b5 100644 --- a/src/raw_client.rs +++ b/src/raw_client.rs @@ -299,23 +299,29 @@ impl RawClient { ))] mod danger { use crate::raw_client::ServerName; - use rustls::client::danger::ServerCertVerified; - use rustls::pki_types::CertificateDer; - use rustls::pki_types::UnixTime; - use rustls::Error; + use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified}; + use rustls::crypto::CryptoProvider; + use rustls::pki_types::{CertificateDer, UnixTime}; + use rustls::DigitallySignedStruct; #[derive(Debug)] - pub struct NoCertificateVerification {} + pub struct NoCertificateVerification(CryptoProvider); + + impl NoCertificateVerification { + pub fn new(provider: CryptoProvider) -> Self { + Self(provider) + } + } impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _end_entity: &CertificateDer, - _intermediates: &[CertificateDer], - _server_name: &ServerName, - _ocsp_response: &[u8], + _end_entity: &CertificateDer<'_>, + _intermediates: &[CertificateDer<'_>], + _server_name: &ServerName<'_>, + _ocsp: &[u8], _now: UnixTime, - ) -> Result { + ) -> Result { Ok(ServerCertVerified::assertion()) } @@ -323,22 +329,22 @@ mod danger { &self, _message: &[u8], _cert: &CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, - ) -> Result { - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) } fn verify_tls13_signature( &self, _message: &[u8], _cert: &CertificateDer<'_>, - _dss: &rustls::DigitallySignedStruct, - ) -> Result { - Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + _dss: &DigitallySignedStruct, + ) -> Result { + Ok(HandshakeSignatureValid::assertion()) } fn supported_verify_schemes(&self) -> Vec { - vec![] + self.0.signature_verification_algorithms.supported_schemes() } } } @@ -420,7 +426,10 @@ impl RawClient { builder .dangerous() .with_custom_certificate_verifier(std::sync::Arc::new( - danger::NoCertificateVerification {}, + #[cfg(feature = "use-rustls")] + danger::NoCertificateVerification::new(rustls::crypto::aws_lc_rs::default_provider()), + #[cfg(feature = "use-rustls-ring")] + danger::NoCertificateVerification::new(rustls::crypto::ring::default_provider()), )) .with_no_client_auth() }; @@ -1093,6 +1102,38 @@ impl ElectrumApi for RawClient { Ok(serde_json::from_value(result)?) } + fn transaction_id_from_pos(&self, height: usize, tx_pos: usize) -> Result { + let params = vec![Param::Usize(height), Param::Usize(tx_pos)]; + let req = Request::new_id( + self.last_id.fetch_add(1, Ordering::SeqCst), + "blockchain.transaction.id_from_pos", + params, + ); + let result = self.call(req)?; + + Ok(serde_json::from_value(result)?) + } + + fn transaction_id_from_pos_with_merkle( + &self, + height: usize, + tx_pos: usize, + ) -> Result { + let params = vec![ + Param::Usize(height), + Param::Usize(tx_pos), + Param::Bool(true), + ]; + let req = Request::new_id( + self.last_id.fetch_add(1, Ordering::SeqCst), + "blockchain.transaction.id_from_pos", + params, + ); + let result = self.call(req)?; + + Ok(serde_json::from_value(result)?) + } + fn server_features(&self) -> Result { let req = Request::new_id( self.last_id.fetch_add(1, Ordering::SeqCst), @@ -1387,6 +1428,41 @@ mod test { )); } + #[test] + fn test_transaction_id_from_pos() { + use bitcoin::Txid; + + let client = RawClient::new(get_test_server(), None).unwrap(); + + let txid = + Txid::from_str("1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d") + .unwrap(); + let resp = client.transaction_id_from_pos(630000, 68).unwrap(); + assert_eq!(resp, txid); + } + + #[test] + fn test_transaction_id_from_pos_with_merkle() { + use bitcoin::Txid; + + let client = RawClient::new(get_test_server(), None).unwrap(); + + let txid = + Txid::from_str("1f7ff3c407f33eabc8bec7d2cc230948f2249ec8e591bcf6f971ca9366c8788d") + .unwrap(); + let resp = client + .transaction_id_from_pos_with_merkle(630000, 68) + .unwrap(); + assert_eq!(resp.tx_hash, txid); + assert_eq!( + resp.merkle[0], + [ + 34, 65, 51, 64, 49, 139, 115, 189, 185, 246, 70, 225, 168, 193, 217, 195, 47, 66, + 179, 240, 153, 24, 114, 215, 144, 196, 212, 41, 39, 155, 246, 25 + ] + ); + } + #[test] fn test_ping() { let client = RawClient::new(get_test_server(), None).unwrap(); diff --git a/src/types.rs b/src/types.rs index 84f9c94..f92b817 100644 --- a/src/types.rs +++ b/src/types.rs @@ -240,6 +240,17 @@ pub struct GetMerkleRes { pub merkle: Vec<[u8; 32]>, } +/// Response to a [`transaction_id_from_pos_with_merkle`](../client/struct.Client.html#method.transaction_id_from_pos_with_merkle) +/// request. +#[derive(Clone, Debug, Deserialize)] +pub struct GetIdFromPosRes { + /// Txid of the transaction. + pub tx_hash: Txid, + /// The merkle path of the transaction. + #[serde(deserialize_with = "from_hex_array")] + pub merkle: Vec<[u8; 32]>, +} + /// Notification of a new block header #[derive(Clone, Debug, Deserialize)] pub struct HeaderNotification {