Skip to content

Commit 8f3914c

Browse files
committed
Merge rust-bitcoin#188: Improve ListUnspent data handling: Fix deserialization & add into_model
f1573c2 Fix deserialization for ListUnspentItem and implement into_model for ListUnspent (GideonBature) Pull request description: While working on [rust-bitcoin#187](rust-bitcoin#187), I wrote a test for the listunspent method to better understand the issue. The test timed out in my local environment, so I pushed it here to run in CI and confirm the behavior. I found that although the account field has been deprecated and is no longer included in the response, ListUnspentItem still expects it to be present, which causes a deserialization error. While writing this test, I also discovered a related issue: The into_model method is implemented for ListUnspentItem and ListUnspentItemError, but not for the top-level ListUnspent type. This makes it harder to convert the full response into a usable model and limits how we can work with the data. Closes rust-bitcoin#187 Closes rust-bitcoin#189 ACKs for top commit: tcharding: ACK f1573c2 Tree-SHA512: 7bab482a4547d03f15b66787241a2b5b30ce0479f9f654f3a0b3015140143da4c10aa70bb42b061f8d5f5de89f236d2b6a822c41aa3656d6e7b875ad7bee12f4
2 parents ddf06ca + f1573c2 commit 8f3914c

File tree

17 files changed

+247
-122
lines changed

17 files changed

+247
-122
lines changed

integration_test/tests/wallet.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,22 @@ fn wallet__get_transaction__modelled() {
215215
model.unwrap();
216216
}
217217

218+
#[test]
219+
fn wallet__list_unspent__modelled() {
220+
let node = match () {
221+
#[cfg(feature = "v17")]
222+
() => Node::with_wallet(Wallet::Default, &["-deprecatedrpc=accounts"]),
223+
#[cfg(not(feature = "v17"))]
224+
() => Node::with_wallet(Wallet::Default, &[]),
225+
};
226+
227+
node.fund_wallet();
228+
229+
let json: ListUnspent = node.client.list_unspent().expect("listunspent");
230+
let model: Result<mtype::ListUnspent, ListUnspentItemError> = json.into_model();
231+
model.unwrap();
232+
}
233+
218234
#[test]
219235
fn wallet__load_wallet__modelled() {
220236
create_load_unload_wallet();

types/src/v17/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@
187187
//! | listreceivedbyaddress | version + model | UNTESTED |
188188
//! | listsinceblock | version + model | UNTESTED |
189189
//! | listtransactions | version + model | UNTESTED |
190-
//! | listunspent | version + model | UNTESTED |
190+
//! | listunspent | version + model | |
191191
//! | listwallets | version + model | UNTESTED |
192192
//! | loadwallet | version + model | |
193193
//! | lockunspent | returns boolean | |

types/src/v17/wallet/into.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,17 @@ impl ListTransactionsItem {
622622
}
623623
}
624624

625+
impl ListUnspent {
626+
/// Converts version specific type to a version nonspecific, more strongly typed type.
627+
pub fn into_model(self) -> Result<model::ListUnspent, ListUnspentItemError> {
628+
self.0
629+
.into_iter()
630+
.map(|item| item.into_model())
631+
.collect::<Result<Vec<_>, _>>()
632+
.map(model::ListUnspent)
633+
}
634+
}
635+
625636
impl ListUnspentItem {
626637
/// Converts version specific type to a version nonspecific, more strongly typed type.
627638
pub fn into_model(self) -> Result<model::ListUnspentItem, ListUnspentItemError> {

types/src/v18/mod.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@
190190
//! | listreceivedbylabel | version + model | TODO |
191191
//! | listsinceblock | version + model | UNTESTED |
192192
//! | listtransactions | version + model | UNTESTED |
193-
//! | listunspent | version + model | UNTESTED |
193+
//! | listunspent | version + model | |
194194
//! | listwalletdir | version | TODO |
195195
//! | listwallets | version + model | UNTESTED |
196196
//! | loadwallet | version + model | |
@@ -226,6 +226,7 @@
226226
mod control;
227227
mod network;
228228
mod raw_transactions;
229+
mod wallet;
229230

230231
#[doc(inline)]
231232
pub use self::{
@@ -235,6 +236,7 @@ pub use self::{
235236
AnalyzePsbt, AnalyzePsbtError, AnalyzePsbtInput, AnalyzePsbtInputMissing,
236237
AnalyzePsbtInputMissingError, JoinPsbts, UtxoUpdatePsbt,
237238
},
239+
wallet::{ListUnspent, ListUnspentItem},
238240
};
239241
#[doc(inline)]
240242
pub use crate::v17::{
@@ -263,14 +265,14 @@ pub use crate::v17::{
263265
ListBanned, ListLabels, ListLockUnspent, ListLockUnspentItem, ListLockUnspentItemError,
264266
ListReceivedByAddress, ListReceivedByAddressError, ListReceivedByAddressItem, ListSinceBlock,
265267
ListSinceBlockError, ListSinceBlockTransaction, ListSinceBlockTransactionError,
266-
ListTransactions, ListTransactionsItem, ListTransactionsItemError, ListUnspent,
267-
ListUnspentItem, ListUnspentItemError, ListWallets, LoadWallet, Locked, Logging,
268-
MapMempoolEntryError, MempoolAcceptance, MempoolEntry, MempoolEntryError, MempoolEntryFees,
269-
MempoolEntryFeesError, PeerInfo, PruneBlockchain, PsbtInput, PsbtOutput, PsbtScript,
270-
RawTransaction, RawTransactionError, RawTransactionInput, RawTransactionOutput,
271-
RescanBlockchain, SendMany, SendRawTransaction, SendToAddress, SignFail, SignFailError,
272-
SignMessage, SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError, Softfork,
273-
SoftforkReject, TestMempoolAccept, TransactionCategory, UploadTarget, ValidateAddress,
274-
ValidateAddressError, VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt,
275-
WalletCreateFundedPsbtError, WalletProcessPsbt, WitnessUtxo,
268+
ListTransactions, ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError,
269+
ListWallets, LoadWallet, Locked, Logging, MapMempoolEntryError, MempoolAcceptance,
270+
MempoolEntry, MempoolEntryError, MempoolEntryFees, MempoolEntryFeesError, PeerInfo,
271+
PruneBlockchain, PsbtInput, PsbtOutput, PsbtScript, RawTransaction, RawTransactionError,
272+
RawTransactionInput, RawTransactionOutput, RescanBlockchain, SendMany, SendRawTransaction,
273+
SendToAddress, SignFail, SignFailError, SignMessage, SignMessageWithPrivKey,
274+
SignRawTransaction, SignRawTransactionError, Softfork, SoftforkReject, TestMempoolAccept,
275+
TransactionCategory, UploadTarget, ValidateAddress, ValidateAddressError, VerifyChain,
276+
VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt, WalletCreateFundedPsbtError,
277+
WalletProcessPsbt, WitnessUtxo,
276278
};

types/src/v18/wallet/into.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
use bitcoin::{Address, ScriptBuf, SignedAmount, Txid};
4+
5+
// TODO: Use explicit imports?
6+
use super::*;
7+
use crate::model;
8+
use crate::v17::ListUnspentItemError;
9+
10+
impl ListUnspent {
11+
/// Converts version specific type to a version nonspecific, more strongly typed type.
12+
pub fn into_model(self) -> Result<model::ListUnspent, ListUnspentItemError> {
13+
self.0
14+
.into_iter()
15+
.map(|item| item.into_model())
16+
.collect::<Result<Vec<_>, _>>()
17+
.map(model::ListUnspent)
18+
}
19+
}
20+
21+
impl ListUnspentItem {
22+
/// Converts version specific type to a version nonspecific, more strongly typed type.
23+
pub fn into_model(self) -> Result<model::ListUnspentItem, ListUnspentItemError> {
24+
use ListUnspentItemError as E;
25+
26+
let txid = self.txid.parse::<Txid>().map_err(E::Txid)?;
27+
let vout = crate::to_u32(self.vout, "vout")?;
28+
let address = self.address.parse::<Address<_>>().map_err(E::Address)?;
29+
let script_pubkey = ScriptBuf::from_hex(&self.script_pubkey).map_err(E::ScriptPubkey)?;
30+
31+
let amount = SignedAmount::from_btc(self.amount).map_err(E::Amount)?;
32+
let confirmations = crate::to_u32(self.confirmations, "confirmations")?;
33+
let redeem_script = self
34+
.redeem_script
35+
.map(|hex| ScriptBuf::from_hex(&hex).map_err(E::RedeemScript))
36+
.transpose()?;
37+
38+
Ok(model::ListUnspentItem {
39+
txid,
40+
vout,
41+
address,
42+
label: self.label,
43+
script_pubkey,
44+
amount,
45+
confirmations,
46+
redeem_script,
47+
spendable: self.spendable,
48+
solvable: self.solvable,
49+
safe: self.safe,
50+
})
51+
}
52+
}

types/src/v18/wallet/mod.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: CC0-1.0
2+
3+
//! The JSON-RPC API for Bitcoin Core `v0.17` - wallet.
4+
//!
5+
//! Types for methods found under the `== Wallet ==` section of the API docs.
6+
7+
mod into;
8+
9+
use serde::{Deserialize, Serialize};
10+
11+
/// Result of the JSON-RPC method `listunspent`.
12+
///
13+
/// > listunspent ( minconf maxconf ["addresses",...] `[include_unsafe]` `[query_options]`)
14+
/// >
15+
/// > Returns array of unspent transaction outputs
16+
/// > with between minconf and maxconf (inclusive) confirmations.
17+
/// > Optionally filter to only include txouts paid to specified addresses.
18+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
19+
pub struct ListUnspent(pub Vec<ListUnspentItem>);
20+
21+
/// Unspent transaction output, returned as part of `listunspent`.
22+
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
23+
pub struct ListUnspentItem {
24+
/// The transaction id.
25+
pub txid: String,
26+
/// The vout value.
27+
pub vout: i64,
28+
/// The bitcoin address of the transaction.
29+
pub address: String,
30+
/// The associated label, or "" for the default label.
31+
pub label: String,
32+
/// The script key.
33+
#[serde(rename = "scriptPubKey")]
34+
pub script_pubkey: String,
35+
/// The transaction amount in BTC.
36+
pub amount: f64,
37+
/// The number of confirmations.
38+
pub confirmations: i64,
39+
/// The redeemScript if scriptPubKey is P2SH.
40+
#[serde(rename = "redeemScript")]
41+
pub redeem_script: Option<String>,
42+
/// Whether we have the private keys to spend this output.
43+
pub spendable: bool,
44+
/// Whether we know how to spend this output, ignoring the lack of keys.
45+
pub solvable: bool,
46+
/// Whether this output is considered safe to spend. Unconfirmed transactions from outside keys
47+
/// and unconfirmed replacement transactions are considered unsafe and are not eligible for
48+
/// spending by fundrawtransaction and sendtoaddress.
49+
pub safe: bool,
50+
}

types/src/v19/mod.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@
191191
//! | listreceivedbylabel | version + model | TODO |
192192
//! | listsinceblock | version + model | UNTESTED |
193193
//! | listtransactions | version + model | UNTESTED |
194-
//! | listunspent | version + model | UNTESTED |
194+
//! | listunspent | version + model | |
195195
//! | listwalletdir | version | TODO |
196196
//! | listwallets | version + model | UNTESTED |
197197
//! | loadwallet | version + model | |
@@ -264,17 +264,17 @@ pub use crate::v17::{
264264
ListLockUnspentItemError, ListReceivedByAddress, ListReceivedByAddressError,
265265
ListReceivedByAddressItem, ListSinceBlock, ListSinceBlockError, ListSinceBlockTransaction,
266266
ListSinceBlockTransactionError, ListTransactions, ListTransactionsItem,
267-
ListTransactionsItemError, ListUnspent, ListUnspentItem, ListUnspentItemError, ListWallets,
268-
LoadWallet, Locked, Logging, PeerInfo, PruneBlockchain, RawTransactionError,
269-
RawTransactionInput, RawTransactionOutput, RescanBlockchain, SendMany, SendRawTransaction,
270-
SendToAddress, SignMessage, SignMessageWithPrivKey, SignRawTransaction,
271-
SignRawTransactionError, SoftforkReject, TestMempoolAccept, TransactionCategory, UploadTarget,
272-
ValidateAddress, ValidateAddressError, VerifyChain, VerifyMessage, VerifyTxOutProof,
273-
WalletCreateFundedPsbt, WalletCreateFundedPsbtError, WalletProcessPsbt, WitnessUtxo,
267+
ListTransactionsItemError, ListUnspentItemError, ListWallets, LoadWallet, Locked, Logging,
268+
PeerInfo, PruneBlockchain, RawTransactionError, RawTransactionInput, RawTransactionOutput,
269+
RescanBlockchain, SendMany, SendRawTransaction, SendToAddress, SignMessage,
270+
SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError, SoftforkReject,
271+
TestMempoolAccept, TransactionCategory, UploadTarget, ValidateAddress, ValidateAddressError,
272+
VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt,
273+
WalletCreateFundedPsbtError, WalletProcessPsbt, WitnessUtxo,
274274
};
275275
#[doc(inline)]
276276
pub use crate::v18::{
277277
ActiveCommand, AnalyzePsbt, AnalyzePsbtError, AnalyzePsbtInput, AnalyzePsbtInputMissing,
278-
AnalyzePsbtInputMissingError, GetNodeAddresses, GetRpcInfo, JoinPsbts, NodeAddress,
279-
UtxoUpdatePsbt,
278+
AnalyzePsbtInputMissingError, GetNodeAddresses, GetRpcInfo, JoinPsbts, ListUnspent,
279+
ListUnspentItem, NodeAddress, UtxoUpdatePsbt,
280280
};

types/src/v20/mod.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@
192192
//! | listreceivedbylabel | version + model | TODO |
193193
//! | listsinceblock | version + model | UNTESTED |
194194
//! | listtransactions | version + model | UNTESTED |
195-
//! | listunspent | version + model | UNTESTED |
195+
//! | listunspent | version + model | |
196196
//! | listwalletdir | version | TODO |
197197
//! | listwallets | version + model | UNTESTED |
198198
//! | loadwallet | version + model | |
@@ -257,19 +257,18 @@ pub use crate::{
257257
ListLockUnspent, ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddress,
258258
ListReceivedByAddressError, ListReceivedByAddressItem, ListSinceBlock, ListSinceBlockError,
259259
ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions,
260-
ListTransactionsItem, ListTransactionsItemError, ListUnspent, ListUnspentItem,
261-
ListUnspentItemError, ListWallets, LoadWallet, Locked, PeerInfo, PruneBlockchain,
262-
RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, SendMany,
263-
SendRawTransaction, SendToAddress, SignMessage, SignMessageWithPrivKey, SignRawTransaction,
264-
SignRawTransactionError, SoftforkReject, TestMempoolAccept, TransactionCategory,
265-
UploadTarget, ValidateAddress, ValidateAddressError, VerifyChain, VerifyMessage,
266-
VerifyTxOutProof, WalletCreateFundedPsbt, WalletCreateFundedPsbtError, WalletProcessPsbt,
267-
WitnessUtxo,
260+
ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets,
261+
LoadWallet, Locked, PeerInfo, PruneBlockchain, RawTransactionError, RawTransactionInput,
262+
RawTransactionOutput, RescanBlockchain, SendMany, SendRawTransaction, SendToAddress,
263+
SignMessage, SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError,
264+
SoftforkReject, TestMempoolAccept, TransactionCategory, UploadTarget, ValidateAddress,
265+
ValidateAddressError, VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt,
266+
WalletCreateFundedPsbtError, WalletProcessPsbt, WitnessUtxo,
268267
},
269268
v18::{
270269
ActiveCommand, AnalyzePsbt, AnalyzePsbtError, AnalyzePsbtInput, AnalyzePsbtInputMissing,
271-
AnalyzePsbtInputMissingError, GetNodeAddresses, GetRpcInfo, JoinPsbts, NodeAddress,
272-
UtxoUpdatePsbt,
270+
AnalyzePsbtInputMissingError, GetNodeAddresses, GetRpcInfo, JoinPsbts, ListUnspent,
271+
ListUnspentItem, NodeAddress, UtxoUpdatePsbt,
273272
},
274273
v19::{
275274
Bip9SoftforkInfo, Bip9SoftforkStatistics, Bip9SoftforkStatus, GetBalances, GetBalancesMine,

types/src/v21/mod.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@
196196
//! | listreceivedbylabel | version + model | TODO |
197197
//! | listsinceblock | version + model | UNTESTED |
198198
//! | listtransactions | version + model | UNTESTED |
199-
//! | listunspent | version + model | UNTESTED |
199+
//! | listunspent | version + model | |
200200
//! | listwalletdir | version | TODO |
201201
//! | listwallets | version + model | UNTESTED |
202202
//! | loadwallet | version + model | |
@@ -263,19 +263,18 @@ pub use crate::{
263263
ListLockUnspent, ListLockUnspentItem, ListLockUnspentItemError, ListReceivedByAddress,
264264
ListReceivedByAddressError, ListReceivedByAddressItem, ListSinceBlock, ListSinceBlockError,
265265
ListSinceBlockTransaction, ListSinceBlockTransactionError, ListTransactions,
266-
ListTransactionsItem, ListTransactionsItemError, ListUnspent, ListUnspentItem,
267-
ListUnspentItemError, ListWallets, LoadWallet, Locked, PeerInfo, PruneBlockchain,
268-
RawTransactionError, RawTransactionInput, RawTransactionOutput, RescanBlockchain, SendMany,
269-
SendRawTransaction, SendToAddress, SignMessage, SignMessageWithPrivKey, SignRawTransaction,
270-
SignRawTransactionError, SoftforkReject, TestMempoolAccept, TransactionCategory,
271-
UploadTarget, ValidateAddress, ValidateAddressError, VerifyChain, VerifyMessage,
272-
VerifyTxOutProof, WalletCreateFundedPsbt, WalletCreateFundedPsbtError, WalletProcessPsbt,
273-
WitnessUtxo,
266+
ListTransactionsItem, ListTransactionsItemError, ListUnspentItemError, ListWallets,
267+
LoadWallet, Locked, PeerInfo, PruneBlockchain, RawTransactionError, RawTransactionInput,
268+
RawTransactionOutput, RescanBlockchain, SendMany, SendRawTransaction, SendToAddress,
269+
SignMessage, SignMessageWithPrivKey, SignRawTransaction, SignRawTransactionError,
270+
SoftforkReject, TestMempoolAccept, TransactionCategory, UploadTarget, ValidateAddress,
271+
ValidateAddressError, VerifyChain, VerifyMessage, VerifyTxOutProof, WalletCreateFundedPsbt,
272+
WalletCreateFundedPsbtError, WalletProcessPsbt, WitnessUtxo,
274273
},
275274
v18::{
276275
ActiveCommand, AnalyzePsbt, AnalyzePsbtError, AnalyzePsbtInput, AnalyzePsbtInputMissing,
277-
AnalyzePsbtInputMissingError, GetNodeAddresses, GetRpcInfo, JoinPsbts, NodeAddress,
278-
UtxoUpdatePsbt,
276+
AnalyzePsbtInputMissingError, GetNodeAddresses, GetRpcInfo, JoinPsbts, ListUnspent,
277+
ListUnspentItem, NodeAddress, UtxoUpdatePsbt,
279278
},
280279
v19::{
281280
Bip9SoftforkInfo, Bip9SoftforkStatistics, Bip9SoftforkStatus, GetBalances, GetBalancesMine,

0 commit comments

Comments
 (0)