add txids param to listspendtxs

This commit is contained in:
pythcoiner 2024-01-14 12:09:17 +01:00
parent 3fcbb0b67d
commit da1ebce5b6
5 changed files with 91 additions and 15 deletions

View File

@ -218,12 +218,14 @@ This command does not return anything for now.
List stored Spend transactions.
If `txids` is specified, only list transactions whose `txid` is in `txids`(empty list of `txids` is not allowed).
#### Request
This command does not take any parameter for now.
| Field | Type | Description |
| ------------- | ----------------- | ----------------------------------------------------------- |
| Field | Type | Description |
| ------------- | -------------------------- | ------------------------------------ |
| `txids` | array of string (optional) | Ids of the transactions to retrieve |
#### Response

View File

@ -62,6 +62,7 @@ pub enum CommandError {
/// Overflowing or unhardened derivation index.
InvalidDerivationIndex,
RbfError(RbfErrorInfo),
EmptyFilterList,
}
impl fmt::Display for CommandError {
@ -114,6 +115,7 @@ impl fmt::Display for CommandError {
write!(f, "Unhardened or overflowing BIP32 derivation index.")
}
Self::RbfError(e) => write!(f, "RBF error: '{}'.", e),
Self::EmptyFilterList => write!(f, "Filter list is empty, should supply None instead."),
}
}
}
@ -589,14 +591,32 @@ impl DaemonControl {
}
}
pub fn list_spend(&self) -> ListSpendResult {
pub fn list_spend(
&self,
txids: Option<Vec<bitcoin::Txid>>,
) -> Result<ListSpendResult, CommandError> {
if let Some(ids) = &txids {
if ids.is_empty() {
return Err(CommandError::EmptyFilterList);
}
}
let mut db_conn = self.db.connection();
let spend_txs = db_conn
.list_spend()
let spend_psbts = db_conn.list_spend();
let txids_set: Option<HashSet<_>> = txids.as_ref().map(|list| list.iter().collect());
let spend_txs = spend_psbts
.into_iter()
.map(|(psbt, updated_at)| ListSpendEntry { psbt, updated_at })
.filter_map(|(psbt, updated_at)| {
if let Some(set) = &txids_set {
if !set.contains(&psbt.unsigned_tx.txid()) {
return None;
}
}
Some(ListSpendEntry { psbt, updated_at })
})
.collect();
ListSpendResult { spend_txs }
Ok(ListSpendResult { spend_txs })
}
pub fn delete_spend(&self, txid: &bitcoin::Txid) {

View File

@ -10,7 +10,7 @@ use std::{
str::FromStr,
};
use miniscript::bitcoin::{self, psbt::Psbt};
use miniscript::bitcoin::{self, psbt::Psbt, Txid};
fn create_spend(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
let destinations = params
@ -226,6 +226,32 @@ fn list_confirmed(control: &DaemonControl, params: Params) -> Result<serde_json:
))
}
fn list_spendtxs(
control: &DaemonControl,
params: Option<Params>,
) -> Result<serde_json::Value, Error> {
let txids: Option<Vec<bitcoin::Txid>> = if let Some(p) = params {
let tx_ids = p.get(0, "txids");
if let Some(ids) = tx_ids {
let ids: Vec<Txid> = ids
.as_array()
.and_then(|arr| {
arr.iter()
.map(|entry| entry.as_str().and_then(|e| bitcoin::Txid::from_str(e).ok()))
.collect()
})
.ok_or_else(|| Error::invalid_params("Invalid 'txids' parameter."))?;
Some(ids)
} else {
None
}
} else {
None
};
Ok(serde_json::json!(&control.list_spend(txids)?))
}
fn list_transactions(control: &DaemonControl, params: Params) -> Result<serde_json::Value, Error> {
let txids: Vec<bitcoin::Txid> = params
.get(0, "txids")
@ -391,7 +417,7 @@ pub fn handle_request(control: &DaemonControl, req: Request) -> Result<Response,
})?;
list_confirmed(control, params)?
}
"listspendtxs" => serde_json::json!(&control.list_spend()),
"listspendtxs" => list_spendtxs(control, req.params)?,
"listtransactions" => {
let params = req.params.ok_or_else(|| {
Error::invalid_params(

View File

@ -165,6 +165,7 @@ impl From<commands::CommandError> for Error {
| commands::CommandError::AlreadyRescanning
| commands::CommandError::InvalidDerivationIndex
| commands::CommandError::RbfError(..)
| commands::CommandError::EmptyFilterList
| commands::CommandError::RecoveryNotAvailable => {
Error::new(ErrorCode::InvalidParams, e.to_string())
}

View File

@ -22,7 +22,7 @@ from test_framework.utils import (
def test_getinfo(lianad):
res = lianad.rpc.getinfo()
assert 'timestamp' in res.keys()
assert "timestamp" in res.keys()
assert res["version"] == "4.0.0-dev"
assert res["network"] == "regtest"
wait_for(lambda: lianad.rpc.getinfo()["block_height"] == 101)
@ -458,6 +458,35 @@ def test_list_spend(lianad, bitcoind):
lianad.rpc.updatespend(res["psbt"])
lianad.rpc.updatespend(res_b["psbt"])
# Check 'txids' parameter
list_res = lianad.rpc.listspendtxs()["spend_txs"]
txid = PSBT.from_base64(list_res[0]["psbt"]).tx.txid().hex()
filtered_res = lianad.rpc.listspendtxs(txids=[txid])
assert filtered_res["spend_txs"][0]["psbt"] == list_res[0]["psbt"]
assert len(filtered_res) == 1
with pytest.raises(
RpcError, match="Filter list is empty, should supply None instead."
):
lianad.rpc.listspendtxs(txids=[])
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
lianad.rpc.listspendtxs(txids=[txid, 123])
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
lianad.rpc.listspendtxs(txids=[0])
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
lianad.rpc.listspendtxs(txids=[123])
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
lianad.rpc.listspendtxs(txids=["abc"])
with pytest.raises(RpcError, match="Invalid params: Invalid 'txids' parameter."):
lianad.rpc.listspendtxs(txids=["123"])
# Listing all Spend transactions will list them both. It'll tell us which one has
# change and which one doesn't.
list_res = lianad.rpc.listspendtxs()["spend_txs"]
@ -1232,9 +1261,7 @@ def test_rbfpsbt_cancel(lianad, bitcoind):
# But we can't set the feerate explicitly.
with pytest.raises(
RpcError,
match=re.escape(
"A feerate must not be provided if creating a cancel."
),
match=re.escape("A feerate must not be provided if creating a cancel."),
):
rbf_1_res = lianad.rpc.rbfpsbt(first_txid, True, 2)
rbf_1_psbt = PSBT.from_base64(rbf_1_res["psbt"])