diff --git a/integration_test/tests/wallet.rs b/integration_test/tests/wallet.rs index 4ab9920c..6e90f529 100644 --- a/integration_test/tests/wallet.rs +++ b/integration_test/tests/wallet.rs @@ -593,6 +593,41 @@ fn wallet__list_received_by_address__modelled() { assert!(model.0.iter().any(|item| &item.address == unchecked_addr)); } +#[test] +fn wallet__list_since_block__modelled() { + let node = Node::with_wallet(Wallet::Default, &[]); + node.fund_wallet(); + let addr = node.client.new_address().expect("newaddress"); + let amount = Amount::from_sat(5_000); + node.client.send_to_address(&addr, amount).expect("sendtoaddress"); + node.mine_a_block(); + + let json: ListSinceBlock = node.client.list_since_block().expect("listsinceblock"); + let model: Result = json.into_model(); + let model = model.unwrap(); + + let first_tx: mtype::TransactionItem = model.transactions[0].clone(); + assert_eq!(first_tx.txid.unwrap().to_string().len(), 64); +} + +#[test] +fn wallet__list_transactions__modelled() { + let node = Node::with_wallet(Wallet::Default, &[]); + + node.fund_wallet(); + let addr = node.client.new_address().expect("newaddress"); + let amount = Amount::from_sat(5_000); + node.client.send_to_address(&addr, amount).expect("sendtoaddress"); + node.mine_a_block(); + + let json: ListTransactions = node.client.list_transactions().expect("listtransactions"); + let model: Result = json.into_model(); + let model = model.unwrap(); + + let first_tx: mtype::TransactionItem = model.0[0].clone(); + assert_eq!(first_tx.txid.unwrap().to_string().len(), 64); +} + #[test] fn wallet__import_multi() { let node = match () { diff --git a/types/src/model/mod.rs b/types/src/model/mod.rs index 59200227..5f67f899 100644 --- a/types/src/model/mod.rs +++ b/types/src/model/mod.rs @@ -58,10 +58,10 @@ pub use self::{ GetTransactionDetail, GetUnconfirmedBalance, GetWalletInfo, GetWalletInfoScanning, HdKey, HdKeyDescriptor, LastProcessedBlock, ListAddressGroupings, ListAddressGroupingsItem, ListLockUnspent, ListLockUnspentItem, ListReceivedByAddress, ListReceivedByAddressItem, - ListReceivedByLabel, ListReceivedByLabelItem, ListSinceBlock, ListSinceBlockTransaction, - ListTransactions, ListTransactionsItem, ListUnspent, ListUnspentItem, ListWallets, - LoadWallet, PsbtBumpFee, RescanBlockchain, ScriptType, Send, SendAll, SendMany, - SendManyVerbose, SendToAddress, SignMessage, SimulateRawTransaction, TransactionCategory, - UnloadWallet, WalletCreateFundedPsbt, WalletDisplayAddress, WalletProcessPsbt, + ListReceivedByLabel, ListReceivedByLabelItem, ListSinceBlock, ListTransactions, + ListUnspent, ListUnspentItem, ListWallets, LoadWallet, PsbtBumpFee, RescanBlockchain, + ScriptType, Send, SendAll, SendMany, SendManyVerbose, SendToAddress, SignMessage, + SimulateRawTransaction, TransactionCategory, TransactionItem, UnloadWallet, + WalletCreateFundedPsbt, WalletDisplayAddress, WalletProcessPsbt, }, }; diff --git a/types/src/model/wallet.rs b/types/src/model/wallet.rs index 889d4895..9dfe877b 100644 --- a/types/src/model/wallet.rs +++ b/types/src/model/wallet.rs @@ -590,12 +590,12 @@ pub struct ListReceivedByLabelItem { #[serde(deny_unknown_fields)] pub struct ListSinceBlock { /// All the transactions. - pub transactions: Vec, + pub transactions: Vec, /// Only present if `include_removed=true`. /// /// Note: transactions that were re-added in the active chain will appear as-is in this array, /// and may thus have a positive confirmation count. - pub removed: Vec, + pub removed: Vec, /// The hash of the block (target_confirmations-1) from the best block on the main chain. /// /// This is typically used to feed back into listsinceblock the next time you call it. So you @@ -604,11 +604,12 @@ pub struct ListSinceBlock { pub last_block: BlockHash, } -/// Transaction list item, part of `ListSinceBlock`. -// https://github.com/rust-bitcoin/rust-bitcoin/issues/3516 +/// Transaction item, part of `listsinceblock` and `listtransactions`. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] -pub struct ListSinceBlockTransaction { +pub struct TransactionItem { + /// Only returns true if imported addresses were involved in transaction. + pub involves_watch_only: Option, /// The bitcoin address of the transaction. pub address: Option>, /// The transaction category. @@ -631,96 +632,64 @@ pub struct ListSinceBlockTransaction { /// Available for 'send' and 'receive' category of transactions. When it's < 0, it means the /// transaction conflicted that many blocks ago. pub confirmations: i64, + /// Only present if the transaction's only input is a coinbase one. Only documented from v0.20 and later. + pub generated: Option, + /// Whether we consider the transaction to be trusted and safe to spend from. Only present + /// when the transaction has 0 confirmations (or negative confirmations, if conflicted). v0.20 and later only. + pub trusted: Option, /// The block hash containing the transaction. /// /// Available for 'send' and 'receive' category of transactions. - pub block_hash: BlockHash, + pub block_hash: Option, + /// The block height containing the transaction. v20 and later only. + pub block_height: Option, /// The index of the transaction in the block that includes it. /// /// Available for 'send' and 'receive' category of transactions. - pub block_index: u32, + pub block_index: Option, /// The block time in seconds since epoch (1 Jan 1970 GMT). - pub block_time: u32, + pub block_time: Option, /// The transaction id. /// /// Available for 'send' and 'receive' category of transactions. pub txid: Option, + /// The hash of serialized transaction, including witness data. v24 and later only. + pub wtxid: Option, + /// Conflicting transaction ids. Only documented from v0.20 and later. + pub wallet_conflicts: Option>, + /// The txid if this tx was replaced. v23 and later only. + pub replaced_by_txid: Option, + /// The txid if this tx replaces one. v23 and later only. + pub replaces_txid: Option, + /// Transactions in the mempool that directly conflict with either this transaction or an ancestor transaction. v28 and later only. + pub mempool_conflicts: Option>, + /// If a comment to is associated with the transaction. + pub to: Option, /// The transaction time in seconds since epoch (Jan 1 1970 GMT). pub time: u32, /// The time received in seconds since epoch (Jan 1 1970 GMT). /// /// Available for 'send' and 'receive' category of transactions. pub time_received: u32, + /// If a comment is associated with the transaction. + pub comment: Option, /// Whether this transaction could be replaced due to BIP125 (replace-by-fee); /// may be unknown for unconfirmed transactions not in the mempool pub bip125_replaceable: Bip125Replaceable, + /// Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin. v24 and later only. + pub parent_descriptors: Option>, /// If the transaction has been abandoned (inputs are respendable). /// /// Only available for the 'send' category of transactions. pub abandoned: Option, - /// If a comment is associated with the transaction. - pub comment: Option, /// A comment for the address/transaction, if any. pub label: Option, - /// If a comment to is associated with the transaction. - pub to: Option, } /// Models the result of JSON-RPC method `listtransactions`. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] -pub struct ListTransactions(pub Vec); - -/// Transaction list item, part of `ListTransactions`. -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct ListTransactionsItem { - /// The bitcoin address of the transaction. - pub address: Address, - /// The transaction category. - pub category: TransactionCategory, - /// The amount. - /// - /// This is negative for the 'send' category, and is positive for the 'receive' category. - #[serde(default, with = "bitcoin::amount::serde::as_btc")] - pub amount: SignedAmount, - /// A comment for the address/transaction, if any. - pub label: Option, - /// The vout value. - pub vout: u32, - /// The amount of the fee in BTC. - /// - /// This is negative and only available for the 'send' category of transactions. - #[serde(default, with = "bitcoin::amount::serde::as_btc")] - pub fee: SignedAmount, - /// The number of confirmations for the transaction. - /// - /// Negative confirmations indicate the transaction conflicts with the block chain. - pub confirmations: i64, - /// Whether we consider the outputs of this unconfirmed transaction safe to spend. - pub trusted: bool, - /// The block hash containing the transaction. - pub block_hash: BlockHash, - /// The index of the transaction in the block that includes it. - pub block_index: u32, - /// The block time in seconds since epoch (1 Jan 1970 GMT). - pub block_time: u32, - /// The transaction id. - pub txid: Txid, - /// The transaction time in seconds since epoch (Jan 1 1970 GMT). - pub time: u32, - /// The time received in seconds since epoch (Jan 1 1970 GMT). - pub time_received: u32, - /// If a comment is associated with the transaction. - pub comment: Option, - /// Whether this transaction could be replaced due to BIP125 (replace-by-fee); - /// may be unknown for unconfirmed transactions not in the mempool - pub bip125_replaceable: Bip125Replaceable, - /// If the transaction has been abandoned (inputs are respendable). - /// - /// Only available for the 'send' category of transactions. - pub abandoned: Option, -} +pub struct ListTransactions(pub Vec); /// Models the result of JSON-RPC method `listunspent`. #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] diff --git a/types/src/v17/mod.rs b/types/src/v17/mod.rs index b8eae11b..cc1462b6 100644 --- a/types/src/v17/mod.rs +++ b/types/src/v17/mod.rs @@ -185,8 +185,8 @@ //! | listlockunspent | version + model | | //! | listreceivedbyaccount | returns nothing | | //! | listreceivedbyaddress | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwallets | version + model | | //! | loadwallet | version + model | | @@ -281,10 +281,9 @@ pub use self::{ ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddress, ListReceivedByAddressError, ListReceivedByAddressItem, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspent, ListUnspentItem, - ListUnspentItemError, ListWallets, LoadWallet, LockUnspent, RescanBlockchain, ScriptType, - SendMany, SendToAddress, SetTxFee, SignMessage, TransactionCategory, + ListTransactions, ListUnspent, ListUnspentItem, ListUnspentItemError, ListWallets, + LoadWallet, LockUnspent, RescanBlockchain, ScriptType, SendMany, SendToAddress, SetTxFee, + SignMessage, TransactionCategory, TransactionItem, TransactionItemError, WalletCreateFundedPsbt, WalletCreateFundedPsbtError, WalletProcessPsbt, }, zmq::GetZmqNotifications, diff --git a/types/src/v17/wallet/error.rs b/types/src/v17/wallet/error.rs index 5cebbff5..b04a8a62 100644 --- a/types/src/v17/wallet/error.rs +++ b/types/src/v17/wallet/error.rs @@ -496,9 +496,9 @@ impl std::error::Error for ListReceivedByAddressError { #[derive(Debug)] pub enum ListSinceBlockError { /// Conversion of item in `transactions` list failed. - Transactions(ListSinceBlockTransactionError), + Transactions(TransactionItemError), /// Conversion of item in `removed` list failed. - Removed(ListSinceBlockTransactionError), + Removed(TransactionItemError), /// Conversion of the `last_block` field failed. LastBlock(hex::HexToArrayError), } @@ -529,9 +529,9 @@ impl std::error::Error for ListSinceBlockError { } } -/// Error when converting a `ListSinceBlockTransaction` type into the model type. +/// Error when converting a `TransactionItem` type into the model type. #[derive(Debug)] -pub enum ListSinceBlockTransactionError { +pub enum TransactionItemError { /// Conversion of numeric type to expected type failed. Numeric(NumericError), /// Conversion of the `address` field failed. @@ -546,9 +546,9 @@ pub enum ListSinceBlockTransactionError { Txid(hex::HexToArrayError), } -impl fmt::Display for ListSinceBlockTransactionError { +impl fmt::Display for TransactionItemError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ListSinceBlockTransactionError as E; + use TransactionItemError as E; match *self { E::Numeric(ref e) => write_err!(f, "numeric"; e), @@ -562,9 +562,9 @@ impl fmt::Display for ListSinceBlockTransactionError { } #[cfg(feature = "std")] -impl std::error::Error for ListSinceBlockTransactionError { +impl std::error::Error for TransactionItemError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use ListSinceBlockTransactionError as E; + use TransactionItemError as E; match *self { E::Numeric(ref e) => Some(e), @@ -577,59 +577,7 @@ impl std::error::Error for ListSinceBlockTransactionError { } } -impl From for ListSinceBlockTransactionError { - fn from(e: NumericError) -> Self { Self::Numeric(e) } -} - -/// Error when converting a `ListTransactionsItem` type into the model type. -#[derive(Debug)] -pub enum ListTransactionsItemError { - /// Conversion of numeric type to expected type failed. - Numeric(NumericError), - /// Conversion of the `address` field failed. - Address(address::ParseError), - /// Conversion of the `amount` field failed. - Amount(ParseAmountError), - /// Conversion of the `fee` field failed. - Fee(ParseAmountError), - /// Conversion of the `block_hash` field failed. - BlockHash(hex::HexToArrayError), - /// Conversion of the `txid` field failed. - Txid(hex::HexToArrayError), -} - -impl fmt::Display for ListTransactionsItemError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ListTransactionsItemError as E; - - match *self { - E::Numeric(ref e) => write_err!(f, "numeric"; e), - E::Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), - E::Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), - E::Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), - E::BlockHash(ref e) => write_err!(f, "conversion of the `block_hash` field failed"; e), - E::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), - } - } -} - -#[cfg(feature = "std")] -impl std::error::Error for ListTransactionsItemError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use ListTransactionsItemError as E; - - match *self { - E::Numeric(ref e) => Some(e), - E::Address(ref e) => Some(e), - E::Amount(ref e) => Some(e), - E::Fee(ref e) => Some(e), - E::BlockHash(ref e) => Some(e), - E::Txid(ref e) => Some(e), - } - } -} - -impl From for ListTransactionsItemError { +impl From for TransactionItemError { fn from(e: NumericError) -> Self { Self::Numeric(e) } } diff --git a/types/src/v17/wallet/into.rs b/types/src/v17/wallet/into.rs index 87fe6615..220cfe47 100644 --- a/types/src/v17/wallet/into.rs +++ b/types/src/v17/wallet/into.rs @@ -578,91 +578,66 @@ impl ListSinceBlock { } } -impl ListSinceBlockTransaction { +impl TransactionItem { /// Converts version specific type to a version nonspecific, more strongly typed type. - pub fn into_model( - self, - ) -> Result { - use ListSinceBlockTransactionError as E; + pub fn into_model(self) -> Result { + use TransactionItemError as E; let address = self.address.parse::>().map_err(E::Address)?; let category = self.category.into_model(); let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?; let vout = crate::to_u32(self.vout, "vout")?; - let fee = SignedAmount::from_btc(self.fee).map_err(E::Fee)?; + let fee = self + .fee + .map(|f| SignedAmount::from_btc(f).map_err(E::Fee)) + .transpose()? // optional historically + .unwrap_or_else(|| SignedAmount::from_sat(0)); let block_hash = self.block_hash.parse::().map_err(E::BlockHash)?; let block_index = crate::to_u32(self.block_index, "block_index")?; let txid = self.txid.map(|s| s.parse::().map_err(E::Txid)).transpose()?; let bip125_replaceable = self.bip125_replaceable.into_model(); - Ok(model::ListSinceBlockTransaction { + Ok(model::TransactionItem { + involves_watch_only: None, address: Some(address), category, amount, vout, fee, confirmations: self.confirmations, - block_hash, - block_index, - block_time: self.block_time, + block_hash: Some(block_hash), + block_index: Some(block_index), + block_time: Some(self.block_time), txid, + wtxid: None, time: self.time, time_received: self.time_received, bip125_replaceable, + generated: None, + trusted: None, abandoned: self.abandoned, comment: self.comment, label: self.label, to: self.to, + block_height: None, + wallet_conflicts: None, + replaced_by_txid: None, + replaces_txid: None, + mempool_conflicts: None, + parent_descriptors: None, }) } } impl ListTransactions { /// Converts version specific type to a version nonspecific, more strongly typed type. - pub fn into_model(self) -> Result { + pub fn into_model(self) -> Result { let transactions = self.0.into_iter().map(|tx| tx.into_model()).collect::, _>>()?; Ok(model::ListTransactions(transactions)) } } -impl ListTransactionsItem { - /// Converts version specific type to a version nonspecific, more strongly typed type. - pub fn into_model(self) -> Result { - use ListTransactionsItemError as E; - - let address = self.address.parse::>().map_err(E::Address)?; - let category = self.category.into_model(); - let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?; - let vout = crate::to_u32(self.vout, "vout")?; - let fee = SignedAmount::from_btc(self.fee).map_err(E::Fee)?; - let block_hash = self.block_hash.parse::().map_err(E::BlockHash)?; - let block_index = crate::to_u32(self.block_index, "block_index")?; - let txid = self.txid.parse::().map_err(E::Txid)?; - let bip125_replaceable = self.bip125_replaceable.into_model(); - - Ok(model::ListTransactionsItem { - address, - category, - amount, - label: self.label, - vout, - fee, - confirmations: self.confirmations, - trusted: self.trusted, - block_hash, - block_index, - block_time: self.block_time, - txid, - time: self.time, - time_received: self.time_received, - comment: self.comment, - bip125_replaceable, - abandoned: self.abandoned, - }) - } -} - impl ListUnspent { /// Converts version specific type to a version nonspecific, more strongly typed type. pub fn into_model(self) -> Result { diff --git a/types/src/v17/wallet/mod.rs b/types/src/v17/wallet/mod.rs index a06200e7..f23b0596 100644 --- a/types/src/v17/wallet/mod.rs +++ b/types/src/v17/wallet/mod.rs @@ -21,8 +21,8 @@ pub use self::error::*; // // The following structs are very similar but have slightly different fields and docs. // - GetTransaction -// - ListSinceBlockTransaction -// - ListTransactionsItem +// - TransactionItem +// - TransactionItem /// Returned as part of `getaddressesbylabel` and `getaddressinfo`. #[derive(Copy, Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] @@ -695,12 +695,12 @@ pub struct ListReceivedByAddressItem { #[serde(deny_unknown_fields)] pub struct ListSinceBlock { /// All the transactions. - pub transactions: Vec, + pub transactions: Vec, /// Only present if `include_removed=true`. /// /// Note: transactions that were re-added in the active chain will appear as-is in this array, /// and may thus have a positive confirmation count. - pub removed: Vec, + pub removed: Vec, /// The hash of the block (target_confirmations-1) from the best block on the main chain. /// /// This is typically used to feed back into listsinceblock the next time you call it. So you @@ -710,13 +710,12 @@ pub struct ListSinceBlock { pub last_block: String, } -/// Transaction item returned as part of `listsinceblock`. -// FIXME: These docs from Core seem to buggy, there is only partial mention of 'move' category? +/// Transaction item returned as part of `listsinceblock` and `listtransactions`. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] -pub struct ListSinceBlockTransaction { +pub struct TransactionItem { /// DEPRECATED. The account name associated with the transaction. Will be "" for the default account. - pub account: String, + pub account: Option, /// The bitcoin address of the transaction. /// /// Not present for move transactions (category = move). @@ -734,12 +733,14 @@ pub struct ListSinceBlockTransaction { /// The amount of the fee in BTC. /// /// This is negative and only available for the 'send' category of transactions. - pub fee: f64, + pub fee: Option, /// The number of confirmations for the transaction. /// /// Available for 'send' and 'receive' category of transactions. When it's < 0, it means the /// transaction conflicted that many blocks ago. pub confirmations: i64, + /// Only present if transaction only input is a coinbase one. + pub generated: Option, /// The block hash containing the transaction. /// /// Available for 'send' and 'receive' category of transactions. @@ -757,6 +758,9 @@ pub struct ListSinceBlockTransaction { /// /// Available for 'send' and 'receive' category of transactions. pub txid: Option, + /// Conflicting transaction ids. + #[serde(rename = "walletconflicts")] + pub wallet_conflicts: Vec, /// The transaction time in seconds since epoch (Jan 1 1970 GMT). pub time: u32, /// The time received in seconds since epoch (Jan 1 1970 GMT). @@ -791,61 +795,7 @@ pub struct ListSinceBlockTransaction { /// > bitcoind with -deprecatedrpc=accounts #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] -pub struct ListTransactions(pub Vec); - -/// Transaction item returned as part of `listtransactions`. -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct ListTransactionsItem { - /// The bitcoin address of the transaction. - pub address: String, - /// The transaction category. - pub category: TransactionCategory, // FIXME: It appears ok to reuse this? - /// The amount in BTC. - /// - /// This is negative for the 'send' category, and is positive for the 'receive' category. - pub amount: f64, - /// A comment for the address/transaction, if any. - pub label: Option, - /// The vout value. - pub vout: i64, - /// The amount of the fee in BTC. - /// - /// This is negative and only available for the 'send' category of transactions. - pub fee: f64, - /// The number of confirmations for the transaction. - /// - /// Negative confirmations indicate the transaction conflicts with the block chain. - pub confirmations: i64, - /// Whether we consider the outputs of this unconfirmed transaction safe to spend. - pub trusted: bool, - /// The block hash containing the transaction. - #[serde(rename = "blockhash")] - pub block_hash: String, - /// The index of the transaction in the block that includes it. - #[serde(rename = "blockindex")] - pub block_index: i64, - /// The block time in seconds since epoch (1 Jan 1970 GMT). - #[serde(rename = "blocktime")] - pub block_time: u32, - /// The transaction id. - pub txid: String, - /// The transaction time in seconds since epoch (Jan 1 1970 GMT). - pub time: u32, - /// The time received in seconds since epoch (Jan 1 1970 GMT). - #[serde(rename = "timereceived")] - pub time_received: u32, - /// If a comment is associated with the transaction. - pub comment: Option, - /// Whether this transaction could be replaced due to BIP125 (replace-by-fee); - /// may be unknown for unconfirmed transactions not in the mempool - #[serde(rename = "bip125-replaceable")] - pub bip125_replaceable: Bip125Replaceable, - /// If the transaction has been abandoned (inputs are respendable). - /// - /// Only available for the 'send' category of transactions. - pub abandoned: Option, -} +pub struct ListTransactions(pub Vec); /// Result of the JSON-RPC method `listunspent`. /// diff --git a/types/src/v18/mod.rs b/types/src/v18/mod.rs index 3c1dbaac..edffdfda 100644 --- a/types/src/v18/mod.rs +++ b/types/src/v18/mod.rs @@ -188,8 +188,8 @@ //! | listlockunspent | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -275,15 +275,14 @@ pub use crate::v17::{ GetTxOutSetInfoError, GetUnconfirmedBalance, GetWalletInfoError, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListBanned, ListLabels, ListLockUnspent, ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, - ListSinceBlockError, ListSinceBlockTransaction, ListSinceBlockTransactionError, - ListTransactions, ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, - ListWallets, LoadWallet, LockUnspent, Locked, Logging, MapMempoolEntryError, MempoolAcceptance, - MempoolEntryError, MempoolEntryFees, MempoolEntryFeesError, PruneBlockchain, PsbtInput, - PsbtOutput, PsbtScript, RawTransaction, RawTransactionError, RawTransactionInput, - RawTransactionOutput, RescanBlockchain, ScriptType, SendMany, SendRawTransaction, - SendToAddress, SetNetworkActive, SetTxFee, SignFail, SignFailError, SignMessage, - SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError, Softfork, SoftforkReject, - TestMempoolAccept, TransactionCategory, UploadTarget, ValidateAddress, ValidateAddressError, + ListSinceBlockError, ListTransactions, ListUnspentItemError, ListWallets, LoadWallet, + LockUnspent, Locked, Logging, MapMempoolEntryError, MempoolAcceptance, MempoolEntryError, + MempoolEntryFees, MempoolEntryFeesError, PruneBlockchain, PsbtInput, PsbtOutput, PsbtScript, + RawTransaction, RawTransactionError, RawTransactionInput, RawTransactionOutput, + RescanBlockchain, ScriptType, SendMany, SendRawTransaction, SendToAddress, SetNetworkActive, + SetTxFee, SignFail, SignFailError, SignMessage, SignMessageWithPrivKey, SignRawTransaction, + SignRawTransactionError, Softfork, SoftforkReject, TestMempoolAccept, TransactionCategory, + TransactionItem, TransactionItemError, UploadTarget, ValidateAddress, ValidateAddressError, VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt, WalletCreateFundedPsbtError, WalletProcessPsbt, WitnessUtxo, }; diff --git a/types/src/v19/mod.rs b/types/src/v19/mod.rs index 14773787..b7a018a4 100644 --- a/types/src/v19/mod.rs +++ b/types/src/v19/mod.rs @@ -189,8 +189,8 @@ //! | listlockunspent | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -271,14 +271,13 @@ pub use crate::v17::{ GetTxOutSetInfoError, GetUnconfirmedBalance, GetWalletInfoError, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListBanned, ListLabels, ListLockUnspent, ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, - ListSinceBlockError, ListSinceBlockTransaction, ListSinceBlockTransactionError, - ListTransactions, ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, - ListWallets, LoadWallet, LockUnspent, Locked, Logging, PruneBlockchain, RawTransactionError, - RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendMany, - SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, - SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError, SoftforkReject, - TestMempoolAccept, TransactionCategory, UploadTarget, ValidateAddress, ValidateAddressError, - VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt, + ListSinceBlockError, ListTransactions, ListUnspentItemError, ListWallets, LoadWallet, + LockUnspent, Locked, Logging, PruneBlockchain, RawTransactionError, RawTransactionInput, + RawTransactionOutput, RescanBlockchain, ScriptType, SendMany, SendRawTransaction, + SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, + SignRawTransaction, SignRawTransactionError, SoftforkReject, TestMempoolAccept, + TransactionCategory, TransactionItem, TransactionItemError, UploadTarget, ValidateAddress, + ValidateAddressError, VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt, WalletCreateFundedPsbtError, WalletProcessPsbt, WitnessUtxo, }; #[doc(inline)] diff --git a/types/src/v20/mod.rs b/types/src/v20/mod.rs index 44d295d1..debec268 100644 --- a/types/src/v20/mod.rs +++ b/types/src/v20/mod.rs @@ -190,8 +190,8 @@ //! | listlockunspent | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -240,7 +240,8 @@ pub use self::{ util::CreateMultisig, wallet::{ AddMultisigAddress, GetAddressInfo, GetAddressInfoEmbedded, GetTransaction, - GetTransactionDetail, + GetTransactionDetail, ListSinceBlock, ListSinceBlockError, ListTransactions, + TransactionItem, TransactionItemError, }, }; #[doc(inline)] @@ -264,14 +265,12 @@ pub use crate::{ GetTransactionDetailError, GetTransactionError, GetTxOut, GetTxOutError, GetTxOutSetInfo, GetTxOutSetInfoError, GetUnconfirmedBalance, GetWalletInfoError, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, - ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, - ListSinceBlockError, ListSinceBlockTransaction, ListSinceBlockTransactionError, - ListTransactions, ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, - ListWallets, LoadWallet, LockUnspent, Locked, PruneBlockchain, RawTransactionError, - RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendMany, - SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, - SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError, SoftforkReject, - TestMempoolAccept, TransactionCategory, UploadTarget, ValidateAddress, + ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddressError, + ListUnspentItemError, ListWallets, LoadWallet, LockUnspent, Locked, PruneBlockchain, + RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, + ScriptType, SendMany, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, + SignMessage, SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError, + SoftforkReject, TestMempoolAccept, TransactionCategory, UploadTarget, ValidateAddress, ValidateAddressError, VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt, WalletCreateFundedPsbtError, WalletProcessPsbt, WitnessUtxo, }, diff --git a/types/src/v20/wallet/error.rs b/types/src/v20/wallet/error.rs new file mode 100644 index 00000000..bda57914 --- /dev/null +++ b/types/src/v20/wallet/error.rs @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! Errors for wallet types newly (re)defined in v20. + +use core::fmt; + +use bitcoin::amount::ParseAmountError; +use bitcoin::{address, hex}; + +use crate::error::write_err; +use crate::NumericError; + +/// Error when converting a `ListSinceBlock` type into the model type. +#[derive(Debug)] +pub enum ListSinceBlockError { + /// Conversion of item in `transactions` list failed. + Transactions(TransactionItemError), + /// Conversion of item in `removed` list failed. + Removed(TransactionItemError), + /// Conversion of the `last_block` field failed. + LastBlock(hex::HexToArrayError), +} + +impl fmt::Display for ListSinceBlockError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ListSinceBlockError::*; + + match *self { + Transactions(ref e) => + write_err!(f, "conversion of the `transactions` field failed"; e), + Removed(ref e) => write_err!(f, "conversion of the `removed` field failed"; e), + LastBlock(ref e) => write_err!(f, "conversion of the `last_block` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ListSinceBlockError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ListSinceBlockError::*; + + match *self { + Transactions(ref e) => Some(e), + Removed(ref e) => Some(e), + LastBlock(ref e) => Some(e), + } + } +} + +/// Error when converting a `TransactionItem` type into the model type. +/// +/// Note: Additional fields introduced in v20 (e.g. `generated`, `trusted`, `block_height`, +/// `wallet_conflicts`, `involvesWatchonly`) are currently not modelled and therefore are +/// intentionally ignored during conversion; as such they have no dedicated error variants. +#[derive(Debug)] +pub enum TransactionItemError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `address` field failed. + Address(address::ParseError), + /// Conversion of the `amount` field failed. + Amount(ParseAmountError), + /// Conversion of the `fee` field failed. + Fee(ParseAmountError), + /// Conversion of the `block_hash` field failed. + BlockHash(hex::HexToArrayError), + /// Conversion of the `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of an item in the `wallet_conflicts` list failed. + WalletConflicts(hex::HexToArrayError), +} + +impl fmt::Display for TransactionItemError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use TransactionItemError as E; + + match *self { + E::Numeric(ref e) => write_err!(f, "numeric"; e), + E::Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), + E::Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), + E::Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), + E::BlockHash(ref e) => write_err!(f, "conversion of the `block_hash` field failed"; e), + E::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + E::WalletConflicts(ref e) => + write_err!(f, "conversion of an item in the `wallet_conflicts` list failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionItemError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use TransactionItemError as E; + + match *self { + E::Numeric(ref e) => Some(e), + E::Address(ref e) => Some(e), + E::Amount(ref e) => Some(e), + E::Fee(ref e) => Some(e), + E::BlockHash(ref e) => Some(e), + E::Txid(ref e) => Some(e), + E::WalletConflicts(ref e) => Some(e), + } + } +} + +impl From for TransactionItemError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} diff --git a/types/src/v20/wallet/into.rs b/types/src/v20/wallet/into.rs index 198cd93b..7245350a 100644 --- a/types/src/v20/wallet/into.rs +++ b/types/src/v20/wallet/into.rs @@ -11,7 +11,8 @@ use bitcoin::{ use super::{ AddMultisigAddress, AddMultisigAddressError, GetAddressInfo, GetAddressInfoEmbedded, GetAddressInfoEmbeddedError, GetAddressInfoError, GetTransaction, GetTransactionDetail, - GetTransactionDetailError, GetTransactionError, + GetTransactionDetailError, GetTransactionError, ListSinceBlock, ListSinceBlockError, + ListTransactions, TransactionItem, TransactionItemError, }; use crate::model; @@ -200,7 +201,7 @@ impl GetTransaction { Ok(model::GetTransaction { amount, - fee, + fee, // Option in model confirmations: self.confirmations, generated: self.generated, trusted: self.trusted, @@ -251,3 +252,92 @@ impl GetTransactionDetail { }) } } + +impl ListSinceBlock { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> Result { + use ListSinceBlockError as E; + + let transactions = self + .transactions + .into_iter() + .map(|tx| tx.into_model()) + .collect::, _>>() + .map_err(E::Transactions)?; + let removed = self + .removed + .into_iter() + .map(|tx| tx.into_model()) + .collect::, _>>() + .map_err(E::Removed)?; + let last_block = self.last_block.parse::().map_err(E::LastBlock)?; + + Ok(model::ListSinceBlock { transactions, removed, last_block }) + } +} + +impl TransactionItem { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> Result { + use TransactionItemError as E; + + let address = self.address.parse::>().map_err(E::Address)?; + let category = self.category.into_model(); + let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?; + let vout = crate::to_u32(self.vout, "vout")?; + let fee = self + .fee + .map(|f| SignedAmount::from_btc(f).map_err(E::Fee)) + .transpose()? // optional historically + .unwrap_or_else(|| SignedAmount::from_sat(0)); + let block_hash = self.block_hash.parse::().map_err(E::BlockHash)?; + let block_height = crate::to_u32(self.block_height, "block_height")?; + let block_index = crate::to_u32(self.block_index, "block_index")?; + let txid = Some(self.txid.parse::().map_err(E::Txid)?); + let wallet_conflicts = self + .wallet_conflicts + .into_iter() + .map(|s| s.parse::().map_err(E::WalletConflicts)) + .collect::, _>>()?; + let bip125_replaceable = self.bip125_replaceable.into_model(); + + Ok(model::TransactionItem { + involves_watch_only: self.involves_watch_only, + address: Some(address), + category, + amount, + vout, + fee, + confirmations: self.confirmations, + generated: self.generated, + trusted: self.trusted, + block_hash: Some(block_hash), + block_height: Some(block_height), + block_index: Some(block_index), + block_time: Some(self.block_time), + txid, + wtxid: None, + wallet_conflicts: Some(wallet_conflicts), + replaced_by_txid: None, + replaces_txid: None, + mempool_conflicts: None, + to: self.to, + time: self.time, + time_received: self.time_received, + comment: self.comment, + bip125_replaceable, + parent_descriptors: None, + abandoned: self.abandoned, + label: self.label, + }) + } +} + +impl ListTransactions { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> Result { + let transactions = + self.0.into_iter().map(|tx| tx.into_model()).collect::, _>>()?; + Ok(model::ListTransactions(transactions)) + } +} diff --git a/types/src/v20/wallet/mod.rs b/types/src/v20/wallet/mod.rs index ff15b8f8..40a85666 100644 --- a/types/src/v20/wallet/mod.rs +++ b/types/src/v20/wallet/mod.rs @@ -4,11 +4,13 @@ //! //! Types for methods found under the `== Wallet ==` section of the API docs. +mod error; mod into; use bitcoin::Transaction; use serde::{Deserialize, Serialize}; +pub use self::error::{ListSinceBlockError, TransactionItemError}; pub use super::{ AddMultisigAddressError, Bip125Replaceable, GetAddressInfoEmbeddedError, GetAddressInfoError, GetTransactionDetailError, GetTransactionError, ScriptType, TransactionCategory, @@ -249,3 +251,107 @@ pub struct GetTransactionDetail { /// Only available for the 'send' category of transactions. pub abandoned: Option, } + +/// Result of the JSON-RPC method `listsinceblock`. +/// +/// > listsinceblock ( "blockhash" target_confirmations include_watchonly include_removed ) +/// > +/// > Get all transactions in blocks since block `blockhash`, or all transactions if omitted. +/// > If "blockhash" is no longer a part of the main chain, transactions from the fork point onward are included. +/// > Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the "removed" array. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ListSinceBlock { + /// All the transactions. + pub transactions: Vec, + /// Only present if `include_removed=true`. + /// + /// Note: transactions that were re-added in the active chain will appear as-is in this array, + /// and may thus have a positive confirmation count. + pub removed: Vec, + /// The hash of the block (target_confirmations-1) from the best block on the main chain. + /// + /// This is typically used to feed back into listsinceblock the next time you call it. So you + /// would generally use a target_confirmations of say 6, so you will be continually + /// re-notified of transactions until they've reached 6 confirmations plus any new ones. + #[serde(rename = "lastblock")] + pub last_block: String, +} + +/// Transaction item returned as part of `listsinceblock`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct TransactionItem { + /// Only returns true if imported addresses were involved in transaction. + #[serde(rename = "involvesWatchonly")] + pub involves_watch_only: Option, + /// The bitcoin address of the transaction. + pub address: String, + /// The transaction category. + pub category: TransactionCategory, + /// The amount in BTC. + /// + /// This is negative for the 'send' category, and is positive for all other categories. + pub amount: f64, + /// The vout value. + pub vout: i64, + /// The amount of the fee in BTC. + /// + /// This is negative and only available for the 'send' category of transactions. + pub fee: Option, + /// The number of confirmations for the transaction. Negative confirmations means the + /// transaction conflicted that many blocks ago. + pub confirmations: i64, + /// Only present if transaction only input is a coinbase one. + pub generated: Option, + /// Only present if we consider transaction to be trusted and so safe to spend from. + pub trusted: Option, + /// The block hash containing the transaction. + #[serde(rename = "blockhash")] + pub block_hash: String, + /// The block height containing the transaction. + #[serde(rename = "blockheight")] + pub block_height: i64, + /// The index of the transaction in the block that includes it. + #[serde(rename = "blockindex")] + pub block_index: i64, + /// The block time expressed in UNIX epoch time. + #[serde(rename = "blocktime")] + pub block_time: u32, + /// The transaction id. + pub txid: String, + /// Conflicting transaction ids. + #[serde(rename = "walletconflicts")] + pub wallet_conflicts: Vec, + /// The transaction time expressed in UNIX epoch time. + pub time: u32, + /// The time received expressed in UNIX epoch time. + #[serde(rename = "timereceived")] + pub time_received: u32, + /// If a comment is associated with the transaction, only present if not empty. + pub comment: Option, + /// ("yes|no|unknown") Whether this transaction could be replaced due to BIP125 (replace-by-fee); + /// may be unknown for unconfirmed transactions not in the mempool + #[serde(rename = "bip125-replaceable")] + pub bip125_replaceable: Bip125Replaceable, + /// 'true' if the transaction has been abandoned (inputs are respendable). Only available for the + /// 'send' category of transactions. + pub abandoned: Option, + /// A comment for the address/transaction, if any. + pub label: Option, + /// If a comment to is associated with the transaction. + pub to: Option, +} + +/// Result of the JSON-RPC method `listtransactions`. +/// +/// > listtransactions (label count skip include_watchonly) +/// > +/// > If a label name is provided, this will return only incoming transactions paying to addresses with the specified label. +/// > +/// > Returns up to 'count' most recent transactions skipping the first 'from' transactions. +/// > Note that the "account" argument and "otheraccount" return value have been removed in V0.17. To use this RPC with an "account" argument, restart +/// > bitcoind with -deprecatedrpc=accounts +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ListTransactions(pub Vec); diff --git a/types/src/v21/mod.rs b/types/src/v21/mod.rs index 5bbba1d4..0b13a32e 100644 --- a/types/src/v21/mod.rs +++ b/types/src/v21/mod.rs @@ -194,8 +194,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -281,12 +281,10 @@ pub use crate::{ GetTransactionDetailError, GetTransactionError, GetTxOut, GetTxOutError, GetTxOutSetInfo, GetTxOutSetInfoError, GetUnconfirmedBalance, GetWalletInfoError, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, - ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, - ListSinceBlockError, ListSinceBlockTransaction, ListSinceBlockTransactionError, - ListTransactions, ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, - ListWallets, LoadWallet, LockUnspent, Locked, PruneBlockchain, RawTransactionError, - RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, - SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, + ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddressError, + ListUnspentItemError, ListWallets, LoadWallet, LockUnspent, Locked, PruneBlockchain, + RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, + ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError, SoftforkReject, TransactionCategory, UploadTarget, ValidateAddress, ValidateAddressError, VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt, WalletCreateFundedPsbtError, @@ -308,6 +306,7 @@ pub use crate::{ }, v20::{ AddMultisigAddress, Banned, CreateMultisig, GenerateToDescriptor, GetAddressInfo, - GetAddressInfoEmbedded, GetTransaction, GetTransactionDetail, ListBanned, Logging, + GetAddressInfoEmbedded, GetTransaction, GetTransactionDetail, ListBanned, ListSinceBlock, + ListSinceBlockError, ListTransactions, Logging, TransactionItem, TransactionItemError, }, }; diff --git a/types/src/v22/mod.rs b/types/src/v22/mod.rs index 21fbee1e..1e8fc424 100644 --- a/types/src/v22/mod.rs +++ b/types/src/v22/mod.rs @@ -204,8 +204,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -283,9 +283,7 @@ pub use crate::{ GetTransactionError, GetTxOut, GetTxOutError, GetTxOutSetInfo, GetTxOutSetInfoError, GetUnconfirmedBalance, GetWalletInfoError, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, - ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets, + ListLockUnspentItemError, ListReceivedByAddressError, ListUnspentItemError, ListWallets, LoadWallet, LockUnspent, Locked, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, @@ -309,7 +307,8 @@ pub use crate::{ }, v20::{ AddMultisigAddress, CreateMultisig, GenerateToDescriptor, GetTransaction, - GetTransactionDetail, + GetTransactionDetail, ListSinceBlock, ListSinceBlockError, ListTransactions, + TransactionItem, TransactionItemError, }, v21::{ AddPeerAddress, Bip9SoftforkInfo, GenerateBlock, GetBlockchainInfo, GetIndexInfo, diff --git a/types/src/v23/mod.rs b/types/src/v23/mod.rs index c2a90387..f6a090a8 100644 --- a/types/src/v23/mod.rs +++ b/types/src/v23/mod.rs @@ -196,8 +196,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -260,7 +260,8 @@ pub use self::{ util::CreateMultisig, wallet::{ AddMultisigAddress, GetTransaction, GetTransactionError, GetWalletInfo, - GetWalletInfoScanning, RestoreWallet, + GetWalletInfoScanning, ListSinceBlock, ListSinceBlockError, ListTransactions, + RestoreWallet, TransactionItem, TransactionItemError, }, }; #[doc(inline)] @@ -284,9 +285,7 @@ pub use crate::{ GetTxOutError, GetTxOutSetInfo, GetTxOutSetInfoError, GetUnconfirmedBalance, GetWalletInfoError, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, - ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets, + ListLockUnspentItemError, ListReceivedByAddressError, ListUnspentItemError, ListWallets, LoadWallet, LockUnspent, Locked, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, diff --git a/types/src/v23/wallet/error.rs b/types/src/v23/wallet/error.rs index 3feae06c..583f0a89 100644 --- a/types/src/v23/wallet/error.rs +++ b/types/src/v23/wallet/error.rs @@ -35,6 +35,106 @@ pub enum GetTransactionError { Details(GetTransactionDetailError), } +/// Error when converting a `ListSinceBlock` type into the model type. +#[derive(Debug)] +pub enum ListSinceBlockError { + /// Conversion of the `transactions` field failed. + Transactions(TransactionItemError), + /// Conversion of the `removed` field failed. + Removed(TransactionItemError), + /// Conversion of the `last_block` field failed. + LastBlock(hex::HexToArrayError), +} + +impl fmt::Display for ListSinceBlockError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ListSinceBlockError::*; + match *self { + Transactions(ref e) => + write_err!(f, "conversion of the `transactions` field failed"; e), + Removed(ref e) => write_err!(f, "conversion of the `removed` field failed"; e), + LastBlock(ref e) => write_err!(f, "conversion of the `last_block` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ListSinceBlockError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ListSinceBlockError::*; + match *self { + Transactions(ref e) => Some(e), + Removed(ref e) => Some(e), + LastBlock(ref e) => Some(e), + } + } +} + +/// Error when converting a `TransactionItem` type into the model type. +#[derive(Debug)] +pub enum TransactionItemError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `address` field failed. + Address(bitcoin::address::ParseError), + /// Conversion of the `amount` field failed. + Amount(ParseAmountError), + /// Conversion of the `fee` field failed. + Fee(ParseAmountError), + /// Conversion of the `block_hash` field failed. + BlockHash(hex::HexToArrayError), + /// Conversion of the `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of the `wallet_conflicts` field failed. + WalletConflicts(hex::HexToArrayError), + /// Conversion of the `replaced_by_txid` field failed. + ReplacedByTxid(hex::HexToArrayError), + /// Conversion of the `replaces_txid` field failed. + ReplacesTxid(hex::HexToArrayError), +} + +impl fmt::Display for TransactionItemError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use TransactionItemError as E; + match *self { + E::Numeric(ref e) => write_err!(f, "numeric"; e), + E::Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), + E::Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), + E::Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), + E::BlockHash(ref e) => write_err!(f, "conversion of the `block_hash` field failed"; e), + E::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + E::WalletConflicts(ref e) => + write_err!(f, "conversion of the `wallet_conflicts` field failed"; e), + E::ReplacedByTxid(ref e) => + write_err!(f, "conversion of the `replaced_by_txid` field failed"; e), + E::ReplacesTxid(ref e) => + write_err!(f, "conversion of the `replaces_txid` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionItemError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use TransactionItemError as E; + match *self { + E::Numeric(ref e) => Some(e), + E::Address(ref e) => Some(e), + E::Amount(ref e) => Some(e), + E::Fee(ref e) => Some(e), + E::BlockHash(ref e) => Some(e), + E::Txid(ref e) => Some(e), + E::WalletConflicts(ref e) => Some(e), + E::ReplacedByTxid(ref e) => Some(e), + E::ReplacesTxid(ref e) => Some(e), + } + } +} + +impl From for TransactionItemError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} + impl fmt::Display for GetTransactionError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use GetTransactionError as E; diff --git a/types/src/v23/wallet/into.rs b/types/src/v23/wallet/into.rs index 899f9365..f1a0e21f 100644 --- a/types/src/v23/wallet/into.rs +++ b/types/src/v23/wallet/into.rs @@ -5,7 +5,8 @@ use bitcoin::{Address, BlockHash, ScriptBuf, SignedAmount, Transaction, Txid}; use super::{ AddMultisigAddress, AddMultisigAddressError, GetTransaction, GetTransactionError, - GetWalletInfo, GetWalletInfoError, GetWalletInfoScanning, + GetWalletInfo, GetWalletInfoError, GetWalletInfoScanning, ListSinceBlock, ListSinceBlockError, + ListTransactions, TransactionItem, TransactionItemError, }; use crate::model; @@ -60,7 +61,7 @@ impl GetTransaction { Ok(model::GetTransaction { amount, - fee, + fee, // Option in model confirmations: self.confirmations, generated: self.generated, trusted: self.trusted, @@ -142,3 +143,97 @@ impl GetWalletInfo { }) } } + +impl ListSinceBlock { + pub fn into_model(self) -> Result { + use ListSinceBlockError as E; + + let transactions = self + .transactions + .into_iter() + .map(|t| t.into_model()) + .collect::, _>>() + .map_err(E::Transactions)?; + let removed = self + .removed + .into_iter() + .map(|t| t.into_model()) + .collect::, _>>() + .map_err(E::Removed)?; + let last_block = self.last_block.parse::().map_err(E::LastBlock)?; + + Ok(model::ListSinceBlock { transactions, removed, last_block }) + } +} +impl TransactionItem { + pub fn into_model(self) -> Result { + use TransactionItemError as E; + + let address = self.address.parse::>().map_err(E::Address)?; + let category = self.category.into_model(); + let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?; + let vout = crate::to_u32(self.vout, "vout")?; + let fee = self + .fee + .map(|f| SignedAmount::from_btc(f).map_err(E::Fee)) + .transpose()? // optional historically + .unwrap_or_else(|| SignedAmount::from_sat(0)); + let block_hash = + self.block_hash.map(|h| h.parse::().map_err(E::BlockHash)).transpose()?; + let block_height = + self.block_height.map(|h| crate::to_u32(h, "block_height")).transpose()?; + let block_index = self.block_index.map(|h| crate::to_u32(h, "block_index")).transpose()?; + let txid = self.txid.parse::().map_err(E::Txid)?; + let wallet_conflicts = self + .wallet_conflicts + .into_iter() + .map(|s| s.parse::().map_err(E::WalletConflicts)) + .collect::, _>>()?; + let replaced_by_txid = self + .replaced_by_txid + .map(|s| s.parse::().map_err(E::ReplacedByTxid)) + .transpose()?; + let replaces_txid = + self.replaces_txid.map(|s| s.parse::().map_err(E::ReplacesTxid)).transpose()?; + let bip125_replaceable = self.bip125_replaceable.into_model(); + + Ok(model::TransactionItem { + involves_watch_only: self.involves_watch_only, + address: Some(address), + category, + amount, + vout, + fee, + confirmations: self.confirmations, + generated: self.generated, + trusted: self.trusted, + block_hash, + block_height, + block_index, + block_time: self.block_time, + txid: Some(txid), + wtxid: None, + wallet_conflicts: Some(wallet_conflicts), + replaced_by_txid, + replaces_txid, + mempool_conflicts: None, + to: self.to, + time: self.time, + time_received: self.time_received, + comment: self.comment, + bip125_replaceable, + parent_descriptors: None, + abandoned: self.abandoned, + label: self.label, + }) + } +} + +impl ListTransactions { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> Result { + let transactions = + self.0.into_iter().map(|tx| tx.into_model()).collect::, _>>()?; + Ok(model::ListTransactions(transactions)) + } +} diff --git a/types/src/v23/wallet/mod.rs b/types/src/v23/wallet/mod.rs index f25d6fcc..f1cf2a25 100644 --- a/types/src/v23/wallet/mod.rs +++ b/types/src/v23/wallet/mod.rs @@ -10,7 +10,7 @@ mod into; use bitcoin::Transaction; use serde::{Deserialize, Serialize}; -pub use self::error::GetTransactionError; +pub use self::error::{GetTransactionError, ListSinceBlockError, TransactionItemError}; pub use super::{ AddMultisigAddressError, Bip125Replaceable, GetTransactionDetail, GetTransactionDetailError, GetWalletInfoError, @@ -189,3 +189,105 @@ pub enum GetWalletInfoScanning { /// Not scanning (false). NotScanning(bool), } + +/// Result of the JSON-RPC method `listsinceblock`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ListSinceBlock { + /// All the transactions. + pub transactions: Vec, + /// Only present if `include_removed=true`. + /// + /// Note: transactions that were re-added in the active chain will appear as-is in this array, + /// and may thus have a positive confirmation count. + pub removed: Vec, + /// The hash of the block (target_confirmations-1) from the best block on the main chain. + /// + /// This is typically used to feed back into listsinceblock the next time you call it. So you + /// would generally use a target_confirmations of say 6, so you will be continually + /// re-notified of transactions until they've reached 6 confirmations plus any new ones. + #[serde(rename = "lastblock")] + pub last_block: String, +} + +/// Transaction item returned as part of `listsinceblock`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct TransactionItem { + /// Only returns true if imported addresses were involved in transaction. + #[serde(rename = "involvesWatchonly")] + pub involves_watch_only: Option, + /// The bitcoin address of the transaction. + pub address: String, + /// The transaction category. + pub category: super::TransactionCategory, + /// The amount in BTC. + /// + /// This is negative for the 'send' category, and is positive for all other categories. + pub amount: f64, + /// The vout value. + pub vout: i64, + /// The amount of the fee in BTC. + /// + /// This is negative and only available for the 'send' category of transactions. + pub fee: Option, + /// The number of confirmations for the transaction. Negative confirmations means the + /// transaction conflicted that many blocks ago. + pub confirmations: i64, + /// Only present if transaction only input is a coinbase one. + pub generated: Option, + /// Only present if we consider transaction to be trusted and so safe to spend from. + pub trusted: Option, + /// The block hash containing the transaction. + #[serde(rename = "blockhash")] + pub block_hash: Option, + /// The block height containing the transaction. + #[serde(rename = "blockheight")] + pub block_height: Option, + /// The index of the transaction in the block that includes it. + #[serde(rename = "blockindex")] + pub block_index: Option, + /// The block time expressed in UNIX epoch time. + #[serde(rename = "blocktime")] + pub block_time: Option, + /// The transaction id. + pub txid: String, + /// Conflicting transaction ids. + #[serde(rename = "walletconflicts")] + pub wallet_conflicts: Vec, + /// The txid if this tx was replaced. + pub replaced_by_txid: Option, + /// The txid if this tx replaces one. + pub replaces_txid: Option, + /// If a comment is associated with the transaction, only present if not empty. + pub comment: Option, + /// If a comment to is associated with the transaction. + pub to: Option, + /// The transaction time expressed in UNIX epoch time. + pub time: u32, + /// The time received expressed in UNIX epoch time. + #[serde(rename = "timereceived")] + pub time_received: u32, + /// ("yes|no|unknown") Whether this transaction could be replaced due to BIP125 (replace-by-fee); + /// may be unknown for unconfirmed transactions not in the mempool + #[serde(rename = "bip125-replaceable")] + pub bip125_replaceable: Bip125Replaceable, + /// 'true' if the transaction has been abandoned (inputs are respendable). Only available for the + /// 'send' category of transactions. + pub abandoned: Option, + /// A comment for the address/transaction, if any. + pub label: Option, +} + +/// Result of the JSON-RPC method `listtransactions`. +/// +/// > listtransactions (label count skip include_watchonly) +/// > +/// > If a label name is provided, this will return only incoming transactions paying to addresses with the specified label. +/// > +/// > Returns up to 'count' most recent transactions skipping the first 'from' transactions. +/// > Note that the "account" argument and "otheraccount" return value have been removed in V0.17. To use this RPC with an "account" argument, restart +/// > bitcoind with -deprecatedrpc=accounts +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ListTransactions(pub Vec); diff --git a/types/src/v24/mod.rs b/types/src/v24/mod.rs index e5cd611e..7557ecbc 100644 --- a/types/src/v24/mod.rs +++ b/types/src/v24/mod.rs @@ -198,8 +198,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -258,8 +258,9 @@ pub use self::{ TaprootBip32Deriv, TaprootLeaf, TaprootScript, TaprootScriptPathSig, }, wallet::{ - GetTransaction, GetTransactionDetail, GetTransactionError, ListUnspent, ListUnspentItem, - MigrateWallet, SendAll, SendAllError, SimulateRawTransaction, + GetTransaction, GetTransactionDetail, GetTransactionError, ListSinceBlock, + ListSinceBlockError, ListTransactions, ListUnspent, ListUnspentItem, MigrateWallet, + SendAll, SendAllError, SimulateRawTransaction, TransactionItem, TransactionItemError, }, }; #[doc(inline)] @@ -283,9 +284,7 @@ pub use crate::{ GetTxOutError, GetTxOutSetInfo, GetTxOutSetInfoError, GetUnconfirmedBalance, GetWalletInfoError, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, - ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets, + ListLockUnspentItemError, ListReceivedByAddressError, ListUnspentItemError, ListWallets, LoadWallet, LockUnspent, Locked, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, diff --git a/types/src/v24/wallet/error.rs b/types/src/v24/wallet/error.rs index 668438a5..f3821387 100644 --- a/types/src/v24/wallet/error.rs +++ b/types/src/v24/wallet/error.rs @@ -91,6 +91,110 @@ impl From for GetTransactionError { fn from(e: NumericError) -> Self { Self::Numeric(e) } } +/// Error when converting a `ListSinceBlock` type into the model type. +#[derive(Debug)] +pub enum ListSinceBlockError { + /// Conversion of the `transactions` field failed. + Transactions(TransactionItemError), + /// Conversion of the `removed` field failed. + Removed(TransactionItemError), + /// Conversion of the `last_block` field failed. + LastBlock(hex::HexToArrayError), +} + +impl fmt::Display for ListSinceBlockError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ListSinceBlockError::*; + match *self { + Transactions(ref e) => + write_err!(f, "conversion of the `transactions` field failed"; e), + Removed(ref e) => write_err!(f, "conversion of the `removed` field failed"; e), + LastBlock(ref e) => write_err!(f, "conversion of the `last_block` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ListSinceBlockError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ListSinceBlockError::*; + match *self { + Transactions(ref e) => Some(e), + Removed(ref e) => Some(e), + LastBlock(ref e) => Some(e), + } + } +} + +/// Error when converting a `TransactionItem` type into the model type. +#[derive(Debug)] +pub enum TransactionItemError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `address` field failed. + Address(bitcoin::address::ParseError), + /// Conversion of the `amount` field failed. + Amount(ParseAmountError), + /// Conversion of the `fee` field failed. + Fee(ParseAmountError), + /// Conversion of the `block_hash` field failed. + BlockHash(hex::HexToArrayError), + /// Conversion of the `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of the `wtxid` field failed. + Wtxid(hex::HexToArrayError), + /// Conversion of the `wallet_conflicts` field failed. + WalletConflicts(hex::HexToArrayError), + /// Conversion of the `replaced_by_txid` field failed. + ReplacedByTxid(hex::HexToArrayError), + /// Conversion of the `replaces_txid` field failed. + ReplacesTxid(hex::HexToArrayError), +} + +impl fmt::Display for TransactionItemError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use TransactionItemError as E; + match *self { + E::Numeric(ref e) => write_err!(f, "numeric"; e), + E::Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), + E::Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), + E::Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), + E::BlockHash(ref e) => write_err!(f, "conversion of the `block_hash` field failed"; e), + E::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + E::Wtxid(ref e) => write_err!(f, "conversion of the `wtxid` field failed"; e), + E::WalletConflicts(ref e) => + write_err!(f, "conversion of the `wallet_conflicts` field failed"; e), + E::ReplacedByTxid(ref e) => + write_err!(f, "conversion of the `replaced_by_txid` field failed"; e), + E::ReplacesTxid(ref e) => + write_err!(f, "conversion of the `replaces_txid` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionItemError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use TransactionItemError as E; + match *self { + E::Numeric(ref e) => Some(e), + E::Address(ref e) => Some(e), + E::Amount(ref e) => Some(e), + E::Fee(ref e) => Some(e), + E::BlockHash(ref e) => Some(e), + E::Txid(ref e) => Some(e), + E::Wtxid(ref e) => Some(e), + E::WalletConflicts(ref e) => Some(e), + E::ReplacedByTxid(ref e) => Some(e), + E::ReplacesTxid(ref e) => Some(e), + } + } +} + +impl From for TransactionItemError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} + /// Error when converting a `SendAll` type into the model type. #[derive(Debug)] pub enum SendAllError { diff --git a/types/src/v24/wallet/into.rs b/types/src/v24/wallet/into.rs index bf391156..322349e2 100644 --- a/types/src/v24/wallet/into.rs +++ b/types/src/v24/wallet/into.rs @@ -6,8 +6,9 @@ use bitcoin::{Address, BlockHash, ScriptBuf, SignedAmount, Transaction, Txid}; use super::{ GetTransaction, GetTransactionDetail, GetTransactionDetailError, GetTransactionError, - ListUnspent, ListUnspentItem, ListUnspentItemError, SendAll, SendAllError, - SimulateRawTransaction, + ListSinceBlock, ListSinceBlockError, ListTransactions, ListUnspent, ListUnspentItem, + ListUnspentItemError, SendAll, SendAllError, SimulateRawTransaction, TransactionItem, + TransactionItemError, }; use crate::model; @@ -98,6 +99,103 @@ impl GetTransactionDetail { } } +impl ListSinceBlock { + pub fn into_model(self) -> Result { + use ListSinceBlockError as E; + + let transactions = self + .transactions + .into_iter() + .map(|t| t.into_model()) + .collect::, _>>() + .map_err(E::Transactions)?; + let removed = self + .removed + .into_iter() + .map(|t| t.into_model()) + .collect::, _>>() + .map_err(E::Removed)?; + let last_block = self.last_block.parse::().map_err(E::LastBlock)?; + + Ok(model::ListSinceBlock { transactions, removed, last_block }) + } +} + +impl TransactionItem { + pub fn into_model(self) -> Result { + use TransactionItemError as E; + + let address = + self.address.map(|a| a.parse::>().map_err(E::Address)).transpose()?; + let category = self.category.into_model(); + let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?; + let vout = crate::to_u32(self.vout, "vout")?; + let fee = self + .fee + .map(|f| SignedAmount::from_btc(f).map_err(E::Fee)) + .transpose()? // optional historically + .unwrap_or_else(|| SignedAmount::from_sat(0)); + let block_hash = + self.block_hash.map(|h| h.parse::().map_err(E::BlockHash)).transpose()?; + let block_height = + self.block_height.map(|h| crate::to_u32(h, "block_height")).transpose()?; + let block_index = self.block_index.map(|h| crate::to_u32(h, "block_index")).transpose()?; + let txid = self.txid.parse::().map_err(E::Txid)?; + let wtxid = self.wtxid.parse::().map_err(E::Wtxid)?; + let wallet_conflicts = self + .wallet_conflicts + .into_iter() + .map(|s| s.parse::().map_err(E::WalletConflicts)) + .collect::, _>>()?; + let replaced_by_txid = self + .replaced_by_txid + .map(|s| s.parse::().map_err(E::ReplacedByTxid)) + .transpose()?; + let replaces_txid = + self.replaces_txid.map(|s| s.parse::().map_err(E::ReplacesTxid)).transpose()?; + let bip125_replaceable = self.bip125_replaceable.into_model(); + + Ok(model::TransactionItem { + involves_watch_only: self.involves_watch_only, + address, + category, + amount, + vout, + fee, + confirmations: self.confirmations, + generated: self.generated, + trusted: self.trusted, + block_hash, + block_height, + block_index, + block_time: self.block_time, + txid: Some(txid), + wtxid: Some(wtxid), + wallet_conflicts: Some(wallet_conflicts), + replaced_by_txid, + replaces_txid, + mempool_conflicts: None, + to: self.to, + time: self.time, + time_received: self.time_received, + comment: self.comment, + bip125_replaceable, + parent_descriptors: self.parent_descriptors, + abandoned: self.abandoned, + label: self.label, + }) + } +} + +impl ListTransactions { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> Result { + let transactions = + self.0.into_iter().map(|tx| tx.into_model()).collect::, _>>()?; + Ok(model::ListTransactions(transactions)) + } +} + impl ListUnspent { /// Converts version specific type to a version nonspecific, more strongly typed type. pub fn into_model(self) -> Result { diff --git a/types/src/v24/wallet/mod.rs b/types/src/v24/wallet/mod.rs index da24b7bc..26384d35 100644 --- a/types/src/v24/wallet/mod.rs +++ b/types/src/v24/wallet/mod.rs @@ -10,7 +10,9 @@ mod into; use bitcoin::Transaction; use serde::{Deserialize, Serialize}; -pub use self::error::{GetTransactionError, SendAllError}; +pub use self::error::{ + GetTransactionError, ListSinceBlockError, SendAllError, TransactionItemError, +}; pub use super::{ Bip125Replaceable, GetTransactionDetailError, ListUnspentItemError, TransactionCategory, }; @@ -118,6 +120,113 @@ pub struct GetTransactionDetail { pub parent_descriptors: Option>, } +/// Result of the JSON-RPC method `listsinceblock`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ListSinceBlock { + /// All the transactions. + pub transactions: Vec, + /// Only present if `include_removed=true`. + /// + /// Note: transactions that were re-added in the active chain will appear as-is in this array, + /// and may thus have a positive confirmation count. + pub removed: Vec, + /// The hash of the block (target_confirmations-1) from the best block on the main chain. + /// + /// This is typically used to feed back into listsinceblock the next time you call it. So you + /// would generally use a target_confirmations of say 6, so you will be continually + /// re-notified of transactions until they've reached 6 confirmations plus any new ones. + #[serde(rename = "lastblock")] + pub last_block: String, +} + +/// Transaction item returned as part of `listsinceblock`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct TransactionItem { + /// Only returns true if imported addresses were involved in transaction. + #[serde(rename = "involvesWatchonly")] + pub involves_watch_only: Option, + /// The bitcoin address of the transaction. + pub address: Option, + /// The transaction category. + pub category: super::TransactionCategory, + /// The amount in BTC. + /// + /// This is negative for the 'send' category, and is positive for all other categories. + pub amount: f64, + /// The vout value. + pub vout: i64, + /// The amount of the fee in BTC. + /// + /// This is negative and only available for the 'send' category of transactions. + pub fee: Option, + /// The number of confirmations for the transaction. Negative confirmations means the + /// transaction conflicted that many blocks ago. + pub confirmations: i64, + /// Only present if transaction only input is a coinbase one. + pub generated: Option, + /// Only present if we consider transaction to be trusted and so safe to spend from. + pub trusted: Option, + /// The block hash containing the transaction. + #[serde(rename = "blockhash")] + pub block_hash: Option, + /// The block height containing the transaction. + #[serde(rename = "blockheight")] + pub block_height: Option, + /// The index of the transaction in the block that includes it. + #[serde(rename = "blockindex")] + pub block_index: Option, + /// The block time expressed in UNIX epoch time. + #[serde(rename = "blocktime")] + pub block_time: Option, + /// The transaction id. + pub txid: String, + /// The hash of serialized transaction, including witness data. + pub wtxid: String, + /// Conflicting transaction ids. + #[serde(rename = "walletconflicts")] + pub wallet_conflicts: Vec, + /// The txid if this tx was replaced. + pub replaced_by_txid: Option, + /// The txid if this tx replaces one. + pub replaces_txid: Option, + /// If a comment is associated with the transaction, only present if not empty. + pub comment: Option, + /// If a comment to is associated with the transaction. + pub to: Option, + /// The transaction time expressed in UNIX epoch time. + pub time: u32, + /// The time received expressed in UNIX epoch time. + #[serde(rename = "timereceived")] + pub time_received: u32, + /// ("yes|no|unknown") Whether this transaction could be replaced due to BIP125 (replace-by-fee); + /// may be unknown for unconfirmed transactions not in the mempool + #[serde(rename = "bip125-replaceable")] + pub bip125_replaceable: Bip125Replaceable, + /// Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin. + #[serde(rename = "parent_descs")] + pub parent_descriptors: Option>, + /// 'true' if the transaction has been abandoned (inputs are respendable). Only available for the + /// 'send' category of transactions. + pub abandoned: Option, + /// A comment for the address/transaction, if any. + pub label: Option, +} + +/// Result of the JSON-RPC method `listtransactions`. +/// +/// > listtransactions (label count skip include_watchonly) +/// > +/// > If a label name is provided, this will return only incoming transactions paying to addresses with the specified label. +/// > +/// > Returns up to 'count' most recent transactions skipping the first 'from' transactions. +/// > Note that the "account" argument and "otheraccount" return value have been removed in V0.17. To use this RPC with an "account" argument, restart +/// > bitcoind with -deprecatedrpc=accounts +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ListTransactions(pub Vec); + /// Result of the JSON-RPC method `listunspent`. /// /// > listunspent ( minconf maxconf ["addresses",...] `[include_unsafe]` `[query_options]`) diff --git a/types/src/v25/mod.rs b/types/src/v25/mod.rs index 4c960b55..6536f930 100644 --- a/types/src/v25/mod.rs +++ b/types/src/v25/mod.rs @@ -199,8 +199,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -279,9 +279,7 @@ pub use crate::{ GetTxOutError, GetTxOutSetInfo, GetTxOutSetInfoError, GetUnconfirmedBalance, GetWalletInfoError, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, - ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets, + ListLockUnspentItemError, ListReceivedByAddressError, ListUnspentItemError, ListWallets, LockUnspent, Locked, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, @@ -322,9 +320,10 @@ pub use crate::{ DecodePsbt, DecodePsbtError, GetMempoolAncestors, GetMempoolAncestorsVerbose, GetMempoolDescendants, GetMempoolDescendantsVerbose, GetMempoolEntry, GetMempoolInfo, GetPeerInfo, GetTransaction, GetTransactionDetail, GetTransactionError, - GetTxSpendingPrevout, GetTxSpendingPrevoutError, GlobalXpub, ListUnspent, ListUnspentItem, - MempoolEntry, MigrateWallet, PeerInfo, Proprietary, PsbtInput, PsbtOutput, SendAll, - SendAllError, SimulateRawTransaction, TaprootBip32Deriv, TaprootLeaf, TaprootScript, - TaprootScriptPathSig, + GetTxSpendingPrevout, GetTxSpendingPrevoutError, GlobalXpub, ListSinceBlock, + ListSinceBlockError, ListTransactions, ListUnspent, ListUnspentItem, MempoolEntry, + MigrateWallet, PeerInfo, Proprietary, PsbtInput, PsbtOutput, SendAll, SendAllError, + SimulateRawTransaction, TaprootBip32Deriv, TaprootLeaf, TaprootScript, + TaprootScriptPathSig, TransactionItem, TransactionItemError, }, }; diff --git a/types/src/v25/wallet/into.rs b/types/src/v25/wallet/into.rs index e69de29b..20205029 100644 --- a/types/src/v25/wallet/into.rs +++ b/types/src/v25/wallet/into.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: CC0-1.0 + +use super::{CreateWallet, LoadWallet, UnloadWallet}; +use crate::model; + +impl CreateWallet { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> model::CreateWallet { + // As the content of the deprecated `warning` field would be the same as `warnings`, we + // simply ignore the field, even in case it's set. + model::CreateWallet { name: self.name, warnings: self.warnings.unwrap_or_default() } + } + + /// Returns the created wallet name. + pub fn name(self) -> String { self.into_model().name } +} + +impl LoadWallet { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> model::LoadWallet { + // As the content of the deprecated `warning` field would be the same as `warnings`, we + // simply ignore the field, even in case it's set. + model::LoadWallet { name: self.name, warnings: self.warnings.unwrap_or_default() } + } + + /// Returns the loaded wallet name. + pub fn name(self) -> String { self.into_model().name } +} + +impl UnloadWallet { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> model::UnloadWallet { + model::UnloadWallet { warnings: self.warnings.unwrap_or_default() } + } +} diff --git a/types/src/v25/wallet.rs b/types/src/v25/wallet/mod.rs similarity index 83% rename from types/src/v25/wallet.rs rename to types/src/v25/wallet/mod.rs index 36a2df49..148f6c2d 100644 --- a/types/src/v25/wallet.rs +++ b/types/src/v25/wallet/mod.rs @@ -4,9 +4,9 @@ //! //! Types for methods found under the `== Wallet ==` section of the API docs. -use serde::{Deserialize, Serialize}; +mod into; -use crate::model; +use serde::{Deserialize, Serialize}; /// Result of the JSON-RPC method `createwallet`. /// @@ -39,17 +39,6 @@ pub struct CreateWallet { pub warnings: Option>, } -impl CreateWallet { - /// Converts version specific type to a version nonspecific, more strongly typed type. - pub fn into_model(self) -> model::CreateWallet { - // As the content of the deprecated `warning` field would be the same as `warnings`, we - // simply ignore the field, even in case it's set. - model::CreateWallet { name: self.name, warnings: self.warnings.unwrap_or_default() } - } - - /// Returns the created wallet name. - pub fn name(self) -> String { self.into_model().name } -} /// Result of JSON-RPC method `listdescriptors`. /// /// > List descriptors imported into a descriptor-enabled wallet. @@ -108,18 +97,6 @@ pub struct LoadWallet { pub warnings: Option>, } -impl LoadWallet { - /// Converts version specific type to a version nonspecific, more strongly typed type. - pub fn into_model(self) -> model::LoadWallet { - // As the content of the deprecated `warning` field would be the same as `warnings`, we - // simply ignore the field, even in case it's set. - model::LoadWallet { name: self.name, warnings: self.warnings.unwrap_or_default() } - } - - /// Returns the loaded wallet name. - pub fn name(self) -> String { self.into_model().name } -} - /// Result of the JSON-RPC method `unloadwallet`. /// /// > unloadwallet ( "wallet_name" load_on_startup ) @@ -141,10 +118,3 @@ pub struct UnloadWallet { /// Warning messages, if any, related to loading the wallet. pub warnings: Option>, } - -impl UnloadWallet { - /// Converts version specific type to a version nonspecific, more strongly typed type. - pub fn into_model(self) -> model::UnloadWallet { - model::UnloadWallet { warnings: self.warnings.unwrap_or_default() } - } -} diff --git a/types/src/v26/mod.rs b/types/src/v26/mod.rs index b9d37fc3..4a9dddb7 100644 --- a/types/src/v26/mod.rs +++ b/types/src/v26/mod.rs @@ -207,8 +207,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -295,9 +295,7 @@ pub use crate::{ GetRawTransactionVerboseError, GetReceivedByAddress, GetTransactionDetailError, GetTxOut, GetTxOutError, GetUnconfirmedBalance, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, - ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets, + ListLockUnspentItemError, ListReceivedByAddressError, ListUnspentItemError, ListWallets, LockUnspent, Locked, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, @@ -337,9 +335,10 @@ pub use crate::{ DecodePsbt, DecodePsbtError, GetMempoolAncestors, GetMempoolAncestorsVerbose, GetMempoolDescendants, GetMempoolDescendantsVerbose, GetMempoolEntry, GetMempoolInfo, GetTransactionDetail, GetTxSpendingPrevout, GetTxSpendingPrevoutError, GlobalXpub, - ListUnspent, ListUnspentItem, MempoolEntry, MigrateWallet, Proprietary, PsbtInput, - PsbtOutput, SendAll, SendAllError, SimulateRawTransaction, TaprootBip32Deriv, TaprootLeaf, - TaprootScript, TaprootScriptPathSig, + ListSinceBlock, ListSinceBlockError, ListTransactions, ListUnspent, ListUnspentItem, + MempoolEntry, MigrateWallet, Proprietary, PsbtInput, PsbtOutput, SendAll, SendAllError, + SimulateRawTransaction, TaprootBip32Deriv, TaprootLeaf, TaprootScript, + TaprootScriptPathSig, TransactionItem, TransactionItemError, }, v25::{ GenerateBlock, GenerateBlockError, GetBlockStats, ListDescriptors, MempoolAcceptance, diff --git a/types/src/v27/mod.rs b/types/src/v27/mod.rs index 51321bce..0adaaba9 100644 --- a/types/src/v27/mod.rs +++ b/types/src/v27/mod.rs @@ -207,8 +207,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -272,9 +272,7 @@ pub use crate::{ GetRawTransactionVerboseError, GetReceivedByAddress, GetTransactionDetailError, GetTxOut, GetTxOutError, GetUnconfirmedBalance, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, - ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets, + ListLockUnspentItemError, ListReceivedByAddressError, ListUnspentItemError, ListWallets, LockUnspent, Locked, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, @@ -314,9 +312,10 @@ pub use crate::{ DecodePsbt, DecodePsbtError, GetMempoolAncestors, GetMempoolAncestorsVerbose, GetMempoolDescendants, GetMempoolDescendantsVerbose, GetMempoolEntry, GetMempoolInfo, GetTransactionDetail, GetTxSpendingPrevout, GetTxSpendingPrevoutError, GlobalXpub, - ListUnspent, ListUnspentItem, MempoolEntry, MigrateWallet, Proprietary, PsbtInput, - PsbtOutput, SendAll, SendAllError, SimulateRawTransaction, TaprootBip32Deriv, TaprootLeaf, - TaprootScript, TaprootScriptPathSig, + ListSinceBlock, ListSinceBlockError, ListTransactions, ListUnspent, ListUnspentItem, + MempoolEntry, MigrateWallet, Proprietary, PsbtInput, PsbtOutput, SendAll, SendAllError, + SimulateRawTransaction, TaprootBip32Deriv, TaprootLeaf, TaprootScript, + TaprootScriptPathSig, TransactionItem, TransactionItemError, }, v25::{ GenerateBlock, GenerateBlockError, GetBlockStats, ListDescriptors, MempoolAcceptance, diff --git a/types/src/v28/mod.rs b/types/src/v28/mod.rs index 92055cc2..4c74f6e7 100644 --- a/types/src/v28/mod.rs +++ b/types/src/v28/mod.rs @@ -209,8 +209,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -269,7 +269,8 @@ pub use self::{ }, wallet::{ CreateWalletDescriptor, GetAddressInfo, GetAddressInfoEmbedded, GetHdKeys, GetHdKeysError, - GetTransaction, HdKey, HdKeyDescriptor, + GetTransaction, HdKey, HdKeyDescriptor, ListSinceBlock, ListSinceBlockError, + ListTransactions, TransactionItem, TransactionItemError, }, }; #[doc(inline)] @@ -292,9 +293,7 @@ pub use crate::{ GetRawTransactionVerboseError, GetReceivedByAddress, GetTransactionDetailError, GetTxOut, GetTxOutError, GetUnconfirmedBalance, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, - ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets, + ListLockUnspentItemError, ListReceivedByAddressError, ListUnspentItemError, ListWallets, LockUnspent, Locked, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, diff --git a/types/src/v28/wallet/error.rs b/types/src/v28/wallet/error.rs index 5ad7ddfa..27ff4154 100644 --- a/types/src/v28/wallet/error.rs +++ b/types/src/v28/wallet/error.rs @@ -2,7 +2,8 @@ use core::fmt; -use bitcoin::bip32; +use bitcoin::amount::ParseAmountError; +use bitcoin::{bip32, hex}; use crate::error::write_err; use crate::NumericError; @@ -44,3 +45,107 @@ impl std::error::Error for GetHdKeysError { impl From for GetHdKeysError { fn from(e: NumericError) -> Self { Self::Numeric(e) } } + +/// Error when converting a `ListSinceBlock` type into the model type. +#[derive(Debug)] +pub enum ListSinceBlockError { + /// Conversion of the `transactions` field failed. + Transactions(TransactionItemError), + /// Conversion of the `removed` field failed. + Removed(TransactionItemError), + /// Conversion of the `last_block` field failed. + LastBlock(hex::HexToArrayError), +} + +impl fmt::Display for ListSinceBlockError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ListSinceBlockError::*; + match *self { + Transactions(ref e) => + write_err!(f, "conversion of the `transactions` field failed"; e), + Removed(ref e) => write_err!(f, "conversion of the `removed` field failed"; e), + LastBlock(ref e) => write_err!(f, "conversion of the `last_block` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ListSinceBlockError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ListSinceBlockError::*; + match *self { + Transactions(ref e) => Some(e), + Removed(ref e) => Some(e), + LastBlock(ref e) => Some(e), + } + } +} + +/// Error when converting a `TransactionItem` type into the model type. +#[derive(Debug)] +pub enum TransactionItemError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `address` field failed. + Address(bitcoin::address::ParseError), + /// Conversion of the `amount` field failed. + Amount(ParseAmountError), + /// Conversion of the `fee` field failed. + Fee(ParseAmountError), + /// Conversion of the `block_hash` field failed. + BlockHash(hex::HexToArrayError), + /// Conversion of the `txid` field failed. + Txid(hex::HexToArrayError), + /// Conversion of the `wtxid` field failed. + Wtxid(hex::HexToArrayError), + /// Conversion of the `wallet_conflicts` field failed. + WalletConflicts(hex::HexToArrayError), + /// Conversion of the `replaced_by_txid` field failed. + ReplacedByTxid(hex::HexToArrayError), + /// Conversion of the `replaces_txid` field failed. + ReplacesTxid(hex::HexToArrayError), +} + +impl fmt::Display for TransactionItemError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use TransactionItemError as E; + match *self { + E::Numeric(ref e) => write_err!(f, "numeric"; e), + E::Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), + E::Amount(ref e) => write_err!(f, "conversion of the `amount` field failed"; e), + E::Fee(ref e) => write_err!(f, "conversion of the `fee` field failed"; e), + E::BlockHash(ref e) => write_err!(f, "conversion of the `block_hash` field failed"; e), + E::Txid(ref e) => write_err!(f, "conversion of the `txid` field failed"; e), + E::Wtxid(ref e) => write_err!(f, "conversion of the `wtxid` field failed"; e), + E::WalletConflicts(ref e) => + write_err!(f, "conversion of the `wallet_conflicts` field failed"; e), + E::ReplacedByTxid(ref e) => + write_err!(f, "conversion of the `replaced_by_txid` field failed"; e), + E::ReplacesTxid(ref e) => + write_err!(f, "conversion of the `replaces_txid` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TransactionItemError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use TransactionItemError as E; + match *self { + E::Numeric(ref e) => Some(e), + E::Address(ref e) => Some(e), + E::Amount(ref e) => Some(e), + E::Fee(ref e) => Some(e), + E::BlockHash(ref e) => Some(e), + E::Txid(ref e) => Some(e), + E::Wtxid(ref e) => Some(e), + E::WalletConflicts(ref e) => Some(e), + E::ReplacedByTxid(ref e) => Some(e), + E::ReplacesTxid(ref e) => Some(e), + } + } +} + +impl From for TransactionItemError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} diff --git a/types/src/v28/wallet/into.rs b/types/src/v28/wallet/into.rs index 4b0d26bb..afb1e3d8 100644 --- a/types/src/v28/wallet/into.rs +++ b/types/src/v28/wallet/into.rs @@ -11,7 +11,8 @@ use bitcoin::{ use super::{ GetAddressInfo, GetAddressInfoEmbedded, GetAddressInfoEmbeddedError, GetAddressInfoError, - GetHdKeys, GetHdKeysError, GetTransaction, GetTransactionError, + GetHdKeys, GetHdKeysError, GetTransaction, GetTransactionError, ListSinceBlock, + ListSinceBlockError, ListTransactions, TransactionItem, TransactionItemError, }; use crate::model; @@ -249,3 +250,103 @@ impl GetTransaction { }) } } + +impl ListSinceBlock { + pub fn into_model(self) -> Result { + use ListSinceBlockError as E; + + let transactions = self + .transactions + .into_iter() + .map(|t| t.into_model()) + .collect::, _>>() + .map_err(E::Transactions)?; + let removed = self + .removed + .into_iter() + .map(|t| t.into_model()) + .collect::, _>>() + .map_err(E::Removed)?; + let last_block = self.last_block.parse::().map_err(E::LastBlock)?; + + Ok(model::ListSinceBlock { transactions, removed, last_block }) + } +} + +impl TransactionItem { + pub fn into_model(self) -> Result { + use TransactionItemError as E; + + let address = + self.address.map(|s| s.parse::>().map_err(E::Address)).transpose()?; + let category = self.category.into_model(); + let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?; + let vout = crate::to_u32(self.vout, "vout")?; + let fee = self + .fee + .map(|f| SignedAmount::from_btc(f).map_err(E::Fee)) + .transpose()? // optional historically + .unwrap_or_else(|| SignedAmount::from_sat(0)); + let block_hash = + self.block_hash.map(|s| s.parse::().map_err(E::BlockHash)).transpose()?; + let block_height = + self.block_height.map(|h| crate::to_u32(h, "block_height")).transpose()?; + let block_index = self.block_index.map(|h| crate::to_u32(h, "block_index")).transpose()?; + let txid = self.txid.parse::().map_err(E::Txid)?; + let wtxid = self.wtxid.parse::().map_err(E::Wtxid)?; + let wallet_conflicts = self + .wallet_conflicts + .into_iter() + .map(|s| s.parse::().map_err(E::WalletConflicts)) + .collect::, _>>()?; + let replaced_by_txid = self + .replaced_by_txid + .map(|s| s.parse::().map_err(E::ReplacedByTxid)) + .transpose()?; + let replaces_txid = + self.replaces_txid.map(|s| s.parse::().map_err(E::ReplacesTxid)).transpose()?; + let mempool_conflicts = self + .mempool_conflicts + .map(|v| v.into_iter().filter_map(|s| s.parse::().ok()).collect::>()); + let bip125_replaceable = self.bip125_replaceable.into_model(); + + Ok(model::TransactionItem { + involves_watch_only: self.involves_watch_only, + address, + category, + amount, + vout, + fee, + confirmations: self.confirmations, + generated: self.generated, + trusted: self.trusted, + block_hash, + block_height, + block_index, + block_time: self.block_time, + txid: Some(txid), + wtxid: Some(wtxid), + wallet_conflicts: Some(wallet_conflicts), + replaced_by_txid, + replaces_txid, + mempool_conflicts, + to: self.to, + time: self.time, + time_received: self.time_received, + comment: self.comment, + bip125_replaceable, + parent_descriptors: self.parent_descriptors, + abandoned: self.abandoned, + label: self.label, + }) + } +} + +impl ListTransactions { + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> Result { + let transactions = + self.0.into_iter().map(|tx| tx.into_model()).collect::, _>>()?; + Ok(model::ListTransactions(transactions)) + } +} diff --git a/types/src/v28/wallet/mod.rs b/types/src/v28/wallet/mod.rs index e8e0f5b9..a5864c55 100644 --- a/types/src/v28/wallet/mod.rs +++ b/types/src/v28/wallet/mod.rs @@ -8,9 +8,9 @@ mod error; mod into; use bitcoin::Transaction; -pub use error::GetHdKeysError; use serde::{Deserialize, Serialize}; +pub use self::error::{GetHdKeysError, ListSinceBlockError, TransactionItemError}; pub use super::{ Bip125Replaceable, GetAddressInfoEmbeddedError, GetAddressInfoError, GetTransactionDetail, GetTransactionError, LastProcessedBlock, ScriptType, @@ -275,3 +275,113 @@ pub struct GetTransaction { #[serde(rename = "lastprocessedblock")] pub last_processed_block: Option, } + +/// Result of the JSON-RPC method `listsinceblock`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ListSinceBlock { + /// All the transactions. + pub transactions: Vec, + /// Only present if `include_removed=true`. + /// + /// Note: transactions that were re-added in the active chain will appear as-is in this array, + /// and may thus have a positive confirmation count. + pub removed: Vec, + /// The hash of the block (target_confirmations-1) from the best block on the main chain. + /// + /// This is typically used to feed back into listsinceblock the next time you call it. So you + /// would generally use a target_confirmations of say 6, so you will be continually + /// re-notified of transactions until they've reached 6 confirmations plus any new ones. + #[serde(rename = "lastblock")] + pub last_block: String, +} + +/// Transaction item returned as part of `listsinceblock`. +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct TransactionItem { + /// Only returns true if imported addresses were involved in transaction. + #[serde(rename = "involvesWatchonly")] + pub involves_watch_only: Option, + /// The bitcoin address of the transaction. + pub address: Option, + /// The transaction category. + pub category: super::TransactionCategory, + /// The amount in BTC. + /// + /// This is negative for the 'send' category, and is positive for all other categories. + pub amount: f64, + /// The vout value. + pub vout: i64, + /// The amount of the fee in BTC. + /// + /// This is negative and only available for the 'send' category of transactions. + pub fee: Option, + /// The number of confirmations for the transaction. Negative confirmations means the + /// transaction conflicted that many blocks ago. + pub confirmations: i64, + /// Only present if transaction only input is a coinbase one. + pub generated: Option, + /// Only present if we consider transaction to be trusted and so safe to spend from. + pub trusted: Option, + /// The block hash containing the transaction. + #[serde(rename = "blockhash")] + pub block_hash: Option, + /// The block height containing the transaction. + #[serde(rename = "blockheight")] + pub block_height: Option, + /// The index of the transaction in the block that includes it. + #[serde(rename = "blockindex")] + pub block_index: Option, + /// The block time expressed in UNIX epoch time. + #[serde(rename = "blocktime")] + pub block_time: Option, + /// The transaction id. + pub txid: String, + /// The hash of serialized transaction, including witness data. + pub wtxid: String, + /// Conflicting transaction ids. + #[serde(rename = "walletconflicts")] + pub wallet_conflicts: Vec, + /// The txid if this tx was replaced. + pub replaced_by_txid: Option, + /// The txid if this tx replaces one. + pub replaces_txid: Option, + /// If a comment is associated with the transaction, only present if not empty. + pub comment: Option, + /// Transactions in the mempool that directly conflict with either this transaction or an ancestor transaction. + #[serde(rename = "mempoolconflicts")] + pub mempool_conflicts: Option>, + /// If a comment to is associated with the transaction. + pub to: Option, + /// The transaction time expressed in UNIX epoch time. + pub time: u32, + /// The time received expressed in UNIX epoch time. + #[serde(rename = "timereceived")] + pub time_received: u32, + /// ("yes|no|unknown") Whether this transaction could be replaced due to BIP125 (replace-by-fee); + /// may be unknown for unconfirmed transactions not in the mempool + #[serde(rename = "bip125-replaceable")] + pub bip125_replaceable: Bip125Replaceable, + /// Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin. + #[serde(rename = "parent_descs")] + pub parent_descriptors: Option>, + /// 'true' if the transaction has been abandoned (inputs are respendable). Only available for the + /// 'send' category of transactions. + pub abandoned: Option, + /// A comment for the address/transaction, if any. + pub label: Option, +} + +/// Result of the JSON-RPC method `listtransactions`. +/// +/// > listtransactions (label count skip include_watchonly) +/// > +/// > If a label name is provided, this will return only incoming transactions paying to addresses with the specified label. +/// > +/// > Returns up to 'count' most recent transactions skipping the first 'from' transactions. +/// > Note that the "account" argument and "otheraccount" return value have been removed in V0.17. To use this RPC with an "account" argument, restart +/// > bitcoind with -deprecatedrpc=accounts +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct ListTransactions(pub Vec); diff --git a/types/src/v29/mod.rs b/types/src/v29/mod.rs index 67683151..60524f1a 100644 --- a/types/src/v29/mod.rs +++ b/types/src/v29/mod.rs @@ -210,8 +210,8 @@ //! | psbtbumpfee | version + model | | //! | listreceivedbyaddress | version + model | | //! | listreceivedbylabel | version + model | | -//! | listsinceblock | version + model | UNTESTED | -//! | listtransactions | version + model | UNTESTED | +//! | listsinceblock | version + model | | +//! | listtransactions | version + model | | //! | listunspent | version + model | | //! | listwalletdir | version | | //! | listwallets | version + model | | @@ -289,9 +289,7 @@ pub use crate::{ GetRawTransactionVerboseError, GetReceivedByAddress, GetTransactionDetailError, GetTxOut, GetTxOutError, GetUnconfirmedBalance, ListAddressGroupings, ListAddressGroupingsError, ListAddressGroupingsItem, ListLabels, ListLockUnspent, ListLockUnspentItem, - ListLockUnspentItemError, ListReceivedByAddressError, ListSinceBlock, ListSinceBlockError, - ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions, - ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets, + ListLockUnspentItemError, ListReceivedByAddressError, ListUnspentItemError, ListWallets, LockUnspent, Locked, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, ScriptType, SendRawTransaction, SendToAddress, SetNetworkActive, SetTxFee, SignMessage, SignMessageWithPrivKey, SignRawTransaction, @@ -349,8 +347,9 @@ pub use crate::{ v27::{GetPrioritisedTransactions, PrioritisedTransaction}, v28::{ CreateWalletDescriptor, GetAddressInfo, GetAddressInfoEmbedded, GetHdKeys, GetHdKeysError, - GetNetworkInfo, GetTransaction, HdKey, HdKeyDescriptor, Logging, SubmitPackage, - SubmitPackageError, SubmitPackageTxResult, SubmitPackageTxResultError, - SubmitPackageTxResultFees, SubmitPackageTxResultFeesError, + GetNetworkInfo, GetTransaction, HdKey, HdKeyDescriptor, ListSinceBlock, + ListSinceBlockError, ListTransactions, Logging, SubmitPackage, SubmitPackageError, + SubmitPackageTxResult, SubmitPackageTxResultError, SubmitPackageTxResultFees, + SubmitPackageTxResultFeesError, TransactionItem, TransactionItemError, }, };