refactor: streamline blockchain clients error names

This commit is contained in:
thunderbiscuit 2024-05-16 10:33:14 -04:00
parent 8f4c80cb98
commit ca8a3d0471
No known key found for this signature in database
GPG Key ID: 88253696EB836462
5 changed files with 95 additions and 51 deletions

View File

@ -102,7 +102,7 @@ interface DescriptorKeyError {
}; };
[Error] [Error]
interface ElectrumClientError { interface ElectrumError {
IOError(string error_message); IOError(string error_message);
Json(string error_message); Json(string error_message);
Hex(string error_message); Hex(string error_message);
@ -493,16 +493,16 @@ interface EsploraClient {
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
interface ElectrumClient { interface ElectrumClient {
[Throws=ElectrumClientError] [Throws=ElectrumError]
constructor(string url); constructor(string url);
[Throws=ElectrumClientError] [Throws=ElectrumError]
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 batch_size, boolean fetch_prev_txouts); Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 batch_size, boolean fetch_prev_txouts);
[Throws=ElectrumClientError] [Throws=ElectrumError]
Update sync(SyncRequest sync_request, u64 batch_size, boolean fetch_prev_txouts); Update sync(SyncRequest sync_request, u64 batch_size, boolean fetch_prev_txouts);
[Throws=ElectrumClientError] [Throws=ElectrumError]
string broadcast([ByRef] Transaction transaction); string broadcast([ByRef] Transaction transaction);
}; };

View File

@ -1,7 +1,7 @@
use crate::error::ElectrumClientError; use crate::bitcoin::Transaction;
use crate::error::ElectrumError;
use crate::types::{FullScanRequest, SyncRequest}; use crate::types::{FullScanRequest, SyncRequest};
use crate::wallet::Update; use crate::wallet::Update;
use std::collections::BTreeMap;
use bdk::bitcoin::Transaction as BdkTransaction; use bdk::bitcoin::Transaction as BdkTransaction;
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest; use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
@ -12,13 +12,13 @@ use bdk::KeychainKind;
use bdk_electrum::electrum_client::{Client as BdkBlockingClient, ElectrumApi}; use bdk_electrum::electrum_client::{Client as BdkBlockingClient, ElectrumApi};
use bdk_electrum::{ElectrumExt, ElectrumFullScanResult, ElectrumSyncResult}; use bdk_electrum::{ElectrumExt, ElectrumFullScanResult, ElectrumSyncResult};
use crate::bitcoin::Transaction; use std::collections::BTreeMap;
use std::sync::Arc; use std::sync::Arc;
pub struct ElectrumClient(BdkBlockingClient); pub struct ElectrumClient(BdkBlockingClient);
impl ElectrumClient { impl ElectrumClient {
pub fn new(url: String) -> Result<Self, ElectrumClientError> { pub fn new(url: String) -> Result<Self, ElectrumError> {
let client = BdkBlockingClient::new(url.as_str())?; let client = BdkBlockingClient::new(url.as_str())?;
Ok(Self(client)) Ok(Self(client))
} }
@ -29,14 +29,14 @@ impl ElectrumClient {
stop_gap: u64, stop_gap: u64,
batch_size: u64, batch_size: u64,
fetch_prev_txouts: bool, fetch_prev_txouts: bool,
) -> Result<Arc<Update>, ElectrumClientError> { ) -> Result<Arc<Update>, ElectrumError> {
// using option and take is not ideal but the only way to take full ownership of the request // using option and take is not ideal but the only way to take full ownership of the request
let request: BdkFullScanRequest<KeychainKind> = request let request: BdkFullScanRequest<KeychainKind> = request
.0 .0
.lock() .lock()
.unwrap() .unwrap()
.take() .take()
.ok_or(ElectrumClientError::RequestAlreadyConsumed)?; .ok_or(ElectrumError::RequestAlreadyConsumed)?;
let electrum_result: ElectrumFullScanResult<KeychainKind> = self.0.full_scan( let electrum_result: ElectrumFullScanResult<KeychainKind> = self.0.full_scan(
request, request,
@ -61,14 +61,14 @@ impl ElectrumClient {
request: Arc<SyncRequest>, request: Arc<SyncRequest>,
batch_size: u64, batch_size: u64,
fetch_prev_txouts: bool, fetch_prev_txouts: bool,
) -> Result<Arc<Update>, ElectrumClientError> { ) -> Result<Arc<Update>, ElectrumError> {
// using option and take is not ideal but the only way to take full ownership of the request // using option and take is not ideal but the only way to take full ownership of the request
let request: BdkSyncRequest = request let request: BdkSyncRequest = request
.0 .0
.lock() .lock()
.unwrap() .unwrap()
.take() .take()
.ok_or(ElectrumClientError::RequestAlreadyConsumed)?; .ok_or(ElectrumError::RequestAlreadyConsumed)?;
let electrum_result: ElectrumSyncResult = let electrum_result: ElectrumSyncResult =
self.0 self.0
@ -85,11 +85,11 @@ impl ElectrumClient {
Ok(Arc::new(Update(update))) Ok(Arc::new(Update(update)))
} }
pub fn broadcast(&self, transaction: &Transaction) -> Result<String, ElectrumClientError> { pub fn broadcast(&self, transaction: &Transaction) -> Result<String, ElectrumError> {
let bdk_transaction: BdkTransaction = transaction.into(); let bdk_transaction: BdkTransaction = transaction.into();
self.0 self.0
.transaction_broadcast(&bdk_transaction) .transaction_broadcast(&bdk_transaction)
.map_err(ElectrumClientError::from) .map_err(ElectrumError::from)
.map(|txid| txid.to_string()) .map(|txid| txid.to_string())
} }
} }

View File

@ -257,7 +257,7 @@ pub enum DescriptorKeyError {
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum ElectrumClientError { pub enum ElectrumError {
#[error("{error_message}")] #[error("{error_message}")]
IOError { error_message: String }, IOError { error_message: String },
@ -558,44 +558,44 @@ impl From<BdkAddressError> for AddressError {
} }
} }
impl From<BdkElectrumError> for ElectrumClientError { impl From<BdkElectrumError> for ElectrumError {
fn from(error: BdkElectrumError) -> Self { fn from(error: BdkElectrumError) -> Self {
match error { match error {
BdkElectrumError::IOError(e) => ElectrumClientError::IOError { BdkElectrumError::IOError(e) => ElectrumError::IOError {
error_message: e.to_string(), error_message: e.to_string(),
}, },
BdkElectrumError::JSON(e) => ElectrumClientError::Json { BdkElectrumError::JSON(e) => ElectrumError::Json {
error_message: e.to_string(), error_message: e.to_string(),
}, },
BdkElectrumError::Hex(e) => ElectrumClientError::Hex { BdkElectrumError::Hex(e) => ElectrumError::Hex {
error_message: e.to_string(), error_message: e.to_string(),
}, },
BdkElectrumError::Protocol(e) => ElectrumClientError::Protocol { BdkElectrumError::Protocol(e) => ElectrumError::Protocol {
error_message: e.to_string(), error_message: e.to_string(),
}, },
BdkElectrumError::Bitcoin(e) => ElectrumClientError::Bitcoin { BdkElectrumError::Bitcoin(e) => ElectrumError::Bitcoin {
error_message: e.to_string(), error_message: e.to_string(),
}, },
BdkElectrumError::AlreadySubscribed(_) => ElectrumClientError::AlreadySubscribed, BdkElectrumError::AlreadySubscribed(_) => ElectrumError::AlreadySubscribed,
BdkElectrumError::NotSubscribed(_) => ElectrumClientError::NotSubscribed, BdkElectrumError::NotSubscribed(_) => ElectrumError::NotSubscribed,
BdkElectrumError::InvalidResponse(e) => ElectrumClientError::InvalidResponse { BdkElectrumError::InvalidResponse(e) => ElectrumError::InvalidResponse {
error_message: e.to_string(), error_message: e.to_string(),
}, },
BdkElectrumError::Message(e) => ElectrumClientError::Message { BdkElectrumError::Message(e) => ElectrumError::Message {
error_message: e.to_string(), error_message: e.to_string(),
}, },
BdkElectrumError::InvalidDNSNameError(domain) => { BdkElectrumError::InvalidDNSNameError(domain) => {
ElectrumClientError::InvalidDNSNameError { domain } ElectrumError::InvalidDNSNameError { domain }
} }
BdkElectrumError::MissingDomain => ElectrumClientError::MissingDomain, BdkElectrumError::MissingDomain => ElectrumError::MissingDomain,
BdkElectrumError::AllAttemptsErrored(_) => ElectrumClientError::AllAttemptsErrored, BdkElectrumError::AllAttemptsErrored(_) => ElectrumError::AllAttemptsErrored,
BdkElectrumError::SharedIOError(e) => ElectrumClientError::SharedIOError { BdkElectrumError::SharedIOError(e) => ElectrumError::SharedIOError {
error_message: e.to_string(), error_message: e.to_string(),
}, },
BdkElectrumError::CouldntLockReader => ElectrumClientError::CouldntLockReader, BdkElectrumError::CouldntLockReader => ElectrumError::CouldntLockReader,
BdkElectrumError::Mpsc => ElectrumClientError::Mpsc, BdkElectrumError::Mpsc => ElectrumError::Mpsc,
BdkElectrumError::CouldNotCreateConnection(error_message) => { BdkElectrumError::CouldNotCreateConnection(error_message) => {
ElectrumClientError::CouldNotCreateConnection { ElectrumError::CouldNotCreateConnection {
error_message: error_message.to_string(), error_message: error_message.to_string(),
} }
} }
@ -1079,7 +1079,7 @@ impl From<NewOrLoadError> for WalletCreationError {
mod test { mod test {
use crate::error::{ use crate::error::{
AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError, AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError,
DescriptorKeyError, ElectrumClientError, EsploraError, ExtractTxError, FeeRateError, DescriptorKeyError, ElectrumError, EsploraError, ExtractTxError, FeeRateError,
ParseAmountError, PersistenceError, PsbtParseError, TransactionError, TxidParseError, ParseAmountError, PersistenceError, PsbtParseError, TransactionError, TxidParseError,
WalletCreationError, WalletCreationError,
}; };
@ -1488,77 +1488,77 @@ mod test {
fn test_error_electrum_client() { fn test_error_electrum_client() {
let cases = vec![ let cases = vec![
( (
ElectrumClientError::IOError { error_message: "message".to_string(), }, ElectrumError::IOError { error_message: "message".to_string(), },
"message", "message",
), ),
( (
ElectrumClientError::Json { error_message: "message".to_string(), }, ElectrumError::Json { error_message: "message".to_string(), },
"message", "message",
), ),
( (
ElectrumClientError::Hex { error_message: "message".to_string(), }, ElectrumError::Hex { error_message: "message".to_string(), },
"message", "message",
), ),
( (
ElectrumClientError::Protocol { error_message: "message".to_string(), }, ElectrumError::Protocol { error_message: "message".to_string(), },
"electrum server error: message", "electrum server error: message",
), ),
( (
ElectrumClientError::Bitcoin { ElectrumError::Bitcoin {
error_message: "message".to_string(), error_message: "message".to_string(),
}, },
"message", "message",
), ),
( (
ElectrumClientError::AlreadySubscribed, ElectrumError::AlreadySubscribed,
"already subscribed to the notifications of an address", "already subscribed to the notifications of an address",
), ),
( (
ElectrumClientError::NotSubscribed, ElectrumError::NotSubscribed,
"not subscribed to the notifications of an address", "not subscribed to the notifications of an address",
), ),
( (
ElectrumClientError::InvalidResponse { ElectrumError::InvalidResponse {
error_message: "message".to_string(), error_message: "message".to_string(),
}, },
"error during the deserialization of a response from the server: message", "error during the deserialization of a response from the server: message",
), ),
( (
ElectrumClientError::Message { ElectrumError::Message {
error_message: "message".to_string(), error_message: "message".to_string(),
}, },
"message", "message",
), ),
( (
ElectrumClientError::InvalidDNSNameError { ElectrumError::InvalidDNSNameError {
domain: "domain".to_string(), domain: "domain".to_string(),
}, },
"invalid domain name domain not matching SSL certificate", "invalid domain name domain not matching SSL certificate",
), ),
( (
ElectrumClientError::MissingDomain, ElectrumError::MissingDomain,
"missing domain while it was explicitly asked to validate it", "missing domain while it was explicitly asked to validate it",
), ),
( (
ElectrumClientError::AllAttemptsErrored, ElectrumError::AllAttemptsErrored,
"made one or multiple attempts, all errored", "made one or multiple attempts, all errored",
), ),
( (
ElectrumClientError::SharedIOError { ElectrumError::SharedIOError {
error_message: "message".to_string(), error_message: "message".to_string(),
}, },
"message", "message",
), ),
( (
ElectrumClientError::CouldntLockReader, ElectrumError::CouldntLockReader,
"couldn't take a lock on the reader mutex. This means that there's already another reader thread is running" "couldn't take a lock on the reader mutex. This means that there's already another reader thread is running"
), ),
( (
ElectrumClientError::Mpsc, ElectrumError::Mpsc,
"broken IPC communication channel: the other thread probably has exited", "broken IPC communication channel: the other thread probably has exited",
), ),
( (
ElectrumClientError::CouldNotCreateConnection { ElectrumError::CouldNotCreateConnection {
error_message: "message".to_string(), error_message: "message".to_string(),
}, },
"message", "message",

View File

@ -25,7 +25,7 @@ use crate::error::CannotConnectError;
use crate::error::CreateTxError; use crate::error::CreateTxError;
use crate::error::DescriptorError; use crate::error::DescriptorError;
use crate::error::DescriptorKeyError; use crate::error::DescriptorKeyError;
use crate::error::ElectrumClientError; use crate::error::ElectrumError;
use crate::error::EsploraError; use crate::error::EsploraError;
use crate::error::ExtractTxError; use crate::error::ExtractTxError;
use crate::error::FeeRateError; use crate::error::FeeRateError;

View File

@ -0,0 +1,44 @@
import XCTest
@testable import BitcoinDevKit
private let SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"
final class LiveElectrumClientTests: XCTestCase {
func testSyncedBalance() throws {
let descriptor = try Descriptor(
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
network: Network.signet
)
let wallet = try Wallet.newNoPersist(
descriptor: descriptor,
changeDescriptor: nil,
network: .signet
)
let electrumClient: ElectrumClient = try ElectrumClient(url: SIGNET_ELECTRUM_URL)
let fullScanRequest: FullScanRequest = wallet.startFullScan()
let update = try electrumClient.fullScan(
fullScanRequest: fullScanRequest,
stopGap: 10,
batchSize: 10,
fetchPrevTxouts: false
)
try wallet.applyUpdate(update: update)
let _ = try wallet.commit()
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
XCTAssertGreaterThan(
wallet.getBalance().total.toSat(),
UInt64(0),
"Wallet must have positive balance, please send funds to \(address)"
)
print("Transactions count: \(wallet.transactions().count)")
let transactions = wallet.transactions().prefix(3)
for tx in transactions {
let sentAndReceived = wallet.sentAndReceived(tx: tx.transaction)
print("Transaction: \(tx.transaction.txid())")
print("Sent \(sentAndReceived.sent)")
print("Received \(sentAndReceived.received)")
}
}
}