diff --git a/engine/src/elections/voter_api.rs b/engine/src/elections/voter_api.rs index d718ebec009..0516b42331a 100644 --- a/engine/src/elections/voter_api.rs +++ b/engine/src/elections/voter_api.rs @@ -81,6 +81,6 @@ macro_rules! generate_voter_api_tuple_impls { } generate_voter_api_tuple_impls!(tuple_1_impls: ((A, A0))); -generate_voter_api_tuple_impls!(tuple_2_impls: ((A, A0), (B, B0))); generate_voter_api_tuple_impls!(tuple_3_impls: ((A, A0), (B, B0), (C, C0))); +generate_voter_api_tuple_impls!(tuple_4_impls: ((A, A0), (B, B0), (C, C0), (D, D0))); generate_voter_api_tuple_impls!(tuple_7_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0), (GG, G0))); diff --git a/engine/src/witness/btc.rs b/engine/src/witness/btc.rs index 5c41cd8c109..cdba3748f8f 100644 --- a/engine/src/witness/btc.rs +++ b/engine/src/witness/btc.rs @@ -3,32 +3,13 @@ pub mod deposits; pub mod source; pub mod vault_swaps; -use crate::{ - btc::{ - retry_rpc::{BtcRetryRpcApi, BtcRetryRpcClient}, - rpc::VerboseTransaction, - }, - db::PersistentKeyDB, - state_chain_observer::client::{ - extrinsic_api::signed::SignedExtrinsicApi, - storage_api::StorageApi, - stream_api::{StreamApi, FINALIZED, UNFINALIZED}, - }, -}; +use crate::btc::rpc::VerboseTransaction; use bitcoin::{hashes::Hash, BlockHash}; use cf_chains::btc::{self, deposit_address::DepositAddress, BlockNumber, CHANGE_ADDRESS_SALT}; -use cf_primitives::{EpochIndex, NetworkEnvironment}; -use cf_utilities::task_scope::Scope; +use cf_primitives::EpochIndex; use futures_core::Future; -use source::BtcSource; -use std::sync::Arc; -use super::common::{ - chain_source::{extension::ChainSourceExt, Header}, - epoch_source::{EpochSourceBuilder, Vault}, -}; - -use anyhow::Result; +use super::common::{chain_source::Header, epoch_source::Vault}; pub async fn process_egress( epoch: Vault, @@ -64,128 +45,6 @@ pub async fn process_egress( - scope: &Scope<'_, anyhow::Error>, - btc_client: BtcRetryRpcClient, - process_call: ProcessCall, - prewitness_call: PrewitnessCall, - state_chain_client: Arc, - state_chain_stream: StateChainStream, - unfinalised_state_chain_stream: impl StreamApi + Clone, - epoch_source: EpochSourceBuilder<'_, '_, StateChainClient, (), ()>, - db: Arc, -) -> Result<()> -where - StateChainClient: StorageApi + SignedExtrinsicApi + 'static + Send + Sync, - StateChainStream: StreamApi + Clone, - ProcessCall: Fn(state_chain_runtime::RuntimeCall, EpochIndex) -> ProcessingFut - + Send - + Sync - + Clone - + 'static, - ProcessingFut: Future + Send + 'static, - PrewitnessCall: Fn(state_chain_runtime::RuntimeCall, EpochIndex) -> PrewitnessFut - + Send - + Sync - + Clone - + 'static, - PrewitnessFut: Future + Send + 'static, -{ - let btc_source = BtcSource::new(btc_client.clone()).strictly_monotonic().shared(scope); - - btc_source - .clone() - .chunk_by_time(epoch_source.clone(), scope) - .chain_tracking(state_chain_client.clone(), btc_client.clone()) - .logging("chain tracking") - .spawn(scope); - - let vaults = epoch_source.vaults::().await; - - let block_source = btc_source - .then({ - let btc_client = btc_client.clone(); - move |header| { - let btc_client = btc_client.clone(); - async move { - let block = btc_client.block(header.hash).await.expect("TODO: Delete this"); - (header.data, block.txdata) - } - } - }) - .shared(scope); - - // Pre-witnessing stream. - block_source - .clone() - .chunk_by_vault(vaults.clone(), scope) - .deposit_addresses( - scope, - unfinalised_state_chain_stream.clone(), - state_chain_client.clone(), - ) - .await - .private_deposit_channels(scope, unfinalised_state_chain_stream, state_chain_client.clone()) - .await - .btc_deposits(prewitness_call) - .logging("pre-witnessing") - .spawn(scope); - - let btc_safety_margin = match state_chain_client - .storage_value::>(state_chain_stream.cache().hash) - .await? - { - Some(margin) => margin, - None => { - use chainflip_node::chain_spec::{berghain, devnet, perseverance}; - match state_chain_client - .storage_value::>( - state_chain_stream.cache().hash, - ) - .await? - { - NetworkEnvironment::Mainnet => berghain::BITCOIN_SAFETY_MARGIN, - NetworkEnvironment::Testnet => perseverance::BITCOIN_SAFETY_MARGIN, - NetworkEnvironment::Development => devnet::BITCOIN_SAFETY_MARGIN, - } - }, - }; - - tracing::info!("Safety margin for Bitcoin is set to {btc_safety_margin} blocks.",); - - // Full witnessing stream. - block_source - .lag_safety(btc_safety_margin) - .logging("safe block produced") - .chunk_by_vault(vaults, scope) - .deposit_addresses(scope, state_chain_stream.clone(), state_chain_client.clone()) - .await - .private_deposit_channels(scope, state_chain_stream.clone(), state_chain_client.clone()) - .await - .btc_deposits(process_call.clone()) - .egress_items(scope, state_chain_stream, state_chain_client.clone()) - .await - .then({ - let process_call = process_call.clone(); - move |epoch, header| process_egress(epoch, header, process_call.clone()) - }) - .continuous("Bitcoin".to_string(), db) - .logging("witnessing") - .spawn(scope); - - Ok(()) -} - fn success_witnesses<'a>( monitored_tx_hashes: impl Iterator + Clone, txs: Vec, diff --git a/engine/src/witness/btc/deposits.rs b/engine/src/witness/btc/deposits.rs index cde7cf44e69..d65ee0e1198 100644 --- a/engine/src/witness/btc/deposits.rs +++ b/engine/src/witness/btc/deposits.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; -use cf_primitives::EpochIndex; +use cf_primitives::{AccountId, ChannelId, EpochIndex}; use futures_core::Future; use itertools::Itertools; -use pallet_cf_ingress_egress::{DepositChannelDetails, DepositWitness}; +use pallet_cf_ingress_egress::{DepositChannelDetails, DepositWitness, VaultDepositWitness}; use state_chain_runtime::BitcoinInstance; use super::{ @@ -15,9 +15,12 @@ use super::{ }; use crate::{ btc::rpc::VerboseTransaction, - witness::common::{ - chunked_chain_source::chunked_by_vault::deposit_addresses::Addresses, RuntimeCallHasChain, - RuntimeHasChain, + witness::{ + btc::vault_swaps::try_extract_vault_swap_witness, + common::{ + chunked_chain_source::chunked_by_vault::deposit_addresses::Addresses, + RuntimeCallHasChain, RuntimeHasChain, + }, }, }; use bitcoin::{hashes::Hash, BlockHash}; @@ -128,6 +131,22 @@ impl ChunkedByVaultBuilder { } } +pub fn vault_deposits( + txs: &[VerboseTransaction], + vaults: &Vec<(DepositAddress, AccountId, ChannelId)>, +) -> Vec> { + txs.iter() + .filter_map(|tx| { + for (vault, broker, id) in vaults { + if let Some(vault_swap) = try_extract_vault_swap_witness(tx, vault, *id, broker) { + return Some(vault_swap); + } + } + None + }) + .collect() +} + pub fn deposit_witnesses( txs: &[VerboseTransaction], deposit_addresses: &HashMap, DepositAddress>, diff --git a/engine/src/witness/btc_e.rs b/engine/src/witness/btc_e.rs index 733b779098f..4a0c386723b 100644 --- a/engine/src/witness/btc_e.rs +++ b/engine/src/witness/btc_e.rs @@ -42,9 +42,10 @@ use crate::{ use anyhow::Result; use sp_core::H256; +use state_chain_runtime::chainflip::bitcoin_elections::BitcoinVaultDepositWitnessingES; use std::sync::Arc; -use crate::btc::retry_rpc::BtcRetryRpcClient; +use crate::{btc::retry_rpc::BtcRetryRpcClient, witness::btc::deposits::vault_deposits}; #[derive(Clone)] pub struct BitcoinDepositChannelWitnessingVoter { @@ -59,7 +60,8 @@ impl VoterApi for BitcoinDepositChannelWitnes deposit_addresses: ::ElectionProperties, ) -> Result>, anyhow::Error> { let (witness_range, deposit_addresses, _extra) = deposit_addresses; - let witness_range = BlockWitnessRange::try_new(witness_range).unwrap(); + let witness_range = BlockWitnessRange::try_new(witness_range) + .map_err(|_| anyhow::anyhow!("Failed to create witness range"))?; tracing::info!("Deposit channel witnessing properties: {:?}", deposit_addresses); let mut txs = vec![]; @@ -80,13 +82,43 @@ impl VoterApi for BitcoinDepositChannelWitnes let witnesses = deposit_witnesses(&txs, &deposit_addresses); - if witnesses.is_empty() { - tracing::info!("No witnesses found for BTCE"); - } else { - tracing::info!("Witnesses from BTCE: {:?}", witnesses); + Ok(Some(ConstantIndex::new(witnesses))) + } +} + +#[derive(Clone)] +pub struct BitcoinVaultDepositWitnessingVoter { + client: BtcRetryRpcClient, +} + +#[async_trait::async_trait] +impl VoterApi for BitcoinVaultDepositWitnessingVoter { + async fn vote( + &self, + _settings: ::ElectoralSettings, + properties: ::ElectionProperties, + ) -> Result>, anyhow::Error> { + let (witness_range, vaults, _extra) = properties; + let witness_range = BlockWitnessRange::try_new(witness_range) + .map_err(|_| anyhow::anyhow!("Failed to create witness range"))?; + + let mut txs = vec![]; + // we only ever expect this to be one for bitcoin, but for completeness, we loop. + tracing::info!("Witness range: {:?}", witness_range); + for block in BlockWitnessRange::::into_range_inclusive(witness_range) { + tracing::info!("Checking block {:?}", block); + + // TODO: these queries should not be infinite + let block_hash = self.client.block_hash(block).await; + + let block = self.client.block(block_hash).await?; + + txs.extend(block.txdata); } - Ok(Some(ConstantIndex { data: witnesses, _phantom: Default::default() })) + let witnesses = vault_deposits(&txs, &vaults); + + Ok(Some(ConstantIndex::new(witnesses))) } } @@ -216,6 +248,7 @@ where CompositeVoter::::new(( BitcoinBlockHeightTrackingVoter { client: client.clone() }, BitcoinDepositChannelWitnessingVoter { client: client.clone() }, + BitcoinVaultDepositWitnessingVoter { client: client.clone() }, BitcoinLivenessVoter { client }, )), ) diff --git a/state-chain/chains/src/evm.rs b/state-chain/chains/src/evm.rs index e2227afd6e1..8a6e125d9bc 100644 --- a/state-chain/chains/src/evm.rs +++ b/state-chain/chains/src/evm.rs @@ -23,7 +23,20 @@ use sp_std::{convert::TryFrom, str, vec}; use crate::DepositDetailsToTransactionInId; -#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, TypeInfo, Default)] +#[derive( + Clone, + Debug, + PartialEq, + Eq, + Encode, + Decode, + TypeInfo, + Default, + Serialize, + Deserialize, + Ord, + PartialOrd, +)] pub struct DepositDetails { // In the case of EVM Native Deposits (ETH or arbETH), because we need to detect ingresses by // checking balances rather than using events, there can be more than one hash associated with diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index 424f07b0fc6..93d0ebb5dac 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -426,7 +426,9 @@ pub trait Chain: Member + Parameter + ChainInstanceAlias { + Into + TryFrom + IntoEnumIterator - + Unpin; + + Unpin + + Ord + + PartialOrd; type ChainAssetMap< T: Member @@ -456,7 +458,9 @@ pub trait Chain: Member + Parameter + ChainInstanceAlias { + TryFrom + IntoForeignChainAddress + Unpin - + ToHumanreadableAddress; + + ToHumanreadableAddress + + Serialize + + for<'a> Deserialize<'a>; type DepositFetchId: Member + Parameter @@ -471,7 +475,11 @@ pub trait Chain: Member + Parameter + ChainInstanceAlias { type DepositDetails: Member + Parameter + BenchmarkValue - + DepositDetailsToTransactionInId; + + DepositDetailsToTransactionInId + + Serialize + + for<'a> Deserialize<'a> + + Ord + + PartialOrd; type Transaction: Member + Parameter + BenchmarkValue + FeeRefundCalculator; @@ -510,7 +518,11 @@ pub trait ChainCrypto: ChainCryptoInstanceAlias + Sized { + Parameter + Unpin + IntoTransactionInIdForAnyChain - + BenchmarkValue; + + BenchmarkValue + + Serialize + + for<'a> Deserialize<'a> + + Ord + + PartialOrd; /// Uniquely identifies a transaction on the outgoing direction. type TransactionOutId: Member + Parameter + Unpin + BenchmarkValue; diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/block_witnesser/state_machine.rs b/state-chain/pallets/cf-elections/src/electoral_systems/block_witnesser/state_machine.rs index 948be0f512c..e9077312a67 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/block_witnesser/state_machine.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/block_witnesser/state_machine.rs @@ -96,19 +96,6 @@ pub trait BWProcessorTypes: Sized { + 'static; type BlockData: PartialEq + Clone + Debug + Eq + Serde + 'static; - // type ChainBlockNumber: Serde - // + Copy - // + Eq - // + Ord - // + SaturatingStep - // + Step - // + BlockZero - // + Debug - // + Default - // + 'static; - - // type BlockData: Serde + Clone; - type Event: Serde + Debug + Clone + Eq; type Rules: Hook> + Default + Serde + Debug + Clone + Eq; type Execute: Hook> + Default + Serde + Debug + Clone + Eq; diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs b/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs index fbf78057a62..71c8a0afa85 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/composite.rs @@ -488,6 +488,6 @@ macro_rules! generate_electoral_system_tuple_impls { } generate_electoral_system_tuple_impls!(tuple_1_impls: ((A, A0))); -generate_electoral_system_tuple_impls!(tuple_2_impls: ((A, A0), (B, B0))); generate_electoral_system_tuple_impls!(tuple_3_impls: ((A, A0), (B, B0), (C, C0))); +generate_electoral_system_tuple_impls!(tuple_4_impls: ((A, A0), (B, B0), (C, C0), (D, D0))); generate_electoral_system_tuple_impls!(tuple_7_impls: ((A, A0), (B, B0), (C, C0), (D, D0), (EE, E0), (FF, F0), (GG, G0))); diff --git a/state-chain/pallets/cf-elections/src/electoral_systems/state_machine/core.rs b/state-chain/pallets/cf-elections/src/electoral_systems/state_machine/core.rs index 00c64b61b67..08f74f3540c 100644 --- a/state-chain/pallets/cf-elections/src/electoral_systems/state_machine/core.rs +++ b/state-chain/pallets/cf-elections/src/electoral_systems/state_machine/core.rs @@ -153,7 +153,7 @@ impl> Indexed for (A, B) { )] pub struct ConstantIndex { pub data: A, - pub _phantom: sp_std::marker::PhantomData, + _phantom: sp_std::marker::PhantomData, } impl ConstantIndex { pub fn new(data: A) -> Self { diff --git a/state-chain/pallets/cf-elections/src/vote_storage/composite.rs b/state-chain/pallets/cf-elections/src/vote_storage/composite.rs index 69e97f0fd57..42adcab1d44 100644 --- a/state-chain/pallets/cf-elections/src/vote_storage/composite.rs +++ b/state-chain/pallets/cf-elections/src/vote_storage/composite.rs @@ -285,6 +285,6 @@ macro_rules! generate_vote_storage_tuple_impls { } generate_vote_storage_tuple_impls!(tuple_1_impls: (A)); -generate_vote_storage_tuple_impls!(tuple_2_impls: (A, B)); generate_vote_storage_tuple_impls!(tuple_3_impls: (A, B, C)); +generate_vote_storage_tuple_impls!(tuple_4_impls: (A, B, C, D)); generate_vote_storage_tuple_impls!(tuple_7_impls: (A, B, C, D, EE, FF, GG)); diff --git a/state-chain/pallets/cf-ingress-egress/src/lib.rs b/state-chain/pallets/cf-ingress-egress/src/lib.rs index edb5b7de2cb..7643790e37d 100644 --- a/state-chain/pallets/cf-ingress-egress/src/lib.rs +++ b/state-chain/pallets/cf-ingress-egress/src/lib.rs @@ -20,7 +20,6 @@ mod boost_pool; pub use boost_pool::OwedAmount; use boost_pool::{BoostPool, DepositFinalisationOutcomeForPool}; - use cf_chains::{ address::{ AddressConverter, AddressDerivationApi, AddressDerivationError, IntoForeignChainAddress, @@ -49,6 +48,7 @@ use cf_traits::{ SwapRequestHandler, SwapRequestType, }; use frame_support::{ + OrdNoBound, PartialOrdNoBound, __private::sp_tracing::warn, pallet_prelude::{OptionQuery, *}, sp_runtime::{traits::Zero, DispatchError, Permill, Saturating}, @@ -411,9 +411,20 @@ pub mod pallet { } #[derive( - CloneNoBound, RuntimeDebugNoBound, PartialEqNoBound, EqNoBound, Encode, Decode, TypeInfo, + CloneNoBound, + RuntimeDebugNoBound, + PartialEqNoBound, + EqNoBound, + Encode, + Decode, + TypeInfo, + Serialize, + Deserialize, + OrdNoBound, + PartialOrdNoBound, )] #[scale_info(skip_type_params(T, I))] + #[serde(bound(serialize = "", deserialize = ""))] pub struct VaultDepositWitness, I: 'static> { pub input_asset: TargetChainAsset, pub deposit_address: Option>, @@ -2199,7 +2210,7 @@ impl, I: 'static> Pallet { None } - fn process_vault_swap_request_prewitness( + pub fn process_vault_swap_request_prewitness( block_height: TargetChainBlockNumber, VaultDepositWitness { input_asset: asset, diff --git a/state-chain/primitives/src/lib.rs b/state-chain/primitives/src/lib.rs index 8a82aeefc25..ed98bbef609 100644 --- a/state-chain/primitives/src/lib.rs +++ b/state-chain/primitives/src/lib.rs @@ -190,7 +190,20 @@ pub const DEFAULT_MAX_AUTHORITY_SET_CONTRACTION: Percent = Percent::from_percent // Polkadot extrinsics are uniquely identified by - // https://wiki.polkadot.network/docs/build-protocol-info -#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Eq)] +#[derive( + Clone, + Encode, + Decode, + MaxEncodedLen, + TypeInfo, + Debug, + PartialEq, + Eq, + Serialize, + Deserialize, + Ord, + PartialOrd, +)] pub struct TxId { pub block_number: PolkadotBlockNumber, pub extrinsic_index: u32, diff --git a/state-chain/runtime/src/chainflip/address_derivation/btc.rs b/state-chain/runtime/src/chainflip/address_derivation/btc.rs index 273bec6b290..3232dbb4a24 100644 --- a/state-chain/runtime/src/chainflip/address_derivation/btc.rs +++ b/state-chain/runtime/src/chainflip/address_derivation/btc.rs @@ -7,6 +7,7 @@ use cf_chains::{ }; use cf_primitives::ChannelId; use cf_traits::KeyProvider; +use sp_std::vec::Vec; impl AddressDerivationApi for AddressDerivation { fn generate_address( @@ -43,6 +44,22 @@ impl AddressDerivationApi for AddressDerivation { Ok((channel_state.script_pubkey(), channel_state)) } } +pub fn derive_current_and_previous_epoch_private_btc_vaults( + channel_id: ChannelId, +) -> Result::DepositChannelState>, AddressDerivationError> { + let channel_id: u32 = channel_id + .try_into() + .map_err(|_| AddressDerivationError::BitcoinChannelIdTooLarge)?; + + let active_epoch_key = BitcoinThresholdSigner::active_epoch_key() + .ok_or(AddressDerivationError::MissingBitcoinVault)? + .key; + + Ok([Some(active_epoch_key.current), active_epoch_key.previous] + .into_iter() + .filter_map(|key| key.map(|k| DepositAddress::new(k, channel_id))) + .collect()) +} /// ONLY FOR USE IN RPC CALLS. /// diff --git a/state-chain/runtime/src/chainflip/bitcoin_block_processor.rs b/state-chain/runtime/src/chainflip/bitcoin_block_processor.rs index eb778acb381..18ffbdc5fb3 100644 --- a/state-chain/runtime/src/chainflip/bitcoin_block_processor.rs +++ b/state-chain/runtime/src/chainflip/bitcoin_block_processor.rs @@ -1,63 +1,83 @@ use sp_std::{collections::btree_map::BTreeMap, iter::Step, vec, vec::Vec}; -use crate::{chainflip::bitcoin_elections::BlockData, BitcoinIngressEgress, Runtime}; +use crate::{BitcoinIngressEgress, Runtime}; use cf_chains::{btc::BlockNumber, instances::BitcoinInstance}; use cf_primitives::chains::Bitcoin; -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{Decode, Encode}; use frame_support::{pallet_prelude::TypeInfo, Deserialize, Serialize}; -use log::warn; +use crate::chainflip::bitcoin_elections::{ + BitcoinVaultDepositWitnessing, BlockDataDepositChannel, BlockDataVaultDeposit, +}; use pallet_cf_elections::electoral_systems::{ block_witnesser::state_machine::{ DedupEventsHook, ExecuteHook, HookTypeFor, RulesHook, SafetyMarginHook, }, state_machine::core::Hook, }; -use pallet_cf_ingress_egress::{DepositWitness, ProcessedUpTo}; +use pallet_cf_ingress_egress::{DepositWitness, VaultDepositWitness}; use super::{bitcoin_elections::BitcoinDepositChannelWitnessing, elections::TypesFor}; -#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo, Deserialize, Serialize)] -pub enum BtcEvent { - PreWitness(DepositWitness), - Witness(DepositWitness), +#[derive( + Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo, Deserialize, Serialize, Ord, PartialOrd, +)] +pub enum BtcEvent { + PreWitness(T), + Witness(T), } -impl BtcEvent { - fn deposit_witness(&self) -> &DepositWitness { +impl BtcEvent { + fn deposit_witness(&self) -> &T { match self { BtcEvent::PreWitness(dw) | BtcEvent::Witness(dw) => dw, } } } -type Types = TypesFor; +type TypesDepositChannelWitnessing = TypesFor; +type TypesVaultDepositWitnessing = TypesFor; -impl Hook> for Types { - fn run(&mut self, (block, input): (BlockNumber, BtcEvent)) { +impl Hook> + for TypesDepositChannelWitnessing +{ + fn run(&mut self, (block, input): (BlockNumber, BtcEvent>)) { match input { BtcEvent::PreWitness(deposit) => { let _ = BitcoinIngressEgress::process_channel_deposit_prewitness(deposit, block); }, BtcEvent::Witness(deposit) => { BitcoinIngressEgress::process_channel_deposit_full_witness(deposit, block); - warn!("Witness executed"); - ProcessedUpTo::::set(block); }, } } } -impl Hook> for Types { +impl Hook> for TypesVaultDepositWitnessing { fn run( &mut self, - (block, age, block_data): (BlockNumber, u32, BlockData), - ) -> Vec<(BlockNumber, BtcEvent)> { + (block, input): (BlockNumber, BtcEvent>), + ) { + match input { + BtcEvent::PreWitness(deposit) => { + let _ = BitcoinIngressEgress::process_vault_swap_request_prewitness(block, deposit); + }, + BtcEvent::Witness(deposit) => { + BitcoinIngressEgress::process_vault_swap_request_full_witness(block, deposit); + }, + } + } +} +impl Hook> for TypesDepositChannelWitnessing { + fn run( + &mut self, + (block, age, block_data): (BlockNumber, u32, BlockDataDepositChannel), + ) -> Vec<(BlockNumber, BtcEvent>)> { // Prewitness rule if age == 0 { return block_data .iter() .map(|deposit_witness| (block, BtcEvent::PreWitness(deposit_witness.clone()))) - .collect::>(); + .collect::>(); } //Full witness rule if age == @@ -67,7 +87,33 @@ impl Hook> for Types { return block_data .iter() .map(|deposit_witness| (block, BtcEvent::Witness(deposit_witness.clone()))) - .collect::>(); + .collect::>(); + } + vec![] + } +} + +impl Hook> for TypesVaultDepositWitnessing { + fn run( + &mut self, + (block, age, block_data): (BlockNumber, u32, BlockDataVaultDeposit), + ) -> Vec<(BlockNumber, BtcEvent>)> { + // Prewitness rule + if age == 0 { + return block_data + .iter() + .map(|vault_deposit| (block, BtcEvent::PreWitness(vault_deposit.clone()))) + .collect::>(); + } + //Full witness rule + if age == + u64::steps_between(&0, &BitcoinIngressEgress::witness_safety_margin().unwrap_or(0)).0 + as u32 + { + return block_data + .iter() + .map(|vault_deposit| (block, BtcEvent::Witness(vault_deposit.clone()))) + .collect::>(); } vec![] } @@ -75,12 +121,19 @@ impl Hook> for Types { /// Returns one event per deposit witness. If multiple events share the same deposit witness: /// - keep only the `Witness` variant, -impl Hook> for Types { - fn run(&mut self, events: Vec<(BlockNumber, BtcEvent)>) -> Vec<(BlockNumber, BtcEvent)> { +impl Hook> + for TypesDepositChannelWitnessing +{ + fn run( + &mut self, + events: Vec<(BlockNumber, BtcEvent>)>, + ) -> Vec<(BlockNumber, BtcEvent>)> { // Map: deposit_witness -> chosen BtcEvent // todo! this is annoying, it require us to implement Ord down to the Chain type - let mut chosen: BTreeMap, (BlockNumber, BtcEvent)> = - BTreeMap::new(); + let mut chosen: BTreeMap< + DepositWitness, + (BlockNumber, BtcEvent>), + > = BTreeMap::new(); for (block, event) in events { let deposit: DepositWitness = event.deposit_witness().clone(); @@ -109,13 +162,62 @@ impl Hook> for Types { } } -impl Hook> for Types { +impl Hook> + for TypesVaultDepositWitnessing +{ + fn run( + &mut self, + events: Vec<(BlockNumber, BtcEvent>)>, + ) -> Vec<(BlockNumber, BtcEvent>)> { + // Map: deposit_witness -> chosen BtcEvent + // todo! this is annoying, it require us to implement Ord down to the Chain type + let mut chosen: BTreeMap< + VaultDepositWitness, + (BlockNumber, BtcEvent>), + > = BTreeMap::new(); + + for (block, event) in events { + let deposit: VaultDepositWitness = + event.deposit_witness().clone(); + + match chosen.get(&deposit) { + None => { + // No event yet for this deposit, store it + chosen.insert(deposit, (block, event)); + }, + Some((_, existing_event)) => { + // There's already an event for this deposit + match (existing_event, &event) { + // If we already have a Witness, do nothing + (BtcEvent::Witness(_), BtcEvent::PreWitness(_)) => (), + // If we have a PreWitness and the new event is a Witness, override it + (BtcEvent::PreWitness(_), BtcEvent::Witness(_)) => { + chosen.insert(deposit, (block, event)); + }, + // This should be impossible to reach! + (_, _) => (), + } + }, + } + } + chosen.into_values().collect() + } +} + +impl Hook> + for TypesDepositChannelWitnessing +{ + fn run(&mut self, _input: ()) -> u32 { + u64::steps_between(&0, &BitcoinIngressEgress::witness_safety_margin().unwrap_or(0)).0 as u32 + } +} +impl Hook> + for TypesVaultDepositWitnessing +{ fn run(&mut self, _input: ()) -> u32 { u64::steps_between(&0, &BitcoinIngressEgress::witness_safety_margin().unwrap_or(0)).0 as u32 } } -#[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub struct BlockWitnessingProcessorDefinition {} #[cfg(test)] mod tests { @@ -144,54 +246,54 @@ mod tests { }; use serde::{Deserialize, Serialize}; - #[allow(dead_code)] - fn block_data() -> BoxedStrategy { - (any::(), any::()) - .prop_map(|(amount, numb)| MockDeposit { amount, deposit_address: numb.to_string() }) - .boxed() - } - #[allow(dead_code)] - fn blocks_data( - number_of_blocks: u64, - ) -> BoxedStrategy> { - prop::collection::btree_map( - 0..number_of_blocks, - (vec![block_data()], (0..=0u32)), - RangeInclusive::new(0, number_of_blocks as usize), - ) - .boxed() - } - #[allow(dead_code)] - fn generate_state() -> BoxedStrategy> { - blocks_data(10) - .prop_map(|data| BlockProcessor { - blocks_data: data, - reorg_events: Default::default(), - rules: Default::default(), - execute: IncreasingHook::<(BlockNumber, MockBtcEvent), ()>::default(), - dedup_events: DedupEventsHook {}, - safety_margin: Default::default(), - }) - .boxed() - } - #[allow(dead_code)] - fn generate_input() -> BoxedStrategy> { - prop_oneof![ - (any::(), block_data()).prop_map(|(n, data)| SMBlockProcessorInput::NewBlockData( - n, - n, - vec![data] - )), - prop_oneof![ - (0..=5u64).prop_map(ChainProgressInner::Progress), - (0..=5u64).prop_map(|n| ChainProgressInner::Reorg( - RangeInclusive::::new(n, n + 2) - )), - ] - .prop_map(SMBlockProcessorInput::ChainProgress), - ] - .boxed() - } + #[allow(dead_code)] + fn block_data() -> BoxedStrategy { + (any::(), any::()) + .prop_map(|(amount, numb)| MockDeposit { amount, deposit_address: numb.to_string() }) + .boxed() + } + #[allow(dead_code)] + fn blocks_data( + number_of_blocks: u64, + ) -> BoxedStrategy> { + prop::collection::btree_map( + 0..number_of_blocks, + (vec![block_data()], (0..=0u32)), + RangeInclusive::new(0, number_of_blocks as usize), + ) + .boxed() + } + #[allow(dead_code)] + fn generate_state() -> BoxedStrategy> { + blocks_data(10) + .prop_map(|data| BlockProcessor { + blocks_data: data, + reorg_events: Default::default(), + rules: ApplyRulesHook {}, + execute: IncreasingHook::<(BlockNumber, MockBtcEvent), ()>::default(), + dedup_events: DedupEventsHook {}, + safety_margin: SafetyMarginHook {}, + }) + .boxed() + } + #[allow(dead_code)] + fn generate_input() -> BoxedStrategy> { + prop_oneof![ + (any::(), block_data()).prop_map(|(n, data)| SMBlockProcessorInput::NewBlockData( + n, + n, + vec![data] + )), + prop_oneof![ + (0..=5u64).prop_map(ChainProgressInner::Progress), + (0..=5u64).prop_map(|n| ChainProgressInner::Reorg( + RangeInclusive::::new(n, n + 2) + )), + ] + .prop_map(SMBlockProcessorInput::ChainProgress), + ] + .boxed() + } #[derive( Clone, diff --git a/state-chain/runtime/src/chainflip/bitcoin_elections.rs b/state-chain/runtime/src/chainflip/bitcoin_elections.rs index be27b12021c..7763901f3bc 100644 --- a/state-chain/runtime/src/chainflip/bitcoin_elections.rs +++ b/state-chain/runtime/src/chainflip/bitcoin_elections.rs @@ -1,11 +1,20 @@ use crate::{ - chainflip::ReportFailedLivenessCheck, BitcoinChainTracking, BitcoinIngressEgress, Runtime, + chainflip::{ + address_derivation::btc::derive_current_and_previous_epoch_private_btc_vaults, + ReportFailedLivenessCheck, + }, + BitcoinChainTracking, BitcoinIngressEgress, Runtime, }; use cf_chains::{ - btc::{self, BitcoinFeeInfo, BitcoinTrackedData, BlockNumber, Hash}, + btc::{ + self, deposit_address::DepositAddress, BitcoinFeeInfo, BitcoinTrackedData, BlockNumber, + Hash, + }, instances::BitcoinInstance, Bitcoin, }; +use cf_primitives::{AccountId, ChannelId}; +use cf_runtime_utilities::log_or_panic; use cf_traits::Chainflip; use frame_system::pallet_prelude::BlockNumberFor; use pallet_cf_elections::{ @@ -26,7 +35,7 @@ use pallet_cf_elections::{ }, }, composite::{ - tuple_3_impls::{DerivedElectoralAccess, Hooks}, + tuple_4_impls::{DerivedElectoralAccess, Hooks}, CompositeRunner, }, liveness::Liveness, @@ -39,7 +48,7 @@ use pallet_cf_elections::{ RunnerStorageAccess, }; use pallet_cf_ingress_egress::{ - DepositChannelDetails, DepositWitness, PalletSafeMode, ProcessedUpTo, + DepositChannelDetails, DepositWitness, PalletSafeMode, VaultDepositWitness, }; use scale_info::TypeInfo; use sp_core::{Decode, Encode, Get, MaxEncodedLen}; @@ -48,7 +57,12 @@ use sp_std::vec::Vec; use super::{bitcoin_block_processor::BtcEvent, elections::TypesFor}; pub type BitcoinElectoralSystemRunner = CompositeRunner< - (BitcoinBlockHeightTrackingES, BitcoinDepositChannelWitnessingES, BitcoinLiveness), + ( + BitcoinBlockHeightTrackingES, + BitcoinDepositChannelWitnessingES, + BitcoinVaultDepositWitnessingES, + BitcoinLiveness, + ), ::ValidatorId, RunnerStorageAccess, BitcoinElectionHooks, @@ -129,8 +143,8 @@ pub type BitcoinBlockHeightTrackingES = /// The electoral system for deposit channel witnessing pub struct BitcoinDepositChannelWitnessing; -type ElectionProperties = Vec>; -pub(crate) type BlockData = Vec>; +type ElectionPropertiesDepositChannel = Vec>; +pub(crate) type BlockDataDepositChannel = Vec>; impls! { for TypesFor: @@ -138,9 +152,9 @@ impls! { /// Associating BW processor types BWProcessorTypes { type ChainBlockNumber = btc::BlockNumber; - type BlockData = BlockData; + type BlockData = BlockDataDepositChannel; - type Event = BtcEvent; + type Event = BtcEvent>; type Rules = Self; type Execute = Self; type DedupEvents = Self; @@ -149,7 +163,7 @@ impls! { /// Associating BW types to the struct BWTypes { - type ElectionProperties = ElectionProperties; + type ElectionProperties = ElectionPropertiesDepositChannel; type ElectionPropertiesHook = Self; type SafeModeEnabledHook = Self; } @@ -163,12 +177,12 @@ impls! { type ElectoralUnsynchronisedSettings = BWSettings; type ElectoralSettings = (); type ElectionIdentifierExtra = (); - type ElectionProperties = (btc::BlockNumber, ElectionProperties, u8); + type ElectionProperties = (btc::BlockNumber, ElectionPropertiesDepositChannel, u8); type ElectionState = (); type VoteStorage = vote_storage::bitmap::Bitmap< - ConstantIndex<(btc::BlockNumber, ElectionProperties, u8), BlockData>, + ConstantIndex<(btc::BlockNumber, ElectionPropertiesDepositChannel, u8), BlockDataDepositChannel>, >; - type Consensus = ConstantIndex<(btc::BlockNumber, ElectionProperties, u8), BlockData>; + type Consensus = ConstantIndex<(btc::BlockNumber, ElectionPropertiesDepositChannel, u8), BlockDataDepositChannel>; type OnFinalizeContext = Vec>; type OnFinalizeReturn = Vec<()>; } @@ -180,15 +194,15 @@ impls! { type OnFinalizeReturnItem = (); // restating types since we have to prove that they have the correct bounds - type Consensus2 = ConstantIndex<(btc::BlockNumber, ElectionProperties, u8), BlockData>; - type Vote2 = ConstantIndex<(btc::BlockNumber, ElectionProperties, u8), BlockData>; + type Consensus2 = ConstantIndex<(btc::BlockNumber, ElectionPropertiesDepositChannel, u8), BlockDataDepositChannel>; + type Vote2 = ConstantIndex<(btc::BlockNumber, ElectionPropertiesDepositChannel, u8), BlockDataDepositChannel>; type VoteStorage2 = vote_storage::bitmap::Bitmap< - ConstantIndex<(btc::BlockNumber, ElectionProperties, u8), BlockData>, + ConstantIndex<(btc::BlockNumber, ElectionPropertiesDepositChannel, u8), BlockDataDepositChannel>, >; // the actual state machine and consensus mechanisms of this ES type StateMachine = BWStateMachine; - type ConsensusMechanism = BWConsensus; + type ConsensusMechanism = BWConsensus; } /// implementation of safe mode reading hook @@ -223,6 +237,111 @@ impls! { pub type BitcoinDepositChannelWitnessingES = StateMachineESInstance>; +// ------------------------ vault deposit witnessing --------------------------- +/// The electoral system for vault deposit witnessing + +pub struct BitcoinVaultDepositWitnessing; + +type ElectionPropertiesVaultDeposit = Vec<(DepositAddress, AccountId, ChannelId)>; +pub(crate) type BlockDataVaultDeposit = Vec>; + +impls! { + for TypesFor: + + /// Associating BW processor types + BWProcessorTypes { + type ChainBlockNumber = BlockNumber; + type BlockData = BlockDataVaultDeposit; + + type Event = BtcEvent>; + type Rules = Self; + type Execute = Self; + type DedupEvents = Self; + type SafetyMargin = Self; + } + + /// Associating BW types to the struct + BWTypes { + type ElectionProperties = ElectionPropertiesVaultDeposit; + type ElectionPropertiesHook = Self; + type SafeModeEnabledHook = Self; + } + + /// Associating the ES related types to the struct + ElectoralSystemTypes { + type ValidatorId = ::ValidatorId; + type ElectoralUnsynchronisedState = BWState; + type ElectoralUnsynchronisedStateMapKey = (); + type ElectoralUnsynchronisedStateMapValue = (); + type ElectoralUnsynchronisedSettings = BWSettings; + type ElectoralSettings = (); + type ElectionIdentifierExtra = (); + type ElectionProperties = (btc::BlockNumber, ElectionPropertiesVaultDeposit, u8); + type ElectionState = (); + type VoteStorage = vote_storage::bitmap::Bitmap< + ConstantIndex<(btc::BlockNumber, ElectionPropertiesVaultDeposit, u8), BlockDataVaultDeposit>, + >; + type Consensus = ConstantIndex<(btc::BlockNumber, ElectionPropertiesVaultDeposit, u8), BlockDataVaultDeposit>; + type OnFinalizeContext = Vec>; + type OnFinalizeReturn = Vec<()>; + } + + /// Associating the state machine and consensus mechanism to the struct + StateMachineES { + // both context and return have to be vectors, these are the item types + type OnFinalizeContextItem = ChainProgress; + type OnFinalizeReturnItem = (); + + // restating types since we have to prove that they have the correct bounds + type Consensus2 = ConstantIndex<(btc::BlockNumber, ElectionPropertiesVaultDeposit, u8), BlockDataVaultDeposit>; + type Vote2 = ConstantIndex<(btc::BlockNumber, ElectionPropertiesVaultDeposit, u8), BlockDataVaultDeposit>; + type VoteStorage2 = vote_storage::bitmap::Bitmap< + ConstantIndex<(btc::BlockNumber, ElectionPropertiesVaultDeposit, u8), BlockDataVaultDeposit>, + >; + + // the actual state machine and consensus mechanisms of this ES + type StateMachine = BWStateMachine; + type ConsensusMechanism = BWConsensus; + } + + /// implementation of safe mode reading hook + Hook> { + fn run(&mut self, _input: ()) -> SafeModeStatus { + if <>::SafeMode as Get< + PalletSafeMode, + >>::get() + .deposits_enabled + { + SafeModeStatus::Disabled + } else { + SafeModeStatus::Enabled + } + } + } + + /// implementation of reading vault hook + Hook> { + fn run(&mut self, _block_witness_root: BlockNumber) -> ElectionPropertiesVaultDeposit { + pallet_cf_swapping::BrokerPrivateBtcChannels::::iter() + .flat_map(|(broker_id, channel_id)| { + derive_current_and_previous_epoch_private_btc_vaults(channel_id) + .map_err(|err| { + log_or_panic!("Error while deriving private BTC addresses: {err:#?}") + }) + .ok() + .into_iter() + .flat_map(|addresses| addresses) + .map(move |address| (address, broker_id.clone(), channel_id)) + }) + .collect::>() + } + } + +} + +/// Generating the state machine-based electoral system +pub type BitcoinVaultDepositWitnessingES = + StateMachineESInstance>; pub type BitcoinLiveness = Liveness< BlockNumber, Hash, @@ -233,11 +352,16 @@ pub type BitcoinLiveness = Liveness< pub struct BitcoinElectionHooks; -impl Hooks - for BitcoinElectionHooks +impl + Hooks< + BitcoinBlockHeightTrackingES, + BitcoinDepositChannelWitnessingES, + BitcoinVaultDepositWitnessingES, + BitcoinLiveness, + > for BitcoinElectionHooks { fn on_finalize( - (block_height_tracking_identifiers, deposit_channel_witnessing_identifiers, liveness_identifiers): ( + (block_height_tracking_identifiers, deposit_channel_witnessing_identifiers, vault_deposits_identifiers, liveness_identifiers): ( Vec< ElectionIdentifier< ::ElectionIdentifierExtra, @@ -248,6 +372,11 @@ impl Hooks::ElectionIdentifierExtra, >, >, + Vec< + ElectionIdentifier< + ::ElectionIdentifierExtra, + >, + >, Vec< ElectionIdentifier< ::ElectionIdentifierExtra, @@ -275,18 +404,23 @@ impl Hooks, >(deposit_channel_witnessing_identifiers.clone(), &chain_progress)?; - // We use `ProcessedUpTo` as our upper limit to avoid not reaching consensus in - // case there is a reorg, using this block means safety margin will be kept into account for - // this election, and thus are much less likely to ask nodes to query for a block they don't - // have. - let last_processed_block = ProcessedUpTo::::get(); + BitcoinVaultDepositWitnessingES::on_finalize::< + DerivedElectoralAccess< + _, + BitcoinVaultDepositWitnessingES, + RunnerStorageAccess, + >, + >(vault_deposits_identifiers.clone(), &chain_progress)?; + + let last_btc_block = + pallet_cf_chain_tracking::CurrentChainState::::get().unwrap(); BitcoinLiveness::on_finalize::< DerivedElectoralAccess< _, BitcoinLiveness, RunnerStorageAccess, >, - >(liveness_identifiers, &(current_sc_block_number, last_processed_block))?; + >(liveness_identifiers, &(current_sc_block_number, last_btc_block.block_height - 3))?; Ok(()) } @@ -301,13 +435,24 @@ const LIVENESS_CHECK_DURATION: BlockNumberFor = 10; // yet processed, the channel was not expired. pub fn initial_state() -> InitialStateOf { InitialState { - unsynchronised_state: (Default::default(), Default::default(), Default::default()), + unsynchronised_state: ( + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ), unsynchronised_settings: ( Default::default(), // TODO: Write a migration to set this too. BWSettings { max_concurrent_elections: 15 }, + BWSettings { max_concurrent_elections: 15 }, (), ), - settings: (Default::default(), Default::default(), LIVENESS_CHECK_DURATION), + settings: ( + Default::default(), + Default::default(), + Default::default(), + LIVENESS_CHECK_DURATION, + ), } }