feat(electrum): include option for previous TxOut
s for fee calculation
The previous `TxOut` for transactions received from an external wallet may be optionally added as floating `TxOut`s to `TxGraph` to allow for fee calculation.
This commit is contained in:
parent
c0d7d60a58
commit
b2f3cacce6
@ -20,15 +20,18 @@ pub trait ElectrumExt {
|
||||
///
|
||||
/// - `request`: struct with data required to perform a spk-based blockchain client full scan,
|
||||
/// see [`FullScanRequest`]
|
||||
///
|
||||
/// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
|
||||
/// transactions. `batch_size` specifies the max number of script pubkeys to request for in a
|
||||
/// single batch request.
|
||||
/// - `stop_gap`: the full scan for each keychain stops after a gap of script pubkeys with no
|
||||
/// associated transactions
|
||||
/// - `batch_size`: specifies the max number of script pubkeys to request for in a single batch
|
||||
/// request
|
||||
/// - `fetch_prev_txouts`: specifies whether or not we want previous `TxOut`s for fee
|
||||
/// calculation
|
||||
fn full_scan<K: Ord + Clone>(
|
||||
&self,
|
||||
request: FullScanRequest<K>,
|
||||
stop_gap: usize,
|
||||
batch_size: usize,
|
||||
fetch_prev_txouts: bool,
|
||||
) -> Result<ElectrumFullScanResult<K>, Error>;
|
||||
|
||||
/// Sync a set of scripts with the blockchain (via an Electrum client) for the data specified
|
||||
@ -36,15 +39,21 @@ pub trait ElectrumExt {
|
||||
///
|
||||
/// - `request`: struct with data required to perform a spk-based blockchain client sync,
|
||||
/// see [`SyncRequest`]
|
||||
///
|
||||
/// `batch_size` specifies the max number of script pubkeys to request for in a single batch
|
||||
/// request.
|
||||
/// - `batch_size`: specifies the max number of script pubkeys to request for in a single batch
|
||||
/// request
|
||||
/// - `fetch_prev_txouts`: specifies whether or not we want previous `TxOut`s for fee
|
||||
/// calculation
|
||||
///
|
||||
/// If the scripts to sync are unknown, such as when restoring or importing a keychain that
|
||||
/// may include scripts that have been used, use [`full_scan`] with the keychain.
|
||||
///
|
||||
/// [`full_scan`]: ElectrumExt::full_scan
|
||||
fn sync(&self, request: SyncRequest, batch_size: usize) -> Result<ElectrumSyncResult, Error>;
|
||||
fn sync(
|
||||
&self,
|
||||
request: SyncRequest,
|
||||
batch_size: usize,
|
||||
fetch_prev_txouts: bool,
|
||||
) -> Result<ElectrumSyncResult, Error>;
|
||||
}
|
||||
|
||||
impl<E: ElectrumApi> ElectrumExt for E {
|
||||
@ -53,6 +62,7 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
||||
mut request: FullScanRequest<K>,
|
||||
stop_gap: usize,
|
||||
batch_size: usize,
|
||||
fetch_prev_txouts: bool,
|
||||
) -> Result<ElectrumFullScanResult<K>, Error> {
|
||||
let mut request_spks = request.spks_by_keychain;
|
||||
|
||||
@ -110,6 +120,11 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
||||
continue; // reorg
|
||||
}
|
||||
|
||||
// Fetch previous `TxOut`s for fee calculation if flag is enabled.
|
||||
if fetch_prev_txouts {
|
||||
fetch_prev_txout(self, &mut request.tx_cache, &mut graph_update)?;
|
||||
}
|
||||
|
||||
let chain_update = tip;
|
||||
|
||||
let keychain_update = request_spks
|
||||
@ -133,14 +148,19 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
||||
Ok(ElectrumFullScanResult(update))
|
||||
}
|
||||
|
||||
fn sync(&self, request: SyncRequest, batch_size: usize) -> Result<ElectrumSyncResult, Error> {
|
||||
fn sync(
|
||||
&self,
|
||||
request: SyncRequest,
|
||||
batch_size: usize,
|
||||
fetch_prev_txouts: bool,
|
||||
) -> Result<ElectrumSyncResult, Error> {
|
||||
let mut tx_cache = request.tx_cache.clone();
|
||||
|
||||
let full_scan_req = FullScanRequest::from_chain_tip(request.chain_tip.clone())
|
||||
.cache_txs(request.tx_cache)
|
||||
.set_spks_for_keychain((), request.spks.enumerate().map(|(i, spk)| (i as u32, spk)));
|
||||
let mut full_scan_res = self
|
||||
.full_scan(full_scan_req, usize::MAX, batch_size)?
|
||||
.full_scan(full_scan_req, usize::MAX, batch_size, false)?
|
||||
.with_confirmation_height_anchor();
|
||||
|
||||
let (tip, _) = construct_update_tip(self, request.chain_tip)?;
|
||||
@ -165,6 +185,11 @@ impl<E: ElectrumApi> ElectrumExt for E {
|
||||
request.outpoints,
|
||||
)?;
|
||||
|
||||
// Fetch previous `TxOut`s for fee calculation if flag is enabled.
|
||||
if fetch_prev_txouts {
|
||||
fetch_prev_txout(self, &mut tx_cache, &mut full_scan_res.graph_update)?;
|
||||
}
|
||||
|
||||
Ok(ElectrumSyncResult(SyncResult {
|
||||
chain_update: full_scan_res.chain_update,
|
||||
graph_update: full_scan_res.graph_update,
|
||||
@ -374,7 +399,7 @@ fn populate_with_outpoints(
|
||||
client: &impl ElectrumApi,
|
||||
cps: &BTreeMap<u32, CheckPoint>,
|
||||
tx_cache: &mut TxCache,
|
||||
tx_graph: &mut TxGraph<ConfirmationHeightAnchor>,
|
||||
graph_update: &mut TxGraph<ConfirmationHeightAnchor>,
|
||||
outpoints: impl IntoIterator<Item = OutPoint>,
|
||||
) -> Result<(), Error> {
|
||||
for outpoint in outpoints {
|
||||
@ -399,18 +424,18 @@ fn populate_with_outpoints(
|
||||
continue;
|
||||
}
|
||||
has_residing = true;
|
||||
if tx_graph.get_tx(res.tx_hash).is_none() {
|
||||
let _ = tx_graph.insert_tx(tx.clone());
|
||||
if graph_update.get_tx(res.tx_hash).is_none() {
|
||||
let _ = graph_update.insert_tx(tx.clone());
|
||||
}
|
||||
} else {
|
||||
if has_spending {
|
||||
continue;
|
||||
}
|
||||
let res_tx = match tx_graph.get_tx(res.tx_hash) {
|
||||
let res_tx = match graph_update.get_tx(res.tx_hash) {
|
||||
Some(tx) => tx,
|
||||
None => {
|
||||
let res_tx = fetch_tx(client, tx_cache, res.tx_hash)?;
|
||||
let _ = tx_graph.insert_tx(Arc::clone(&res_tx));
|
||||
let _ = graph_update.insert_tx(Arc::clone(&res_tx));
|
||||
res_tx
|
||||
}
|
||||
};
|
||||
@ -424,7 +449,7 @@ fn populate_with_outpoints(
|
||||
};
|
||||
|
||||
if let Some(anchor) = determine_tx_anchor(cps, res.height, res.tx_hash) {
|
||||
let _ = tx_graph.insert_anchor(res.tx_hash, anchor);
|
||||
let _ = graph_update.insert_anchor(res.tx_hash, anchor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -484,6 +509,27 @@ fn fetch_tx<C: ElectrumApi>(
|
||||
})
|
||||
}
|
||||
|
||||
// Helper function which fetches the `TxOut`s of our relevant transactions' previous transactions,
|
||||
// which we do not have by default. This data is needed to calculate the transaction fee.
|
||||
fn fetch_prev_txout<C: ElectrumApi>(
|
||||
client: &C,
|
||||
tx_cache: &mut TxCache,
|
||||
graph_update: &mut TxGraph<ConfirmationHeightAnchor>,
|
||||
) -> Result<(), Error> {
|
||||
let full_txs: Vec<Arc<Transaction>> =
|
||||
graph_update.full_txs().map(|tx_node| tx_node.tx).collect();
|
||||
for tx in full_txs {
|
||||
for vin in &tx.input {
|
||||
let outpoint = vin.previous_output;
|
||||
let prev_tx = fetch_tx(client, tx_cache, outpoint.txid)?;
|
||||
for txout in prev_tx.output.clone() {
|
||||
let _ = graph_update.insert_txout(outpoint, txout);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn populate_with_spks<I: Ord + Clone>(
|
||||
client: &impl ElectrumApi,
|
||||
cps: &BTreeMap<u32, CheckPoint>,
|
||||
|
@ -66,6 +66,7 @@ fn scan_detects_confirmed_tx() -> Result<()> {
|
||||
SyncRequest::from_chain_tip(recv_chain.tip())
|
||||
.chain_spks(core::iter::once(spk_to_track)),
|
||||
5,
|
||||
true,
|
||||
)?
|
||||
.with_confirmation_time_height_anchor(&client)?;
|
||||
|
||||
@ -83,6 +84,29 @@ fn scan_detects_confirmed_tx() -> Result<()> {
|
||||
},
|
||||
);
|
||||
|
||||
for tx in recv_graph.graph().full_txs() {
|
||||
// Retrieve the calculated fee from `TxGraph`, which will panic if we do not have the
|
||||
// floating txouts available from the transaction's previous outputs.
|
||||
let fee = recv_graph
|
||||
.graph()
|
||||
.calculate_fee(&tx.tx)
|
||||
.expect("fee must exist");
|
||||
|
||||
// Retrieve the fee in the transaction data from `bitcoind`.
|
||||
let tx_fee = env
|
||||
.bitcoind
|
||||
.client
|
||||
.get_transaction(&tx.txid, None)
|
||||
.expect("Tx must exist")
|
||||
.fee
|
||||
.expect("Fee must exist")
|
||||
.abs()
|
||||
.to_sat() as u64;
|
||||
|
||||
// Check that the calculated fee matches the fee from the transaction data.
|
||||
assert_eq!(fee, tx_fee);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -132,6 +156,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
|
||||
.sync(
|
||||
SyncRequest::from_chain_tip(recv_chain.tip()).chain_spks([spk_to_track.clone()]),
|
||||
5,
|
||||
false,
|
||||
)?
|
||||
.with_confirmation_time_height_anchor(&client)?;
|
||||
|
||||
@ -162,6 +187,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
|
||||
.sync(
|
||||
SyncRequest::from_chain_tip(recv_chain.tip()).chain_spks([spk_to_track.clone()]),
|
||||
5,
|
||||
false,
|
||||
)?
|
||||
.with_confirmation_time_height_anchor(&client)?;
|
||||
|
||||
|
@ -190,7 +190,7 @@ fn main() -> anyhow::Result<()> {
|
||||
};
|
||||
|
||||
let res = client
|
||||
.full_scan::<_>(request, stop_gap, scan_options.batch_size)
|
||||
.full_scan::<_>(request, stop_gap, scan_options.batch_size, false)
|
||||
.context("scanning the blockchain")?
|
||||
.with_confirmation_height_anchor();
|
||||
(
|
||||
@ -311,7 +311,7 @@ fn main() -> anyhow::Result<()> {
|
||||
});
|
||||
|
||||
let res = client
|
||||
.sync(request, scan_options.batch_size)
|
||||
.sync(request, scan_options.batch_size, false)
|
||||
.context("scanning the blockchain")?
|
||||
.with_confirmation_height_anchor();
|
||||
|
||||
|
@ -53,7 +53,7 @@ fn main() -> Result<(), anyhow::Error> {
|
||||
.inspect_spks_for_all_keychains(|_, _, _| std::io::stdout().flush().expect("must flush"));
|
||||
|
||||
let mut update = client
|
||||
.full_scan(request, STOP_GAP, BATCH_SIZE)?
|
||||
.full_scan(request, STOP_GAP, BATCH_SIZE, false)?
|
||||
.with_confirmation_time_height_anchor(&client)?;
|
||||
|
||||
let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
|
||||
|
Loading…
x
Reference in New Issue
Block a user