diff --git a/Cargo.lock b/Cargo.lock index bedce2950c8..b9513bd95e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5869,6 +5869,7 @@ name = "ic-btc-kyt" version = "0.9.0" dependencies = [ "askama", + "base64 0.13.1", "bitcoin 0.32.3", "candid", "candid_parser", @@ -5889,6 +5890,7 @@ dependencies = [ "serde_json", "time", "tokio", + "url", ] [[package]] diff --git a/rs/bitcoin/kyt/BUILD.bazel b/rs/bitcoin/kyt/BUILD.bazel index 639bce9e5a1..5dad0717c3b 100644 --- a/rs/bitcoin/kyt/BUILD.bazel +++ b/rs/bitcoin/kyt/BUILD.bazel @@ -18,6 +18,7 @@ rust_library( # Keep sorted. "//rs/rust_canisters/http_types", "@crate_index//:askama", + "@crate_index//:base64", "@crate_index//:bitcoin_0_32", "@crate_index//:candid", "@crate_index//:ciborium", @@ -29,6 +30,7 @@ rust_library( "@crate_index//:serde", "@crate_index//:serde_json", "@crate_index//:time", + "@crate_index//:url", ], ) diff --git a/rs/bitcoin/kyt/Cargo.toml b/rs/bitcoin/kyt/Cargo.toml index a8357093fdb..81e1aa7a812 100644 --- a/rs/bitcoin/kyt/Cargo.toml +++ b/rs/bitcoin/kyt/Cargo.toml @@ -12,6 +12,7 @@ path = "src/main.rs" [dependencies] askama = { workspace = true } +base64 = { workspace = true } bitcoin = { version = "0.32.2", default-features = false } candid = { workspace = true } ciborium = { workspace = true } @@ -24,6 +25,7 @@ ic-stable-structures = { workspace = true } serde = { workspace = true } serde_json = {workspace = true } time = { workspace = true } +url = { workspace = true } [dev-dependencies] candid_parser = { workspace = true } diff --git a/rs/bitcoin/kyt/src/lib.rs b/rs/bitcoin/kyt/src/lib.rs index 90d43c0994e..b03c9c5214f 100644 --- a/rs/bitcoin/kyt/src/lib.rs +++ b/rs/bitcoin/kyt/src/lib.rs @@ -64,10 +64,15 @@ impl FetchEnv for KytCanisterEnv { txid: Txid, max_response_bytes: u32, ) -> Result { - let request = provider.create_request(txid, max_response_bytes); + let request = provider + .create_request(txid, max_response_bytes) + .map_err(|err| HttpGetTxError::Rejected { + code: RejectionCode::SysFatal, + message: err, + })?; let url = request.url.clone(); let cycles = get_tx_cycle_cost(max_response_bytes); - match http_request(request, cycles).await { + match http_request(request.clone(), cycles).await { Ok((response,)) => { // Ensure response is 200 before decoding if response.status != 200u32 { diff --git a/rs/bitcoin/kyt/src/providers.rs b/rs/bitcoin/kyt/src/providers.rs index 6c53234dc33..fa2a145668b 100644 --- a/rs/bitcoin/kyt/src/providers.rs +++ b/rs/bitcoin/kyt/src/providers.rs @@ -75,21 +75,26 @@ impl Provider { &self, txid: Txid, max_response_bytes: u32, - ) -> CanisterHttpRequestArgument { + ) -> Result { match (self.provider_id, &self.btc_network) { (_, BtcNetwork::Regtest { json_rpc_url }) => { make_post_request(json_rpc_url, txid, max_response_bytes) } - (ProviderId::Blockstream, _) => make_get_request( + (ProviderId::Blockstream, _) => Ok(make_get_request( "blockstream.info", &self.btc_network, txid, max_response_bytes, - ), - (ProviderId::MempoolSpace, _) => { - make_get_request("mempool.space", &self.btc_network, txid, max_response_bytes) + )), + (ProviderId::MempoolSpace, _) => Ok(make_get_request( + "mempool.space", + &self.btc_network, + txid, + max_response_bytes, + )), + (ProviderId::Btcscan, BtcNetwork::Mainnet) => { + Ok(btcscan_request(txid, max_response_bytes)) } - (ProviderId::Btcscan, BtcNetwork::Mainnet) => btcscan_request(txid, max_response_bytes), (provider, btc_network) => { panic!( "Provider {} does not support bitcoin {}", @@ -152,23 +157,41 @@ fn make_post_request( json_rpc_url: &str, txid: Txid, max_response_bytes: u32, -) -> CanisterHttpRequestArgument { +) -> Result { + let mut url = url::Url::parse(json_rpc_url).map_err(|err| err.to_string())?; + let username = url.username(); + let password = url.password().unwrap_or_default(); + let authorization = base64::encode(format!( + "{}:{}", + url::form_urlencoded::parse(username.as_bytes()) + .next() + .ok_or(format!("Failed to url_decode {}", username))? + .0, + url::form_urlencoded::parse(password.as_bytes()) + .next() + .ok_or(format!("Failed to url_decode {}", password))? + .0, + )); + url.set_username("") + .map_err(|()| format!("Invalid JSON RPC URL {}", json_rpc_url))?; + url.set_password(None) + .map_err(|()| format!("Invalid JSON RPC URL {}", json_rpc_url))?; let request_headers = vec![HttpHeader { name: "Authorization".to_string(), - value: "Basic aWMtYnRjLWludGVncmF0aW9uOlFQUWlOYXBoMTlGcVVzQ3JCUk4wRklJN2x5TTI2QjUxZkFNZUJRekNiLUU9".to_string(), + value: format!("Basic {}", authorization), }]; let body = format!( "{{\"method\": \"gettransaction\", \"params\": [\"{}\"]}}", txid ); - CanisterHttpRequestArgument { - url: json_rpc_url.to_string(), + Ok(CanisterHttpRequestArgument { + url: url.to_string(), method: HttpMethod::POST, body: Some(body.as_bytes().to_vec()), max_response_bytes: Some(max_response_bytes as u64), transform: param_transform(), headers: request_headers, - } + }) } fn param_transform() -> Option { diff --git a/rs/tests/ckbtc/BUILD.bazel b/rs/tests/ckbtc/BUILD.bazel index b794544acc8..c08cda902d3 100644 --- a/rs/tests/ckbtc/BUILD.bazel +++ b/rs/tests/ckbtc/BUILD.bazel @@ -8,10 +8,10 @@ DEPENDENCIES = [ # Keep sorted. "//packages/icrc-ledger-agent:icrc_ledger_agent", "//packages/icrc-ledger-types:icrc_ledger_types", - "//rs/bitcoin/kyt:btc_kyt_lib", "//rs/bitcoin/ckbtc/agent", "//rs/bitcoin/ckbtc/kyt", "//rs/bitcoin/ckbtc/minter", + "//rs/bitcoin/kyt:btc_kyt_lib", "//rs/canister_client", "//rs/config", "//rs/ledger_suite/icp:icp_ledger",