spend: increase candidate weight to pay for ancestor

This commit is contained in:
jp1ac4 2024-01-18 15:59:50 +00:00
parent 5f0022083d
commit 94ef66c03a
No known key found for this signature in database
GPG Key ID: A7ACD32423568D7B
2 changed files with 78 additions and 6 deletions

View File

@ -9,7 +9,7 @@ use crate::{
database::{Coin, DatabaseConnection, DatabaseInterface},
descriptors,
spend::{
create_spend, AddrInfo, CandidateCoin, CreateSpendRes, SpendCreationError,
create_spend, AddrInfo, AncestorInfo, CandidateCoin, CreateSpendRes, SpendCreationError,
SpendOutputAddress, SpendTxFees, TxGetter,
},
DaemonControl, VERSION,
@ -179,6 +179,7 @@ fn coin_to_candidate(
coin: &Coin,
must_select: bool,
sequence: Option<bitcoin::Sequence>,
ancestor_info: Option<AncestorInfo>,
) -> CandidateCoin {
CandidateCoin {
outpoint: coin.outpoint,
@ -187,6 +188,7 @@ fn coin_to_candidate(
is_change: coin.is_change,
must_select,
sequence,
ancestor_info,
}
}
@ -466,7 +468,10 @@ impl DaemonControl {
.coins(&[CoinStatus::Confirmed], &[])
.into_values()
.map(|c| {
coin_to_candidate(&c, /*must_select=*/ false, /*sequence=*/ None)
coin_to_candidate(
&c, /*must_select=*/ false, /*sequence=*/ None,
/*ancestor_info=*/ None,
)
})
.collect()
} else {
@ -483,7 +488,12 @@ impl DaemonControl {
}
coins
.into_values()
.map(|c| coin_to_candidate(&c, /*must_select=*/ true, /*sequence=*/ None))
.map(|c| {
coin_to_candidate(
&c, /*must_select=*/ true, /*sequence=*/ None,
/*ancestor_info=*/ None,
)
})
.collect()
};
@ -795,7 +805,10 @@ impl DaemonControl {
let mut candidate_coins: Vec<CandidateCoin> = prev_coins
.values()
.map(|c| {
coin_to_candidate(c, /*must_select=*/ !is_cancel, /*sequence=*/ None)
coin_to_candidate(
c, /*must_select=*/ !is_cancel, /*sequence=*/ None,
/*ancestor_info=*/ None,
)
})
.collect();
let confirmed_cands: Vec<CandidateCoin> = db_conn
@ -807,6 +820,7 @@ impl DaemonControl {
if !prev_coins.contains_key(&c.outpoint) {
Some(coin_to_candidate(
&c, /*must_select=*/ false, /*sequence=*/ None,
/*ancestor_info=*/ None,
))
} else {
None
@ -996,6 +1010,7 @@ impl DaemonControl {
&c,
/*must_select=*/ true,
/*sequence=*/ Some(bitcoin::Sequence::from_height(timelock)),
/*ancestor_info=*/ None,
))
} else {
None

View File

@ -1,4 +1,4 @@
use crate::descriptors;
use crate::{bitcoin::MempoolEntry, descriptors};
use std::{collections::BTreeMap, convert::TryInto, fmt};
@ -11,6 +11,7 @@ use miniscript::bitcoin::{
self,
absolute::{Height, LockTime},
bip32,
constants::WITNESS_SCALE_FACTOR,
psbt::{Input as PsbtIn, Output as PsbtOut, Psbt},
secp256k1,
};
@ -154,6 +155,29 @@ fn sanity_check_psbt(
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AncestorInfo {
pub vsize: u32,
pub fee: u32,
}
impl From<MempoolEntry> for AncestorInfo {
fn from(entry: MempoolEntry) -> Self {
Self {
vsize: entry
.ancestor_vsize
.try_into()
.expect("vsize must fit in a u32"),
fee: entry
.fees
.ancestor
.to_sat()
.try_into()
.expect("fee in sats should fit in a u32"),
}
}
}
/// A candidate for coin selection when creating a transaction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CandidateCoin {
@ -169,6 +193,8 @@ pub struct CandidateCoin {
pub must_select: bool,
/// The nSequence field to set for an input spending this coin.
pub sequence: Option<bitcoin::Sequence>,
/// Information about in-mempool ancestors of the coin.
pub ancestor_info: Option<AncestorInfo>,
}
/// A coin selection result.
@ -291,12 +317,43 @@ fn select_coins_for_spend(
base_weight = base_weight.saturating_sub(2);
}
let max_input_weight = TXIN_BASE_WEIGHT + max_sat_weight;
// Get feerate as u32 for calculation relating to ancestor below.
// We expect `feerate_vb` to be a positive integer, but take ceil()
// just in case to be sure we pay enough for ancestors.
let feerate_vb_u32 = feerate_vb.ceil() as u32;
let witness_factor: u32 = WITNESS_SCALE_FACTOR
.try_into()
.expect("scale factor must fit in u32");
let candidates: Vec<Candidate> = candidate_coins
.iter()
.map(|cand| Candidate {
input_count: 1,
value: cand.amount.to_sat(),
weight: max_input_weight,
weight: {
let extra = cand
.ancestor_info
.map(|info| {
// The implied ancestor vsize if the fee had been paid at our target feerate.
let ancestor_vsize_at_feerate: u32 = info
.fee
.checked_div(feerate_vb_u32)
.expect("feerate is greater than zero");
// If the actual ancestor vsize is bigger than the implied vsize, we will need to
// pay the difference in order for the combined feerate to be at the target value.
// We multiply the vsize by 4 to get the ancestor weight, which is an upper bound
// on its true weight (vsize*4 - 3 <= weight <= vsize*4), to ensure we pay enough.
// Note that if candidates share ancestors, we may add this difference more than
// once in the resulting transaction.
info.vsize
.saturating_sub(ancestor_vsize_at_feerate)
.checked_mul(witness_factor)
.expect("weight difference must fit in u32")
})
.unwrap_or(0);
max_input_weight
.checked_add(extra)
.expect("effective weight must fit in u32")
},
is_segwit: true, // We only support receiving on Segwit scripts.
})
.collect();