From 5589a32d3a4588b3ae1f70a22e165cbf19dc6623 Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Mon, 11 Nov 2024 21:36:31 +0100 Subject: [PATCH 1/7] listtransactions --- node/src/bin/space-cli.rs | 15 +++++++++ node/src/rpc.rs | 24 ++++++++++++++- node/src/wallets.rs | 64 ++++++++++++++++++++++++++++++++++----- wallet/src/lib.rs | 2 +- 4 files changed, 96 insertions(+), 9 deletions(-) diff --git a/node/src/bin/space-cli.rs b/node/src/bin/space-cli.rs index 8eaf52d..8042989 100644 --- a/node/src/bin/space-cli.rs +++ b/node/src/bin/space-cli.rs @@ -207,6 +207,14 @@ enum Commands { #[arg(long, short)] fee_rate: Option, }, + /// List last transactions + #[command(name = "listtransactions")] + ListTransactions { + #[arg(default_value = "1")] + count: usize, + #[arg(default_value = "0")] + skip: usize, + }, /// List won spaces including ones /// still in auction with a winning bid #[command(name = "listspaces")] @@ -574,6 +582,13 @@ async fn handle_commands( let spaces = cli.client.wallet_list_auction_outputs(&cli.wallet).await?; println!("{}", serde_json::to_string_pretty(&spaces)?); } + Commands::ListTransactions { count, skip } => { + let txs = cli + .client + .wallet_list_transactions(&cli.wallet, count, skip) + .await?; + println!("{}", serde_json::to_string_pretty(&txs)?); + } Commands::ListSpaces => { let spaces = cli.client.wallet_list_spaces(&cli.wallet).await?; println!("{}", serde_json::to_string_pretty(&spaces)?); diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 1a22e92..d7cb7f4 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -44,7 +44,8 @@ use crate::{ source::BitcoinRpc, store::{ChainState, LiveSnapshot}, wallets::{ - AddressKind, Balance, RpcWallet, TxResponse, WalletCommand, WalletOutput, WalletResponse, + AddressKind, Balance, RpcWallet, TxInfo, TxResponse, WalletCommand, WalletOutput, + WalletResponse, }, }; @@ -163,6 +164,14 @@ pub trait Rpc { fee_rate: FeeRate, ) -> Result, ErrorObjectOwned>; + #[method(name = "walletlisttransactions")] + async fn wallet_list_transactions( + &self, + wallet: &str, + count: usize, + skip: usize, + ) -> Result, ErrorObjectOwned>; + #[method(name = "walletlistspaces")] async fn wallet_list_spaces(&self, wallet: &str) -> Result, ErrorObjectOwned>; @@ -715,6 +724,19 @@ impl RpcServer for RpcServerImpl { .map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::)) } + async fn wallet_list_transactions( + &self, + wallet: &str, + count: usize, + skip: usize, + ) -> Result, ErrorObjectOwned> { + self.wallet(&wallet) + .await? + .send_list_transactions(count, skip) + .await + .map_err(|error| ErrorObjectOwned::owned(-1, error.to_string(), None::)) + } + async fn wallet_list_spaces( &self, wallet: &str, diff --git a/node/src/wallets.rs b/node/src/wallets.rs index 0dbf5a2..bd77ebe 100644 --- a/node/src/wallets.rs +++ b/node/src/wallets.rs @@ -20,13 +20,12 @@ use tokio::{ }; use wallet::{ address::SpaceAddress, - bdk_wallet, bdk_wallet::{ + self, chain::{local_chain::CheckPoint, BlockId}, KeychainKind, LocalOutput, }, - bitcoin, - bitcoin::{Address, Amount, FeeRate}, + bitcoin::{self, Address, Amount, FeeRate}, builder::{ CoinTransfer, SpaceTransfer, SpacesAwareCoinSelection, TransactionTag, TransferRequest, }, @@ -51,6 +50,12 @@ pub struct TxResponse { pub error: Option>, } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TxInfo { + pub txid: Txid, + pub confirmed: bool, +} + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WalletResponse { pub sent: Vec, @@ -83,6 +88,11 @@ pub enum WalletCommand { fee_rate: FeeRate, resp: crate::rpc::Responder>>, }, + ListTransactions { + count: usize, + skip: usize, + resp: crate::rpc::Responder>>, + }, ListSpaces { resp: crate::rpc::Responder>>, }, @@ -154,8 +164,10 @@ impl RpcWallet { balance, dust: unspent .into_iter() - .filter(|output| output.is_spaceout || - output.output.txout.value <= SpacesAwareCoinSelection::DUST_THRESHOLD) + .filter(|output| { + output.is_spaceout + || output.output.txout.value <= SpacesAwareCoinSelection::DUST_THRESHOLD + }) .map(|output| output.output.txout.value) .sum(), }; @@ -224,6 +236,10 @@ impl RpcWallet { WalletCommand::ListUnspent { resp } => { _ = resp.send(Self::list_unspent(wallet, state)); } + WalletCommand::ListTransactions { count, skip, resp } => { + let transactions = Self::list_transactions(wallet, count, skip); + _ = resp.send(transactions); + } WalletCommand::ListSpaces { resp } => { let result = Self::list_unspent(wallet, state); match result { @@ -375,6 +391,26 @@ impl RpcWallet { Ok(SpacesAwareCoinSelection::new(excluded)) } + fn list_transactions( + wallet: &mut SpacesWallet, + count: usize, + skip: usize, + ) -> anyhow::Result> { + let transactions = wallet + .spaces + .transactions() + .into_iter() + .skip(skip) + .take(count) + .map(|tx| { + let txid = tx.tx_node.txid.clone(); + let confirmed = tx.chain_position.is_confirmed(); + TxInfo { txid, confirmed } + }) + .collect(); + Ok(transactions) + } + fn list_unspent( wallet: &mut SpacesWallet, store: &mut LiveSnapshot, @@ -446,8 +482,10 @@ impl RpcWallet { if dust > SpacesAwareCoinSelection::DUST_THRESHOLD { // Allowing higher dust may space outs to be accidentally // spent during coin selection - return Err(anyhow!("dust cannot be higher than {}", - SpacesAwareCoinSelection::DUST_THRESHOLD)); + return Err(anyhow!( + "dust cannot be higher than {}", + SpacesAwareCoinSelection::DUST_THRESHOLD + )); } } @@ -789,6 +827,18 @@ impl RpcWallet { resp_rx.await? } + pub async fn send_list_transactions( + &self, + count: usize, + skip: usize, + ) -> anyhow::Result> { + let (resp, resp_rx) = oneshot::channel(); + self.sender + .send(WalletCommand::ListTransactions { count, skip, resp }) + .await?; + resp_rx.await? + } + pub async fn send_list_spaces(&self) -> anyhow::Result> { let (resp, resp_rx) = oneshot::channel(); self.sender.send(WalletCommand::ListSpaces { resp }).await?; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 36f3880..4902952 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -153,7 +153,7 @@ impl SpacesWallet { } pub fn get_info(&self) -> WalletInfo { - let mut descriptors = Vec::with_capacity(4); + let mut descriptors = Vec::with_capacity(2); descriptors.push(DescriptorInfo { descriptor: self From 063a956185e014350149b83c3c6994d43e6291a3 Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Mon, 11 Nov 2024 22:23:44 +0100 Subject: [PATCH 2/7] fix json reason format --- protocol/src/lib.rs | 9 +++------ protocol/src/validate.rs | 6 +++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/protocol/src/lib.rs b/protocol/src/lib.rs index 02265a5..0f83bcf 100644 --- a/protocol/src/lib.rs +++ b/protocol/src/lib.rs @@ -115,10 +115,7 @@ pub enum Covenant { #[derive(Copy, Clone, PartialEq, Debug)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "bincode", derive(Encode, Decode))] -#[cfg_attr( - feature = "serde", - serde(rename_all = "snake_case", tag = "revoke_reason") -)] +#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))] pub enum RevokeReason { BidPsbt(BidPsbtReason), /// Space was prematurely spent during the auctions phase @@ -138,14 +135,14 @@ pub enum RevokeReason { #[cfg_attr(feature = "bincode", derive(Encode, Decode))] pub enum RejectReason { AlreadyExists, - BidPSBT(BidPsbtReason), + BidPsbt(BidPsbtReason), } #[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde(rename_all = "snake_case", tag = "bid_psbt_reason") + serde(rename_all = "snake_case") )] #[cfg_attr(feature = "bincode", derive(Encode, Decode))] pub enum BidPsbtReason { diff --git a/protocol/src/validate.rs b/protocol/src/validate.rs index 8232728..7af94e6 100644 --- a/protocol/src/validate.rs +++ b/protocol/src/validate.rs @@ -338,7 +338,7 @@ impl Validator { None => { let reject = ScriptError::Reject(RejectParams { name, - reason: RejectReason::BidPSBT(BidPsbtReason::Required), + reason: RejectReason::BidPsbt(BidPsbtReason::Required), }); changeset.spends[spend_index].script_error = Some(reject); @@ -350,7 +350,7 @@ impl Validator { if auctiond.output.is_none() { let reject = ScriptError::Reject(RejectParams { name, - reason: RejectReason::BidPSBT(BidPsbtReason::OutputSpent), + reason: RejectReason::BidPsbt(BidPsbtReason::OutputSpent), }); changeset.spends[spend_index].script_error = Some(reject); return; @@ -379,7 +379,7 @@ impl Validator { if !fullspaceout.verify_bid_sig() { let reject = ScriptError::Reject(RejectParams { name: fullspaceout.spaceout.space.unwrap().name, - reason: RejectReason::BidPSBT(BidPsbtReason::BadSignature), + reason: RejectReason::BidPsbt(BidPsbtReason::BadSignature), }); changeset.spends[spend_index].script_error = Some(reject); return; From 2518ab96f0d8c40cd03808d080304867c35a72be Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Mon, 11 Nov 2024 23:39:39 +0100 Subject: [PATCH 3/7] more fields for TxInfo --- node/src/bin/space-cli.rs | 2 +- node/src/wallets.rs | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/node/src/bin/space-cli.rs b/node/src/bin/space-cli.rs index 8042989..fe3ffb3 100644 --- a/node/src/bin/space-cli.rs +++ b/node/src/bin/space-cli.rs @@ -210,7 +210,7 @@ enum Commands { /// List last transactions #[command(name = "listtransactions")] ListTransactions { - #[arg(default_value = "1")] + #[arg(default_value = "10")] count: usize, #[arg(default_value = "0")] skip: usize, diff --git a/node/src/wallets.rs b/node/src/wallets.rs index bd77ebe..ecc3436 100644 --- a/node/src/wallets.rs +++ b/node/src/wallets.rs @@ -54,6 +54,9 @@ pub struct TxResponse { pub struct TxInfo { pub txid: Txid, pub confirmed: bool, + pub sent: Amount, + pub received: Amount, + pub fee: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -402,10 +405,19 @@ impl RpcWallet { .into_iter() .skip(skip) .take(count) - .map(|tx| { - let txid = tx.tx_node.txid.clone(); - let confirmed = tx.chain_position.is_confirmed(); - TxInfo { txid, confirmed } + .map(|ctx| { + let tx = ctx.tx_node.tx; + let txid = ctx.tx_node.txid.clone(); + let confirmed = ctx.chain_position.is_confirmed(); + let (sent, received) = wallet.spaces.sent_and_received(&tx); + let fee = wallet.spaces.calculate_fee(&tx).ok(); + TxInfo { + txid, + confirmed, + sent, + received, + fee, + } }) .collect(); Ok(transactions) From b82188ffe3f4131b19579d12ecd1f34576b734d0 Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Tue, 12 Nov 2024 01:13:34 +0100 Subject: [PATCH 4/7] reverse the order --- node/src/wallets.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/node/src/wallets.rs b/node/src/wallets.rs index ecc3436..679023c 100644 --- a/node/src/wallets.rs +++ b/node/src/wallets.rs @@ -399,14 +399,17 @@ impl RpcWallet { count: usize, skip: usize, ) -> anyhow::Result> { - let transactions = wallet - .spaces - .transactions() - .into_iter() - .skip(skip) - .take(count) + let transactions = wallet.spaces.transactions().into_iter().collect::>(); + + let total = transactions.len(); + let start = total.saturating_sub(skip + count); + let end = total.saturating_sub(skip); + + let transactions = transactions[start..end] + .iter() + .rev() .map(|ctx| { - let tx = ctx.tx_node.tx; + let tx = &ctx.tx_node.tx; let txid = ctx.tx_node.txid.clone(); let confirmed = ctx.chain_position.is_confirmed(); let (sent, received) = wallet.spaces.sent_and_received(&tx); From f2eb07e4d83350ed1216febc53f92a98c92394f1 Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Tue, 12 Nov 2024 17:49:27 +0100 Subject: [PATCH 5/7] listtransactions sort --- node/src/wallets.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/node/src/wallets.rs b/node/src/wallets.rs index 679023c..7422baf 100644 --- a/node/src/wallets.rs +++ b/node/src/wallets.rs @@ -399,17 +399,16 @@ impl RpcWallet { count: usize, skip: usize, ) -> anyhow::Result> { - let transactions = wallet.spaces.transactions().into_iter().collect::>(); + let mut transactions: Vec<_> = wallet.spaces.transactions().collect(); + transactions.sort(); - let total = transactions.len(); - let start = total.saturating_sub(skip + count); - let end = total.saturating_sub(skip); - - let transactions = transactions[start..end] + Ok(transactions .iter() .rev() + .skip(skip) + .take(count) .map(|ctx| { - let tx = &ctx.tx_node.tx; + let tx = ctx.tx_node.tx.clone(); let txid = ctx.tx_node.txid.clone(); let confirmed = ctx.chain_position.is_confirmed(); let (sent, received) = wallet.spaces.sent_and_received(&tx); @@ -422,8 +421,7 @@ impl RpcWallet { fee, } }) - .collect(); - Ok(transactions) + .collect()) } fn list_unspent( From 0ea6dabbe7643b304379e56af96b114e82b8d199 Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Mon, 18 Nov 2024 20:53:52 +0100 Subject: [PATCH 6/7] styling --- node/src/wallets.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/node/src/wallets.rs b/node/src/wallets.rs index 34aac66..7de8e2f 100644 --- a/node/src/wallets.rs +++ b/node/src/wallets.rs @@ -27,7 +27,7 @@ use wallet::{ wallet::tx_builder::TxOrdering, KeychainKind, LocalOutput, }, - bitcoin::{self,Address, Amount, FeeRate, OutPoint}, + bitcoin::{self, Address, Amount, FeeRate, OutPoint}, builder::{ CoinTransfer, SelectionOutput, SpaceTransfer, SpacesAwareCoinSelection, TransactionTag, TransferRequest, @@ -943,12 +943,6 @@ impl RpcWallet { resp_rx.await? } - pub async fn send_list_spaces(&self) -> anyhow::Result> { - let (resp, resp_rx) = oneshot::channel(); - self.sender.send(WalletCommand::ListSpaces { resp }).await?; - resp_rx.await? - } - pub async fn send_force_spend( &self, outpoint: OutPoint, @@ -965,6 +959,12 @@ impl RpcWallet { resp_rx.await? } + pub async fn send_list_spaces(&self) -> anyhow::Result> { + let (resp, resp_rx) = oneshot::channel(); + self.sender.send(WalletCommand::ListSpaces { resp }).await?; + resp_rx.await? + } + pub async fn send_list_bidouts(&self) -> anyhow::Result> { let (resp, resp_rx) = oneshot::channel(); self.sender From b8dada2e11d510ae2656f7c9c7ad329e02ef80ea Mon Sep 17 00:00:00 2001 From: Alex Tsokurov Date: Mon, 18 Nov 2024 20:59:41 +0100 Subject: [PATCH 7/7] remove self from use --- node/src/wallets.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/node/src/wallets.rs b/node/src/wallets.rs index 7de8e2f..545175f 100644 --- a/node/src/wallets.rs +++ b/node/src/wallets.rs @@ -21,13 +21,14 @@ use tokio::{ }; use wallet::{ address::SpaceAddress, + bdk_wallet, bdk_wallet::{ - self, chain::{local_chain::CheckPoint, BlockId}, wallet::tx_builder::TxOrdering, KeychainKind, LocalOutput, }, - bitcoin::{self, Address, Amount, FeeRate, OutPoint}, + bitcoin, + bitcoin::{Address, Amount, FeeRate, OutPoint}, builder::{ CoinTransfer, SelectionOutput, SpaceTransfer, SpacesAwareCoinSelection, TransactionTag, TransferRequest,