Skip to content

Commit

Permalink
feat: BTC vault deposit electoral system (#5640)
Browse files Browse the repository at this point in the history
* vault deposit ES

* rebase on Hook changes
  • Loading branch information
marcellorigotti authored Feb 19, 2025
1 parent cf5f1cb commit 53c2852
Show file tree
Hide file tree
Showing 15 changed files with 496 additions and 285 deletions.
2 changes: 1 addition & 1 deletion engine/src/elections/voter_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
147 changes: 3 additions & 144 deletions engine/src/witness/btc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ProcessCall, ProcessingFut, ExtraInfo, ExtraHistoricInfo>(
epoch: Vault<cf_chains::Bitcoin, ExtraInfo, ExtraHistoricInfo>,
Expand Down Expand Up @@ -64,128 +45,6 @@ pub async fn process_egress<ProcessCall, ProcessingFut, ExtraInfo, ExtraHistoric
}
}

pub async fn start<
StateChainClient,
StateChainStream,
ProcessCall,
ProcessingFut,
PrewitnessCall,
PrewitnessFut,
>(
scope: &Scope<'_, anyhow::Error>,
btc_client: BtcRetryRpcClient,
process_call: ProcessCall,
prewitness_call: PrewitnessCall,
state_chain_client: Arc<StateChainClient>,
state_chain_stream: StateChainStream,
unfinalised_state_chain_stream: impl StreamApi<UNFINALIZED> + Clone,
epoch_source: EpochSourceBuilder<'_, '_, StateChainClient, (), ()>,
db: Arc<PersistentKeyDB>,
) -> Result<()>
where
StateChainClient: StorageApi + SignedExtrinsicApi + 'static + Send + Sync,
StateChainStream: StreamApi<FINALIZED> + Clone,
ProcessCall: Fn(state_chain_runtime::RuntimeCall, EpochIndex) -> ProcessingFut
+ Send
+ Sync
+ Clone
+ 'static,
ProcessingFut: Future<Output = ()> + Send + 'static,
PrewitnessCall: Fn(state_chain_runtime::RuntimeCall, EpochIndex) -> PrewitnessFut
+ Send
+ Sync
+ Clone
+ 'static,
PrewitnessFut: Future<Output = ()> + 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::<cf_chains::Bitcoin>().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::<pallet_cf_ingress_egress::WitnessSafetyMargin<
state_chain_runtime::Runtime,
state_chain_runtime::BitcoinInstance,
>>(state_chain_stream.cache().hash)
.await?
{
Some(margin) => margin,
None => {
use chainflip_node::chain_spec::{berghain, devnet, perseverance};
match state_chain_client
.storage_value::<pallet_cf_environment::ChainflipNetworkEnvironment<state_chain_runtime::Runtime>>(
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<Item = &'a btc::Hash> + Clone,
txs: Vec<VerboseTransaction>,
Expand Down
29 changes: 24 additions & 5 deletions engine/src/witness/btc/deposits.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -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};
Expand Down Expand Up @@ -128,6 +131,22 @@ impl<Inner: ChunkedByVault> ChunkedByVaultBuilder<Inner> {
}
}

pub fn vault_deposits(
txs: &[VerboseTransaction],
vaults: &Vec<(DepositAddress, AccountId, ChannelId)>,
) -> Vec<VaultDepositWitness<state_chain_runtime::Runtime, BitcoinInstance>> {
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<Vec<u8>, DepositAddress>,
Expand Down
47 changes: 40 additions & 7 deletions engine/src/witness/btc_e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -59,7 +60,8 @@ impl VoterApi<BitcoinDepositChannelWitnessingES> for BitcoinDepositChannelWitnes
deposit_addresses: <BitcoinDepositChannelWitnessingES as ElectoralSystemTypes>::ElectionProperties,
) -> Result<Option<VoteOf<BitcoinDepositChannelWitnessingES>>, 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![];
Expand All @@ -80,13 +82,43 @@ impl VoterApi<BitcoinDepositChannelWitnessingES> 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<BitcoinVaultDepositWitnessingES> for BitcoinVaultDepositWitnessingVoter {
async fn vote(
&self,
_settings: <BitcoinVaultDepositWitnessingES as ElectoralSystemTypes>::ElectoralSettings,
properties: <BitcoinVaultDepositWitnessingES as ElectoralSystemTypes>::ElectionProperties,
) -> Result<Option<VoteOf<BitcoinVaultDepositWitnessingES>>, 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::<cf_chains::Bitcoin>::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)))
}
}

Expand Down Expand Up @@ -216,6 +248,7 @@ where
CompositeVoter::<BitcoinElectoralSystemRunner, _>::new((
BitcoinBlockHeightTrackingVoter { client: client.clone() },
BitcoinDepositChannelWitnessingVoter { client: client.clone() },
BitcoinVaultDepositWitnessingVoter { client: client.clone() },
BitcoinLivenessVoter { client },
)),
)
Expand Down
15 changes: 14 additions & 1 deletion state-chain/chains/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 16 additions & 4 deletions state-chain/chains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,9 @@ pub trait Chain: Member + Parameter + ChainInstanceAlias {
+ Into<cf_primitives::ForeignChain>
+ TryFrom<cf_primitives::Asset, Error: Debug>
+ IntoEnumIterator
+ Unpin;
+ Unpin
+ Ord
+ PartialOrd;

type ChainAssetMap<
T: Member
Expand Down Expand Up @@ -456,7 +458,9 @@ pub trait Chain: Member + Parameter + ChainInstanceAlias {
+ TryFrom<ForeignChainAddress>
+ IntoForeignChainAddress<Self>
+ Unpin
+ ToHumanreadableAddress;
+ ToHumanreadableAddress
+ Serialize
+ for<'a> Deserialize<'a>;

type DepositFetchId: Member
+ Parameter
Expand All @@ -471,7 +475,11 @@ pub trait Chain: Member + Parameter + ChainInstanceAlias {
type DepositDetails: Member
+ Parameter
+ BenchmarkValue
+ DepositDetailsToTransactionInId<Self::ChainCrypto>;
+ DepositDetailsToTransactionInId<Self::ChainCrypto>
+ Serialize
+ for<'a> Deserialize<'a>
+ Ord
+ PartialOrd;

type Transaction: Member + Parameter + BenchmarkValue + FeeRefundCalculator<Self>;

Expand Down Expand Up @@ -510,7 +518,11 @@ pub trait ChainCrypto: ChainCryptoInstanceAlias + Sized {
+ Parameter
+ Unpin
+ IntoTransactionInIdForAnyChain<Self>
+ BenchmarkValue;
+ BenchmarkValue
+ Serialize
+ for<'a> Deserialize<'a>
+ Ord
+ PartialOrd;

/// Uniquely identifies a transaction on the outgoing direction.
type TransactionOutId: Member + Parameter + Unpin + BenchmarkValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<HookTypeFor<Self, RulesHook>> + Default + Serde + Debug + Clone + Eq;
type Execute: Hook<HookTypeFor<Self, ExecuteHook>> + Default + Serde + Debug + Clone + Eq;
Expand Down
Loading

0 comments on commit 53c2852

Please sign in to comment.