From 69f13b3c1bcf8a71a28bbb28fbcba4a912a421db Mon Sep 17 00:00:00 2001 From: Matteo Almanza Date: Mon, 9 Dec 2024 10:37:51 +0100 Subject: [PATCH] fix: don't ask for total supply if token doesn't exist (#1058) * fix: don't ask for total supply if token doesn't exist * nit: move sc deployed flag in state * nit: check for deployed sc even if not coordinator --- signer/src/block_observer.rs | 28 ++- signer/src/context/signer_state.rs | 21 +- signer/src/main.rs | 1 - signer/src/stacks/api.rs | 3 +- signer/src/testing/stacks.rs | 18 ++ signer/src/testing/transaction_coordinator.rs | 8 +- signer/src/transaction_coordinator.rs | 11 +- signer/tests/integration/block_observer.rs | 214 ++++++++++++++---- signer/tests/integration/emily.rs | 16 +- .../integration/transaction_coordinator.rs | 56 ++--- 10 files changed, 252 insertions(+), 124 deletions(-) diff --git a/signer/src/block_observer.rs b/signer/src/block_observer.rs index 1711dc5f2..91fa94478 100644 --- a/signer/src/block_observer.rs +++ b/signer/src/block_observer.rs @@ -489,18 +489,22 @@ impl BlockObserver { /// Update the sBTC peg limits from Emily async fn update_sbtc_limits(&self) -> Result<(), Error> { let limits = self.context.get_emily_client().get_limits().await?; - let max_mintable = match limits.total_cap() { - Amount::MAX_MONEY => Amount::MAX_MONEY, - total_cap => { - let sbtc_supply = self - .context - .get_stacks_client() - .get_sbtc_total_supply(&self.context.config().signer.deployer) - .await?; - // The maximum amount of sBTC that can be minted is the total cap - // minus the current supply. - total_cap.checked_sub(sbtc_supply).unwrap_or(Amount::ZERO) - } + let sbtc_deployed = self.context.state().sbtc_contracts_deployed(); + + let max_mintable = if limits.total_cap_exists() && sbtc_deployed { + let sbtc_supply = self + .context + .get_stacks_client() + .get_sbtc_total_supply(&self.context.config().signer.deployer) + .await?; + // The maximum amount of sBTC that can be minted is the total cap + // minus the current supply. + limits + .total_cap() + .checked_sub(sbtc_supply) + .unwrap_or(Amount::ZERO) + } else { + Amount::MAX_MONEY }; let limits = SbtcLimits::new( diff --git a/signer/src/context/signer_state.rs b/signer/src/context/signer_state.rs index ed5b97f46..36d47380d 100644 --- a/signer/src/context/signer_state.rs +++ b/signer/src/context/signer_state.rs @@ -1,6 +1,9 @@ //! Module for signer state -use std::sync::RwLock; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + RwLock, +}; use bitcoin::Amount; use hashbrown::HashSet; @@ -15,6 +18,7 @@ use crate::keys::PublicKey; pub struct SignerState { current_signer_set: SignerSet, current_limits: RwLock, + sbtc_contracts_deployed: AtomicBool, } impl SignerState { @@ -41,6 +45,16 @@ impl SignerState { .expect("BUG: Failed to acquire write lock"); *limits = new_limits; } + + /// Returns true if sbtc smart contracts are deployed + pub fn sbtc_contracts_deployed(&self) -> bool { + self.sbtc_contracts_deployed.load(Ordering::SeqCst) + } + + /// Set the sbtc smart contracts deployed flag + pub fn set_sbtc_contracts_deployed(&self) { + self.sbtc_contracts_deployed.store(true, Ordering::SeqCst); + } } /// Represents the current sBTC limits. @@ -87,6 +101,11 @@ impl SbtcLimits { self.total_cap.unwrap_or(Amount::MAX_MONEY) } + /// Check if total cap is set + pub fn total_cap_exists(&self) -> bool { + self.total_cap.is_some() + } + /// Get the maximum amount of BTC allowed to be pegged-in per transaction. pub fn per_deposit_cap(&self) -> Amount { self.per_deposit_cap.unwrap_or(Amount::MAX_MONEY) diff --git a/signer/src/main.rs b/signer/src/main.rs index 1c3b2d70a..a6126e209 100644 --- a/signer/src/main.rs +++ b/signer/src/main.rs @@ -341,7 +341,6 @@ async fn run_transaction_coordinator(ctx: impl Context) -> Result<(), Error> { bitcoin_presign_request_max_duration: config.signer.bitcoin_presign_request_max_duration, threshold: config.signer.bootstrap_signatures_required, dkg_max_duration: config.signer.dkg_max_duration, - sbtc_contracts_deployed: false, is_epoch3: false, }; diff --git a/signer/src/stacks/api.rs b/signer/src/stacks/api.rs index 029312df5..37567041f 100644 --- a/signer/src/stacks/api.rs +++ b/signer/src/stacks/api.rs @@ -43,6 +43,7 @@ use crate::storage::DbRead; use crate::util::ApiFallbackClient; use super::contracts::AsTxPayload; +use super::contracts::SmartContract; use super::wallet::SignerWallet; const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); @@ -1281,7 +1282,7 @@ impl StacksInteract for StacksClient { let result = self .call_read( deployer, - &ContractName::from("sbtc-token"), + &ContractName::from(SmartContract::SbtcToken.contract_name()), &ClarityName::from("get-total-supply"), deployer, ) diff --git a/signer/src/testing/stacks.rs b/signer/src/testing/stacks.rs index 0a3c860b9..2eb252422 100644 --- a/signer/src/testing/stacks.rs +++ b/signer/src/testing/stacks.rs @@ -4,6 +4,8 @@ use blockstack_lib::chainstate::nakamoto::NakamotoBlock; use blockstack_lib::chainstate::nakamoto::NakamotoBlockHeader; use blockstack_lib::net::api::getsortition::SortitionInfo; +use blockstack_lib::net::api::gettenureinfo::RPCGetTenureInfo; +use clarity::types::chainstate::StacksBlockId; use stacks_common::types::chainstate::BurnchainHeaderHash; use stacks_common::types::chainstate::ConsensusHash; use stacks_common::types::chainstate::SortitionId; @@ -26,6 +28,22 @@ pub const DUMMY_SORTITION_INFO: SortitionInfo = SortitionInfo { committed_block_hash: None, }; +/// Some dummy tenure info +pub const DUMMY_TENURE_INFO: RPCGetTenureInfo = RPCGetTenureInfo { + consensus_hash: ConsensusHash([0; 20]), + tenure_start_block_id: StacksBlockId([0; 32]), + parent_consensus_hash: ConsensusHash([0; 20]), + // The following bytes are the ones returned by StacksBlockId::first_mined() + parent_tenure_start_block_id: StacksBlockId([ + 0x55, 0xc9, 0x86, 0x1b, 0xe5, 0xcf, 0xf9, 0x84, 0xa2, 0x0c, 0xe6, 0xd9, 0x9d, 0x4a, 0xa6, + 0x59, 0x41, 0x41, 0x28, 0x89, 0xbd, 0xc6, 0x65, 0x09, 0x41, 0x36, 0x42, 0x9b, 0x84, 0xf8, + 0xc2, 0xee, + ]), + tip_block_id: StacksBlockId([0; 32]), + tip_height: 0, + reward_cycle: 0, +}; + impl TenureBlocks { /// Create a TenureBlocks struct that is basically empty. pub fn nearly_empty() -> Result { diff --git a/signer/src/testing/transaction_coordinator.rs b/signer/src/testing/transaction_coordinator.rs index 34d3ea22d..34d26c783 100644 --- a/signer/src/testing/transaction_coordinator.rs +++ b/signer/src/testing/transaction_coordinator.rs @@ -89,7 +89,6 @@ where context_window: u16, private_key: PrivateKey, threshold: u16, - sbtc_contracts_deployed: bool, ) -> Self { Self { event_loop: transaction_coordinator::TxCoordinatorEventLoop { @@ -98,7 +97,6 @@ where private_key, context_window, threshold, - sbtc_contracts_deployed, signing_round_max_duration: Duration::from_secs(10), bitcoin_presign_request_max_duration: Duration::from_secs(10), dkg_max_duration: Duration::from_secs(10), @@ -200,6 +198,7 @@ where .await; // Create the coordinator + self.context.state().set_sbtc_contracts_deployed(); let signer_network = SignerNetwork::single(&self.context); let mut coordinator = TxCoordinatorEventLoop { context: self.context, @@ -210,7 +209,6 @@ where signing_round_max_duration: Duration::from_millis(500), bitcoin_presign_request_max_duration: Duration::from_millis(500), dkg_max_duration: Duration::from_millis(500), - sbtc_contracts_deployed: true, is_epoch3: true, }; @@ -349,13 +347,13 @@ where let private_key = select_coordinator(&bitcoin_chain_tip.block_hash, &signer_info); // Bootstrap the tx coordinator within an event loop harness. + self.context.state().set_sbtc_contracts_deployed(); let event_loop_harness = TxCoordinatorEventLoopHarness::create( self.context.clone(), network.connect(), self.context_window, private_key, self.signing_threshold, - true, ); // Start the tx coordinator run loop. @@ -539,13 +537,13 @@ where let private_key = select_coordinator(&bitcoin_chain_tip.block_hash, &signer_info); // Bootstrap the tx coordinator within an event loop harness. + // We don't `set_sbtc_contracts_deployed` to force the coordinator to deploy the contracts let event_loop_harness = TxCoordinatorEventLoopHarness::create( self.context.clone(), network.connect(), self.context_window, private_key, self.signing_threshold, - false, // Force the coordinator to deploy the contracts ); // Start the tx coordinator run loop. diff --git a/signer/src/transaction_coordinator.rs b/signer/src/transaction_coordinator.rs index c2ad70fb2..59351d4d0 100644 --- a/signer/src/transaction_coordinator.rs +++ b/signer/src/transaction_coordinator.rs @@ -158,8 +158,6 @@ pub struct TxCoordinatorEventLoop { /// The maximum duration of distributed key generation before the /// coordinator will time out and return an error. pub dkg_max_duration: Duration, - /// Whether the coordinator has already deployed the contracts. - pub sbtc_contracts_deployed: bool, /// An indicator for whether the Stacks blockchain has reached Nakamoto /// 3. If we are not in Nakamoto 3 or later, then the coordinator does /// not do any work. @@ -294,6 +292,11 @@ where // coordinating DKG or constructing bitcoin and stacks // transactions, might as well return early. if !self.is_coordinator(&bitcoin_chain_tip, &signer_public_keys) { + // Before returning, we also check if all the smart contracts are + // deployed: we do this as some other coordinator could have deployed + // them, in which case we need to updated our state. + self.all_smart_contracts_deployed().await?; + tracing::debug!("we are not the coordinator, so nothing to do"); return Ok(()); } @@ -1477,7 +1480,7 @@ where } async fn all_smart_contracts_deployed(&mut self) -> Result { - if self.sbtc_contracts_deployed { + if self.context.state().sbtc_contracts_deployed() { return Ok(true); } @@ -1490,7 +1493,7 @@ where } } - self.sbtc_contracts_deployed = true; + self.context.state().set_sbtc_contracts_deployed(); Ok(true) } diff --git a/signer/tests/integration/block_observer.rs b/signer/tests/integration/block_observer.rs index bf2efb3d6..50aef83aa 100644 --- a/signer/tests/integration/block_observer.rs +++ b/signer/tests/integration/block_observer.rs @@ -7,13 +7,12 @@ use std::time::Duration; use bitcoin::Address; use bitcoin::AddressType; +use bitcoin::Amount; use bitcoin::OutPoint; use bitcoincore_rpc::RpcApi as _; use blockstack_lib::chainstate::nakamoto::NakamotoBlock; use blockstack_lib::chainstate::nakamoto::NakamotoBlockHeader; use blockstack_lib::net::api::getpoxinfo::RPCPoxInfoData; -use blockstack_lib::net::api::getsortition::SortitionInfo; -use blockstack_lib::net::api::gettenureinfo::RPCGetTenureInfo; use emily_client::apis::deposit_api; use emily_client::apis::testing_api; use emily_client::models::CreateDepositRequestBody; @@ -39,10 +38,8 @@ use signer::storage::model::TxPrevout; use signer::storage::model::TxPrevoutType; use signer::storage::postgres::PgStore; use signer::storage::DbWrite; -use stacks_common::types::chainstate::BurnchainHeaderHash; -use stacks_common::types::chainstate::ConsensusHash; -use stacks_common::types::chainstate::SortitionId; -use stacks_common::types::chainstate::StacksBlockId; +use signer::testing::stacks::DUMMY_SORTITION_INFO; +use signer::testing::stacks::DUMMY_TENURE_INFO; use signer::bitcoin::zmq::BitcoinCoreMessageStream; use signer::block_observer::BlockObserver; @@ -58,6 +55,7 @@ use tokio_stream::wrappers::ReceiverStream; use url::Url; use crate::setup::TestSweepSetup; +use crate::transaction_coordinator::mock_reqwests_status_code_error; use crate::utxo_construction::make_deposit_request; use crate::zmq::BITCOIN_CORE_ZMQ_ENDPOINT; use crate::DATABASE_NUM; @@ -115,18 +113,9 @@ async fn load_latest_deposit_requests_persists_requests_from_past(blocks_ago: u6 // information about the Stacks blockchain, so we need to prep it, even // though it isn't necessary for our test. ctx.with_stacks_client(|client| { - client.expect_get_tenure_info().returning(move || { - let response = Ok(RPCGetTenureInfo { - consensus_hash: ConsensusHash([0; 20]), - tenure_start_block_id: StacksBlockId([0; 32]), - parent_consensus_hash: ConsensusHash([0; 20]), - parent_tenure_start_block_id: StacksBlockId::first_mined(), - tip_block_id: StacksBlockId([0; 32]), - tip_height: 0, - reward_cycle: 0, - }); - Box::pin(std::future::ready(response)) - }); + client + .expect_get_tenure_info() + .returning(move || Box::pin(std::future::ready(Ok(DUMMY_TENURE_INFO.clone())))); client.expect_get_block().returning(|_| { let response = Ok(NakamotoBlock { @@ -146,22 +135,9 @@ async fn load_latest_deposit_requests_persists_requests_from_past(blocks_ago: u6 Box::pin(std::future::ready(response)) }); - client.expect_get_sortition_info().returning(move |_| { - let response = Ok(SortitionInfo { - burn_block_hash: BurnchainHeaderHash([0; 32]), - burn_block_height: 0, - burn_header_timestamp: 0, - sortition_id: SortitionId([0; 32]), - parent_sortition_id: SortitionId([0; 32]), - consensus_hash: ConsensusHash([0; 20]), - was_sortition: true, - miner_pk_hash160: None, - stacks_parent_ch: None, - last_sortition_ch: None, - committed_block_hash: None, - }); - Box::pin(std::future::ready(response)) - }); + client + .expect_get_sortition_info() + .returning(move |_| Box::pin(std::future::ready(Ok(DUMMY_SORTITION_INFO.clone())))); }) .await; @@ -223,7 +199,7 @@ async fn load_latest_deposit_requests_persists_requests_from_past(blocks_ago: u6 assert!(deposit_requests.is_empty()); - // Let's generate a new block and wait for out block observer to send a + // Let's generate a new block and wait for our block observer to send a // BitcoinBlockObserved signal. let chain_tip: BitcoinBlockHash = faucet.generate_blocks(1).pop().unwrap().into(); @@ -439,18 +415,9 @@ async fn block_observer_stores_donation_and_sbtc_utxos() { // up-to-date information. We don't have stacks-core running so we mock // these calls. ctx.with_stacks_client(|client| { - client.expect_get_tenure_info().returning(move || { - let response = Ok(RPCGetTenureInfo { - consensus_hash: ConsensusHash([0; 20]), - tenure_start_block_id: StacksBlockId([0; 32]), - parent_consensus_hash: ConsensusHash([0; 20]), - parent_tenure_start_block_id: StacksBlockId::first_mined(), - tip_block_id: StacksBlockId([0; 32]), - tip_height: 0, - reward_cycle: 0, - }); - Box::pin(std::future::ready(response)) - }); + client + .expect_get_tenure_info() + .returning(move || Box::pin(std::future::ready(Ok(DUMMY_TENURE_INFO.clone())))); let chain_tip = BitcoinBlockHash::from(chain_tip_info.hash); client.expect_get_tenure().returning(move |_| { @@ -698,3 +665,156 @@ async fn block_observer_stores_donation_and_sbtc_utxos() { testing::storage::drop_db(db).await; } + +#[cfg_attr(not(feature = "integration-tests"), ignore)] +#[test_case::test_case(false, SbtcLimits::default(); "no contracts, default limits")] +#[test_case::test_case(false, SbtcLimits::new(Some(bitcoin::Amount::from_sat(1_000)), None, None, None); "no contracts, total cap limit")] +#[test_case::test_case(true, SbtcLimits::default(); "deployed contracts, default limits")] +#[test_case::test_case(true, SbtcLimits::new(Some(bitcoin::Amount::from_sat(1_000)), None, None, None); "deployed contracts, total cap limit")] +#[tokio::test] +async fn block_observer_handles_update_limits(deployed: bool, sbtc_limits: SbtcLimits) { + // We start with the typical setup with a fresh database and context + // with a real bitcoin core client and a real connection to our + // database. + let (_, faucet) = regtest::initialize_blockchain(); + let db_num = DATABASE_NUM.fetch_add(1, Ordering::SeqCst); + let db = testing::storage::new_test_database(db_num, true).await; + let mut ctx = TestContext::builder() + .with_storage(db.clone()) + .with_first_bitcoin_core_client() + .with_mocked_emily_client() + .with_mocked_stacks_client() + .build(); + + // We need to set up the stacks client as well. We use it to fetch + // information about the Stacks blockchain, so we need to prep it, even + // though it isn't necessary for our test. + ctx.with_stacks_client(|client| { + client + .expect_get_tenure_info() + .returning(move || Box::pin(std::future::ready(Ok(DUMMY_TENURE_INFO.clone())))); + client.expect_get_block().returning(|_| { + let response = Ok(NakamotoBlock { + header: NakamotoBlockHeader::empty(), + txs: Vec::new(), + }); + Box::pin(std::future::ready(response)) + }); + client + .expect_get_tenure() + .returning(|_| Box::pin(std::future::ready(TenureBlocks::nearly_empty()))); + client.expect_get_pox_info().returning(|| { + let response = serde_json::from_str::(GET_POX_INFO_JSON) + .map_err(Error::JsonSerialize); + Box::pin(std::future::ready(response)) + }); + client + .expect_get_sortition_info() + .returning(move |_| Box::pin(std::future::ready(Ok(DUMMY_SORTITION_INFO.clone())))); + }) + .await; + + ctx.with_emily_client(|client| { + client + .expect_get_deposits() + .returning(move || Box::pin(std::future::ready(Ok(vec![])))); + }) + .await; + + // Now we do the actual test case setup. + + ctx.with_emily_client(|client| { + client + .expect_get_limits() + .once() + .returning(move || Box::pin(std::future::ready(Ok(sbtc_limits.clone())))); + }) + .await; + + if deployed { + ctx.with_stacks_client(move |client| { + client + .expect_get_sbtc_total_supply() + .returning(|_| Box::pin(std::future::ready(Ok(Amount::from_sat(1))))); + }) + .await; + ctx.state().set_sbtc_contracts_deployed(); + } else { + ctx.with_stacks_client(|client| { + client.expect_get_sbtc_total_supply().returning(|_| { + // The real error is `UnexpectedStacksResponse`: error decoding + // response body: missing field `result` at line 1 column 108 + Box::pin(std::future::ready(Err(Error::InvalidStacksResponse("")))) + }); + client.expect_get_contract_source().returning(|_, _| { + Box::pin(async { + Err(Error::StacksNodeResponse( + mock_reqwests_status_code_error(404).await, + )) + }) + }); + }) + .await; + } + + // We only proceed with the test after the BlockObserver "process" has + // started, and we use this counter to notify us when that happens. + let start_flag = Arc::new(AtomicBool::new(false)); + let flag = start_flag.clone(); + + // We jump through all of these hoops to make sure that the block + // stream object is Send + Sync. + let zmq_stream = + BitcoinCoreMessageStream::new_from_endpoint(BITCOIN_CORE_ZMQ_ENDPOINT, &["hashblock"]) + .await + .unwrap(); + let (sender, receiver) = tokio::sync::mpsc::channel(100); + + tokio::spawn(async move { + let mut stream = zmq_stream.to_block_hash_stream(); + while let Some(block) = stream.next().await { + sender.send(block).await.unwrap(); + } + }); + + let block_observer = BlockObserver { + context: ctx.clone(), + bitcoin_blocks: ReceiverStream::new(receiver), + horizon: 10, + }; + + let mut signal_receiver = ctx.get_signal_receiver(); + + tokio::spawn(async move { + flag.store(true, Ordering::Relaxed); + block_observer.run().await + }); + + // Wait for the task to start. + while !start_flag.load(Ordering::SeqCst) { + tokio::time::sleep(Duration::from_millis(10)).await; + } + + // Let's generate a new block and wait for our block observer to send a + // BitcoinBlockObserved signal. + let expected_tip = faucet.generate_blocks(1).pop().unwrap(); + + let waiting_fut = async { + let signal = signal_receiver.recv(); + let Ok(SignerSignal::Event(SignerEvent::BitcoinBlockObserved)) = signal.await else { + panic!("Not the right signal") + }; + }; + + tokio::time::timeout(Duration::from_secs(3), waiting_fut) + .await + .unwrap(); + + // If we pass the above without panicking it should be fine, this is just a + // sanity check. + let db_chain_tip = db + .get_bitcoin_canonical_chain_tip() + .await + .expect("cannot get chain tip"); + assert_eq!(db_chain_tip, Some(expected_tip.into())) +} diff --git a/signer/tests/integration/emily.rs b/signer/tests/integration/emily.rs index a4de78948..83d111846 100644 --- a/signer/tests/integration/emily.rs +++ b/signer/tests/integration/emily.rs @@ -16,10 +16,7 @@ use bitcoincore_rpc_json::Utxo; use blockstack_lib::net::api::getpoxinfo::RPCPoxInfoData; use blockstack_lib::net::api::getsortition::SortitionInfo; -use blockstack_lib::net::api::gettenureinfo::RPCGetTenureInfo; use clarity::types::chainstate::BurnchainHeaderHash; -use clarity::types::chainstate::ConsensusHash; -use clarity::types::chainstate::StacksBlockId; use emily_client::apis::deposit_api; use emily_client::apis::testing_api::wipe_databases; use emily_client::models::CreateDepositRequestBody; @@ -55,6 +52,7 @@ use signer::testing::context::WrappedMock; use signer::testing::dummy; use signer::testing::dummy::DepositTxConfig; use signer::testing::stacks::DUMMY_SORTITION_INFO; +use signer::testing::stacks::DUMMY_TENURE_INFO; use signer::testing::storage::model::TestData; use fake::Fake as _; @@ -137,16 +135,6 @@ where coinbase_tx } -const DUMMY_TENURE_INFO: RPCGetTenureInfo = RPCGetTenureInfo { - consensus_hash: ConsensusHash([0; 20]), - tenure_start_block_id: StacksBlockId([0; 32]), - parent_consensus_hash: ConsensusHash([0; 20]), - parent_tenure_start_block_id: StacksBlockId([0; 32]), - tip_block_id: StacksBlockId([0; 32]), - tip_height: 0, - reward_cycle: 0, -}; - /// End to end test for deposits via Emily: a deposit request is created on Emily, /// then is picked up by the block observer, inserted into the storage and accepted. /// After a signing round, the sweep tx for the request is broadcasted and we check @@ -421,6 +409,7 @@ async fn deposit_flow() { let private_key = select_coordinator(&deposit_block_hash.into(), &signer_info); // Bootstrap the tx coordinator event loop + context.state().set_sbtc_contracts_deployed(); let tx_coordinator = transaction_coordinator::TxCoordinatorEventLoop { context: context.clone(), network: network.connect(), @@ -430,7 +419,6 @@ async fn deposit_flow() { signing_round_max_duration: Duration::from_secs(10), dkg_max_duration: Duration::from_secs(10), bitcoin_presign_request_max_duration: Duration::from_secs(10), - sbtc_contracts_deployed: true, is_epoch3: true, }; let tx_coordinator_handle = tokio::spawn(async move { tx_coordinator.run().await }); diff --git a/signer/tests/integration/transaction_coordinator.rs b/signer/tests/integration/transaction_coordinator.rs index 8b926b3bd..d0c2cfd9c 100644 --- a/signer/tests/integration/transaction_coordinator.rs +++ b/signer/tests/integration/transaction_coordinator.rs @@ -20,7 +20,6 @@ use blockstack_lib::chainstate::stacks::TransactionPayload; use blockstack_lib::net::api::getcontractsrc::ContractSrcResponse; use blockstack_lib::net::api::getpoxinfo::RPCPoxInfoData; use blockstack_lib::net::api::getsortition::SortitionInfo; -use blockstack_lib::net::api::gettenureinfo::RPCGetTenureInfo; use emily_client::apis::deposit_api; use emily_client::apis::testing_api; use emily_client::models::CreateDepositRequestBody; @@ -53,11 +52,11 @@ use signer::stacks::contracts::SmartContract; use signer::storage::model::BitcoinBlockHash; use signer::storage::model::BitcoinTx; use signer::storage::postgres::PgStore; +use signer::testing::stacks::DUMMY_TENURE_INFO; use signer::testing::transaction_coordinator::select_coordinator; use stacks_common::types::chainstate::BurnchainHeaderHash; use stacks_common::types::chainstate::ConsensusHash; use stacks_common::types::chainstate::SortitionId; -use stacks_common::types::chainstate::StacksBlockId; use test_case::test_case; use test_log::test; use tokio_stream::wrappers::BroadcastStream; @@ -188,7 +187,7 @@ where .unwrap(); } -async fn mock_reqwests_status_code_error(status_code: usize) -> reqwest::Error { +pub async fn mock_reqwests_status_code_error(status_code: usize) -> reqwest::Error { let mut server: mockito::ServerGuard = mockito::Server::new_async().await; let _mock = server.mock("GET", "/").with_status(status_code).create(); reqwest::get(server.url()) @@ -535,6 +534,7 @@ async fn process_complete_deposit() { let private_key = select_coordinator(&setup.sweep_block_hash.into(), &signer_info); // Bootstrap the tx coordinator event loop + context.state().set_sbtc_contracts_deployed(); let tx_coordinator = transaction_coordinator::TxCoordinatorEventLoop { context: context.clone(), network: network.connect(), @@ -544,7 +544,6 @@ async fn process_complete_deposit() { signing_round_max_duration: Duration::from_secs(10), bitcoin_presign_request_max_duration: Duration::from_secs(10), dkg_max_duration: Duration::from_secs(10), - sbtc_contracts_deployed: true, is_epoch3: true, }; let tx_coordinator_handle = tokio::spawn(async move { tx_coordinator.run().await }); @@ -711,7 +710,6 @@ async fn deploy_smart_contracts_coordinator( signing_round_max_duration: Duration::from_secs(10), bitcoin_presign_request_max_duration: Duration::from_secs(10), dkg_max_duration: Duration::from_secs(10), - sbtc_contracts_deployed: false, is_epoch3: true, }; let tx_coordinator_handle = tokio::spawn(async move { tx_coordinator.run().await }); @@ -798,6 +796,7 @@ async fn get_signer_public_keys_and_aggregate_key_falls_back() { let network = InMemoryNetwork::new(); + ctx.state().set_sbtc_contracts_deployed(); // Skip contract deployment let coord = TxCoordinatorEventLoop { network: network.connect(), context: ctx.clone(), @@ -807,7 +806,6 @@ async fn get_signer_public_keys_and_aggregate_key_falls_back() { bitcoin_presign_request_max_duration: Duration::from_secs(10), threshold: 2, dkg_max_duration: Duration::from_secs(10), - sbtc_contracts_deployed: true, // Skip contract deployment is_epoch3: true, }; @@ -1008,9 +1006,9 @@ async fn run_dkg_from_scratch() { // 4. Start the [`TxCoordinatorEventLoop`] and [`TxSignerEventLoop`] // processes for each signer. - let tx_coordinator_processes = signers - .iter() - .map(|(ctx, _, kp, net)| TxCoordinatorEventLoop { + let tx_coordinator_processes = signers.iter().map(|(ctx, _, kp, net)| { + ctx.state().set_sbtc_contracts_deployed(); // Skip contract deployment + TxCoordinatorEventLoop { network: net.spawn(), context: ctx.clone(), context_window: 10000, @@ -1019,9 +1017,9 @@ async fn run_dkg_from_scratch() { bitcoin_presign_request_max_duration: Duration::from_secs(10), threshold: ctx.config().signer.bootstrap_signatures_required, dkg_max_duration: Duration::from_secs(10), - sbtc_contracts_deployed: true, // Skip contract deployment is_epoch3: true, - }); + } + }); let tx_signer_processes = signers .iter() @@ -1216,18 +1214,9 @@ async fn sign_bitcoin_transaction() { let broadcast_stacks_tx = broadcast_stacks_tx.clone(); ctx.with_stacks_client(|client| { - client.expect_get_tenure_info().returning(move || { - let response = Ok(RPCGetTenureInfo { - consensus_hash: ConsensusHash([0; 20]), - tenure_start_block_id: StacksBlockId([0; 32]), - parent_consensus_hash: ConsensusHash([0; 20]), - parent_tenure_start_block_id: StacksBlockId::first_mined(), - tip_block_id: StacksBlockId([0; 32]), - tip_height: 1, - reward_cycle: 0, - }); - Box::pin(std::future::ready(response)) - }); + client + .expect_get_tenure_info() + .returning(move || Box::pin(std::future::ready(Ok(DUMMY_TENURE_INFO.clone())))); client.expect_get_block().returning(|_| { let response = Ok(NakamotoBlock { @@ -1324,6 +1313,7 @@ async fn sign_bitcoin_transaction() { let start_count = Arc::new(AtomicU8::new(0)); for (ctx, _, kp, network) in signers.iter() { + ctx.state().set_sbtc_contracts_deployed(); let ev = TxCoordinatorEventLoop { network: network.spawn(), context: ctx.clone(), @@ -1333,7 +1323,6 @@ async fn sign_bitcoin_transaction() { bitcoin_presign_request_max_duration: Duration::from_secs(10), threshold: ctx.config().signer.bootstrap_signatures_required, dkg_max_duration: Duration::from_secs(10), - sbtc_contracts_deployed: true, is_epoch3: true, }; let counter = start_count.clone(); @@ -1625,18 +1614,9 @@ async fn skip_smart_contract_deployment_and_key_rotation_if_up_to_date() { for (ctx, db, _, _) in signers.iter_mut() { let db = db.clone(); ctx.with_stacks_client(|client| { - client.expect_get_tenure_info().returning(move || { - let response = Ok(RPCGetTenureInfo { - consensus_hash: ConsensusHash([0; 20]), - tenure_start_block_id: StacksBlockId([0; 32]), - parent_consensus_hash: ConsensusHash([0; 20]), - parent_tenure_start_block_id: StacksBlockId::first_mined(), - tip_block_id: StacksBlockId([0; 32]), - tip_height: 1, - reward_cycle: 0, - }); - Box::pin(std::future::ready(response)) - }); + client + .expect_get_tenure_info() + .returning(move || Box::pin(std::future::ready(Ok(DUMMY_TENURE_INFO.clone())))); client.expect_get_block().returning(|_| { let response = Ok(NakamotoBlock { @@ -1723,6 +1703,7 @@ async fn skip_smart_contract_deployment_and_key_rotation_if_up_to_date() { let start_count = Arc::new(AtomicU8::new(0)); for (ctx, _, kp, network) in signers.iter() { + ctx.state().set_sbtc_contracts_deployed(); let ev = TxCoordinatorEventLoop { network: network.spawn(), context: ctx.clone(), @@ -1732,7 +1713,6 @@ async fn skip_smart_contract_deployment_and_key_rotation_if_up_to_date() { bitcoin_presign_request_max_duration: Duration::from_secs(10), threshold: ctx.config().signer.bootstrap_signatures_required, dkg_max_duration: Duration::from_secs(10), - sbtc_contracts_deployed: true, is_epoch3: true, }; let counter = start_count.clone(); @@ -1929,7 +1909,6 @@ async fn test_get_btc_state_with_no_available_sweep_transactions() { signing_round_max_duration: std::time::Duration::from_secs(5), bitcoin_presign_request_max_duration: Duration::from_secs(5), dkg_max_duration: std::time::Duration::from_secs(5), - sbtc_contracts_deployed: false, is_epoch3: true, }; @@ -2066,7 +2045,6 @@ async fn test_get_btc_state_with_available_sweep_transactions_and_rbf() { signing_round_max_duration: std::time::Duration::from_secs(5), bitcoin_presign_request_max_duration: Duration::from_secs(5), dkg_max_duration: std::time::Duration::from_secs(5), - sbtc_contracts_deployed: false, is_epoch3: true, };