-
Notifications
You must be signed in to change notification settings - Fork 318
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
fix(ckbtc): Fix a problem with retrieve_btc_min_amount setting #2435
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -166,19 +166,14 @@ async fn fetch_main_utxos(main_account: &Account, main_address: &BitcoinAddress) | |
/// Returns the minimum withdrawal amount based on the current median fee rate (in millisatoshi per byte). | ||
/// The returned amount is in satoshi. | ||
fn compute_min_withdrawal_amount( | ||
btc_network: Network, | ||
median_fee_rate_e3s: MillisatoshiPerByte, | ||
min_withdrawal_amount: u64, | ||
) -> u64 { | ||
const PER_REQUEST_RBF_BOUND: u64 = 22_100; | ||
const PER_REQUEST_VSIZE_BOUND: u64 = 221; | ||
const PER_REQUEST_MINTER_FEE_BOUND: u64 = 305; | ||
const PER_REQUEST_KYT_FEE: u64 = 2_000; | ||
|
||
let min_withdrawal_amount = match btc_network { | ||
Network::Testnet | Network::Regtest => 10_000, | ||
Network::Mainnet => 100_000, | ||
}; | ||
|
||
let median_fee_rate = median_fee_rate_e3s / 1_000; | ||
((PER_REQUEST_RBF_BOUND | ||
+ PER_REQUEST_VSIZE_BOUND * median_fee_rate | ||
Comment on lines
178
to
179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current logic increments the fee in chunks of 50k satoshis. Do we want to keep it that way? I think we can leave it like that. What do you think? |
||
|
@@ -206,8 +201,8 @@ pub async fn estimate_fee_per_vbyte() -> Option<MillisatoshiPerByte> { | |
if fees.len() >= 100 { | ||
state::mutate_state(|s| { | ||
s.last_fee_per_vbyte.clone_from(&fees); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here for a lack of a better place: The method
The 1st one is guarded, ensuring that no concurrent execution between 2 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds reasonable to me. Paul, what do you think? |
||
s.retrieve_btc_min_amount = | ||
compute_min_withdrawal_amount(s.btc_network, fees[50]); | ||
s.fee_based_retrieve_btc_min_amount = | ||
compute_min_withdrawal_amount(fees[50], s.retrieve_btc_min_amount); | ||
}); | ||
Some(fees[50]) | ||
} else { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -216,7 +216,7 @@ fn get_minter_info() -> MinterInfo { | |
read_state(|s| MinterInfo { | ||
kyt_fee: s.kyt_fee, | ||
min_confirmations: s.min_confirmations, | ||
retrieve_btc_min_amount: s.retrieve_btc_min_amount, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: maybe some comment on the Candid file mentioning that this amount varies according to the current network fees may be helpful |
||
retrieve_btc_min_amount: s.fee_based_retrieve_btc_min_amount, | ||
}) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -122,6 +122,12 @@ pub fn encode_metrics( | |
"Minimum number of ckBTC a user can withdraw.", | ||
)?; | ||
|
||
metrics.encode_gauge( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good idea to have a metric here! |
||
"ckbtc_minter_fee_based_min_retrievable_amount", | ||
state::read_state(|s| s.fee_based_retrieve_btc_min_amount) as f64, | ||
"Minimum number of ckBTC a user can withdraw (fee based).", | ||
)?; | ||
|
||
metrics.encode_gauge( | ||
"ckbtc_minter_min_confirmations", | ||
state::read_state(|s| s.min_confirmations) as f64, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -174,8 +174,13 @@ pub async fn retrieve_btc(args: RetrieveBtcArgs) -> Result<RetrieveBtcOk, Retrie | |
} | ||
|
||
let _guard = retrieve_btc_guard(caller)?; | ||
let (min_retrieve_amount, btc_network, kyt_fee) = | ||
read_state(|s| (s.retrieve_btc_min_amount, s.btc_network, s.kyt_fee)); | ||
let (min_retrieve_amount, btc_network, kyt_fee) = read_state(|s| { | ||
( | ||
s.fee_based_retrieve_btc_min_amount, | ||
s.btc_network, | ||
s.kyt_fee, | ||
) | ||
}); | ||
|
||
let min_amount = max(min_retrieve_amount, kyt_fee); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can it be the case that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But is there any previous check before this line? |
||
if args.amount < min_amount { | ||
|
@@ -308,8 +313,13 @@ pub async fn retrieve_btc_with_approval( | |
} | ||
|
||
let _guard = retrieve_btc_guard(caller)?; | ||
let (min_retrieve_amount, btc_network, kyt_fee) = | ||
read_state(|s| (s.retrieve_btc_min_amount, s.btc_network, s.kyt_fee)); | ||
let (min_retrieve_amount, btc_network, kyt_fee) = read_state(|s| { | ||
( | ||
s.fee_based_retrieve_btc_min_amount, | ||
s.btc_network, | ||
s.kyt_fee, | ||
) | ||
}); | ||
let min_amount = max(min_retrieve_amount, kyt_fee); | ||
if args.amount < min_amount { | ||
return Err(RetrieveBtcWithApprovalError::AmountTooLow(min_amount)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -631,10 +631,11 @@ struct CkBtcSetup { | |
|
||
impl CkBtcSetup { | ||
pub fn new() -> Self { | ||
Self::new_with(Network::Mainnet) | ||
let retrieve_btc_min_amount = 100_000; | ||
Self::new_with(Network::Mainnet, retrieve_btc_min_amount) | ||
} | ||
|
||
pub fn new_with(btc_network: Network) -> Self { | ||
pub fn new_with(btc_network: Network, retrieve_btc_min_amount: u64) -> Self { | ||
let bitcoin_id = bitcoin_canister_id(btc_network); | ||
let env = StateMachineBuilder::new() | ||
.with_master_ecdsa_public_key() | ||
|
@@ -663,11 +664,6 @@ impl CkBtcSetup { | |
) | ||
.expect("failed to install the ledger"); | ||
|
||
let retrieve_btc_min_amount = match btc_network { | ||
Network::Testnet | Network::Regtest => 10_000, | ||
Network::Mainnet => 100_000, | ||
}; | ||
|
||
env.install_existing_canister( | ||
minter_id, | ||
minter_wasm(), | ||
|
@@ -1364,32 +1360,39 @@ fn test_min_retrieval_amount_mainnet() { | |
|
||
#[test] | ||
fn test_min_retrieval_amount_testnet() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we also add a test that when the |
||
let ckbtc = CkBtcSetup::new_with(Network::Testnet); | ||
let min_amount = 12_345; | ||
let ckbtc = CkBtcSetup::new_with(Network::Testnet, min_amount); | ||
|
||
ckbtc.refresh_fee_percentiles(); | ||
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount; | ||
assert_eq!(retrieve_btc_min_amount, 10_000); | ||
assert_eq!(retrieve_btc_min_amount, min_amount); | ||
|
||
// The numbers used in this test have been re-computed using a python script using integers. | ||
ckbtc.set_fee_percentiles(&vec![0; 100]); | ||
ckbtc.refresh_fee_percentiles(); | ||
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount; | ||
assert_eq!(retrieve_btc_min_amount, 10_000); | ||
assert_eq!(retrieve_btc_min_amount, min_amount); | ||
|
||
ckbtc.set_fee_percentiles(&vec![116_000; 100]); | ||
ckbtc.refresh_fee_percentiles(); | ||
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount; | ||
assert_eq!(retrieve_btc_min_amount, 60_000); | ||
assert_eq!(retrieve_btc_min_amount, 50_000 + min_amount); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Understanding question: why does the minimal amount of BTC that can be withdrawn vary with the network fees? It's clear to me that the recipient will get less than the withdrawn amount (to pay for the transaction fees) and that if the minimum amount is fixed, then potentially the recipient could get 0 or the transaction not be processed before a long time but AFAIK this should not impact other transactions (contrary to Ethereum) |
||
|
||
ckbtc.set_fee_percentiles(&vec![342_000; 100]); | ||
ckbtc.refresh_fee_percentiles(); | ||
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount; | ||
assert_eq!(retrieve_btc_min_amount, 60_000); | ||
assert_eq!(retrieve_btc_min_amount, 50_000 + min_amount); | ||
|
||
ckbtc.set_fee_percentiles(&vec![343_000; 100]); | ||
ckbtc.refresh_fee_percentiles(); | ||
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount; | ||
assert_eq!(retrieve_btc_min_amount, 110_000); | ||
assert_eq!(retrieve_btc_min_amount, 100_000 + min_amount); | ||
|
||
// When fee becomes 0 again, it goes back to the initial setting | ||
ckbtc.set_fee_percentiles(&vec![0; 100]); | ||
ckbtc.refresh_fee_percentiles(); | ||
let retrieve_btc_min_amount = ckbtc.get_minter_info().retrieve_btc_min_amount; | ||
assert_eq!(retrieve_btc_min_amount, min_amount); | ||
} | ||
|
||
#[test] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
understanding question: do you know how those numbers were computed? Are they part of some Bitcoin spec.? (maybe @THLO knows)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the calculation is done here.
Yes, the calculation is done based on a 28-node subnet; however, we assume a Bitcoin price of 10,000 XDR, that is, we are currently overcharging quite a bit.