Skip to content

Commit

Permalink
feat: added bumpfee rpc
Browse files Browse the repository at this point in the history
  • Loading branch information
sander2 committed Jun 10, 2022
1 parent 657eebd commit e4a5f71
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 0 deletions.
13 changes: 13 additions & 0 deletions client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ pub trait RpcApi: Sized {
self.call("addmultisigaddress", handle_defaults(&mut args, &[into_json("")?, null()]))
}

fn bump_fee(
&self,
txid: &bitcoin::Txid,
options: Option<&json::BumpFeeOptions>,
) -> Result<json::BumpFeeResult> {
let opts = match options {
Some(options) => Some(options.to_serializable(self.version()?)),
None => None,
};
let mut args = [into_json(txid)?, opt_into_json(opts)?];
self.call("bumpfee", handle_defaults(&mut args, &[null()]))
}

fn load_wallet(&self, wallet: &str) -> Result<json::LoadWalletResult> {
self.call("loadwallet", &[wallet.into()])
}
Expand Down
22 changes: 22 additions & 0 deletions integration_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ fn main() {
test_key_pool_refill(&cl);
test_create_raw_transaction(&cl);
test_fund_raw_transaction(&cl);
test_bump_fee(&cl);
test_test_mempool_accept(&cl);
test_wallet_create_funded_psbt(&cl);
test_wallet_process_psbt(&cl);
Expand Down Expand Up @@ -676,6 +677,27 @@ fn test_fund_raw_transaction(cl: &Client) {
let _ = funded.transaction().unwrap();
}

fn test_bump_fee(cl: &Client) {
let addr = cl.get_new_address(None, None).unwrap();
let txid = cl.send_to_address(&addr, btc(1), None, None, None, Some(true), None, None).unwrap();

// bump without explicit fee rate
let bump_fee_result_1 = cl.bump_fee(&txid, None).unwrap();
assert!(bump_fee_result_1.origfee < bump_fee_result_1.fee);

// bump with explicit fee rate
let amount_per_vbyte = Amount::from_sat(500);
let new_fee_rate = json::FeeRate::new(amount_per_vbyte);
let options = json::BumpFeeOptions {
fee_rate: Some(new_fee_rate),
replaceable: Some(true),
..Default::default()
};
let bump_fee_result_2 = cl.bump_fee(&bump_fee_result_1.txid.unwrap(), Some(&options)).unwrap();
let vbytes = cl.get_mempool_entry(&bump_fee_result_2.txid.unwrap()).unwrap().vsize;
assert_eq!(bump_fee_result_2.fee, amount_per_vbyte * vbytes);
}

fn test_test_mempool_accept(cl: &Client) {
let options = json::ListUnspentQueryOptions {
minimum_amount: Some(btc(2)),
Expand Down
78 changes: 78 additions & 0 deletions json/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,84 @@ pub struct FundRawTransactionResult {
pub change_position: i32,
}

#[derive(Clone, PartialEq, Eq, Debug, Default)]
pub struct BumpFeeOptions {
pub conf_target: Option<u16>,
/// Specify a fee rate instead of relying on the built-in fee estimator.
pub fee_rate: Option<FeeRate>,
/// Whether this transaction could be replaced due to BIP125 (replace-by-fee)
pub replaceable: Option<bool>,
/// The fee estimate mode
pub estimate_mode: Option<EstimateMode>,
}

impl BumpFeeOptions {
pub fn to_serializable(&self, version: usize) -> SerializableBumpFeeOptions {
let fee_rate = self.fee_rate.map(|x| {
if version < 210000 {
x.btc_per_kvbyte()
} else {
x.sat_per_vbyte()
}
});

SerializableBumpFeeOptions {
fee_rate,
conf_target: self.conf_target,
replaceable: self.replaceable,
estimate_mode: self.estimate_mode,
}
}
}

#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct FeeRate(Amount);

impl FeeRate {
pub fn new(amount_per_vbyte: Amount) -> Self {
Self(amount_per_vbyte)
}
pub fn sat_per_vbyte(&self) -> f64 {
// multiply by the number of decimals to get sat
self.0.as_sat() as f64
}
pub fn btc_per_kvbyte(&self) -> f64 {
// divide by 10^8 to get btc/vbyte, then multiply by 10^3 to get btc/kbyte
self.0.as_sat() as f64 / 100_000.0
}
}

#[derive(Serialize, Clone, PartialEq, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct SerializableBumpFeeOptions {
#[serde(rename = "conf_target", skip_serializing_if = "Option::is_none")]
pub conf_target: Option<u16>,
/// Specify a fee rate instead of relying on the built-in fee estimator.
#[serde(rename = "fee_rate")]
pub fee_rate: Option<f64>,
/// Whether this transaction could be replaced due to BIP125 (replace-by-fee)
#[serde(skip_serializing_if = "Option::is_none")]
pub replaceable: Option<bool>,
/// The fee estimate mode
#[serde(rename = "estimate_mode", skip_serializing_if = "Option::is_none")]
pub estimate_mode: Option<EstimateMode>,
}

#[derive(Deserialize, Clone, PartialEq, Eq, Debug)]
#[serde(rename_all = "camelCase")]
pub struct BumpFeeResult {
/// The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled.
pub psbt: Option<String>,
/// The id of the new transaction. Only returned when wallet private keys are enabled.
pub txid: Option<bitcoin::Txid>,
#[serde(with = "bitcoin::util::amount::serde::as_btc")]
pub origfee: Amount,
#[serde(with = "bitcoin::util::amount::serde::as_btc")]
pub fee: Amount,
/// Errors encountered during processing.
pub errors: Vec<String>,
}

#[derive(Deserialize, Clone, PartialEq, Eq, Debug)]
pub struct GetBalancesResultEntry {
#[serde(with = "bitcoin::util::amount::serde::as_btc")]
Expand Down

0 comments on commit e4a5f71

Please sign in to comment.