Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7a5b24d
DEFI-2518: canbench build_unsigned_transaction_from_inputs
gregorydemay Nov 24, 2025
6172607
Merge branch 'master' into gdemay/DEFI-2518-canbench-build-tx
gregorydemay Nov 27, 2025
68dedff
DEFI-2518: remove benchmark migrate_events_bench
gregorydemay Nov 27, 2025
95bf098
DEFI-2518: benchmark build_unsigned_transaction
gregorydemay Nov 27, 2025
62361e8
DEFI-2518: benchmark result
gregorydemay Nov 27, 2025
aa1dffb
DEFI-2518: useless clone test
gregorydemay Nov 27, 2025
b872071
DEFI-2518: more fine-grained scopes
gregorydemay Nov 27, 2025
80354a4
DEFI-2518: format
gregorydemay Nov 27, 2025
deda680
DEFI-2518: UtxoSet
gregorydemay Nov 28, 2025
1a95bc2
DEFI-2518: UtxoSet with Cow to deal with references
gregorydemay Nov 28, 2025
eb0ab87
DEFI-2518: UtxoSet find lower bound
gregorydemay Nov 28, 2025
feb1557
DEFI-2518: new implementation of greedy
gregorydemay Nov 28, 2025
c61596b
DEFI-2518: fix distinct utxos with same value
gregorydemay Nov 28, 2025
ab1b301
DEFI-2518: preliminary benchmarks for greedy
gregorydemay Nov 28, 2025
c59ed00
DEFI-2518: use UtxoSet in state
gregorydemay Nov 28, 2025
d1381e6
DEFI-2518: Remove lifetime and simplify UtxoSet
gregorydemay Nov 28, 2025
f673b8a
DEFI-2518: fix generation of UTXO set in proptest
gregorydemay Dec 1, 2025
d8836e5
DEFI-2518: fix removing dangling entries in UtxoSet
gregorydemay Dec 1, 2025
c98f386
DEFI-2518: lint
gregorydemay Dec 1, 2025
b18454c
DEFI-2518: remove bench greedy
gregorydemay Dec 1, 2025
ee8bf23
DEFI-2518: update canbench results
gregorydemay Dec 1, 2025
b8ced35
Merge branch 'master' into gdemay/DEFI-2518-sort-utxos
gregorydemay Dec 1, 2025
d082121
DEFI-2518: more proptests
gregorydemay Dec 1, 2025
28e1a96
DEFI-2518: clean-up
gregorydemay Dec 1, 2025
129115e
DEFI-2518: fix dogecoin
gregorydemay Dec 1, 2025
7a0b762
DEFI-2518: implementation using pure sort wrapper and why it's broken
gregorydemay Dec 2, 2025
413bcc0
Revert "DEFI-2518: implementation using pure sort wrapper and why it'…
gregorydemay Dec 2, 2025
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
60 changes: 30 additions & 30 deletions rs/bitcoin/ckbtc/minter/canbench/results.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,163 +2,163 @@ benches:
build_unsigned_transaction_1_50k_sats:
total:
calls: 1
instructions: 14675909
instructions: 2691537
heap_increase: 0
stable_memory_increase: 0
scopes:
build_unsigned_transaction:
calls: 1
instructions: 14673331
instructions: 2688959
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_from_inputs:
calls: 1
instructions: 3131
instructions: 3112
heap_increase: 0
stable_memory_increase: 0
greedy:
calls: 1
instructions: 9504113
instructions: 3817
heap_increase: 0
stable_memory_increase: 0
utxos_selection:
calls: 1
instructions: 14668148
instructions: 2683797
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_2_100k_sats:
total:
calls: 1
instructions: 14675757
instructions: 2691629
heap_increase: 0
stable_memory_increase: 0
scopes:
build_unsigned_transaction:
calls: 1
instructions: 14673179
instructions: 2689051
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_from_inputs:
calls: 1
instructions: 3131
instructions: 3112
heap_increase: 0
stable_memory_increase: 0
greedy:
calls: 1
instructions: 9504166
instructions: 3909
heap_increase: 0
stable_memory_increase: 0
utxos_selection:
calls: 1
instructions: 14667996
instructions: 2683889
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_3_1m_sats:
total:
calls: 1
instructions: 13738959
instructions: 2692824
heap_increase: 0
stable_memory_increase: 0
scopes:
build_unsigned_transaction:
calls: 1
instructions: 13736381
instructions: 2690246
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_from_inputs:
calls: 1
instructions: 3131
instructions: 3112
heap_increase: 0
stable_memory_increase: 0
greedy:
calls: 1
instructions: 8567368
instructions: 5000
heap_increase: 0
stable_memory_increase: 0
utxos_selection:
calls: 1
instructions: 13731198
instructions: 2685084
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_4_10m_sats:
total:
calls: 1
instructions: 14282438
instructions: 2693220
heap_increase: 0
stable_memory_increase: 0
scopes:
build_unsigned_transaction:
calls: 1
instructions: 14279860
instructions: 2690642
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_from_inputs:
calls: 1
instructions: 3131
instructions: 3112
heap_increase: 0
stable_memory_increase: 0
greedy:
calls: 1
instructions: 9110897
instructions: 5480
heap_increase: 0
stable_memory_increase: 0
utxos_selection:
calls: 1
instructions: 14274677
instructions: 2685480
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_5_1_btc:
total:
calls: 1
instructions: 416462644
instructions: 3121724
heap_increase: 0
stable_memory_increase: 0
scopes:
build_unsigned_transaction:
calls: 1
instructions: 416459772
instructions: 3118922
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_from_inputs:
calls: 1
instructions: 39827
instructions: 40049
heap_increase: 0
stable_memory_increase: 0
greedy:
calls: 1
instructions: 416416992
instructions: 404240
heap_increase: 0
stable_memory_increase: 0
utxos_selection:
calls: 1
instructions: 416417893
instructions: 3076823
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_6_10_btc:
total:
calls: 1
instructions: 4916848618
instructions: 7842419
heap_increase: 0
stable_memory_increase: 0
scopes:
build_unsigned_transaction:
calls: 1
instructions: 4916845776
instructions: 7839577
heap_increase: 0
stable_memory_increase: 0
build_unsigned_transaction_from_inputs:
calls: 1
instructions: 443307
instructions: 444118
heap_increase: 0
stable_memory_increase: 0
greedy:
calls: 1
instructions: 4916399516
instructions: 4768255
heap_increase: 0
stable_memory_increase: 0
utxos_selection:
calls: 1
instructions: 4916400417
instructions: 7393409
heap_increase: 0
stable_memory_increase: 0
version: 0.4.0
43 changes: 19 additions & 24 deletions rs/bitcoin/ckbtc/minter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use std::collections::{BTreeMap, BTreeSet};
use std::time::Duration;

use crate::fees::{BitcoinFeeEstimator, FeeEstimator};
use crate::state::utxos::UtxoSet;
use crate::state::{CkBtcMinterState, mutate_state, read_state};
use crate::updates::get_btc_address;
use crate::updates::retrieve_btc::BtcAddressCheckStatus;
Expand Down Expand Up @@ -804,7 +805,7 @@ pub async fn resubmit_transactions<
err,
&submitted_tx.txid,
);
let mut inputs = input_utxos.clone().into_iter().collect::<BTreeSet<_>>();
let mut inputs = UtxoSet::from_iter(input_utxos);
// The following selection is guaranteed to select at least 1 UTXO because
// the value of stuck transaction is no less than retrieve_btc_min_amount.
input_utxos = utxos_selection(retrieve_btc_min_amount, &mut inputs, 0);
Expand Down Expand Up @@ -927,11 +928,7 @@ pub async fn resubmit_transactions<
/// PROPERTY: sum(u.value for u in available_set) ≥ target ⇒ !solution.is_empty()
/// POSTCONDITION: !solution.is_empty() ⇒ sum(u.value for u in solution) ≥ target
/// POSTCONDITION: solution.is_empty() ⇒ available_utxos did not change.
fn utxos_selection(
target: u64,
available_utxos: &mut BTreeSet<Utxo>,
output_count: usize,
) -> Vec<Utxo> {
fn utxos_selection(target: u64, available_utxos: &mut UtxoSet, output_count: usize) -> Vec<Utxo> {
#[cfg(feature = "canbench-rs")]
let _scope = canbench_rs::bench_scope("utxos_selection");

Expand All @@ -943,9 +940,8 @@ fn utxos_selection(

if available_utxos.len() > UTXOS_COUNT_THRESHOLD {
while input_utxos.len() < output_count + 1 {
if let Some(min_utxo) = available_utxos.iter().min_by_key(|u| u.value) {
input_utxos.push(min_utxo.clone());
assert!(available_utxos.remove(&min_utxo.clone()));
if let Some(min_utxo) = available_utxos.pop_first() {
input_utxos.push(min_utxo);
} else {
break;
}
Expand All @@ -963,32 +959,31 @@ fn utxos_selection(
/// PROPERTY: sum(u.value for u in available_set) ≥ target ⇒ !solution.is_empty()
/// POSTCONDITION: !solution.is_empty() ⇒ sum(u.value for u in solution) ≥ target
/// POSTCONDITION: solution.is_empty() ⇒ available_utxos did not change.
fn greedy(target: u64, available_utxos: &mut BTreeSet<Utxo>) -> Vec<Utxo> {
fn greedy(target: u64, available_utxos: &mut UtxoSet) -> Vec<Utxo> {
#[cfg(feature = "canbench-rs")]
let _scope = canbench_rs::bench_scope("greedy");

let mut solution = vec![];
let mut goal = target;
while goal > 0 {
let utxo = match available_utxos.iter().max_by_key(|u| u.value) {
Some(max_utxo) if max_utxo.value < goal => max_utxo.clone(),
Some(_) => available_utxos
.iter()
.filter(|u| u.value >= goal)
.min_by_key(|u| u.value)
.cloned()
.expect("bug: there must be at least one UTXO matching the criteria"),
let candidate_utxo = available_utxos
.find_lower_bound(goal)
.or_else(|| available_utxos.last())
.cloned();
match candidate_utxo {
Some(utxo) => {
let utxo = available_utxos.remove(&utxo).expect("BUG: missing UTXO");
goal = goal.saturating_sub(utxo.value);
solution.push(utxo);
}
None => {
// Not enough available UTXOs to satisfy the request.
for u in solution {
available_utxos.insert(u);
}
return vec![];
}
};
goal = goal.saturating_sub(utxo.value);
assert!(available_utxos.remove(&utxo));
solution.push(utxo);
}
}

debug_assert!(solution.is_empty() || solution.iter().map(|u| u.value).sum::<u64>() >= target);
Expand Down Expand Up @@ -1133,7 +1128,7 @@ pub enum BuildTxError {
/// ```
///
pub fn build_unsigned_transaction<F: FeeEstimator>(
available_utxos: &mut BTreeSet<Utxo>,
available_utxos: &mut UtxoSet,
outputs: Vec<(BitcoinAddress, Satoshi)>,
main_address: BitcoinAddress,
fee_per_vbyte: u64,
Expand Down Expand Up @@ -1328,7 +1323,7 @@ pub fn timer<R: CanisterRuntime + 'static>(runtime: R) {
/// * `maybe_amount` - the withdrawal amount.
/// * `median_fee_millisatoshi_per_vbyte` - the median network fee, in millisatoshi per vbyte.
pub fn estimate_retrieve_btc_fee<F: FeeEstimator>(
available_utxos: &BTreeSet<Utxo>,
available_utxos: &UtxoSet,
withdrawal_amount: u64,
median_fee_millisatoshi_per_vbyte: u64,
fee_estimator: &F,
Expand Down
4 changes: 2 additions & 2 deletions rs/bitcoin/ckbtc/minter/src/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use crate::dashboard::build_dashboard;
use crate::fees::FeeEstimator;
use crate::metrics::encode_metrics;
use crate::state::read_state;
use crate::state::utxos::UtxoSet;
use crate::updates::update_balance::UpdateBalanceArgs;
use crate::{BuildTxError, build_unsigned_transaction_from_inputs, utxos_selection};
use candid::CandidType;
use ic_btc_interface::Utxo;
use ic_http_types::{HttpRequest, HttpResponse, HttpResponseBuilder};
use icrc_ledger_types::icrc1::account::Account;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;

#[derive(CandidType, Deserialize)]
pub struct RetrieveBtcStatusRequest {
Expand Down Expand Up @@ -39,7 +39,7 @@ pub fn get_known_utxos(args: UpdateBalanceArgs) -> Vec<Utxo> {
}

pub fn estimate_withdrawal_fee<F: FeeEstimator>(
available_utxos: &mut BTreeSet<Utxo>,
available_utxos: &mut UtxoSet,
withdrawal_amount: u64,
median_fee_millisatoshi_per_vbyte: u64,
minter_address: BitcoinAddress,
Expand Down
4 changes: 3 additions & 1 deletion rs/bitcoin/ckbtc/minter/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use std::{
pub mod audit;
pub mod eventlog;
pub mod invariants;
pub mod utxos;

use crate::lifecycle::init::InitArgs;
use crate::lifecycle::upgrade::UpgradeArgs;
Expand All @@ -23,6 +24,7 @@ use crate::reimbursement::{
ReimbursedWithdrawalResult, WithdrawalReimbursementReason,
};
use crate::state::invariants::{CheckInvariants, CheckInvariantsImpl};
use crate::state::utxos::UtxoSet;
use crate::updates::update_balance::SuspendedUtxo;
use crate::{
ECDSAPublicKey, GetUtxosCache, Network, Timestamp, WithdrawalFee, address::BitcoinAddress,
Expand Down Expand Up @@ -401,7 +403,7 @@ pub struct CkBtcMinterState {
pub btc_checker_principal: Option<CanisterId>,

/// The set of UTXOs unused in pending transactions.
pub available_utxos: BTreeSet<Utxo>,
pub available_utxos: UtxoSet,

/// The mapping from output points to the ledger accounts to which they
/// belong.
Expand Down
Loading
Loading