From bfe03b91b2a331a63e8a42413a51d3bed898bd66 Mon Sep 17 00:00:00 2001 From: thunderbiscuit Date: Mon, 29 Aug 2022 15:47:37 -0400 Subject: [PATCH] Add inline documentation (#171) * Add docs for AddressInfo and AddressIndex structs * Add docs for DatabaseConfig and ElectrumConfig * Add docs for EsploraConfig struct * Add docs for TransactionDetails struct * Add docs for OutPoint struct * Add docs for TxOut struct * Add docs for TxBuilder --- src/lib.rs | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 131 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8786d10..d7d5030 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,8 +42,11 @@ pub struct AddressAmount { pub amount: u64, } +/// A derived address and the index it was found at. pub struct AddressInfo { + /// Child index of this address pub index: u32, + /// Address pub address: String, } @@ -56,8 +59,17 @@ impl From for AddressInfo { } } +/// The address index selection strategy to use to derived an address from the wallet's external +/// descriptor. pub enum AddressIndex { + /// Return a new address after incrementing the current descriptor index. New, + /// Return the address for the current descriptor index if it has not been used in a received + /// transaction. Otherwise return a new address as with AddressIndex::New. + /// Use with caution, if the wallet has not yet detected an address has been used it could + /// return an already used address. This function is primarily meant for situations where the + /// caller is untrusted; for example when deriving donation addresses on-demand for a public + /// web page. LastUnused, } @@ -70,48 +82,94 @@ impl From for BdkAddressIndex { } } +/// Type that can contain any of the database configurations defined by the library +/// This allows storing a single configuration that can be loaded into an AnyDatabaseConfig +/// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime +/// will find this particularly useful. pub enum DatabaseConfig { + /// Memory database has no config Memory, + /// Simple key-value embedded database based on sled Sled { config: SledDbConfiguration }, + /// Sqlite embedded database using rusqlite Sqlite { config: SqliteDbConfiguration }, } +/// Configuration for an ElectrumBlockchain pub struct ElectrumConfig { + /// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with ssl:// or tcp:// and include a port + /// e.g. ssl://electrum.blockstream.info:60002 pub url: String, + /// URL of the socks5 proxy server or a Tor service pub socks5: Option, + /// Request retry count pub retry: u8, + /// Request timeout (seconds) pub timeout: Option, + /// Stop searching addresses for transactions after finding an unused gap of this length pub stop_gap: u64, } +/// Configuration for an EsploraBlockchain pub struct EsploraConfig { + /// Base URL of the esplora service + /// e.g. https://blockstream.info/api/ pub base_url: String, + /// Optional URL of the proxy to use to make requests to the Esplora server + /// The string should be formatted as: ://:@host:. + /// Note that the format of this value and the supported protocols change slightly between the + /// sync version of esplora (using ureq) and the async version (using reqwest). For more + /// details check with the documentation of the two crates. Both of them are compiled with + /// the socks feature enabled. + /// The proxy is ignored when targeting wasm32. pub proxy: Option, + /// Number of parallel requests sent to the esplora service (default: 4) pub concurrency: Option, + /// Stop searching addresses for transactions after finding an unused gap of this length. pub stop_gap: u64, + /// Socket timeout. pub timeout: Option, } +/// Type that can contain any of the blockchain configurations defined by the library. pub enum BlockchainConfig { + /// Electrum client Electrum { config: ElectrumConfig }, + /// Esplora client Esplora { config: EsploraConfig }, } +/// A wallet transaction #[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct TransactionDetails { - pub fee: Option, - pub received: u64, - pub sent: u64, + /// Transaction id. pub txid: String, + /// Received value (sats) + /// Sum of owned outputs of this transaction. + pub received: u64, + /// Sent value (sats) + /// Sum of owned inputs of this transaction. + pub sent: u64, + /// Fee value (sats) if available. + /// The availability of the fee depends on the backend. It's never None with an Electrum + /// Server backend, but it could be None with a Bitcoin RPC node without txindex that receive + /// funds while offline. + pub fee: Option, } +/// A transaction, either of type Confirmed or Unconfirmed #[derive(Debug, Clone, PartialEq, Eq)] pub enum Transaction { + /// A transaction that has yet to be included in a block Unconfirmed { + /// The details of wallet transaction. details: TransactionDetails, }, + /// A transaction that has been mined and is part of a block Confirmed { + /// The details of wallet transaction details: TransactionDetails, + /// Timestamp and block height of the block in which this transaction was mined confirmation: BlockTime, }, } @@ -197,9 +255,12 @@ struct Wallet { wallet_mutex: Mutex>, } +/// A reference to a transaction output. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct OutPoint { + /// The referenced transaction's txid. txid: String, + /// The index of the referenced output in its transaction's vout. vout: u32, } @@ -212,8 +273,11 @@ impl From<&OutPoint> for BdkOutPoint { } } +/// A transaction output, which defines new coins to be created from old ones. pub struct TxOut { + /// The value of the output, in satoshis. value: u64, + /// The address of the output. address: String, } @@ -252,7 +316,10 @@ impl NetworkLocalUtxo for LocalUtxo { } } +/// Trait that logs at level INFO every update received (if any). pub trait Progress: Send + Sync + 'static { + /// Send a new progress update. The progress value should be in the range 0.0 - 100.0, and the message value is an + /// optional text message that can be displayed to the user. fn update(&self, progress: f32, message: Option); } @@ -298,6 +365,11 @@ impl PartiallySignedBitcoinTransaction { } } +/// A Bitcoin wallet. +/// The Wallet acts as a way of coherently interfacing with output descriptors and related transactions. Its main components are: +/// 1. Output descriptors from which it can derive addresses. +/// 2. A Database where it tracks transactions and utxos related to the descriptors. +/// 3. Signers that can contribute signatures to addresses instantiated from the descriptors. impl Wallet { fn new( descriptor: String, @@ -324,10 +396,12 @@ impl Wallet { self.wallet_mutex.lock().expect("wallet") } + /// Get the Bitcoin network the wallet is using. fn network(&self) -> Network { self.get_wallet().network() } + /// Sync the internal database with the blockchain. fn sync( &self, blockchain: &Blockchain, @@ -344,26 +418,35 @@ impl Wallet { self.get_wallet().sync(blockchain.deref(), bdk_sync_opts) } + /// Return a derived address using the external descriptor, see AddressIndex for available address index selection + /// strategies. If none of the keys in the descriptor are derivable (i.e. the descriptor does not end with a * character) + /// then the same address will always be returned for any AddressIndex. fn get_address(&self, address_index: AddressIndex) -> Result { self.get_wallet() .get_address(address_index.into()) .map(AddressInfo::from) } + /// Return the balance, meaning the sum of this wallet’s unspent outputs’ values. Note that this method only operates + /// on the internal database, which first needs to be Wallet.sync manually. fn get_balance(&self) -> Result { self.get_wallet().get_balance() } + /// Sign a transaction with all the wallet’s signers. fn sign(&self, psbt: &PartiallySignedBitcoinTransaction) -> Result { let mut psbt = psbt.internal.lock().unwrap(); self.get_wallet().sign(&mut psbt, SignOptions::default()) } + /// Return the list of transactions made and received by the wallet. Note that this method only operate on the internal database, which first needs to be [Wallet.sync] manually. fn list_transactions(&self) -> Result, Error> { let transactions = self.get_wallet().list_transactions(true)?; Ok(transactions.iter().map(Transaction::from).collect()) } + /// Return the list of unspent outputs of this wallet. Note that this method only operates on the internal database, + /// which first needs to be Wallet.sync manually. fn list_unspent(&self) -> Result, Error> { let unspents = self.get_wallet().list_unspent()?; Ok(unspents @@ -385,6 +468,9 @@ enum RbfValue { Value(u32), } +/// A transaction builder. +/// After creating the TxBuilder, you set options on it until finally calling finish to consume the builder and generate the transaction. +/// Each method on the TxBuilder returns an instance of a new TxBuilder with the option set/added. #[derive(Clone, Debug)] struct TxBuilder { recipients: Vec<(String, u64)>, @@ -417,6 +503,7 @@ impl TxBuilder { } } + /// Add a recipient to the internal list. fn add_recipient(&self, recipient: String, amount: u64) -> Arc { let mut recipients = self.recipients.to_vec(); recipients.append(&mut vec![(recipient, amount)]); @@ -437,6 +524,8 @@ impl TxBuilder { }) } + /// Add a utxo to the internal list of unspendable utxos. It’s important to note that the "must-be-spent" + /// utxos added with [TxBuilder.addUtxo] have priority over this. See the Rust docs of the two linked methods for more details. fn add_unspendable(&self, unspendable: OutPoint) -> Arc { let mut unspendable_hash_set = self.unspendable.clone(); unspendable_hash_set.insert(unspendable); @@ -446,10 +535,15 @@ impl TxBuilder { }) } + /// Add an outpoint to the internal list of UTXOs that must be spent. These have priority over the "unspendable" + /// utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. fn add_utxo(&self, outpoint: OutPoint) -> Arc { self.add_utxos(vec![outpoint]) } + /// Add the list of outpoints to the internal list of UTXOs that must be spent. If an error occurs while adding + /// any of the UTXOs then none of them are added and the error is returned. These have priority over the "unspendable" + /// utxos, meaning that if a utxo is present both in the "utxos" and the "unspendable" list, it will be spent. fn add_utxos(&self, mut outpoints: Vec) -> Arc { let mut utxos = self.utxos.to_vec(); utxos.append(&mut outpoints); @@ -459,6 +553,7 @@ impl TxBuilder { }) } + /// Do not spend change outputs. This effectively adds all the change outputs to the "unspendable" list. See TxBuilder.unspendable. fn do_not_spend_change(&self) -> Arc { Arc::new(TxBuilder { change_policy: ChangeSpendPolicy::ChangeForbidden, @@ -466,6 +561,8 @@ impl TxBuilder { }) } + /// Only spend utxos added by [add_utxo]. The wallet will not add additional utxos to the transaction even if they are + /// needed to make the transaction valid. fn manually_selected_only(&self) -> Arc { Arc::new(TxBuilder { manually_selected_only: true, @@ -473,6 +570,7 @@ impl TxBuilder { }) } + /// Only spend change outputs. This effectively adds all the non-change outputs to the "unspendable" list. See TxBuilder.unspendable. fn only_spend_change(&self) -> Arc { Arc::new(TxBuilder { change_policy: ChangeSpendPolicy::OnlyChange, @@ -480,6 +578,8 @@ impl TxBuilder { }) } + /// Replace the internal list of unspendable utxos with a new list. It’s important to note that the "must-be-spent" utxos added with + /// TxBuilder.addUtxo have priority over these. See the Rust docs of the two linked methods for more details. fn unspendable(&self, unspendable: Vec) -> Arc { Arc::new(TxBuilder { unspendable: unspendable.into_iter().collect(), @@ -487,6 +587,7 @@ impl TxBuilder { }) } + /// Set a custom fee rate. fn fee_rate(&self, sat_per_vb: f32) -> Arc { Arc::new(TxBuilder { fee_rate: Some(sat_per_vb), @@ -494,6 +595,7 @@ impl TxBuilder { }) } + /// Set an absolute fee. fn fee_absolute(&self, fee_amount: u64) -> Arc { Arc::new(TxBuilder { fee_absolute: Some(fee_amount), @@ -501,6 +603,7 @@ impl TxBuilder { }) } + /// Spend all the available inputs. This respects filters like TxBuilder.unspendable and the change policy. fn drain_wallet(&self) -> Arc { Arc::new(TxBuilder { drain_wallet: true, @@ -508,6 +611,14 @@ impl TxBuilder { }) } + /// Sets the address to drain excess coins to. Usually, when there are excess coins they are sent to a change address + /// generated by the wallet. This option replaces the usual change address with an arbitrary ScriptPubKey of your choosing. + /// Just as with a change output, if the drain output is not needed (the excess coins are too small) it will not be included + /// in the resulting transaction. The only difference is that it is valid to use drain_to without setting any ordinary recipients + /// with add_recipient (but it is perfectly fine to add recipients as well). If you choose not to set any recipients, you should + /// either provide the utxos that the transaction should spend via add_utxos, or set drain_wallet to spend all of them. + /// When bumping the fees of a transaction made with this option, you probably want to use BumpFeeTxBuilder.allow_shrinking + /// to allow this output to be reduced to pay for the extra fees. fn drain_to(&self, address: String) -> Arc { Arc::new(TxBuilder { drain_to: Some(address), @@ -515,6 +626,7 @@ impl TxBuilder { }) } + /// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. fn enable_rbf(&self) -> Arc { Arc::new(TxBuilder { rbf: Some(RbfValue::Default), @@ -522,6 +634,9 @@ impl TxBuilder { }) } + /// Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an + /// "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` is higher than `0xFFFFFFFD` + /// an error will be thrown, since it would not be a valid nSequence to signal RBF. fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc { Arc::new(TxBuilder { rbf: Some(RbfValue::Value(nsequence)), @@ -529,6 +644,7 @@ impl TxBuilder { }) } + /// Add data as an output using OP_RETURN. fn add_data(&self, data: Vec) -> Arc { Arc::new(TxBuilder { data, @@ -536,6 +652,7 @@ impl TxBuilder { }) } + /// Finish building the transaction. Returns the BIP174 PSBT. fn finish(&self, wallet: &Wallet) -> Result, Error> { let wallet = wallet.get_wallet(); let mut tx_builder = wallet.build_tx(); @@ -591,6 +708,7 @@ impl TxBuilder { } } +/// The BumpFeeTxBuilder is used to bump the fee on a transaction that has been broadcast and has its RBF flag set to true. #[derive(Clone)] struct BumpFeeTxBuilder { txid: String, @@ -609,6 +727,11 @@ impl BumpFeeTxBuilder { } } + /// Explicitly tells the wallet that it is allowed to reduce the amount of the output matching this script_pubkey + /// in order to bump the transaction fee. Without specifying this the wallet will attempt to find a change output to + /// shrink instead. Note that the output may shrink to below the dust limit and therefore be removed. If it is preserved + /// then it is currently not guaranteed to be in the same position as it was originally. Returns an error if script_pubkey + /// can’t be found among the recipients of the transaction we are bumping. fn allow_shrinking(&self, address: String) -> Arc { Arc::new(Self { allow_shrinking: Some(address), @@ -616,6 +739,7 @@ impl BumpFeeTxBuilder { }) } + /// Enable signaling RBF. This will use the default `nsequence` value of `0xFFFFFFFD`. fn enable_rbf(&self) -> Arc { Arc::new(Self { rbf: Some(RbfValue::Default), @@ -623,6 +747,9 @@ impl BumpFeeTxBuilder { }) } + /// Enable signaling RBF with a specific nSequence value. This can cause conflicts if the wallet's descriptors contain an + /// "older" (OP_CSV) operator and the given `nsequence` is lower than the CSV value. If the `nsequence` is higher than `0xFFFFFFFD` + /// an error will be thrown, since it would not be a valid nSequence to signal RBF. fn enable_rbf_with_sequence(&self, nsequence: u32) -> Arc { Arc::new(Self { rbf: Some(RbfValue::Value(nsequence)), @@ -630,6 +757,7 @@ impl BumpFeeTxBuilder { }) } + /// Finish building the transaction. Returns the BIP174 PSBT. fn finish(&self, wallet: &Wallet) -> Result, Error> { let wallet = wallet.get_wallet(); let txid = Txid::from_str(self.txid.as_str())?;