Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional tests #420

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions doc/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Note that all addresses are bech32-encoded *version 0* native Segwit `scriptPubK
| [`setspendtx`](#setspendtx) | Announce and broadcast this Spend transaction |
| [`gethistory`](#gethistory) | Retrieve history of funds |
| [`emergency`](#emergency) | Broadcast all Emergency signed transactions |
| [`cpfp`](#cpfp) | Manually trigger the cpfp for transactions. |



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

### `cpfp`

#### Request

| Field | Type | Description |
| -------------- | ------ | ---------------------------------------------- |
| `txids` | array | Array of Txids that must be CPFPed |
| `feerate` |float | The new target feerate. |

#### Response

None; the `result` field will be set to the empty object `{}`. Any value should be
disregarded for forward compatibility.


## User flows

Expand Down
78 changes: 75 additions & 3 deletions src/bitcoind/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ pub mod poller;
pub mod utils;

use crate::config::BitcoindConfig;
use crate::{database::DatabaseError, revaultd::RevaultD, threadmessages::BitcoindMessageOut};
use crate::{
database::{
interface::{db_spend_transaction, db_vault_by_unvault_txid},
DatabaseError,
},
revaultd::RevaultD,
threadmessages::BitcoindMessageOut,
};
use interface::{BitcoinD, WalletTransaction};
use poller::poller_main;
use poller::{cpfp_package, poller_main, should_cpfp, ToBeCpfped};
use revault_tx::bitcoin::{Network, Txid};

use std::{
Expand Down Expand Up @@ -187,6 +194,60 @@ fn wallet_transaction(bitcoind: &BitcoinD, txid: Txid) -> Option<WalletTransacti
.ok()
}

fn cpfp(
revaultd: Arc<RwLock<RevaultD>>,
bitcoind: Arc<RwLock<BitcoinD>>,
txids: Vec<Txid>,
feerate: f64,
) -> Result<(), BitcoindError> {
let db_path = revaultd.read().unwrap().db_file();
assert!(revaultd.read().unwrap().is_manager());

let mut cpfp_txs = Vec::with_capacity(txids.len());
let mut counter = 0;
//
// sats/vbyte -> sats/WU
let sats_wu = feerate / 4.0;
// sats/WU -> msats/WU
let msats_wu = (sats_wu * 1000.0) as u64;

// sats/WU -> sats/kWU
let sats_kwu = (sats_wu * 1000.0) as u64;

for txid in txids.iter() {
let spend_tx = db_spend_transaction(&db_path, &txid).expect("Database must be available");

if let Some(unwrap_spend_tx) = spend_tx {
// If the transaction is of type SpendTransaction
let psbt = unwrap_spend_tx.psbt;
if should_cpfp(&bitcoind.read().unwrap(), &psbt, sats_kwu) {
cpfp_txs.push(ToBeCpfped::Spend(psbt));
counter += 1;
}
} else {
let unvault_pair =
db_vault_by_unvault_txid(&db_path, &txid).expect("Database must be available");
let unvault_tx = match unvault_pair {
Some((_vault, tx)) => tx,
None => return Err(BitcoindError::Custom("Unknown Txid.".to_string())),
};
// The transaction type is asserted to be UnvaultTransaction
let psbt = unvault_tx.psbt.assert_unvault();
if should_cpfp(&bitcoind.read().unwrap(), &psbt, sats_kwu) {
cpfp_txs.push(ToBeCpfped::Unvault(psbt));
counter += 1;
}
}
}

if counter != 0 {
cpfp_package(&revaultd, &bitcoind.read().unwrap(), cpfp_txs, msats_wu)
} else {
log::info!("Nothing to CPFP in the given list.");
Ok(())
}
}

/// The bitcoind event loop.
/// Listens for bitcoind requests (wallet / chain) and poll bitcoind every 30 seconds,
/// updating our state accordingly.
Expand All @@ -208,7 +269,8 @@ pub fn bitcoind_main_loop(
let _bitcoind = bitcoind.clone();
let _sync_progress = sync_progress.clone();
let _shutdown = shutdown.clone();
move || poller_main(revaultd, _bitcoind, _sync_progress, _shutdown)
let _revaultd = revaultd.clone();
move || poller_main(_revaultd, _bitcoind, _sync_progress, _shutdown)
});

for msg in rx {
Expand Down Expand Up @@ -252,6 +314,16 @@ pub fn bitcoind_main_loop(
))
})?;
}
BitcoindMessageOut::CPFPTransaction(txids, feerate, resp_tx) => {
log::trace!("Received 'cpfptransaction' from main thread");

resp_tx
.send(cpfp(revaultd, bitcoind, txids, feerate))
.map_err(|e| {
BitcoindError::Custom(format!("Sending transaction for CPFP: {}", e))
})?;
return Ok(());
}
}
}

Expand Down
10 changes: 7 additions & 3 deletions src/bitcoind/poller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ fn mark_confirmed_emers(
Ok(())
}

enum ToBeCpfped {
pub enum ToBeCpfped {
Spend(SpendTransaction),
Unvault(UnvaultTransaction),
}
Expand Down Expand Up @@ -450,7 +450,7 @@ impl ToBeCpfped {
// CPFP a bunch of transactions, bumping their feerate by at least `target_feerate`.
// `target_feerate` is expressed in sat/kWU.
// All the transactions' feerate MUST be below `target_feerate`.
fn cpfp_package(
pub fn cpfp_package(
revaultd: &Arc<RwLock<RevaultD>>,
bitcoind: &BitcoinD,
to_be_cpfped: Vec<ToBeCpfped>,
Expand Down Expand Up @@ -541,7 +541,11 @@ fn cpfp_package(
}

// `target_feerate` is in sats/kWU
fn should_cpfp(bitcoind: &BitcoinD, tx: &impl CpfpableTransaction, target_feerate: u64) -> bool {
pub fn should_cpfp(
bitcoind: &BitcoinD,
tx: &impl CpfpableTransaction,
target_feerate: u64,
) -> bool {
bitcoind
.get_wallet_transaction(&tx.txid())
// In the unlikely (actually, shouldn't happen but hey) case where
Expand Down
17 changes: 17 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,23 @@ impl DaemonControl {
let revaultd = self.revaultd.read().unwrap();
gethistory(&revaultd, &self.bitcoind_conn, start, end, limit, kind)
}

/// Manually trigger a CPFP for the given transaction ID.
///
/// ## Errors
/// - we don't have access to a CPFP private key
/// - the caller is not a manager
pub fn manual_cpfp(&self, txids: &Vec<Txid>, feerate: f64) -> Result<(), CommandError> {
let revaultd = self.revaultd.read().unwrap();

if revaultd.cpfp_key.is_none() {
return Err(CommandError::MissingCpfpKey);
}
manager_only!(revaultd);

self.bitcoind_conn.cpfp_tx(txids.to_vec(), feerate)?;
Ok(())
}
}

/// Descriptors the daemon was configured with
Expand Down
25 changes: 25 additions & 0 deletions src/jsonrpc/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ pub trait RpcApi {
end: u32,
limit: u64,
) -> jsonrpc_core::Result<serde_json::Value>;

// Manually cpfp the given transaction id.
#[rpc(meta, name = "cpfp")]
fn cpfp(
&self,
meta: Self::Metadata,
txids: Vec<Txid>,
feerate: f64,
) -> jsonrpc_core::Result<serde_json::Value>;
}

macro_rules! parse_vault_status {
Expand Down Expand Up @@ -301,6 +310,10 @@ impl RpcApi for RpcImpl {
"emergency": [

],
"cpfp": [
"txids",
"feerate",
],
}))
}

Expand Down Expand Up @@ -513,4 +526,16 @@ impl RpcApi for RpcImpl {
"events": events,
}))
}

// manual CPFP command
// feerate will be in sat/vbyte
fn cpfp(
&self,
meta: Self::Metadata,
txids: Vec<Txid>,
feerate: f64,
) -> jsonrpc_core::Result<serde_json::Value> {
meta.daemon_control.manual_cpfp(&txids, feerate)?;
Ok(json!({}))
}
}
14 changes: 14 additions & 0 deletions src/threadmessages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub enum BitcoindMessageOut {
Vec<BitcoinTransaction>,
SyncSender<Result<(), BitcoindError>>,
),
CPFPTransaction(Vec<Txid>, f64, SyncSender<Result<(), BitcoindError>>),
}

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

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

bitrep_rx.recv().expect("Receiving from bitcoind thread")
}

fn cpfp_tx(&self, txids: Vec<Txid>, feerate: f64) -> Result<(), BitcoindError> {
let (bitrep_tx, bitrep_rx) = sync_channel(0);
self.0
.send(BitcoindMessageOut::CPFPTransaction(
txids, feerate, bitrep_tx,
))
.expect("Sending to bitcoind thread");
bitrep_rx.recv().expect("Receiving from bitcoind thread")?;

Ok(())
}
}

impl From<Sender<BitcoindMessageOut>> for BitcoindSender {
Expand Down
3 changes: 3 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,5 +178,8 @@ addr = "127.0.0.1:8332"
fn sync_progress(&self) -> f64 {
1.0
}
fn cpfp_tx(&self, _txid: Vec<Txid>, _feerate: f64) -> Result<(), BitcoindError> {
Ok(())
}
}
}
2 changes: 1 addition & 1 deletion tests/servers/coordinatord
2 changes: 1 addition & 1 deletion tests/servers/cosignerd
Submodule cosignerd updated 2 files
+5 −9 Cargo.lock
+5 −5 Cargo.toml
2 changes: 1 addition & 1 deletion tests/servers/miradord
Submodule miradord updated 2 files
+5 −7 Cargo.lock
+3 −3 Cargo.toml
Loading