Skip to content

Commit

Permalink
feat(ckbtc): Use the new KYT canister in ckbtc deposit flow (#2304)
Browse files Browse the repository at this point in the history
Switch to the new KYT canister in the ckBTC deposit flow. 

Also enable real https outcalls in system tests with a SSL reverse proxy
+ bitcoind regtest network.

---------

Co-authored-by: gregorydemay <[email protected]>
Co-authored-by: Thomas Locher <[email protected]>
  • Loading branch information
3 people authored Nov 21, 2024
1 parent f96dec1 commit 0dc55e0
Show file tree
Hide file tree
Showing 22 changed files with 361 additions and 639 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions rs/bitcoin/ckbtc/minter/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ LIB_DEPS = [
# Keep sorted.
"//packages/icrc-ledger-client-cdk:icrc_ledger_client_cdk",
"//packages/icrc-ledger-types:icrc_ledger_types",
"//rs/bitcoin/ckbtc/kyt",
"//rs/bitcoin/kyt:btc_kyt_lib",
"//rs/crypto/getrandom_for_wasm",
"//rs/crypto/secp256k1",
Expand Down Expand Up @@ -156,7 +155,6 @@ rust_ic_test(
srcs = ["tests/tests.rs"],
data = [
":ckbtc_minter_debug.wasm",
"//rs/bitcoin/ckbtc/kyt:kyt_canister",
"//rs/bitcoin/kyt:btc_kyt_canister",
"//rs/bitcoin/mock:bitcoin_canister_mock",
"//rs/ledger_suite/icrc1/ledger:ledger_canister",
Expand All @@ -166,14 +164,12 @@ rust_ic_test(
"IC_CKBTC_MINTER_WASM_PATH": "$(rootpath :ckbtc_minter_debug.wasm)",
"IC_ICRC1_LEDGER_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/ledger:ledger_canister)",
"IC_BTC_KYT_WASM_PATH": "$(rootpath //rs/bitcoin/kyt:btc_kyt_canister)",
"IC_CKBTC_KYT_WASM_PATH": "$(rootpath //rs/bitcoin/ckbtc/kyt:kyt_canister)",
"IC_BITCOIN_CANISTER_MOCK_WASM_PATH": "$(rootpath //rs/bitcoin/mock:bitcoin_canister_mock)",
},
deps = [
# Keep sorted.
":ckbtc_minter_lib",
"//packages/icrc-ledger-types:icrc_ledger_types",
"//rs/bitcoin/ckbtc/kyt",
"//rs/bitcoin/kyt:btc_kyt_lib",
"//rs/bitcoin/mock",
"//rs/config",
Expand Down
1 change: 0 additions & 1 deletion rs/bitcoin/ckbtc/minter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ ic-canister-log = { path = "../../../rust_canisters/canister_log" }
ic-canisters-http-types = { path = "../../../rust_canisters/http_types" }
ic-cdk = { workspace = true }
ic-cdk-macros = { workspace = true }
ic-ckbtc-kyt = { path = "../kyt" }
ic-crypto-secp256k1 = { path = "../../../crypto/secp256k1" }
ic-crypto-getrandom-for-wasm = { path = "../../../crypto/getrandom_for_wasm" }
ic-crypto-sha2 = { path = "../../../crypto/sha2" }
Expand Down
2 changes: 1 addition & 1 deletion rs/bitcoin/ckbtc/minter/src/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ pub fn build_metadata(s: &CkBtcMinterState) -> String {
.unwrap_or_default(),
s.min_confirmations,
s.ledger_id,
s.kyt_principal
s.new_kyt_principal
.map(|p| p.to_string())
.unwrap_or_else(|| "N/A".to_string()),
DisplayAmount(s.kyt_fee),
Expand Down
2 changes: 1 addition & 1 deletion rs/bitcoin/ckbtc/minter/src/guard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ mod tests {
max_time_in_queue_nanos: 0,
min_confirmations: None,
mode: crate::state::Mode::GeneralAvailability,
kyt_principal: None,
new_kyt_principal: Some(CanisterId::from(0)),
kyt_principal: Some(CanisterId::from(0)),
kyt_fee: None,
}
}
Expand Down
72 changes: 22 additions & 50 deletions rs/bitcoin/ckbtc/minter/src/management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ use ic_btc_interface::{
Address, GetCurrentFeePercentilesRequest, GetUtxosRequest, GetUtxosResponse,
MillisatoshiPerByte, Network, Utxo, UtxosFilterInRequest,
};
use ic_btc_kyt::{CheckAddressArgs, CheckAddressResponse};
use ic_btc_kyt::{
CheckAddressArgs, CheckAddressResponse, CheckTransactionArgs, CheckTransactionResponse,
};
use ic_canister_log::log;
use ic_cdk::api::call::RejectionCode;
use ic_ckbtc_kyt::{DepositRequest, Error as KytError, FetchAlertsResponse, WithdrawalAttempt};
use ic_management_canister_types::{
DerivationPath, ECDSAPublicKeyArgs, ECDSAPublicKeyResponse, EcdsaCurve, EcdsaKeyId,
};
Expand Down Expand Up @@ -298,70 +299,41 @@ pub async fn sign_with_ecdsa(
}
}

/// Requests alerts for the given UTXO.
pub async fn fetch_utxo_alerts(
kyt_principal: Principal,
caller: Principal,
utxo: &Utxo,
) -> Result<Result<FetchAlertsResponse, KytError>, CallError> {
let (res,): (Result<FetchAlertsResponse, KytError>,) = ic_cdk::api::call::call(
kyt_principal,
"fetch_utxo_alerts",
(DepositRequest {
caller,
txid: utxo.outpoint.txid.into(),
vout: utxo.outpoint.vout,
},),
)
.await
.map_err(|(code, message)| CallError {
method: "fetch_utxo_alerts".to_string(),
reason: Reason::from_reject(code, message),
})?;
Ok(res)
}

/// Requests alerts for the given Bitcoin address.
pub async fn fetch_withdrawal_alerts(
/// Check if the given Bitcoin address is blocked.
pub async fn check_withdrawal_destination_address(
kyt_principal: Principal,
caller: Principal,
address: String,
amount: u64,
) -> Result<Result<FetchAlertsResponse, KytError>, CallError> {
let now = ic_cdk::api::time();
let id = format!("{caller}:{address}:{amount}:{now}");
let (res,): (Result<FetchAlertsResponse, KytError>,) = ic_cdk::api::call::call(
) -> Result<CheckAddressResponse, CallError> {
let (res,): (CheckAddressResponse,) = ic_cdk::api::call::call(
kyt_principal,
"fetch_withdrawal_alerts",
(WithdrawalAttempt {
caller,
id,
amount,
address,
timestamp_nanos: now,
},),
"check_address",
(CheckAddressArgs { address },),
)
.await
.map_err(|(code, message)| CallError {
method: "fetch_withdrawal_alerts".to_string(),
method: "check_address".to_string(),
reason: Reason::from_reject(code, message),
})?;
Ok(res)
}

/// Check if the given Bitcoin address is blocked.
pub async fn check_withdrawal_destination_address(
/// Check if the given UTXO passes KYT.
pub async fn check_transaction(
kyt_principal: Principal,
address: String,
) -> Result<CheckAddressResponse, CallError> {
let (res,): (CheckAddressResponse,) = ic_cdk::api::call::call(
utxo: &Utxo,
cycle_payment: u128,
) -> Result<CheckTransactionResponse, CallError> {
let (res,): (CheckTransactionResponse,) = ic_cdk::api::call::call_with_payment128(
kyt_principal,
"check_address",
(CheckAddressArgs { address },),
"check_transaction",
(CheckTransactionArgs {
txid: utxo.outpoint.txid.as_ref().to_vec(),
},),
cycle_payment,
)
.await
.map_err(|(code, message)| CallError {
method: "check_address".to_string(),
method: "check_transaction".to_string(),
reason: Reason::from_reject(code, message),
})?;
Ok(res)
Expand Down
54 changes: 31 additions & 23 deletions rs/bitcoin/ckbtc/minter/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,16 @@ impl UtxoCheckStatus {
}
}

/// Relevant data for a checked UTXO. The UUID and `kyt_provider` are kept for
/// backward-compatibility reasons. They should be set to `None` since
/// we dont use KYT providers anymore.
#[derive(Clone, Eq, PartialEq, Debug, Serialize, serde::Deserialize)]
pub struct CheckedUtxo {
pub(crate) status: UtxoCheckStatus,
uuid: Option<String>,
kyt_provider: Option<Principal>,
}

/// Indicates that fee distribution overdrafted.
#[derive(Copy, Clone, Debug)]
pub struct Overdraft(pub u64);
Expand Down Expand Up @@ -337,9 +347,6 @@ pub struct CkBtcMinterState {
/// The CanisterId of the ckBTC Ledger.
pub ledger_id: CanisterId,

/// The principal of the KYT canister.
pub kyt_principal: Option<CanisterId>,

/// The new principal of the KYT canister.
pub new_kyt_principal: Option<CanisterId>,

Expand Down Expand Up @@ -382,7 +389,7 @@ pub struct CkBtcMinterState {
pub owed_kyt_amount: BTreeMap<Principal, u64>,

/// A cache of UTXO KYT check statuses.
pub checked_utxos: BTreeMap<Utxo, (String, UtxoCheckStatus, Principal)>,
pub checked_utxos: BTreeMap<Utxo, CheckedUtxo>,

/// UTXOs whose values are too small to pay the KYT check fee.
pub ignored_utxos: BTreeSet<Utxo>,
Expand Down Expand Up @@ -434,8 +441,8 @@ impl CkBtcMinterState {
min_confirmations,
mode,
kyt_fee,
kyt_principal,
new_kyt_principal,
kyt_principal: _,
}: InitArgs,
) {
self.btc_network = btc_network.into();
Expand All @@ -445,7 +452,6 @@ impl CkBtcMinterState {
self.ledger_id = ledger_id;
self.max_time_in_queue_nanos = max_time_in_queue_nanos;
self.mode = mode;
self.kyt_principal = kyt_principal;
self.new_kyt_principal = new_kyt_principal;
if let Some(kyt_fee) = kyt_fee {
self.kyt_fee = kyt_fee;
Expand All @@ -463,8 +469,8 @@ impl CkBtcMinterState {
min_confirmations,
mode,
new_kyt_principal,
kyt_principal,
kyt_fee,
kyt_principal: _,
}: UpgradeArgs,
) {
if let Some(retrieve_btc_min_amount) = retrieve_btc_min_amount {
Expand Down Expand Up @@ -492,9 +498,6 @@ impl CkBtcMinterState {
if let Some(new_kyt_principal) = new_kyt_principal {
self.new_kyt_principal = Some(new_kyt_principal);
}
if let Some(kyt_principal) = kyt_principal {
self.kyt_principal = Some(kyt_principal);
}
if let Some(kyt_fee) = kyt_fee {
self.kyt_fee = kyt_fee;
}
Expand All @@ -507,9 +510,6 @@ impl CkBtcMinterState {
if self.ecdsa_key_name.is_empty() {
ic_cdk::trap("ecdsa_key_name is not set");
}
if self.kyt_principal.is_none() {
ic_cdk::trap("KYT principal is not set");
}
if self.new_kyt_principal.is_none() {
ic_cdk::trap("New KYT principal is not set");
}
Expand Down Expand Up @@ -959,27 +959,36 @@ impl CkBtcMinterState {
}

/// Marks the given UTXO as checked.
/// If the UTXO is clean, we increase the owed KYT amount and remember that UTXO until we see it
/// again in a [add_utxos] call.
/// If the UTXO is clean, we increase the owed KYT amount if there is a KYT provider, and
/// remember that UTXO until we see it again in an [add_utxos] call.
/// If the UTXO is tainted, we put it in the quarantine area without increasing the owed KYT
/// amount.
fn mark_utxo_checked(
&mut self,
utxo: Utxo,
uuid: String,
uuid: Option<String>,
status: UtxoCheckStatus,
kyt_provider: Principal,
kyt_provider: Option<Principal>,
) {
match status {
UtxoCheckStatus::Clean => {
if self
.checked_utxos
.insert(utxo, (uuid, status, kyt_provider))
.insert(
utxo,
CheckedUtxo {
uuid,
status,
kyt_provider,
},
)
.is_none()
{
// Updated the owed amount only if it's the first time we mark this UTXO as
// clean.
*self.owed_kyt_amount.entry(kyt_provider).or_insert(0) += self.kyt_fee;
if let Some(provider) = kyt_provider {
*self.owed_kyt_amount.entry(provider).or_insert(0) += self.kyt_fee;
}
}
}
UtxoCheckStatus::Tainted => {
Expand Down Expand Up @@ -1108,9 +1117,9 @@ impl CkBtcMinterState {
);

ensure_eq!(
self.kyt_principal,
other.kyt_principal,
"kyt_principal does not match"
self.new_kyt_principal,
other.new_kyt_principal,
"new_kyt_principal does not match"
);

ensure_eq!(
Expand Down Expand Up @@ -1202,7 +1211,6 @@ impl From<InitArgs> for CkBtcMinterState {
tokens_minted: 0,
tokens_burned: 0,
ledger_id: args.ledger_id,
kyt_principal: args.kyt_principal,
new_kyt_principal: args.new_kyt_principal,
available_utxos: Default::default(),
outpoint_account: Default::default(),
Expand Down
8 changes: 4 additions & 4 deletions rs/bitcoin/ckbtc/minter/src/state/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,15 @@ pub fn confirm_transaction(state: &mut CkBtcMinterState, txid: &Txid) {
pub fn mark_utxo_checked(
state: &mut CkBtcMinterState,
utxo: &Utxo,
uuid: String,
uuid: Option<String>,
status: UtxoCheckStatus,
kyt_provider: Principal,
kyt_provider: Option<Principal>,
) {
record_event(&Event::CheckedUtxo {
utxo: utxo.clone(),
uuid: uuid.clone(),
uuid: uuid.clone().unwrap_or_default(),
clean: status.is_clean(),
kyt_provider: Some(kyt_provider),
kyt_provider,
});
state.mark_utxo_checked(utxo.clone(), uuid, status, kyt_provider);
}
Expand Down
Loading

0 comments on commit 0dc55e0

Please sign in to comment.