Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
2 changes: 2 additions & 0 deletions bin/citrea/tests/bitcoin/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ pub async fn spawn_bitcoin_da_service(
utxo_selection_mode,
rpc_timeout_secs: None,
rpc_connect_timeout_secs: None,
max_fee_rate_sat_vb: None,
fee_rate_cap_duration_secs: None,
};

let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
Expand Down
8 changes: 6 additions & 2 deletions crates/bitcoin-da/src/fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ impl FeeService {
) -> Self {
let mempool_space_url =
mempool_space_url.unwrap_or_else(|| DEFAULT_MEMPOOL_SPACE_URL.to_string());

Self {
client,
network,
Expand Down Expand Up @@ -139,10 +140,13 @@ impl FeeService {
.fee_rate
}
};

let sat_vkb = smart_fee.map_or(1000, |rate| rate.to_sat());
let sat_vb = sat_vkb / 1000;

tracing::debug!("Fee rate: {sat_vb} sat/vb");

tracing::debug!("Fee rate: {} sat/vb", sat_vkb / 1000);
Ok(sat_vkb / 1000)
Ok(sat_vb)
}

/// Bump TX fee via cpfp.
Expand Down
52 changes: 51 additions & 1 deletion crates/bitcoin-da/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ use crate::REVEAL_OUTPUT_AMOUNT;

pub(crate) type Result<T> = std::result::Result<T, BitcoinServiceError>;

const POLLING_INTERVAL: u64 = 10; // seconds
const POLLING_INTERVAL: u64 = 10; // 10 seconds

const DEFAULT_FEE_RATE_CAP_DURATION_SECS: u64 = 3600; // 1 hour default cap duration
const DEFAULT_MAX_FEE_RATE_SAT_VB: u64 = 15; // 15sat/vb default max fee rate

/// Map sov Network to Bitcoin Network.
pub fn network_to_bitcoin_network(network: &Network) -> bitcoin::Network {
Expand Down Expand Up @@ -124,6 +127,12 @@ pub struct BitcoinServiceConfig {

/// Connection timeout for RPC in seconds
pub rpc_connect_timeout_secs: Option<u64>,

/// Max fee rate in sat/vb
pub max_fee_rate_sat_vb: Option<u64>,

/// Fee rate cap duration in seconds
pub fee_rate_cap_duration_secs: Option<u64>,
}

impl citrea_common::FromEnv for BitcoinServiceConfig {
Expand All @@ -149,6 +158,12 @@ impl citrea_common::FromEnv for BitcoinServiceConfig {
rpc_connect_timeout_secs: read_env("BITCOIN_RPC_CONNECT_TIMEOUT_SECS")
.ok()
.and_then(|v| v.parse::<u64>().ok()),
max_fee_rate_sat_vb: read_env("BITCOIN_MAX_FEE_RATE_SAT_VB")
.ok()
.and_then(|v| v.parse::<u64>().ok()),
fee_rate_cap_duration_secs: read_env("BITCOIN_FEE_RATE_CAP_DURATION_SECS")
.ok()
.and_then(|v| v.parse::<u64>().ok()),
})
}
}
Expand All @@ -170,6 +185,8 @@ pub struct BitcoinService {
tx_queue: Arc<Mutex<VecDeque<SignedTxPair>>>,
pub(crate) tx_signer: TxSigner,
utxo_selection_mode: UtxoSelectionMode,
max_fee_rate_sat_vb: u64,
fee_rate_cap_duration_secs: u64,
}

impl BitcoinService {
Expand All @@ -185,6 +202,8 @@ impl BitcoinService {
reveal_tx_prefix: Vec<u8>,
tx_backup_dir: PathBuf,
utxo_selection_mode: UtxoSelectionMode,
max_fee_rate_sat_vb: u64,
fee_rate_cap_duration_secs: u64,
) -> Self {
Self {
tx_signer: TxSigner::new(client.clone()),
Expand All @@ -202,6 +221,8 @@ impl BitcoinService {
))),
tx_queue: Arc::new(Mutex::new(VecDeque::new())),
utxo_selection_mode,
max_fee_rate_sat_vb,
fee_rate_cap_duration_secs,
}
}

Expand Down Expand Up @@ -242,6 +263,12 @@ impl BitcoinService {
.map_err(|_| BitcoinServiceError::InvalidPrivateKey)?;

let utxo_selection_mode = config.utxo_selection_mode.clone().unwrap_or_default();
let max_fee_rate_sat_vb = config
.max_fee_rate_sat_vb
.unwrap_or(DEFAULT_MAX_FEE_RATE_SAT_VB);
let fee_rate_cap_duration_secs = config
.fee_rate_cap_duration_secs
.unwrap_or(DEFAULT_FEE_RATE_CAP_DURATION_SECS);
Ok(Self::new(
client,
network,
Expand All @@ -253,6 +280,8 @@ impl BitcoinService {
chain_params.reveal_tx_prefix,
tx_backup_dir.to_path_buf(),
utxo_selection_mode,
max_fee_rate_sat_vb,
fee_rate_cap_duration_secs,
))
}

Expand Down Expand Up @@ -286,6 +315,7 @@ impl BitcoinService {
if let Some(request) = request_opt {
trace!("A new request is received");

let start = std::time::Instant::now();
loop {
// Build and queue tx with retries:
let fee_sat_per_vbyte = match self.fee.get_fee_rate().await {
Expand All @@ -296,6 +326,26 @@ impl BitcoinService {
continue;
}
};

// Cap fee at self.max_fee_rate_sat_vb for a maximum of `self.fee_rate_cap_duration_secs`.
// If `self.fee_rate_cap_duration_secs` is exceeded, send transaction with fee rate above `self.max_fee_rate_sat_vb` anyway
let elapsed = start.elapsed().as_secs();

if fee_sat_per_vbyte > self.max_fee_rate_sat_vb
&& elapsed < self.fee_rate_cap_duration_secs {
warn!("Fee rate {} sat/vb above cap of {}. Waiting (elapsed: {}s / max: {}s)", fee_sat_per_vbyte, self.max_fee_rate_sat_vb, elapsed, self.fee_rate_cap_duration_secs);
tokio::time::sleep(Duration::from_secs(10)).await;
continue;
}

if fee_sat_per_vbyte > self.max_fee_rate_sat_vb
&& elapsed >= self.fee_rate_cap_duration_secs {
warn!(
"Fee rate {} sat/vb above cap of {} sat/vb, but cap duration of {}s exceeded. Sending transaction anyway",
fee_sat_per_vbyte, self.max_fee_rate_sat_vb, self.fee_rate_cap_duration_secs
);
}

match self
.send_transaction_with_fee_rate(
request.tx_request.clone(),
Expand Down
Loading