Skip to content

Commit da1ebce

Browse files
author
pythcoiner
committed
add txids param to listspendtxs
1 parent 3fcbb0b commit da1ebce

File tree

5 files changed

+91
-15
lines changed

5 files changed

+91
-15
lines changed

doc/API.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,14 @@ This command does not return anything for now.
218218

219219
List stored Spend transactions.
220220

221-
#### Request
222221

223-
This command does not take any parameter for now.
222+
If `txids` is specified, only list transactions whose `txid` is in `txids`(empty list of `txids` is not allowed).
224223

225-
| Field | Type | Description |
226-
| ------------- | ----------------- | ----------------------------------------------------------- |
224+
#### Request
225+
226+
| Field | Type | Description |
227+
| ------------- | -------------------------- | ------------------------------------ |
228+
| `txids` | array of string (optional) | Ids of the transactions to retrieve |
227229

228230
#### Response
229231

src/commands/mod.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub enum CommandError {
6262
/// Overflowing or unhardened derivation index.
6363
InvalidDerivationIndex,
6464
RbfError(RbfErrorInfo),
65+
EmptyFilterList,
6566
}
6667

6768
impl fmt::Display for CommandError {
@@ -114,6 +115,7 @@ impl fmt::Display for CommandError {
114115
write!(f, "Unhardened or overflowing BIP32 derivation index.")
115116
}
116117
Self::RbfError(e) => write!(f, "RBF error: '{}'.", e),
118+
Self::EmptyFilterList => write!(f, "Filter list is empty, should supply None instead."),
117119
}
118120
}
119121
}
@@ -589,14 +591,32 @@ impl DaemonControl {
589591
}
590592
}
591593

592-
pub fn list_spend(&self) -> ListSpendResult {
594+
pub fn list_spend(
595+
&self,
596+
txids: Option<Vec<bitcoin::Txid>>,
597+
) -> Result<ListSpendResult, CommandError> {
598+
if let Some(ids) = &txids {
599+
if ids.is_empty() {
600+
return Err(CommandError::EmptyFilterList);
601+
}
602+
}
603+
593604
let mut db_conn = self.db.connection();
594-
let spend_txs = db_conn
595-
.list_spend()
605+
let spend_psbts = db_conn.list_spend();
606+
607+
let txids_set: Option<HashSet<_>> = txids.as_ref().map(|list| list.iter().collect());
608+
let spend_txs = spend_psbts
596609
.into_iter()
597-
.map(|(psbt, updated_at)| ListSpendEntry { psbt, updated_at })
610+
.filter_map(|(psbt, updated_at)| {
611+
if let Some(set) = &txids_set {
612+
if !set.contains(&psbt.unsigned_tx.txid()) {
613+
return None;
614+
}
615+
}
616+
Some(ListSpendEntry { psbt, updated_at })
617+
})
598618
.collect();
599-
ListSpendResult { spend_txs }
619+
Ok(ListSpendResult { spend_txs })
600620
}
601621

602622
pub fn delete_spend(&self, txid: &bitcoin::Txid) {

src/jsonrpc/api.rs

+28-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
str::FromStr,
1111
};
1212

13-
use miniscript::bitcoin::{self, psbt::Psbt};
13+
use miniscript::bitcoin::{self, psbt::Psbt, Txid};
1414

1515
fn create_spend(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
1616
let destinations = params
@@ -226,6 +226,32 @@ fn list_confirmed(control: &DaemonControl, params: Params) -> Result<serde_json:
226226
))
227227
}
228228

229+
fn list_spendtxs(
230+
control: &DaemonControl,
231+
params: Option<Params>,
232+
) -> Result<serde_json::Value, Error> {
233+
let txids: Option<Vec<bitcoin::Txid>> = if let Some(p) = params {
234+
let tx_ids = p.get(0, "txids");
235+
if let Some(ids) = tx_ids {
236+
let ids: Vec<Txid> = ids
237+
.as_array()
238+
.and_then(|arr| {
239+
arr.iter()
240+
.map(|entry| entry.as_str().and_then(|e| bitcoin::Txid::from_str(e).ok()))
241+
.collect()
242+
})
243+
.ok_or_else(|| Error::invalid_params("Invalid 'txids' parameter."))?;
244+
Some(ids)
245+
} else {
246+
None
247+
}
248+
} else {
249+
None
250+
};
251+
252+
Ok(serde_json::json!(&control.list_spend(txids)?))
253+
}
254+
229255
fn list_transactions(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
230256
let txids: Vec<bitcoin::Txid> = params
231257
.get(0, "txids")
@@ -391,7 +417,7 @@ pub fn handle_request(control: &DaemonControl, req: Request) -> Result<Response,
391417
})?;
392418
list_confirmed(control, params)?
393419
}
394-
"listspendtxs" => serde_json::json!(&control.list_spend()),
420+
"listspendtxs" => list_spendtxs(control, req.params)?,
395421
"listtransactions" => {
396422
let params = req.params.ok_or_else(|| {
397423
Error::invalid_params(

src/jsonrpc/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ impl From<commands::CommandError> for Error {
165165
| commands::CommandError::AlreadyRescanning
166166
| commands::CommandError::InvalidDerivationIndex
167167
| commands::CommandError::RbfError(..)
168+
| commands::CommandError::EmptyFilterList
168169
| commands::CommandError::RecoveryNotAvailable => {
169170
Error::new(ErrorCode::InvalidParams, e.to_string())
170171
}

tests/test_rpc.py

+31-4
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
def test_getinfo(lianad):
2424
res = lianad.rpc.getinfo()
25-
assert 'timestamp' in res.keys()
25+
assert "timestamp" in res.keys()
2626
assert res["version"] == "4.0.0-dev"
2727
assert res["network"] == "regtest"
2828
wait_for(lambda: lianad.rpc.getinfo()["block_height"] == 101)
@@ -458,6 +458,35 @@ def test_list_spend(lianad, bitcoind):
458458
lianad.rpc.updatespend(res["psbt"])
459459
lianad.rpc.updatespend(res_b["psbt"])
460460

461+
# Check 'txids' parameter
462+
list_res = lianad.rpc.listspendtxs()["spend_txs"]
463+
464+
txid = PSBT.from_base64(list_res[0]["psbt"]).tx.txid().hex()
465+
466+
filtered_res = lianad.rpc.listspendtxs(txids=[txid])
467+
assert filtered_res["spend_txs"][0]["psbt"] == list_res[0]["psbt"]
468+
assert len(filtered_res) == 1
469+
470+
with pytest.raises(
471+
RpcError, match="Filter list is empty, should supply None instead."
472+
):
473+
lianad.rpc.listspendtxs(txids=[])
474+
475+
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
476+
lianad.rpc.listspendtxs(txids=[txid, 123])
477+
478+
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
479+
lianad.rpc.listspendtxs(txids=[0])
480+
481+
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
482+
lianad.rpc.listspendtxs(txids=[123])
483+
484+
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
485+
lianad.rpc.listspendtxs(txids=["abc"])
486+
487+
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
488+
lianad.rpc.listspendtxs(txids=["123"])
489+
461490
# Listing all Spend transactions will list them both. It'll tell us which one has
462491
# change and which one doesn't.
463492
list_res = lianad.rpc.listspendtxs()["spend_txs"]
@@ -1232,9 +1261,7 @@ def test_rbfpsbt_cancel(lianad, bitcoind):
12321261
# But we can't set the feerate explicitly.
12331262
with pytest.raises(
12341263
RpcError,
1235-
match=re.escape(
1236-
"A feerate must not be provided if creating a cancel."
1237-
),
1264+
match=re.escape("A feerate must not be provided if creating a cancel."),
12381265
):
12391266
rbf_1_res = lianad.rpc.rbfpsbt(first_txid, True, 2)
12401267
rbf_1_psbt = PSBT.from_base64(rbf_1_res["psbt"])

0 commit comments

Comments
 (0)