diff --git a/Cargo.lock b/Cargo.lock index 2faf98d13126..18f1258d2ff0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14244,7 +14244,7 @@ version = "0.9.0" dependencies = [ "anyhow", "assert_matches", - "bitcoin 0.32.5", + "bitcoin 0.32.99", "bitcoincore-rpc", "candid", "canister-test", diff --git a/rs/bitcoin/adapter/tests/adapter_test.rs b/rs/bitcoin/adapter/tests/adapter_test.rs index 20500a2e519b..86095fc6fa4b 100644 --- a/rs/bitcoin/adapter/tests/adapter_test.rs +++ b/rs/bitcoin/adapter/tests/adapter_test.rs @@ -57,11 +57,12 @@ fn make_get_successors_request( anchor: Vec, headers: Vec>, ) -> Result { - let request = BitcoinAdapterRequestWrapper::GetSuccessorsRequest(GetSuccessorsRequestInitial { - network: Network::Regtest, - anchor, - processed_block_hashes: headers, - }); + let request = + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(GetSuccessorsRequestInitial { + network: Network::Regtest, + anchor, + processed_block_hashes: headers, + }); adapter_client.send_blocking( request, @@ -75,7 +76,7 @@ fn make_send_tx_request( adapter_client: &BitcoinAdapterClient, raw_tx: &[u8], ) -> Result { - let request = BitcoinAdapterRequestWrapper::SendTransactionRequest(SendTransactionRequest { + let request = BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(SendTransactionRequest { network: Network::Regtest, transaction: raw_tx.to_vec(), }); diff --git a/rs/bitcoin/client/src/lib.rs b/rs/bitcoin/client/src/lib.rs index 30df49755721..e2d2084f023a 100644 --- a/rs/bitcoin/client/src/lib.rs +++ b/rs/bitcoin/client/src/lib.rs @@ -6,8 +6,8 @@ use crate::metrics::{ }; use ic_adapter_metrics_client::AdapterMetrics; use ic_btc_replica_types::{ - BitcoinAdapterRequestWrapper, BitcoinAdapterResponseWrapper, GetSuccessorsRequestInitial, - GetSuccessorsResponseComplete, SendTransactionRequest, SendTransactionResponse, + BitcoinAdapterRequestInner, BitcoinAdapterRequestWrapper, BitcoinAdapterResponseWrapper, + GetSuccessorsResponseComplete, SendTransactionResponse, }; use ic_btc_service::{ btc_service_client::BtcServiceClient, BtcServiceGetSuccessorsRequest, @@ -65,11 +65,8 @@ impl RpcAdapterClient for BitcoinAdapterClientImpl ); let mut client = self.client.clone(); self.rt_handle.block_on(async move { - let response = match request { - BitcoinAdapterRequestWrapper::SendTransactionRequest(SendTransactionRequest { - transaction, - .. - }) => { + let response = match request.as_inner() { + BitcoinAdapterRequestInner::SendTransaction { transaction } => { request_timer.set_label(LABEL_REQUEST_TYPE, LABEL_SEND_TRANSACTION); let send_transaction_request = BtcServiceSendTransactionRequest { transaction }; let mut tonic_request = tonic::Request::new(send_transaction_request); @@ -86,13 +83,10 @@ impl RpcAdapterClient for BitcoinAdapterClientImpl }) .map_err(convert_tonic_error) } - BitcoinAdapterRequestWrapper::GetSuccessorsRequest( - GetSuccessorsRequestInitial { - anchor, - processed_block_hashes, - .. - }, - ) => { + BitcoinAdapterRequestInner::GetSuccessors { + anchor, + processed_block_hashes, + } => { request_timer.set_label(LABEL_REQUEST_TYPE, LABEL_GET_SUCCESSORS); let get_successors_request = BtcServiceGetSuccessorsRequest { anchor, @@ -153,11 +147,11 @@ impl RpcAdapterClient for BrokenConnectionBitcoinC &REQUESTS_LABEL_NAMES, [UNKNOWN_LABEL, UNKNOWN_LABEL], ); - match request { - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(_) => { + match request.as_inner() { + BitcoinAdapterRequestInner::GetSuccessors { .. } => { request_timer.set_label(LABEL_REQUEST_TYPE, LABEL_GET_SUCCESSORS) } - BitcoinAdapterRequestWrapper::SendTransactionRequest(_) => { + BitcoinAdapterRequestInner::SendTransaction { .. } => { request_timer.set_label(LABEL_REQUEST_TYPE, LABEL_SEND_TRANSACTION) } } @@ -218,6 +212,61 @@ pub struct BitcoinAdapterClients { >, } +pub struct DogecoinAdapterClients { + pub doge_testnet_client: Box< + dyn RpcAdapterClient< + BitcoinAdapterRequestWrapper, + Response = BitcoinAdapterResponseWrapper, + >, + >, + pub doge_mainnet_client: Box< + dyn RpcAdapterClient< + BitcoinAdapterRequestWrapper, + Response = BitcoinAdapterResponseWrapper, + >, + >, +} + +pub fn setup_dogecoin_adapter_clients( + log: ReplicaLogger, + metrics_registry: &MetricsRegistry, + rt_handle: tokio::runtime::Handle, + adapters_config: AdaptersConfig, +) -> DogecoinAdapterClients { + let metrics = Metrics::new(metrics_registry); + + // Register bitcoin adapters metrics. + if let Some(metrics_uds_path) = adapters_config.dogecoin_testnet_uds_metrics_path { + metrics_registry.register_adapter(AdapterMetrics::new( + "dogetestnet", + metrics_uds_path, + rt_handle.clone(), + )); + } + if let Some(metrics_uds_path) = adapters_config.dogecoin_mainnet_uds_metrics_path { + metrics_registry.register_adapter(AdapterMetrics::new( + "dogemainnet", + metrics_uds_path, + rt_handle.clone(), + )); + } + + DogecoinAdapterClients { + doge_testnet_client: setup_bitcoin_adapter_client( + log.clone(), + metrics.clone(), + rt_handle.clone(), + adapters_config.dogecoin_testnet_uds_path, + ), + doge_mainnet_client: setup_bitcoin_adapter_client( + log, + metrics, + rt_handle, + adapters_config.dogecoin_mainnet_uds_path, + ), + } +} + pub fn setup_bitcoin_adapter_clients( log: ReplicaLogger, metrics_registry: &MetricsRegistry, diff --git a/rs/bitcoin/consensus/src/payload_builder.rs b/rs/bitcoin/consensus/src/payload_builder.rs index 24fb3316680d..69e5d27fc5fb 100644 --- a/rs/bitcoin/consensus/src/payload_builder.rs +++ b/rs/bitcoin/consensus/src/payload_builder.rs @@ -8,10 +8,11 @@ mod tests; mod proptests; use crate::metrics::BitcoinPayloadBuilderMetrics; -use ic_btc_interface::Network; +use ic_btc_interface::Network as BtcNetwork; +use ic_btc_interface::Network as DogeNetwork; use ic_btc_replica_types::{ BitcoinAdapterRequestWrapper, BitcoinAdapterResponse, BitcoinAdapterResponseWrapper, - BitcoinReject, + BitcoinReject, Network, }; use ic_config::bitcoin_payload_builder_config::Config; use ic_error_types::RejectCode; @@ -67,21 +68,17 @@ impl GetPayloadError { } } +type AdapterClient = Box< + dyn RpcAdapterClient, +>; + pub struct BitcoinPayloadBuilder { state_manager: Arc>, metrics: Arc, - bitcoin_mainnet_adapter_client: Box< - dyn RpcAdapterClient< - BitcoinAdapterRequestWrapper, - Response = BitcoinAdapterResponseWrapper, - >, - >, - bitcoin_testnet_adapter_client: Box< - dyn RpcAdapterClient< - BitcoinAdapterRequestWrapper, - Response = BitcoinAdapterResponseWrapper, - >, - >, + bitcoin_mainnet_adapter_client: AdapterClient, + bitcoin_testnet_adapter_client: AdapterClient, + dogecoin_mainnet_adapter_client: AdapterClient, + dogecoin_testnet_adapter_client: AdapterClient, subnet_id: SubnetId, registry: Arc, config: Config, @@ -92,18 +89,10 @@ impl BitcoinPayloadBuilder { pub fn new( state_manager: Arc>, metrics_registry: &MetricsRegistry, - bitcoin_mainnet_adapter_client: Box< - dyn RpcAdapterClient< - BitcoinAdapterRequestWrapper, - Response = BitcoinAdapterResponseWrapper, - >, - >, - bitcoin_testnet_adapter_client: Box< - dyn RpcAdapterClient< - BitcoinAdapterRequestWrapper, - Response = BitcoinAdapterResponseWrapper, - >, - >, + bitcoin_mainnet_adapter_client: AdapterClient, + bitcoin_testnet_adapter_client: AdapterClient, + dogecoin_mainnet_adapter_client: AdapterClient, + dogecoin_testnet_adapter_client: AdapterClient, subnet_id: SubnetId, registry: Arc, config: Config, @@ -114,6 +103,8 @@ impl BitcoinPayloadBuilder { metrics: Arc::new(BitcoinPayloadBuilderMetrics::new(metrics_registry)), bitcoin_mainnet_adapter_client, bitcoin_testnet_adapter_client, + dogecoin_mainnet_adapter_client, + dogecoin_testnet_adapter_client, subnet_id, registry, config, @@ -146,8 +137,13 @@ impl BitcoinPayloadBuilder { } let adapter_client = match request.network() { - Network::Mainnet => &self.bitcoin_mainnet_adapter_client, - Network::Testnet | Network::Regtest => &self.bitcoin_testnet_adapter_client, + Network::Bitcoin(BtcNetwork::Mainnet) => &self.bitcoin_mainnet_adapter_client, + Network::Bitcoin(BtcNetwork::Testnet) | Network::Bitcoin(BtcNetwork::Regtest) => { + &self.bitcoin_testnet_adapter_client + } + Network::Dogecoin(DogeNetwork::Mainnet) => &self.dogecoin_mainnet_adapter_client, + Network::Dogecoin(DogeNetwork::Testnet) + | Network::Dogecoin(DogeNetwork::Regtest) => &self.dogecoin_testnet_adapter_client, }; // Send request to the adapter. @@ -198,7 +194,21 @@ impl BitcoinPayloadBuilder { Err(err) => { let error_message = err.to_string(); match request { - BitcoinAdapterRequestWrapper::SendTransactionRequest(context) => { + BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(context) => { + BitcoinAdapterResponseWrapper::SendTransactionReject( + BitcoinReject { + reject_code: RejectCode::SysTransient, + message: error_message, + }, + ) + } + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(context) => { + BitcoinAdapterResponseWrapper::GetSuccessorsReject(BitcoinReject { + reject_code: RejectCode::SysTransient, + message: error_message, + }) + } + BitcoinAdapterRequestWrapper::SendDogeTransactionRequest(context) => { BitcoinAdapterResponseWrapper::SendTransactionReject( BitcoinReject { reject_code: RejectCode::SysTransient, @@ -206,7 +216,7 @@ impl BitcoinPayloadBuilder { }, ) } - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(context) => { + BitcoinAdapterRequestWrapper::GetDogeSuccessorsRequest(context) => { BitcoinAdapterResponseWrapper::GetSuccessorsReject(BitcoinReject { reject_code: RejectCode::SysTransient, message: error_message, @@ -321,7 +331,7 @@ fn bitcoin_requests_iter( .map(|(callback_id, context)| { ( callback_id, - BitcoinAdapterRequestWrapper::SendTransactionRequest(context.payload.clone()), + BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(context.payload.clone()), ) }) .chain( @@ -331,7 +341,9 @@ fn bitcoin_requests_iter( .map(|(callback_id, context)| { ( callback_id, - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(context.payload.clone()), + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest( + context.payload.clone(), + ), ) }), ) diff --git a/rs/bitcoin/consensus/src/payload_builder/tests.rs b/rs/bitcoin/consensus/src/payload_builder/tests.rs index 111f692bb200..37f1ddfdcbd6 100644 --- a/rs/bitcoin/consensus/src/payload_builder/tests.rs +++ b/rs/bitcoin/consensus/src/payload_builder/tests.rs @@ -1,5 +1,5 @@ use crate::{payload_builder::parse, BitcoinPayloadBuilder}; -use ic_btc_interface::Network; +use ic_btc_interface::Network as BtcNetwork; use ic_btc_replica_types::{ BitcoinAdapterRequestWrapper, BitcoinAdapterResponse, BitcoinAdapterResponseWrapper, BitcoinReject, GetSuccessorsRequestInitial, GetSuccessorsResponseComplete, @@ -95,6 +95,8 @@ fn make_subnet_record_key(subnet_id: SubnetId) -> String { fn bitcoin_payload_builder_test( bitcoin_mainnet_adapter_client: MockBitcoinAdapterClient, bitcoin_testnet_adapter_client: MockBitcoinAdapterClient, + dogecoin_mainnet_adapter_client: MockBitcoinAdapterClient, + dogecoin_testnet_adapter_client: MockBitcoinAdapterClient, state_manager: MockStateManager, registry_client: MockRegistryClient, run_test: impl FnOnce(ProposalContext, BitcoinPayloadBuilder), @@ -117,6 +119,8 @@ fn bitcoin_payload_builder_test( &MetricsRegistry::new(), Box::new(bitcoin_mainnet_adapter_client), Box::new(bitcoin_testnet_adapter_client), + Box::new(dogecoin_mainnet_adapter_client), + Box::new(dogecoin_testnet_adapter_client), subnet_test_id(0), Arc::new(registry_client), Config::default(), @@ -152,17 +156,19 @@ fn can_successfully_create_bitcoin_payload() { // Create a mock state manager that returns a `ReplicatedState` with // some bitcoin adapter requests. let state_manager = - mock_state_manager(vec![BitcoinAdapterRequestWrapper::GetSuccessorsRequest( + mock_state_manager(vec![BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest( GetSuccessorsRequestInitial { processed_block_hashes: vec![vec![10; 32]], anchor: vec![10; 32], - network: Network::Testnet, + network: BtcNetwork::Testnet, }, )]); bitcoin_payload_builder_test( MockBitcoinAdapterClient::new(), mock_adapter(), + mock_adapter(), + mock_adapter(), state_manager, registry_client, |proposal_context, bitcoin_payload_builder| { @@ -216,15 +222,15 @@ fn includes_responses_in_the_payload() { } let state_manager = mock_state_manager(vec![ - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(GetSuccessorsRequestInitial { + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(GetSuccessorsRequestInitial { processed_block_hashes: vec![vec![10; 32]], anchor: vec![10; 32], - network: Network::Testnet, + network: BtcNetwork::Testnet, }), - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(GetSuccessorsRequestInitial { + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(GetSuccessorsRequestInitial { processed_block_hashes: vec![vec![20; 32]], anchor: vec![20; 32], - network: Network::Testnet, + network: BtcNetwork::Testnet, }), ]); @@ -233,6 +239,8 @@ fn includes_responses_in_the_payload() { bitcoin_payload_builder_test( MockBitcoinAdapterClient::new(), mock_adapter(), + mock_adapter(), + mock_adapter(), state_manager, registry_client, |proposal_context, bitcoin_payload_builder| { @@ -304,19 +312,21 @@ fn includes_only_responses_for_callback_ids_not_seen_in_past_payloads() { }, )) }); + let dogecoin_mainnet_adapter_client = MockBitcoinAdapterClient::new(); + let dogecoin_testnet_adapter_client = MockBitcoinAdapterClient::new(); // Create a mock state manager that returns a `ReplicatedState` with // some bitcoin adapter requests. let state_manager = mock_state_manager(vec![ - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(GetSuccessorsRequestInitial { + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(GetSuccessorsRequestInitial { processed_block_hashes: vec![vec![10; 32]], anchor: vec![10; 32], - network: Network::Testnet, + network: BtcNetwork::Testnet, }), - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(GetSuccessorsRequestInitial { + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(GetSuccessorsRequestInitial { processed_block_hashes: vec![vec![20; 32]], anchor: vec![20; 32], - network: Network::Testnet, + network: BtcNetwork::Testnet, }), ]); @@ -325,6 +335,8 @@ fn includes_only_responses_for_callback_ids_not_seen_in_past_payloads() { bitcoin_payload_builder_test( bitcoin_mainnet_adapter_client, bitcoin_testnet_adapter_client, + dogecoin_mainnet_adapter_client, + dogecoin_testnet_adapter_client, state_manager, registry_client, |proposal_context, bitcoin_payload_builder| { @@ -407,15 +419,17 @@ fn bitcoin_payload_builder_fits_largest_blocks() { }, )) }); + let dogecoin_mainnet_adapter_client = MockBitcoinAdapterClient::new(); + let dogecoin_testnet_adapter_client = MockBitcoinAdapterClient::new(); // Create a mock state manager that returns a `ReplicatedState` with // some bitcoin adapter requests. let state_manager = - mock_state_manager(vec![BitcoinAdapterRequestWrapper::GetSuccessorsRequest( + mock_state_manager(vec![BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest( GetSuccessorsRequestInitial { processed_block_hashes: vec![vec![10; 32]], anchor: vec![10; 32], - network: Network::Testnet, + network: BtcNetwork::Testnet, }, )]); @@ -424,6 +438,8 @@ fn bitcoin_payload_builder_fits_largest_blocks() { bitcoin_payload_builder_test( bitcoin_mainnet_adapter_client, bitcoin_testnet_adapter_client, + dogecoin_mainnet_adapter_client, + dogecoin_testnet_adapter_client, state_manager, registry_client, |proposal_context, bitcoin_payload_builder| { diff --git a/rs/bitcoin/replica_types/src/lib.rs b/rs/bitcoin/replica_types/src/lib.rs index 269d01b2eeea..ba53feec9621 100644 --- a/rs/bitcoin/replica_types/src/lib.rs +++ b/rs/bitcoin/replica_types/src/lib.rs @@ -5,7 +5,8 @@ //! only for serialization/deserialization of the ReplicatedState. use candid::CandidType; -use ic_btc_interface::Network; +use ic_btc_interface::Network as BtcNetwork; +use ic_btc_interface::Network as DogeNetwork; use ic_error_types::RejectCode; use ic_protobuf::{ bitcoin::v1, @@ -16,9 +17,67 @@ use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use std::mem::size_of_val; +/// Network types for both Bitcoin and Dogecoin. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum Network { + Bitcoin(BtcNetwork), + Dogecoin(DogeNetwork), +} + +impl TryFrom for Network { + type Error = ProxyDecodeError; + fn try_from(network: i32) -> Result { + match v1::Network::try_from(network) { + Ok(v1::Network::Testnet) => Ok(Network::Bitcoin(BtcNetwork::Testnet)), + Ok(v1::Network::Mainnet) => Ok(Network::Bitcoin(BtcNetwork::Mainnet)), + Ok(v1::Network::Regtest) => Ok(Network::Bitcoin(BtcNetwork::Regtest)), + Ok(v1::Network::DogecoinTestnet) => Ok(Network::Dogecoin(DogeNetwork::Testnet)), + Ok(v1::Network::DogecoinMainnet) => Ok(Network::Dogecoin(DogeNetwork::Mainnet)), + Ok(v1::Network::DogecoinRegtest) => Ok(Network::Dogecoin(DogeNetwork::Regtest)), + _ => Err(ProxyDecodeError::MissingField( + "missing network or wrong network type", + )), + } + } +} + +fn from_btc_network(network: BtcNetwork) -> v1::Network { + match network { + BtcNetwork::Testnet => v1::Network::Testnet, + BtcNetwork::Mainnet => v1::Network::Mainnet, + BtcNetwork::Regtest => v1::Network::Regtest, + } +} + +fn from_doge_network(network: DogeNetwork) -> v1::Network { + match network { + DogeNetwork::Testnet => v1::Network::DogecoinTestnet, + DogeNetwork::Mainnet => v1::Network::DogecoinMainnet, + DogeNetwork::Regtest => v1::Network::DogecoinRegtest, + } +} + +fn to_btc_network(network: i32) -> Result { + match v1::Network::try_from(network) { + Ok(v1::Network::Testnet) => Ok(BtcNetwork::Testnet), + Ok(v1::Network::Mainnet) => Ok(BtcNetwork::Mainnet), + Ok(v1::Network::Regtest) => Ok(BtcNetwork::Regtest), + _ => Err(ProxyDecodeError::MissingField("network")), + } +} + +fn to_doge_network(network: i32) -> Result { + match v1::Network::try_from(network) { + Ok(v1::Network::DogecoinTestnet) => Ok(DogeNetwork::Testnet), + Ok(v1::Network::DogecoinMainnet) => Ok(DogeNetwork::Mainnet), + Ok(v1::Network::DogecoinRegtest) => Ok(DogeNetwork::Regtest), + _ => Err(ProxyDecodeError::MissingField("network")), + } +} + #[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize, Serialize)] pub struct SendTransactionRequest { - pub network: Network, + pub network: BtcNetwork, #[serde(with = "serde_bytes")] pub transaction: Vec, } @@ -26,11 +85,7 @@ pub struct SendTransactionRequest { impl From<&SendTransactionRequest> for v1::SendTransactionRequest { fn from(request: &SendTransactionRequest) -> Self { Self { - network: match request.network { - Network::Testnet => 1, - Network::Mainnet => 2, - Network::Regtest => 3, - }, + network: from_btc_network(request.network) as i32, transaction: request.transaction.clone(), } } @@ -40,16 +95,33 @@ impl TryFrom for SendTransactionRequest { type Error = ProxyDecodeError; fn try_from(request: v1::SendTransactionRequest) -> Result { Ok(SendTransactionRequest { - network: match request.network { - 1 => Network::Testnet, - 2 => Network::Mainnet, - 3 => Network::Regtest, - _ => { - return Err(ProxyDecodeError::MissingField( - "SendTransactionRequest::network", - )) - } - }, + network: to_btc_network(request.network)?, + transaction: request.transaction, + }) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize, Serialize)] +pub struct SendDogeTransactionRequest { + pub network: DogeNetwork, + #[serde(with = "serde_bytes")] + pub transaction: Vec, +} + +impl From<&SendDogeTransactionRequest> for v1::SendTransactionRequest { + fn from(request: &SendDogeTransactionRequest) -> Self { + Self { + network: from_doge_network(request.network) as i32, + transaction: request.transaction.clone(), + } + } +} + +impl TryFrom for SendDogeTransactionRequest { + type Error = ProxyDecodeError; + fn try_from(request: v1::SendTransactionRequest) -> Result { + Ok(SendDogeTransactionRequest { + network: to_doge_network(request.network)?, transaction: request.transaction, }) } @@ -57,29 +129,80 @@ impl TryFrom for SendTransactionRequest { #[derive(Clone, Eq, PartialEq, Debug)] pub enum BitcoinAdapterRequestWrapper { - GetSuccessorsRequest(GetSuccessorsRequestInitial), - SendTransactionRequest(SendTransactionRequest), + GetBtcSuccessorsRequest(GetSuccessorsRequestInitial), + SendBtcTransactionRequest(SendTransactionRequest), + GetDogeSuccessorsRequest(GetDogeSuccessorsRequestInitial), + SendDogeTransactionRequest(SendDogeTransactionRequest), +} + +pub enum BitcoinAdapterRequestInner { + GetSuccessors { + anchor: BlockHash, + processed_block_hashes: Vec, + }, + SendTransaction { + transaction: Vec, + }, } impl BitcoinAdapterRequestWrapper { pub fn to_request_type_label(&self) -> &str { match self { - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(_) => "get_successors", - BitcoinAdapterRequestWrapper::SendTransactionRequest(_) => "send_transaction", + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(_) => "get_successors", + BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(_) => "send_transaction", + BitcoinAdapterRequestWrapper::GetDogeSuccessorsRequest(_) => "get_successors", + BitcoinAdapterRequestWrapper::SendDogeTransactionRequest(_) => "send_transaction", + } + } + + pub fn as_inner(self) -> BitcoinAdapterRequestInner { + match self { + BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(SendTransactionRequest { + transaction, + .. + }) => BitcoinAdapterRequestInner::SendTransaction { transaction }, + BitcoinAdapterRequestWrapper::SendDogeTransactionRequest( + SendDogeTransactionRequest { transaction, .. }, + ) => BitcoinAdapterRequestInner::SendTransaction { transaction }, + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest( + GetSuccessorsRequestInitial { + anchor, + processed_block_hashes, + .. + }, + ) => BitcoinAdapterRequestInner::GetSuccessors { + anchor, + processed_block_hashes, + }, + BitcoinAdapterRequestWrapper::GetDogeSuccessorsRequest( + GetDogeSuccessorsRequestInitial { + anchor, + processed_block_hashes, + .. + }, + ) => BitcoinAdapterRequestInner::GetSuccessors { + anchor, + processed_block_hashes, + }, } } /// Returns which bitcoin network the request is for. pub fn network(&self) -> Network { match self { - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(GetSuccessorsRequestInitial { - network, - .. - }) => *network, - BitcoinAdapterRequestWrapper::SendTransactionRequest(SendTransactionRequest { + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest( + GetSuccessorsRequestInitial { network, .. }, + ) => Network::Bitcoin(*network), + BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(SendTransactionRequest { network, .. - }) => *network, + }) => Network::Bitcoin(*network), + BitcoinAdapterRequestWrapper::GetDogeSuccessorsRequest( + GetDogeSuccessorsRequestInitial { network, .. }, + ) => Network::Dogecoin(*network), + BitcoinAdapterRequestWrapper::SendDogeTransactionRequest( + SendDogeTransactionRequest { network, .. }, + ) => Network::Dogecoin(*network), } } } @@ -87,7 +210,7 @@ impl BitcoinAdapterRequestWrapper { impl From<&BitcoinAdapterRequestWrapper> for v1::BitcoinAdapterRequestWrapper { fn from(request_wrapper: &BitcoinAdapterRequestWrapper) -> Self { match request_wrapper { - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(request) => { + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(request) => { v1::BitcoinAdapterRequestWrapper { r: Some( v1::bitcoin_adapter_request_wrapper::R::GetSuccessorsRequest( @@ -96,7 +219,25 @@ impl From<&BitcoinAdapterRequestWrapper> for v1::BitcoinAdapterRequestWrapper { ), } } - BitcoinAdapterRequestWrapper::SendTransactionRequest(request) => { + BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(request) => { + v1::BitcoinAdapterRequestWrapper { + r: Some( + v1::bitcoin_adapter_request_wrapper::R::SendTransactionRequest( + request.into(), + ), + ), + } + } + BitcoinAdapterRequestWrapper::GetDogeSuccessorsRequest(request) => { + v1::BitcoinAdapterRequestWrapper { + r: Some( + v1::bitcoin_adapter_request_wrapper::R::GetSuccessorsRequest( + request.into(), + ), + ), + } + } + BitcoinAdapterRequestWrapper::SendDogeTransactionRequest(request) => { v1::BitcoinAdapterRequestWrapper { r: Some( v1::bitcoin_adapter_request_wrapper::R::SendTransactionRequest( @@ -115,12 +256,26 @@ impl TryFrom for BitcoinAdapterRequestWrapper match request_wrapper.r.ok_or(ProxyDecodeError::MissingField( "BitcoinAdapterRequestWrapper::r", ))? { - v1::bitcoin_adapter_request_wrapper::R::GetSuccessorsRequest(r) => Ok( - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(r.try_into()?), - ), - v1::bitcoin_adapter_request_wrapper::R::SendTransactionRequest(r) => Ok( - BitcoinAdapterRequestWrapper::SendTransactionRequest(r.try_into()?), - ), + v1::bitcoin_adapter_request_wrapper::R::GetSuccessorsRequest(r) => { + Ok(match Network::try_from(r.network)? { + Network::Bitcoin(_) => { + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(r.try_into()?) + } + Network::Dogecoin(_) => { + BitcoinAdapterRequestWrapper::GetDogeSuccessorsRequest(r.try_into()?) + } + }) + } + v1::bitcoin_adapter_request_wrapper::R::SendTransactionRequest(r) => { + Ok(match Network::try_from(r.network)? { + Network::Bitcoin(_) => { + BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(r.try_into()?) + } + Network::Dogecoin(_) => { + BitcoinAdapterRequestWrapper::SendDogeTransactionRequest(r.try_into()?) + } + }) + } } } } @@ -646,7 +801,7 @@ pub enum GetSuccessorsRequest { #[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize, Serialize)] pub struct GetSuccessorsRequestInitial { - pub network: Network, + pub network: BtcNetwork, pub anchor: BlockHash, pub processed_block_hashes: Vec, } @@ -654,11 +809,7 @@ pub struct GetSuccessorsRequestInitial { impl From<&GetSuccessorsRequestInitial> for v1::GetSuccessorsRequestInitial { fn from(request: &GetSuccessorsRequestInitial) -> Self { Self { - network: match request.network { - Network::Testnet => 1, - Network::Mainnet => 2, - Network::Regtest => 3, - }, + network: from_btc_network(request.network) as i32, anchor: request.anchor.clone(), processed_block_hashes: request.processed_block_hashes.clone(), } @@ -669,16 +820,35 @@ impl TryFrom for GetSuccessorsRequestInitial { type Error = ProxyDecodeError; fn try_from(request: v1::GetSuccessorsRequestInitial) -> Result { Ok(GetSuccessorsRequestInitial { - network: match request.network { - 1 => Network::Testnet, - 2 => Network::Mainnet, - 3 => Network::Regtest, - _ => { - return Err(ProxyDecodeError::MissingField( - "GetSuccessorsRequestInitial::network", - )) - } - }, + network: to_btc_network(request.network)?, + anchor: request.anchor, + processed_block_hashes: request.processed_block_hashes, + }) + } +} + +#[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize, Serialize)] +pub struct GetDogeSuccessorsRequestInitial { + pub network: DogeNetwork, + pub anchor: BlockHash, + pub processed_block_hashes: Vec, +} + +impl From<&GetDogeSuccessorsRequestInitial> for v1::GetSuccessorsRequestInitial { + fn from(request: &GetDogeSuccessorsRequestInitial) -> Self { + Self { + network: from_doge_network(request.network) as i32, + anchor: request.anchor.clone(), + processed_block_hashes: request.processed_block_hashes.clone(), + } + } +} + +impl TryFrom for GetDogeSuccessorsRequestInitial { + type Error = ProxyDecodeError; + fn try_from(request: v1::GetSuccessorsRequestInitial) -> Result { + Ok(GetDogeSuccessorsRequestInitial { + network: to_doge_network(request.network)?, anchor: request.anchor, processed_block_hashes: request.processed_block_hashes, }) diff --git a/rs/config/src/adapters.rs b/rs/config/src/adapters.rs index c9a0ba7fd856..c6647b7cd04a 100644 --- a/rs/config/src/adapters.rs +++ b/rs/config/src/adapters.rs @@ -7,6 +7,10 @@ pub struct AdaptersConfig { pub bitcoin_mainnet_uds_metrics_path: Option, pub bitcoin_testnet_uds_path: Option, pub bitcoin_testnet_uds_metrics_path: Option, + pub dogecoin_mainnet_uds_path: Option, + pub dogecoin_mainnet_uds_metrics_path: Option, + pub dogecoin_testnet_uds_path: Option, + pub dogecoin_testnet_uds_metrics_path: Option, pub https_outcalls_uds_path: Option, pub https_outcalls_uds_metrics_path: Option, } diff --git a/rs/config/src/execution_environment.rs b/rs/config/src/execution_environment.rs index 4a2d146fa720..e15516aadaf3 100644 --- a/rs/config/src/execution_environment.rs +++ b/rs/config/src/execution_environment.rs @@ -279,6 +279,9 @@ pub struct Config { /// Bitcoin configuration. pub bitcoin: BitcoinConfig, + /// Dogecoin configuration. + pub dogecoin: DogecoinConfig, + /// Indicates whether composite queries are available or not. pub composite_queries: FlagStatus, @@ -397,6 +400,7 @@ impl Default for Config { testnet_canister_id: Some(bitcoin_testnet_canister_id), mainnet_canister_id: Some(bitcoin_mainnet_canister_id), }, + dogecoin: DogecoinConfig::default(), composite_queries: FlagStatus::Enabled, query_caching: FlagStatus::Enabled, query_cache_capacity: QUERY_CACHE_CAPACITY, @@ -430,3 +434,17 @@ pub struct BitcoinConfig { /// The bitcoin mainnet canister to forward requests to. pub mainnet_canister_id: Option, } + +#[derive(Clone, Eq, PartialEq, Debug, Default, Deserialize, Serialize)] +pub struct DogecoinConfig { + /// Canisters that have access to privileged dogecoin API (e.g. `dogecoin_get_successors`) + /// This list is intentionally separate from the dogecoin canister IDs below because it + /// allows us to spin up new dogecoin canisters without necessarily routing requests to them. + pub privileged_access: Vec, + + /// The dogecoin testnet canister to forward requests to. + pub testnet_canister_id: Option, + + /// The dogecoin mainnet canister to forward requests to. + pub mainnet_canister_id: Option, +} diff --git a/rs/http_endpoints/public/tests/common/mod.rs b/rs/http_endpoints/public/tests/common/mod.rs index 29fc0cb17a67..6737edaeaba4 100644 --- a/rs/http_endpoints/public/tests/common/mod.rs +++ b/rs/http_endpoints/public/tests/common/mod.rs @@ -198,6 +198,8 @@ pub fn default_get_latest_state() -> Labeled> { chain_key_enabled_subnets: Default::default(), bitcoin_mainnet_canister_id: None, bitcoin_testnet_canister_id: None, + dogecoin_mainnet_canister_id: None, + dogecoin_testnet_canister_id: None, }; metadata.network_topology = network_topology; diff --git a/rs/messaging/src/message_routing.rs b/rs/messaging/src/message_routing.rs index 71b8447b26d7..db23b2436ff5 100644 --- a/rs/messaging/src/message_routing.rs +++ b/rs/messaging/src/message_routing.rs @@ -1,6 +1,6 @@ use crate::state_machine::{StateMachine, StateMachineImpl}; use crate::{routing, scheduling}; -use ic_config::execution_environment::{BitcoinConfig, Config as HypervisorConfig}; +use ic_config::execution_environment::{BitcoinConfig, Config as HypervisorConfig, DogecoinConfig}; use ic_config::message_routing::{MAX_STREAM_MESSAGES, TARGET_STREAM_SIZE_BYTES}; use ic_cycles_account_manager::CyclesAccountManager; use ic_interfaces::execution_environment::{ @@ -590,6 +590,7 @@ where state_machine: Box, registry: Arc, bitcoin_config: BitcoinConfig, + dogecoin_config: DogecoinConfig, metrics: MessageRoutingMetrics, log: ReplicaLogger, #[allow(dead_code)] @@ -712,6 +713,7 @@ impl BatchProcessorImpl { state_machine, registry, bitcoin_config: hypervisor_config.bitcoin, + dogecoin_config: hypervisor_config.dogecoin, metrics, log, malicious_flags, @@ -1086,6 +1088,8 @@ impl BatchProcessorImpl { chain_key_enabled_subnets, bitcoin_testnet_canister_id: self.bitcoin_config.testnet_canister_id, bitcoin_mainnet_canister_id: self.bitcoin_config.mainnet_canister_id, + dogecoin_testnet_canister_id: self.dogecoin_config.testnet_canister_id, + dogecoin_mainnet_canister_id: self.dogecoin_config.mainnet_canister_id, }) } diff --git a/rs/messaging/src/message_routing/tests.rs b/rs/messaging/src/message_routing/tests.rs index e496eb3c9e3e..839a44c8fb9c 100644 --- a/rs/messaging/src/message_routing/tests.rs +++ b/rs/messaging/src/message_routing/tests.rs @@ -681,6 +681,7 @@ fn make_batch_processor( state_machine: Box::new(FakeStateMachine(registry_settings.clone())), registry, bitcoin_config: BitcoinConfig::default(), + dogecoin_config: DogecoinConfig::default(), metrics: metrics.clone(), log, malicious_flags: MaliciousFlags::default(), diff --git a/rs/protobuf/def/bitcoin/v1/bitcoin.proto b/rs/protobuf/def/bitcoin/v1/bitcoin.proto index 2c5f36818ae9..8568b1bd05ea 100644 --- a/rs/protobuf/def/bitcoin/v1/bitcoin.proto +++ b/rs/protobuf/def/bitcoin/v1/bitcoin.proto @@ -96,6 +96,9 @@ enum Network { NETWORK_TESTNET = 1; NETWORK_MAINNET = 2; NETWORK_REGTEST = 3; + NETWORK_DOGECOIN_MAINNET = 4; + NETWORK_DOGECOIN_TESTNET = 5; + NETWORK_DOGECOIN_REGTEST = 6; } // A request to retrieve new blocks from the specified Bitcoin network. diff --git a/rs/protobuf/def/state/metadata/v1/metadata.proto b/rs/protobuf/def/state/metadata/v1/metadata.proto index 7f9d5c19ca9d..5685fbde8244 100644 --- a/rs/protobuf/def/state/metadata/v1/metadata.proto +++ b/rs/protobuf/def/state/metadata/v1/metadata.proto @@ -53,6 +53,8 @@ message NetworkTopology { repeated types.v1.CanisterId bitcoin_testnet_canister_ids = 6; repeated types.v1.CanisterId bitcoin_mainnet_canister_ids = 7; repeated ChainKeySubnetEntry chain_key_enabled_subnets = 8; + repeated types.v1.CanisterId dogecoin_testnet_canister_ids = 9; + repeated types.v1.CanisterId dogecoin_mainnet_canister_ids = 10; } message SetupInitialDkgContext { @@ -271,6 +273,8 @@ message SubnetCallContextManager { repeated ReshareChainKeyContextTree reshare_chain_key_contexts = 17; repeated SignWithThresholdContextTree sign_with_threshold_contexts = 18; repeated PreSignatureStashTree pre_signature_stashes = 19; + repeated BitcoinGetSuccessorsContextTree dogecoin_get_successors_contexts = 20; + repeated BitcoinSendTransactionInternalContextTree dogecoin_send_transaction_internal_contexts = 21; } message SubnetMetrics { @@ -371,6 +375,9 @@ message SystemMetadata { BlockmakerMetricsTimeSeries blockmaker_metrics_time_series = 20; repeated ApiBoundaryNodeEntry api_boundary_nodes = 21; + + repeated BitcoinGetSuccessorsFollowUpResponses dogecoin_get_successors_follow_up_responses = 22; + } message StableMemory { diff --git a/rs/protobuf/src/gen/bitcoin/bitcoin.v1.rs b/rs/protobuf/src/gen/bitcoin/bitcoin.v1.rs index fd3afaec7d4a..d6ecb01aff53 100644 --- a/rs/protobuf/src/gen/bitcoin/bitcoin.v1.rs +++ b/rs/protobuf/src/gen/bitcoin/bitcoin.v1.rs @@ -179,6 +179,9 @@ pub enum Network { Testnet = 1, Mainnet = 2, Regtest = 3, + DogecoinMainnet = 4, + DogecoinTestnet = 5, + DogecoinRegtest = 6, } impl Network { /// String value of the enum field names used in the ProtoBuf definition. @@ -191,6 +194,9 @@ impl Network { Self::Testnet => "NETWORK_TESTNET", Self::Mainnet => "NETWORK_MAINNET", Self::Regtest => "NETWORK_REGTEST", + Self::DogecoinMainnet => "NETWORK_DOGECOIN_MAINNET", + Self::DogecoinTestnet => "NETWORK_DOGECOIN_TESTNET", + Self::DogecoinRegtest => "NETWORK_DOGECOIN_REGTEST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -200,6 +206,9 @@ impl Network { "NETWORK_TESTNET" => Some(Self::Testnet), "NETWORK_MAINNET" => Some(Self::Mainnet), "NETWORK_REGTEST" => Some(Self::Regtest), + "NETWORK_DOGECOIN_MAINNET" => Some(Self::DogecoinMainnet), + "NETWORK_DOGECOIN_TESTNET" => Some(Self::DogecoinTestnet), + "NETWORK_DOGECOIN_REGTEST" => Some(Self::DogecoinRegtest), _ => None, } } diff --git a/rs/protobuf/src/gen/state/bitcoin.v1.rs b/rs/protobuf/src/gen/state/bitcoin.v1.rs index 52db3d8c7541..dae80a2afdd4 100644 --- a/rs/protobuf/src/gen/state/bitcoin.v1.rs +++ b/rs/protobuf/src/gen/state/bitcoin.v1.rs @@ -167,6 +167,9 @@ pub enum Network { Testnet = 1, Mainnet = 2, Regtest = 3, + DogecoinMainnet = 4, + DogecoinTestnet = 5, + DogecoinRegtest = 6, } impl Network { /// String value of the enum field names used in the ProtoBuf definition. @@ -179,6 +182,9 @@ impl Network { Self::Testnet => "NETWORK_TESTNET", Self::Mainnet => "NETWORK_MAINNET", Self::Regtest => "NETWORK_REGTEST", + Self::DogecoinMainnet => "NETWORK_DOGECOIN_MAINNET", + Self::DogecoinTestnet => "NETWORK_DOGECOIN_TESTNET", + Self::DogecoinRegtest => "NETWORK_DOGECOIN_REGTEST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -188,6 +194,9 @@ impl Network { "NETWORK_TESTNET" => Some(Self::Testnet), "NETWORK_MAINNET" => Some(Self::Mainnet), "NETWORK_REGTEST" => Some(Self::Regtest), + "NETWORK_DOGECOIN_MAINNET" => Some(Self::DogecoinMainnet), + "NETWORK_DOGECOIN_TESTNET" => Some(Self::DogecoinTestnet), + "NETWORK_DOGECOIN_REGTEST" => Some(Self::DogecoinRegtest), _ => None, } } diff --git a/rs/protobuf/src/gen/state/state.metadata.v1.rs b/rs/protobuf/src/gen/state/state.metadata.v1.rs index 5dbf2b34bf7d..a43466f925a2 100644 --- a/rs/protobuf/src/gen/state/state.metadata.v1.rs +++ b/rs/protobuf/src/gen/state/state.metadata.v1.rs @@ -64,6 +64,12 @@ pub struct NetworkTopology { ::prost::alloc::vec::Vec, #[prost(message, repeated, tag = "8")] pub chain_key_enabled_subnets: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "9")] + pub dogecoin_testnet_canister_ids: + ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "10")] + pub dogecoin_mainnet_canister_ids: + ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct SetupInitialDkgContext { @@ -392,6 +398,11 @@ pub struct SubnetCallContextManager { pub sign_with_threshold_contexts: ::prost::alloc::vec::Vec, #[prost(message, repeated, tag = "19")] pub pre_signature_stashes: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "20")] + pub dogecoin_get_successors_contexts: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "21")] + pub dogecoin_send_transaction_internal_contexts: + ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct SubnetMetrics { @@ -524,6 +535,9 @@ pub struct SystemMetadata { pub blockmaker_metrics_time_series: ::core::option::Option, #[prost(message, repeated, tag = "21")] pub api_boundary_nodes: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "22")] + pub dogecoin_get_successors_follow_up_responses: + ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct StableMemory { diff --git a/rs/protobuf/src/gen/types/bitcoin.v1.rs b/rs/protobuf/src/gen/types/bitcoin.v1.rs index 52db3d8c7541..dae80a2afdd4 100644 --- a/rs/protobuf/src/gen/types/bitcoin.v1.rs +++ b/rs/protobuf/src/gen/types/bitcoin.v1.rs @@ -167,6 +167,9 @@ pub enum Network { Testnet = 1, Mainnet = 2, Regtest = 3, + DogecoinMainnet = 4, + DogecoinTestnet = 5, + DogecoinRegtest = 6, } impl Network { /// String value of the enum field names used in the ProtoBuf definition. @@ -179,6 +182,9 @@ impl Network { Self::Testnet => "NETWORK_TESTNET", Self::Mainnet => "NETWORK_MAINNET", Self::Regtest => "NETWORK_REGTEST", + Self::DogecoinMainnet => "NETWORK_DOGECOIN_MAINNET", + Self::DogecoinTestnet => "NETWORK_DOGECOIN_TESTNET", + Self::DogecoinRegtest => "NETWORK_DOGECOIN_REGTEST", } } /// Creates an enum from field names used in the ProtoBuf definition. @@ -188,6 +194,9 @@ impl Network { "NETWORK_TESTNET" => Some(Self::Testnet), "NETWORK_MAINNET" => Some(Self::Mainnet), "NETWORK_REGTEST" => Some(Self::Regtest), + "NETWORK_DOGECOIN_MAINNET" => Some(Self::DogecoinMainnet), + "NETWORK_DOGECOIN_TESTNET" => Some(Self::DogecoinTestnet), + "NETWORK_DOGECOIN_REGTEST" => Some(Self::DogecoinRegtest), _ => None, } } diff --git a/rs/replica/src/setup_ic_stack.rs b/rs/replica/src/setup_ic_stack.rs index be8c07a2bb24..4ddaf6a9365b 100755 --- a/rs/replica/src/setup_ic_stack.rs +++ b/rs/replica/src/setup_ic_stack.rs @@ -2,7 +2,10 @@ use crate::setup::get_subnet_type; use ic_artifact_pool::{ consensus_pool::ConsensusPoolImpl, ensure_persistent_pool_replica_version_compatibility, }; -use ic_btc_adapter_client::{setup_bitcoin_adapter_clients, BitcoinAdapterClients}; +use ic_btc_adapter_client::{ + setup_bitcoin_adapter_clients, setup_dogecoin_adapter_clients, BitcoinAdapterClients, + DogecoinAdapterClients, +}; use ic_btc_consensus::BitcoinPayloadBuilder; use ic_config::{artifact_pool::ArtifactPoolConfig, subnet_config::SubnetConfig, Config}; use ic_consensus_certification::VerifierImpl; @@ -268,11 +271,22 @@ pub fn construct_ic_stack( rt_handle_main.clone(), config.adapters_config.clone(), ); + let DogecoinAdapterClients { + doge_testnet_client, + doge_mainnet_client, + } = setup_dogecoin_adapter_clients( + log.clone(), + metrics_registry, + rt_handle_main.clone(), + config.adapters_config.clone(), + ); let self_validating_payload_builder = Arc::new(BitcoinPayloadBuilder::new( state_manager.clone(), metrics_registry, btc_mainnet_client, btc_testnet_client, + doge_mainnet_client, + doge_testnet_client, subnet_id, registry.clone(), config.bitcoin_payload_builder_config, diff --git a/rs/replicated_state/src/bitcoin.rs b/rs/replicated_state/src/bitcoin.rs index ded3e2147d50..dc5191b90d2b 100644 --- a/rs/replicated_state/src/bitcoin.rs +++ b/rs/replicated_state/src/bitcoin.rs @@ -20,7 +20,7 @@ use std::cmp::min; const MAX_RESPONSE_SIZE: usize = 2_000_000; /// Pushes a response from the Bitcoin Adapter into the state. -pub fn push_response( +pub fn push_response_bitcoin( state: &mut ReplicatedState, response: BitcoinAdapterResponse, ) -> Result<(), StateError> { @@ -103,6 +103,90 @@ pub fn push_response( } } +/// Pushes a response from the Bitcoin Adapter into the state. +pub fn push_response_dogecoin( + state: &mut ReplicatedState, + response: BitcoinAdapterResponse, +) -> Result<(), StateError> { + match response.response { + BitcoinAdapterResponseWrapper::GetSuccessorsResponse(r) => { + // Received a response to a request from the dogecoin wasm canister. + // Retrieve the associated request. + let callback_id = CallbackId::from(response.callback_id); + let context = state + .metadata + .subnet_call_context_manager + .dogecoin_get_successors_contexts + .get_mut(&callback_id) + .ok_or_else(|| StateError::BitcoinNonMatchingResponse { + callback_id: callback_id.get(), + })?; + + let payload = match maybe_split_response(r) { + Ok((initial_response, follow_ups)) => { + // Store the follow-ups for later (overwrites previous ones). + state + .metadata + .dogecoin_get_successors_follow_up_responses + .insert(context.request.sender(), follow_ups); + + Payload::Data(initial_response.encode()) + } + Err(err) => Payload::Reject(RejectContext::new( + RejectCode::CanisterError, + format!("Received invalid response from adapter: {:?}", err), + )), + }; + + // Add response to the consensus queue. + state + .consensus_queue + .push(ConsensusResponse::new(callback_id, payload)); + + Ok(()) + } + BitcoinAdapterResponseWrapper::SendTransactionResponse(_) => { + // Retrieve the associated request from the call context manager. + let callback_id = CallbackId::from(response.callback_id); + // The response to a `send_transaction` call is always the empty blob. + let payload = Payload::Data(EmptyBlob.encode()); + + // Add response to the consensus queue. + state + .consensus_queue + .push(ConsensusResponse::new(callback_id, payload)); + + Ok(()) + } + BitcoinAdapterResponseWrapper::GetSuccessorsReject(reject) => { + // Retrieve the associated request from the call context manager. + let callback_id = CallbackId::from(response.callback_id); + let reject_payload = + Payload::Reject(RejectContext::new(reject.reject_code, reject.message)); + + // Add response to the consensus queue. + state + .consensus_queue + .push(ConsensusResponse::new(callback_id, reject_payload)); + + Ok(()) + } + BitcoinAdapterResponseWrapper::SendTransactionReject(reject) => { + // Retrieve the associated request from the call context manager. + let callback_id = CallbackId::from(response.callback_id); + let reject_payload = + Payload::Reject(RejectContext::new(reject.reject_code, reject.message)); + + // Add response to the consensus queue. + state + .consensus_queue + .push(ConsensusResponse::new(callback_id, reject_payload)); + + Ok(()) + } + } +} + #[derive(Eq, PartialEq, Debug)] enum SplitError { NotOneBlock, diff --git a/rs/replicated_state/src/metadata_state.rs b/rs/replicated_state/src/metadata_state.rs index 19c2894eb767..e45ef9eea79b 100644 --- a/rs/replicated_state/src/metadata_state.rs +++ b/rs/replicated_state/src/metadata_state.rs @@ -171,6 +171,12 @@ pub struct SystemMetadata { #[validate_eq(Ignore)] pub bitcoin_get_successors_follow_up_responses: BTreeMap>, + /// Responses to `DogecoinGetSuccessors` can be larger than the max inter-canister + /// response limit. To work around this limitation, large responses are paginated + /// and are stored here temporarily until they're fetched by the calling canister. + #[validate_eq(Ignore)] + pub dogecoin_get_successors_follow_up_responses: BTreeMap>, + /// Metrics collecting blockmaker stats (block proposed and failures to propose a block) /// by aggregating them and storing a running total over multiple days by node id and /// timestamp. Observations of blockmaker stats are performed each time a batch is processed. @@ -205,6 +211,12 @@ pub struct NetworkTopology { /// The ID of the canister to forward bitcoin mainnet requests to. pub bitcoin_mainnet_canister_id: Option, + + /// The ID of the canister to forward dogecoin testnet requests to. + pub dogecoin_testnet_canister_id: Option, + + /// The ID of the canister to forward dogecoin mainnet requests to. + pub dogecoin_mainnet_canister_id: Option, } /// Full description of the API Boundary Node, which is saved in the metadata. @@ -232,6 +244,8 @@ impl Default for NetworkTopology { chain_key_enabled_subnets: Default::default(), bitcoin_testnet_canister_id: None, bitcoin_mainnet_canister_id: None, + dogecoin_testnet_canister_id: None, + dogecoin_mainnet_canister_id: None, } } } @@ -274,6 +288,14 @@ impl From<&NetworkTopology> for pb_metadata::NetworkTopology { Some(c) => vec![pb_types::CanisterId::from(c)], None => vec![], }, + dogecoin_testnet_canister_ids: match item.dogecoin_testnet_canister_id { + Some(c) => vec![pb_types::CanisterId::from(c)], + None => vec![], + }, + dogecoin_mainnet_canister_ids: match item.dogecoin_mainnet_canister_id { + Some(c) => vec![pb_types::CanisterId::from(c)], + None => vec![], + }, chain_key_enabled_subnets: item .chain_key_enabled_subnets .iter() @@ -333,6 +355,16 @@ impl TryFrom for NetworkTopology { None => None, }; + let dogecoin_testnet_canister_id = match item.dogecoin_testnet_canister_ids.first() { + Some(canister) => Some(CanisterId::try_from(canister.clone())?), + None => None, + }; + + let dogecoin_mainnet_canister_id = match item.dogecoin_mainnet_canister_ids.first() { + Some(canister) => Some(CanisterId::try_from(canister.clone())?), + None => None, + }; + Ok(Self { subnets, routing_table: try_from_option_field( @@ -351,6 +383,8 @@ impl TryFrom for NetworkTopology { chain_key_enabled_subnets, bitcoin_testnet_canister_id, bitcoin_mainnet_canister_id, + dogecoin_testnet_canister_id, + dogecoin_mainnet_canister_id, }) } } @@ -619,6 +653,17 @@ impl From<&SystemMetadata> for pb_metadata::SystemMetadata { }, ) .collect(), + dogecoin_get_successors_follow_up_responses: item + .dogecoin_get_successors_follow_up_responses + .clone() + .into_iter() + .map( + |(sender, payloads)| pb_metadata::BitcoinGetSuccessorsFollowUpResponses { + sender: Some(sender.into()), + payloads, + }, + ) + .collect(), node_public_keys: item .node_public_keys .iter() @@ -697,6 +742,18 @@ impl TryFrom<(pb_metadata::SystemMetadata, &dyn CheckpointLoadingMetrics)> for S bitcoin_get_successors_follow_up_responses.insert(sender, response.payloads); } + let mut dogecoin_get_successors_follow_up_responses = BTreeMap::new(); + for response in item.dogecoin_get_successors_follow_up_responses { + let sender_pb: pb_types::CanisterId = try_from_option_field( + response.sender, + "BitcoinGetSuccessorsFollowUpResponses::sender", + )?; + + let sender = CanisterId::try_from(sender_pb)?; + + dogecoin_get_successors_follow_up_responses.insert(sender, response.payloads); + } + let batch_time = Time::from_nanos_since_unix_epoch(item.batch_time_nanos); let mut node_public_keys = BTreeMap::>::new(); @@ -763,6 +820,7 @@ impl TryFrom<(pb_metadata::SystemMetadata, &dyn CheckpointLoadingMetrics)> for S }, expected_compiled_wasms: BTreeSet::new(), bitcoin_get_successors_follow_up_responses, + dogecoin_get_successors_follow_up_responses, blockmaker_metrics_time_series: match item.blockmaker_metrics_time_series { Some(blockmaker_metrics) => (blockmaker_metrics, metrics).try_into()?, None => BlockmakerMetricsTimeSeries::default(), @@ -798,6 +856,7 @@ impl SystemMetadata { subnet_metrics: Default::default(), expected_compiled_wasms: BTreeSet::new(), bitcoin_get_successors_follow_up_responses: BTreeMap::default(), + dogecoin_get_successors_follow_up_responses: BTreeMap::default(), blockmaker_metrics_time_series: BlockmakerMetricsTimeSeries::default(), unflushed_checkpoint_ops: Default::default(), } @@ -1077,6 +1136,7 @@ impl SystemMetadata { subnet_metrics: _, ref expected_compiled_wasms, bitcoin_get_successors_follow_up_responses: _, + dogecoin_get_successors_follow_up_responses: _, blockmaker_metrics_time_series: _, unflushed_checkpoint_ops: _, } = self; @@ -2314,6 +2374,7 @@ pub(crate) mod testing { subnet_metrics: Default::default(), expected_compiled_wasms: Default::default(), bitcoin_get_successors_follow_up_responses: Default::default(), + dogecoin_get_successors_follow_up_responses: Default::default(), blockmaker_metrics_time_series: BlockmakerMetricsTimeSeries::default(), unflushed_checkpoint_ops: Default::default(), }; diff --git a/rs/replicated_state/src/metadata_state/subnet_call_context_manager.rs b/rs/replicated_state/src/metadata_state/subnet_call_context_manager.rs index 2f92f9e54eaf..39ab3e64e486 100644 --- a/rs/replicated_state/src/metadata_state/subnet_call_context_manager.rs +++ b/rs/replicated_state/src/metadata_state/subnet_call_context_manager.rs @@ -1,4 +1,7 @@ -use ic_btc_replica_types::{GetSuccessorsRequestInitial, SendTransactionRequest}; +use ic_btc_replica_types::{ + GetDogeSuccessorsRequestInitial, GetSuccessorsRequestInitial, SendDogeTransactionRequest, + SendTransactionRequest, +}; use ic_logger::{info, ReplicaLogger}; use ic_management_canister_types_private::{ EcdsaKeyId, MasterPublicKeyId, SchnorrKeyId, VetKdKeyId, @@ -42,6 +45,8 @@ pub enum SubnetCallContext { ReshareChainKey(ReshareChainKeyContext), BitcoinGetSuccessors(BitcoinGetSuccessorsContext), BitcoinSendTransactionInternal(BitcoinSendTransactionInternalContext), + DogecoinGetSuccessors(DogecoinGetSuccessorsContext), + DogecoinSendTransactionInternal(DogecoinSendTransactionInternalContext), SignWithThreshold(SignWithThresholdContext), } @@ -53,6 +58,8 @@ impl SubnetCallContext { SubnetCallContext::ReshareChainKey(context) => &context.request, SubnetCallContext::BitcoinGetSuccessors(context) => &context.request, SubnetCallContext::BitcoinSendTransactionInternal(context) => &context.request, + SubnetCallContext::DogecoinGetSuccessors(context) => &context.request, + SubnetCallContext::DogecoinSendTransactionInternal(context) => &context.request, SubnetCallContext::SignWithThreshold(context) => &context.request, } } @@ -64,6 +71,8 @@ impl SubnetCallContext { SubnetCallContext::ReshareChainKey(context) => context.time, SubnetCallContext::BitcoinGetSuccessors(context) => context.time, SubnetCallContext::BitcoinSendTransactionInternal(context) => context.time, + SubnetCallContext::DogecoinGetSuccessors(context) => context.time, + SubnetCallContext::DogecoinSendTransactionInternal(context) => context.time, SubnetCallContext::SignWithThreshold(context) => context.batch_time, } } @@ -226,6 +235,9 @@ pub struct SubnetCallContextManager { pub bitcoin_get_successors_contexts: BTreeMap, pub bitcoin_send_transaction_internal_contexts: BTreeMap, + pub dogecoin_get_successors_contexts: BTreeMap, + pub dogecoin_send_transaction_internal_contexts: + BTreeMap, canister_management_calls: CanisterManagementCalls, pub raw_rand_contexts: VecDeque, pub pre_signature_stashes: BTreeMap, @@ -263,6 +275,14 @@ impl SubnetCallContextManager { self.bitcoin_send_transaction_internal_contexts .insert(callback_id, context); } + SubnetCallContext::DogecoinGetSuccessors(context) => { + self.dogecoin_get_successors_contexts + .insert(callback_id, context); + } + SubnetCallContext::DogecoinSendTransactionInternal(context) => { + self.dogecoin_send_transaction_internal_contexts + .insert(callback_id, context); + } }; callback_id @@ -551,6 +571,26 @@ impl From<&SubnetCallContextManager> for pb_metadata::SubnetCallContextManager { } }) .collect(), + dogecoin_get_successors_contexts: item + .dogecoin_get_successors_contexts + .iter() + .map( + |(callback_id, context)| pb_metadata::BitcoinGetSuccessorsContextTree { + callback_id: callback_id.get(), + context: Some(context.into()), + }, + ) + .collect(), + dogecoin_send_transaction_internal_contexts: item + .dogecoin_send_transaction_internal_contexts + .iter() + .map(|(callback_id, context)| { + pb_metadata::BitcoinSendTransactionInternalContextTree { + callback_id: callback_id.get(), + context: Some(context.into()), + } + }) + .collect(), install_code_calls: item .canister_management_calls .install_code_call_manager @@ -689,6 +729,29 @@ impl TryFrom<(Time, pb_metadata::SubnetCallContextManager)> for SubnetCallContex .insert(CallbackId::new(entry.callback_id), context); } + let mut dogecoin_get_successors_contexts = + BTreeMap::::new(); + for entry in item.dogecoin_get_successors_contexts { + let pb_context = try_from_option_field( + entry.context, + "SystemMetadata::DogecoinGetSuccessorsContext", + )?; + let context = DogecoinGetSuccessorsContext::try_from((time, pb_context))?; + dogecoin_get_successors_contexts.insert(CallbackId::new(entry.callback_id), context); + } + + let mut dogecoin_send_transaction_internal_contexts = + BTreeMap::::new(); + for entry in item.dogecoin_send_transaction_internal_contexts { + let pb_context = try_from_option_field( + entry.context, + "SystemMetadata::DogecoinSendTransactionInternalContext", + )?; + let context = DogecoinSendTransactionInternalContext::try_from((time, pb_context))?; + dogecoin_send_transaction_internal_contexts + .insert(CallbackId::new(entry.callback_id), context); + } + let mut install_code_calls = BTreeMap::::new(); // TODO(EXC-1454): Remove when `install_code_requests` field is not needed. for entry in item.install_code_requests { @@ -733,6 +796,8 @@ impl TryFrom<(Time, pb_metadata::SubnetCallContextManager)> for SubnetCallContex canister_http_request_contexts, bitcoin_get_successors_contexts, bitcoin_send_transaction_internal_contexts, + dogecoin_get_successors_contexts, + dogecoin_send_transaction_internal_contexts, canister_management_calls: CanisterManagementCalls { install_code_call_manager, stop_canister_call_manager, @@ -1231,6 +1296,44 @@ impl TryFrom<(Time, pb_metadata::BitcoinGetSuccessorsContext)> for BitcoinGetSuc } } +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct DogecoinGetSuccessorsContext { + pub request: Request, + pub payload: GetDogeSuccessorsRequestInitial, + pub time: Time, +} + +impl From<&DogecoinGetSuccessorsContext> for pb_metadata::BitcoinGetSuccessorsContext { + fn from(context: &DogecoinGetSuccessorsContext) -> Self { + Self { + request: Some((&context.request).into()), + payload: Some((&context.payload).into()), + time: Some(pb_metadata::Time { + time_nanos: context.time.as_nanos_since_unix_epoch(), + }), + } + } +} + +impl TryFrom<(Time, pb_metadata::BitcoinGetSuccessorsContext)> for DogecoinGetSuccessorsContext { + type Error = ProxyDecodeError; + fn try_from( + (time, context): (Time, pb_metadata::BitcoinGetSuccessorsContext), + ) -> Result { + let request: Request = + try_from_option_field(context.request, "BitcoinGetSuccessorsContext::request")?; + let payload: GetDogeSuccessorsRequestInitial = + try_from_option_field(context.payload, "BitcoinGetSuccessorsContext::payload")?; + Ok(DogecoinGetSuccessorsContext { + request, + payload, + time: context + .time + .map_or(time, |t| Time::from_nanos_since_unix_epoch(t.time_nanos)), + }) + } +} + #[derive(Clone, Eq, PartialEq, Debug)] pub struct BitcoinSendTransactionInternalContext { pub request: Request, @@ -1273,6 +1376,48 @@ impl TryFrom<(Time, pb_metadata::BitcoinSendTransactionInternalContext)> } } +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct DogecoinSendTransactionInternalContext { + pub request: Request, + pub payload: SendDogeTransactionRequest, + pub time: Time, +} + +impl From<&DogecoinSendTransactionInternalContext> + for pb_metadata::BitcoinSendTransactionInternalContext +{ + fn from(context: &DogecoinSendTransactionInternalContext) -> Self { + Self { + request: Some((&context.request).into()), + payload: Some((&context.payload).into()), + time: Some(pb_metadata::Time { + time_nanos: context.time.as_nanos_since_unix_epoch(), + }), + } + } +} + +impl TryFrom<(Time, pb_metadata::BitcoinSendTransactionInternalContext)> + for DogecoinSendTransactionInternalContext +{ + type Error = ProxyDecodeError; + fn try_from( + (time, context): (Time, pb_metadata::BitcoinSendTransactionInternalContext), + ) -> Result { + let request: Request = + try_from_option_field(context.request, "BitcoinGetSuccessorsContext::request")?; + let payload: SendDogeTransactionRequest = + try_from_option_field(context.payload, "BitcoinGetSuccessorsContext::payload")?; + Ok(DogecoinSendTransactionInternalContext { + request, + payload, + time: context + .time + .map_or(time, |t| Time::from_nanos_since_unix_epoch(t.time_nanos)), + }) + } +} + #[derive(Clone, Eq, PartialEq, Debug)] pub struct InstallCodeCall { pub call: CanisterCall, @@ -1484,6 +1629,8 @@ mod testing { reshare_chain_key_contexts: Default::default(), bitcoin_get_successors_contexts: Default::default(), bitcoin_send_transaction_internal_contexts: Default::default(), + dogecoin_get_successors_contexts: Default::default(), + dogecoin_send_transaction_internal_contexts: Default::default(), canister_management_calls, raw_rand_contexts: Default::default(), pre_signature_stashes: Default::default(), diff --git a/rs/replicated_state/src/replicated_state.rs b/rs/replicated_state/src/replicated_state.rs index 5ffb9448931c..3eb308d5266e 100644 --- a/rs/replicated_state/src/replicated_state.rs +++ b/rs/replicated_state/src/replicated_state.rs @@ -1065,7 +1065,15 @@ impl ReplicatedState { &mut self, response: BitcoinAdapterResponse, ) -> Result<(), StateError> { - crate::bitcoin::push_response(self, response) + crate::bitcoin::push_response_bitcoin(self, response) + } + + /// Pushes a response from the Bitcoin Adapter into the state. + pub fn push_response_dogecoin( + &mut self, + response: BitcoinAdapterResponse, + ) -> Result<(), StateError> { + crate::bitcoin::push_response_dogecoin(self, response) } /// Times out all messages with expired deadlines (given the state time) in all diff --git a/rs/state_machine_tests/src/lib.rs b/rs/state_machine_tests/src/lib.rs index 422d16941803..0a2f51a6f468 100644 --- a/rs/state_machine_tests/src/lib.rs +++ b/rs/state_machine_tests/src/lib.rs @@ -2,7 +2,7 @@ use candid::Decode; use core::sync::atomic::Ordering; use ed25519_dalek::{pkcs8::EncodePrivateKey, SigningKey}; use ic_artifact_pool::canister_http_pool::CanisterHttpPoolImpl; -use ic_btc_adapter_client::setup_bitcoin_adapter_clients; +use ic_btc_adapter_client::{setup_bitcoin_adapter_clients, setup_dogecoin_adapter_clients}; use ic_btc_consensus::BitcoinPayloadBuilder; use ic_config::{ adapters::AdaptersConfig, @@ -1384,15 +1384,15 @@ impl StateMachineBuilder { sm.replica_logger.clone(), )); - let adapters_config = AdaptersConfig { - bitcoin_mainnet_uds_path: None, - bitcoin_mainnet_uds_metrics_path: None, - bitcoin_testnet_uds_path, - bitcoin_testnet_uds_metrics_path: None, - https_outcalls_uds_path: None, - https_outcalls_uds_metrics_path: None, - }; + let mut adapters_config = AdaptersConfig::default(); + adapters_config.bitcoin_testnet_uds_path = bitcoin_testnet_uds_path; let bitcoin_clients = setup_bitcoin_adapter_clients( + sm.replica_logger.clone(), + &sm.metrics_registry, + sm.runtime.handle().clone(), + adapters_config.clone(), + ); + let dogecoin_clients = setup_dogecoin_adapter_clients( sm.replica_logger.clone(), &sm.metrics_registry, sm.runtime.handle().clone(), @@ -1403,6 +1403,8 @@ impl StateMachineBuilder { &sm.metrics_registry, bitcoin_clients.btc_mainnet_client, bitcoin_clients.btc_testnet_client, + dogecoin_clients.doge_mainnet_client, + dogecoin_clients.doge_testnet_client, sm.subnet_id, sm.registry_client.clone(), BitcoinPayloadBuilderConfig::default(), diff --git a/rs/test_utilities/state/src/lib.rs b/rs/test_utilities/state/src/lib.rs index 4d5076f9052d..27bf57e37b3b 100644 --- a/rs/test_utilities/state/src/lib.rs +++ b/rs/test_utilities/state/src/lib.rs @@ -15,7 +15,9 @@ use ic_replicated_state::{ }, metadata_state::{ subnet_call_context_manager::{ - BitcoinGetSuccessorsContext, BitcoinSendTransactionInternalContext, SubnetCallContext, + BitcoinGetSuccessorsContext, BitcoinSendTransactionInternalContext, + DogecoinGetSuccessorsContext, DogecoinSendTransactionInternalContext, + SubnetCallContext, }, Stream, SubnetMetrics, }, @@ -163,7 +165,7 @@ impl ReplicatedStateBuilder { for request in self.bitcoin_adapter_requests.into_iter() { match request { - BitcoinAdapterRequestWrapper::GetSuccessorsRequest(payload) => { + BitcoinAdapterRequestWrapper::GetBtcSuccessorsRequest(payload) => { state.metadata.subnet_call_context_manager.push_context( SubnetCallContext::BitcoinGetSuccessors(BitcoinGetSuccessorsContext { request: RequestBuilder::default().build(), @@ -172,7 +174,7 @@ impl ReplicatedStateBuilder { }), ); } - BitcoinAdapterRequestWrapper::SendTransactionRequest(payload) => { + BitcoinAdapterRequestWrapper::SendBtcTransactionRequest(payload) => { state.metadata.subnet_call_context_manager.push_context( SubnetCallContext::BitcoinSendTransactionInternal( BitcoinSendTransactionInternalContext { @@ -183,6 +185,26 @@ impl ReplicatedStateBuilder { ), ); } + BitcoinAdapterRequestWrapper::GetDogeSuccessorsRequest(payload) => { + state.metadata.subnet_call_context_manager.push_context( + SubnetCallContext::DogecoinGetSuccessors(DogecoinGetSuccessorsContext { + request: RequestBuilder::default().build(), + payload, + time: UNIX_EPOCH, + }), + ); + } + BitcoinAdapterRequestWrapper::SendDogeTransactionRequest(payload) => { + state.metadata.subnet_call_context_manager.push_context( + SubnetCallContext::DogecoinSendTransactionInternal( + DogecoinSendTransactionInternalContext { + request: RequestBuilder::default().build(), + payload, + time: UNIX_EPOCH, + }, + ), + ); + } } }