From 9273bc4b5fcd5a3d681c0096a5ff3b8553d2a908 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Wed, 31 Jan 2024 23:41:42 +0800 Subject: [PATCH] feat(esplora): include previous `TxOut`s for fee calculation The previous `TxOut` for transactions received from an external wallet are added as floating `TxOut`s to `TxGraph` to allow for fee calculation. --- crates/esplora/src/async_ext.rs | 28 ++++++++++++++++++++++++++-- crates/esplora/src/blocking_ext.rs | 26 +++++++++++++++++++++++++- crates/esplora/tests/async_ext.rs | 13 +++++++++++++ crates/esplora/tests/blocking_ext.rs | 13 +++++++++++++ 4 files changed, 77 insertions(+), 3 deletions(-) diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index 8e697a2a9a..df435606fa 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -1,8 +1,8 @@ use async_trait::async_trait; use bdk_chain::collections::btree_map; use bdk_chain::{ - bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid}, - collections::BTreeMap, + bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid}, + collections::{BTreeMap, BTreeSet}, local_chain::{self, CheckPoint}, BlockId, ConfirmationTimeHeightAnchor, TxGraph, }; @@ -204,6 +204,30 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { if let Some(anchor) = anchor_from_status(&tx.status) { let _ = graph.insert_anchor(tx.txid, anchor); } + + let previous_outputs: Vec<(OutPoint, TxOut)> = tx + .vin + .iter() + .cloned() + .filter_map(|vin| { + vin.prevout.map(|po| { + ( + OutPoint { + txid: vin.txid, + vout: vin.vout, + }, + TxOut { + script_pubkey: po.scriptpubkey, + value: po.value, + }, + ) + }) + }) + .collect(); + + for (outpoint, txout) in previous_outputs { + let _ = graph.insert_txout(outpoint, txout); + } } } diff --git a/crates/esplora/src/blocking_ext.rs b/crates/esplora/src/blocking_ext.rs index a7ee290b00..c8bf43faec 100644 --- a/crates/esplora/src/blocking_ext.rs +++ b/crates/esplora/src/blocking_ext.rs @@ -3,7 +3,7 @@ use std::thread::JoinHandle; use bdk_chain::collections::btree_map; use bdk_chain::collections::BTreeMap; use bdk_chain::{ - bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid}, + bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid}, local_chain::{self, CheckPoint}, BlockId, ConfirmationTimeHeightAnchor, TxGraph, }; @@ -194,6 +194,30 @@ impl EsploraExt for esplora_client::BlockingClient { if let Some(anchor) = anchor_from_status(&tx.status) { let _ = graph.insert_anchor(tx.txid, anchor); } + + let previous_outputs: Vec<(OutPoint, TxOut)> = tx + .vin + .iter() + .cloned() + .filter_map(|vin| { + vin.prevout.map(|po| { + ( + OutPoint { + txid: vin.txid, + vout: vin.vout, + }, + TxOut { + script_pubkey: po.scriptpubkey, + value: po.value, + }, + ) + }) + }) + .collect(); + + for (outpoint, txout) in previous_outputs { + let _ = graph.insert_txout(outpoint, txout); + } } } diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index 3124bd2d11..d6a4785d93 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -109,6 +109,19 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { ) .await?; + // Check to see if we have the floating txouts available from our two created transactions' + // previous outputs in order to calculate transaction fees. + let fee_calculations = graph_update + .full_txs() + .map(|tx| graph_update.calculate_fee(tx.tx).is_ok()) + .collect::>(); + + // Ensure there are exactly two fee calculations. + assert_eq!(fee_calculations.len(), 2); + + // Ensure that both fee calculations are successful. + assert!(fee_calculations.iter().all(|&is_ok| is_ok)); + let mut graph_update_txids: Vec = graph_update.full_txs().map(|tx| tx.txid).collect(); graph_update_txids.sort(); let mut expected_txids = vec![txid1, txid2]; diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs index b91231d1d1..b3ef1cc6bf 100644 --- a/crates/esplora/tests/blocking_ext.rs +++ b/crates/esplora/tests/blocking_ext.rs @@ -136,6 +136,19 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> { 1, )?; + // Check to see if we have the floating txouts available from our two created transactions' + // previous outputs in order to calculate transaction fees. + let fee_calculations = graph_update + .full_txs() + .map(|tx| graph_update.calculate_fee(tx.tx).is_ok()) + .collect::>(); + + // Ensure there are exactly two fee calculations. + assert_eq!(fee_calculations.len(), 2); + + // Ensure that both fee calculations are successful. + assert!(fee_calculations.iter().all(|&is_ok| is_ok)); + let mut graph_update_txids: Vec = graph_update.full_txs().map(|tx| tx.txid).collect(); graph_update_txids.sort(); let mut expected_txids = vec![txid1, txid2];