Skip to content

Commit 1d90a44

Browse files
committed
Addition of new RPC command for manual cpfp.
Along with tests.
1 parent cd21ab8 commit 1d90a44

File tree

11 files changed

+408
-8
lines changed

11 files changed

+408
-8
lines changed

doc/API.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Note that all addresses are bech32-encoded *version 0* native Segwit `scriptPubK
2626
| [`setspendtx`](#setspendtx) | Announce and broadcast this Spend transaction |
2727
| [`gethistory`](#gethistory) | Retrieve history of funds |
2828
| [`emergency`](#emergency) | Broadcast all Emergency signed transactions |
29+
| [`cpfp`](#cpfp) | Manually trigger the cpfp for transactions. |
2930

3031

3132

@@ -483,6 +484,20 @@ of inflows and outflows net of any change amount (that is technically a transact
483484
None; the `result` field will be set to the empty object `{}`. Any value should be
484485
disregarded for forward compatibility.
485486

487+
### `cpfp`
488+
489+
#### Request
490+
491+
| Field | Type | Description |
492+
| -------------- | ------ | ---------------------------------------------- |
493+
| `txids` | array | Array of Txids that must be CPFPed |
494+
| `feerate` |float | The new target feerate. |
495+
496+
#### Response
497+
498+
None; the `result` field will be set to the empty object `{}`. Any value should be
499+
disregarded for forward compatibility.
500+
486501

487502
## User flows
488503

src/bitcoind/mod.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@ pub mod poller;
33
pub mod utils;
44

55
use crate::config::BitcoindConfig;
6-
use crate::{database::DatabaseError, revaultd::RevaultD, threadmessages::BitcoindMessageOut};
6+
use crate::{
7+
database::{
8+
interface::{db_spend_transaction, db_vault_by_unvault_txid},
9+
DatabaseError,
10+
},
11+
revaultd::RevaultD,
12+
threadmessages::BitcoindMessageOut,
13+
};
714
use interface::{BitcoinD, WalletTransaction};
8-
use poller::poller_main;
15+
use poller::{cpfp_package, poller_main, ToBeCpfped};
916
use revault_tx::bitcoin::{Network, Txid};
1017

1118
use std::{
@@ -187,6 +194,43 @@ fn wallet_transaction(bitcoind: &BitcoinD, txid: Txid) -> Option<WalletTransacti
187194
.ok()
188195
}
189196

197+
fn cpfp(
198+
revaultd: Arc<RwLock<RevaultD>>,
199+
bitcoind: Arc<RwLock<BitcoinD>>,
200+
txids: Vec<Txid>,
201+
feerate: f64,
202+
) -> Result<(), BitcoindError> {
203+
let db_path = revaultd.read().unwrap().db_file();
204+
assert!(revaultd.read().unwrap().is_manager());
205+
206+
let mut cpfp_txs = Vec::with_capacity(txids.len());
207+
208+
for txid in txids.iter() {
209+
let spend_tx = db_spend_transaction(&db_path, &txid).expect("Database must be available");
210+
211+
if let Some(unwrap_spend_tx) = spend_tx {
212+
// If the transaction is of type SpendTransaction
213+
cpfp_txs.push(ToBeCpfped::Spend(unwrap_spend_tx.psbt));
214+
} else {
215+
// The transaction type is asserted to be UnvaultTransaction
216+
let unvault_tx = db_vault_by_unvault_txid(&db_path, &txid)
217+
.expect("Database must be available")
218+
.unwrap()
219+
.1
220+
.psbt
221+
.assert_unvault();
222+
cpfp_txs.push(ToBeCpfped::Unvault(unvault_tx));
223+
}
224+
}
225+
226+
// sats/vbyte -> sats/WU
227+
let sats_wu = feerate / 4.0;
228+
// sats/WU -> msats/WU
229+
let msats_wu = (sats_wu * 1000.0) as u64;
230+
231+
cpfp_package(&revaultd, &bitcoind.read().unwrap(), cpfp_txs, msats_wu)
232+
}
233+
190234
/// The bitcoind event loop.
191235
/// Listens for bitcoind requests (wallet / chain) and poll bitcoind every 30 seconds,
192236
/// updating our state accordingly.
@@ -208,7 +252,8 @@ pub fn bitcoind_main_loop(
208252
let _bitcoind = bitcoind.clone();
209253
let _sync_progress = sync_progress.clone();
210254
let _shutdown = shutdown.clone();
211-
move || poller_main(revaultd, _bitcoind, _sync_progress, _shutdown)
255+
let _revaultd = revaultd.clone();
256+
move || poller_main(_revaultd, _bitcoind, _sync_progress, _shutdown)
212257
});
213258

214259
for msg in rx {
@@ -252,6 +297,16 @@ pub fn bitcoind_main_loop(
252297
))
253298
})?;
254299
}
300+
BitcoindMessageOut::CPFPTransaction(txids, feerate, resp_tx) => {
301+
log::trace!("Received 'cpfptransaction' from main thread");
302+
303+
resp_tx
304+
.send(cpfp(revaultd, bitcoind, txids, feerate))
305+
.map_err(|e| {
306+
BitcoindError::Custom(format!("Sending transaction for CPFP: {}", e))
307+
})?;
308+
return Ok(());
309+
}
255310
}
256311
}
257312

src/bitcoind/poller.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ fn mark_confirmed_emers(
408408
Ok(())
409409
}
410410

411-
enum ToBeCpfped {
411+
pub enum ToBeCpfped {
412412
Spend(SpendTransaction),
413413
Unvault(UnvaultTransaction),
414414
}
@@ -450,7 +450,7 @@ impl ToBeCpfped {
450450
// CPFP a bunch of transactions, bumping their feerate by at least `target_feerate`.
451451
// `target_feerate` is expressed in sat/kWU.
452452
// All the transactions' feerate MUST be below `target_feerate`.
453-
fn cpfp_package(
453+
pub fn cpfp_package(
454454
revaultd: &Arc<RwLock<RevaultD>>,
455455
bitcoind: &BitcoinD,
456456
to_be_cpfped: Vec<ToBeCpfped>,

src/commands/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,23 @@ impl DaemonControl {
14431443
let revaultd = self.revaultd.read().unwrap();
14441444
gethistory(&revaultd, &self.bitcoind_conn, start, end, limit, kind)
14451445
}
1446+
1447+
/// Manually trigger a CPFP for the given transaction ID.
1448+
///
1449+
/// ## Errors
1450+
/// - we don't have access to a CPFP private key
1451+
/// - the caller is not a manager
1452+
pub fn manual_cpfp(&self, txids: &Vec<Txid>, feerate: f64) -> Result<(), CommandError> {
1453+
let revaultd = self.revaultd.read().unwrap();
1454+
1455+
if revaultd.cpfp_key.is_none() {
1456+
return Err(CommandError::MissingCpfpKey);
1457+
}
1458+
manager_only!(revaultd);
1459+
1460+
self.bitcoind_conn.cpfp_tx(txids.to_vec(), feerate)?;
1461+
Ok(())
1462+
}
14461463
}
14471464

14481465
/// Descriptors the daemon was configured with

src/jsonrpc/api.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ pub trait RpcApi {
214214
end: u32,
215215
limit: u64,
216216
) -> jsonrpc_core::Result<serde_json::Value>;
217+
218+
// Manually cpfp the given transaction id.
219+
#[rpc(meta, name = "cpfp")]
220+
fn cpfp(
221+
&self,
222+
meta: Self::Metadata,
223+
txids: Vec<Txid>,
224+
feerate: f64,
225+
) -> jsonrpc_core::Result<serde_json::Value>;
217226
}
218227

219228
macro_rules! parse_vault_status {
@@ -301,6 +310,10 @@ impl RpcApi for RpcImpl {
301310
"emergency": [
302311

303312
],
313+
"cpfp": [
314+
"txids",
315+
"feerate",
316+
],
304317
}))
305318
}
306319

@@ -513,4 +526,16 @@ impl RpcApi for RpcImpl {
513526
"events": events,
514527
}))
515528
}
529+
530+
// manual CPFP command
531+
// feerate will be in sat/vbyte
532+
fn cpfp(
533+
&self,
534+
meta: Self::Metadata,
535+
txids: Vec<Txid>,
536+
feerate: f64,
537+
) -> jsonrpc_core::Result<serde_json::Value> {
538+
meta.daemon_control.manual_cpfp(&txids, feerate)?;
539+
Ok(json!({}))
540+
}
516541
}

src/threadmessages.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub enum BitcoindMessageOut {
4141
Vec<BitcoinTransaction>,
4242
SyncSender<Result<(), BitcoindError>>,
4343
),
44+
CPFPTransaction(Vec<Txid>, f64, SyncSender<Result<(), BitcoindError>>),
4445
}
4546

4647
/// Interface to communicate with bitcoind client thread.
@@ -49,6 +50,7 @@ pub trait BitcoindThread {
4950
fn broadcast(&self, transactions: Vec<BitcoinTransaction>) -> Result<(), BitcoindError>;
5051
fn shutdown(&self);
5152
fn sync_progress(&self) -> f64;
53+
fn cpfp_tx(&self, txids: Vec<Txid>, feerate: f64) -> Result<(), BitcoindError>;
5254
}
5355

5456
/// Interface to the bitcoind thread using synchronous MPSCs
@@ -98,6 +100,18 @@ impl<'a> BitcoindThread for BitcoindSender {
98100

99101
bitrep_rx.recv().expect("Receiving from bitcoind thread")
100102
}
103+
104+
fn cpfp_tx(&self, txids: Vec<Txid>, feerate: f64) -> Result<(), BitcoindError> {
105+
let (bitrep_tx, bitrep_rx) = sync_channel(0);
106+
self.0
107+
.send(BitcoindMessageOut::CPFPTransaction(
108+
txids, feerate, bitrep_tx,
109+
))
110+
.expect("Sending to bitcoind thread");
111+
bitrep_rx.recv().expect("Receiving from bitcoind thread")?;
112+
113+
Ok(())
114+
}
101115
}
102116

103117
impl From<Sender<BitcoindMessageOut>> for BitcoindSender {

src/utils.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,5 +178,8 @@ addr = "127.0.0.1:8332"
178178
fn sync_progress(&self) -> f64 {
179179
1.0
180180
}
181+
fn cpfp_tx(&self, _txid: Vec<Txid>, _feerate: f64) -> Result<(), BitcoindError> {
182+
Ok(())
183+
}
181184
}
182185
}

tests/servers/cosignerd

tests/servers/miradord

0 commit comments

Comments
 (0)