From 0844d1a102163bdf0b3afbb6e05bfa69df3ae05b Mon Sep 17 00:00:00 2001 From: Maxim Urschumzew Date: Wed, 6 Nov 2024 15:03:45 +0100 Subject: [PATCH] feat: update custom_rpc, runtime_api and broker api for broker level screening (#5341) * Squashed commit of the following: commit 3166701641c19830c6777bf6959f95b13f5a1f5b Author: Maxim Urschumzew Date: Tue Oct 29 15:27:51 2024 +0100 Add `subscribe_tainted_btc_transaction_events` method to Broker API. It which forwards the node subscription of the same name. commit 010772dcf3db43d58d9066c8348cb80db928c981 Author: Maxim Urschumzew Date: Tue Oct 29 13:56:07 2024 +0100 Add `open_btc_deposit_channels` method to broker api. commit 9ea7e6fb2a2ebf5423ec67406a4256932f82e55e Author: Maxim Urschumzew Date: Tue Oct 29 11:19:07 2024 +0100 Add subscription endpoint for tainted transaction events. commit e81028a46d2a823f52b6ecbd84d012db33e468bb Author: Maxim Urschumzew Date: Mon Oct 28 15:12:47 2024 +0100 Implement `open_btc_deposit_channels`. commit 439ddd1a02a3e370e004e87bf214ed6f92d13b0d Author: Maxim Urschumzew Date: Mon Oct 28 14:51:22 2024 +0100 Add boilerplate for `open_btc_deposit_channels`. commit a3835f13dc231f80a84e86c529f4de2b88b52964 Author: Maxim Urschumzew Date: Mon Oct 28 10:25:51 2024 +0100 Add endpoint for marking btc tx as tainted. * Apply suggestions from review. * Return all channels for *all* channel actions. We do not filter out `LiquidityProvision` channels anymore. * Create tainted event subscription in broker API instead of forwarding from the node. This is now the same as in the LP API binary. The previous version didn't use finalized blocks and thus might not have been stable. Theoretically it would be nice to be able to forward the subscription from the node, see PRO-1768. * Apply clippy suggestion. * Remove now unused `tainted_transaction_events` subscription. * Apply suggestions from @kylezs's review. --- Cargo.lock | 3 ++ api/bin/chainflip-broker-api/Cargo.toml | 6 ++-- api/bin/chainflip-broker-api/src/main.rs | 18 +++++++++-- api/lib/src/lib.rs | 35 +++++++++++++++++++-- state-chain/custom-rpc/Cargo.toml | 1 + state-chain/custom-rpc/src/lib.rs | 28 +++++++++++++++-- state-chain/runtime/src/lib.rs | 40 ++++++++++++++++++++++-- state-chain/runtime/src/runtime_apis.rs | 38 +++++++++++++++++++++- 8 files changed, 156 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbad0f58ede..ae44d390bde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1628,12 +1628,14 @@ name = "chainflip-broker-api" version = "1.7.0" dependencies = [ "anyhow", + "cf-chains", "chainflip-api", "clap", "custom-rpc", "futures", "hex", "jsonrpsee 0.23.2", + "sc-rpc", "serde", "sp-core 34.0.0", "sp-rpc", @@ -2558,6 +2560,7 @@ dependencies = [ "jsonrpsee 0.23.2", "log", "pallet-cf-governance", + "pallet-cf-ingress-egress", "pallet-cf-pools", "pallet-cf-swapping", "pallet-cf-witnesser", diff --git a/api/bin/chainflip-broker-api/Cargo.toml b/api/bin/chainflip-broker-api/Cargo.toml index be7606f79f3..4ab0d5ec544 100644 --- a/api/bin/chainflip-broker-api/Cargo.toml +++ b/api/bin/chainflip-broker-api/Cargo.toml @@ -23,6 +23,7 @@ workspace = true [dependencies] chainflip-api = { workspace = true } +cf-chains = { workspace = true, default-features = true } cf-utilities = { workspace = true, default-features = true } custom-rpc = { workspace = true } @@ -32,8 +33,9 @@ futures = { workspace = true } hex = { workspace = true, default-features = true } jsonrpsee = { workspace = true, features = ["full"] } serde = { workspace = true, default-features = true, features = ["derive"] } -sp-core = { workspace = true } -sp-rpc = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-rpc = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/api/bin/chainflip-broker-api/src/main.rs b/api/bin/chainflip-broker-api/src/main.rs index 5dcd076080d..8e6b6b9ee14 100644 --- a/api/bin/chainflip-broker-api/src/main.rs +++ b/api/bin/chainflip-broker-api/src/main.rs @@ -4,19 +4,25 @@ use cf_utilities::{ }; use chainflip_api::{ self, - primitives::{AccountRole, Affiliates, Asset, BasisPoints, CcmChannelMetadata, DcaParameters}, + primitives::{ + state_chain_runtime::runtime_apis::{ChainAccounts, TaintedTransactionEvents}, + AccountRole, Affiliates, Asset, BasisPoints, CcmChannelMetadata, DcaParameters, + }, settings::StateChain, AccountId32, AddressString, BrokerApi, OperatorApi, RefundParameters, StateChainApi, SwapDepositAddress, WithdrawFeesDetail, }; use clap::Parser; -use futures::FutureExt; +use custom_rpc::CustomApiClient; +use futures::{FutureExt, StreamExt}; use jsonrpsee::{ - core::{async_trait, ClientError}, + core::{async_trait, ClientError, SubscriptionResult}, proc_macros::rpc, server::ServerBuilder, types::{ErrorCode, ErrorObject, ErrorObjectOwned}, + PendingSubscriptionSink, }; +use serde::{Deserialize, Serialize}; use std::{ path::PathBuf, sync::{atomic::AtomicBool, Arc}, @@ -59,6 +65,12 @@ impl From for ErrorObjectOwned { } } +#[derive(Serialize, Deserialize)] +pub enum GetOpenDepositChannelsQuery { + All, + Mine, +} + #[rpc(server, client, namespace = "broker")] pub trait Rpc { #[method(name = "register_account", aliases = ["broker_registerAccount"])] diff --git a/api/lib/src/lib.rs b/api/lib/src/lib.rs index 033d691f2af..ce1b24b8b8f 100644 --- a/api/lib/src/lib.rs +++ b/api/lib/src/lib.rs @@ -7,7 +7,7 @@ use cf_chains::{ dot::PolkadotAccountId, evm::to_evm_address, sol::SolAddress, - CcmChannelMetadata, ChannelRefundParametersGeneric, ForeignChain, ForeignChainAddress, + CcmChannelMetadata, Chain, ChainCrypto, ChannelRefundParametersEncoded, }; pub use cf_primitives::{AccountRole, Affiliates, Asset, BasisPoints, ChannelId, SemVer}; use cf_primitives::{BlockNumber, DcaParameters, NetworkEnvironment, Price}; @@ -28,7 +28,7 @@ pub mod primitives { pub type RedemptionAmount = pallet_cf_funding::RedemptionAmount; pub use cf_chains::{ address::{EncodedAddress, ForeignChainAddress}, - CcmChannelMetadata, CcmDepositMetadata, + CcmChannelMetadata, CcmDepositMetadata, Chain, ChainCrypto, }; } pub use cf_chains::eth::Address as EthereumAddress; @@ -150,6 +150,10 @@ impl StateChainApi { self.state_chain_client.clone() } + pub fn deposit_monitor_api(&self) -> Arc { + self.state_chain_client.clone() + } + pub fn query_api(&self) -> queries::QueryApi { queries::QueryApi { state_chain_client: self.state_chain_client.clone() } } @@ -167,6 +171,8 @@ impl BrokerApi for StateChainClient { impl OperatorApi for StateChainClient {} #[async_trait] impl ValidatorApi for StateChainClient {} +#[async_trait] +impl DepositMonitorApi for StateChainClient {} #[async_trait] pub trait ValidatorApi: SimpleSubmissionApi { @@ -548,6 +554,31 @@ pub fn clean_foreign_chain_address(chain: ForeignChain, address: &str) -> Result }) } +pub type TransactionInIdFor = <::ChainCrypto as ChainCrypto>::TransactionInId; + +#[derive(Serialize, Deserialize)] +pub enum TransactionInId { + Bitcoin(TransactionInIdFor), + // other variants reserved for other chains. +} + +#[async_trait] +pub trait DepositMonitorApi: + SignedExtrinsicApi + StorageApi + Sized + Send + Sync + 'static +{ + async fn mark_transaction_as_tainted(&self, tx_id: TransactionInId) -> Result { + match tx_id { + TransactionInId::Bitcoin(tx_id) => + self.simple_submission_with_dry_run( + state_chain_runtime::RuntimeCall::BitcoinIngressEgress( + pallet_cf_ingress_egress::Call::mark_transaction_as_tainted { tx_id }, + ), + ) + .await, + } + } +} + #[derive(Debug, Zeroize, PartialEq, Eq)] /// Public and Secret keys as bytes pub struct KeyPair { diff --git a/state-chain/custom-rpc/Cargo.toml b/state-chain/custom-rpc/Cargo.toml index 18fc15a7893..66d10ba3a1c 100644 --- a/state-chain/custom-rpc/Cargo.toml +++ b/state-chain/custom-rpc/Cargo.toml @@ -29,6 +29,7 @@ pallet-cf-governance = { workspace = true, default-features = true } pallet-cf-pools = { workspace = true, default-features = true } pallet-cf-witnesser = { workspace = true, default-features = true } pallet-cf-swapping = { workspace = true, default-features = true } +pallet-cf-ingress-egress = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/state-chain/custom-rpc/src/lib.rs b/state-chain/custom-rpc/src/lib.rs index e67cd48b503..a5a9a05d221 100644 --- a/state-chain/custom-rpc/src/lib.rs +++ b/state-chain/custom-rpc/src/lib.rs @@ -52,9 +52,10 @@ use state_chain_runtime::{ PendingBroadcasts, PendingTssCeremonies, RedemptionsInfo, SolanaNonces, }, runtime_apis::{ - AuctionState, BoostPoolDepth, BoostPoolDetails, BrokerInfo, CustomRuntimeApi, - DispatchErrorWithMessage, ElectoralRuntimeApi, FailingWitnessValidators, - LiquidityProviderBoostPoolInfo, LiquidityProviderInfo, RuntimeApiPenalty, ValidatorInfo, + AuctionState, BoostPoolDepth, BoostPoolDetails, BrokerInfo, ChainAccounts, + CustomRuntimeApi, DispatchErrorWithMessage, ElectoralRuntimeApi, FailingWitnessValidators, + LiquidityProviderBoostPoolInfo, LiquidityProviderInfo, RuntimeApiPenalty, + TaintedTransactionEvents, ValidatorInfo, }, safe_mode::RuntimeSafeMode, Hash, NetworkFee, SolanaInstance, @@ -938,6 +939,19 @@ pub trait CustomApi { proposed_votes: Vec, at: Option, ) -> RpcResult>; + + #[method(name = "get_open_deposit_channels")] + fn cf_get_open_deposit_channels( + &self, + broker: Option, + at: Option, + ) -> RpcResult; + + #[method(name = "get_tainted_transaction_events")] + fn cf_get_tainted_transaction_events( + &self, + at: Option, + ) -> RpcResult; } /// An RPC extension for the state chain node. @@ -1190,6 +1204,7 @@ where cf_failed_call_arbitrum(broadcast_id: BroadcastId) -> Option<::Transaction>, cf_boost_pools_depth() -> Vec, cf_pool_price(from_asset: Asset, to_asset: Asset) -> Option, + cf_get_open_deposit_channels(account_id: Option) -> ChainAccounts, } pass_through_and_flatten! { @@ -1725,6 +1740,13 @@ where ) -> RpcResult> { self.with_runtime_api(at, |api, hash| api.cf_filter_votes(hash, validator, proposed_votes)) } + + fn cf_get_tainted_transaction_events( + &self, + at: Option, + ) -> RpcResult { + self.with_runtime_api(at, |api, hash| api.cf_tainted_transaction_events(hash)) + } } impl CustomRpc diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index d09211334ec..c7ab924f408 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -28,7 +28,8 @@ use crate::{ runtime_decl_for_custom_runtime_api::CustomRuntimeApiV1, AuctionState, BoostPoolDepth, BoostPoolDetails, BrokerInfo, DispatchErrorWithMessage, FailingWitnessValidators, LiquidityProviderBoostPoolInfo, LiquidityProviderInfo, RuntimeApiPenalty, - SimulateSwapAdditionalOrder, SimulatedSwapInformation, ValidatorInfo, + SimulateSwapAdditionalOrder, SimulatedSwapInformation, TaintedTransactionEvents, + ValidatorInfo, }, }; use cf_amm::{ @@ -71,8 +72,9 @@ use pallet_cf_pools::{ AskBidMap, AssetPair, HistoricalEarnedFees, OrderId, PoolLiquidity, PoolOrderbook, PoolPriceV1, PoolPriceV2, UnidirectionalPoolDepth, }; +use runtime_apis::ChainAccounts; -use crate::chainflip::EvmLimit; +use crate::{chainflip::EvmLimit, runtime_apis::TaintedTransactionEvent}; use pallet_cf_reputation::{ExclusionList, HeartbeatQualification, ReputationPointsQualification}; use pallet_cf_swapping::SwapLegInfo; @@ -2065,8 +2067,42 @@ impl_runtime_apis! { fn cf_pools() -> Vec> { LiquidityPools::pools() } + + fn cf_get_open_deposit_channels(account_id: Option) -> ChainAccounts { + let btc_chain_accounts = pallet_cf_ingress_egress::DepositChannelLookup::::iter_values() + .filter(|channel_details| account_id.is_none() || Some(&channel_details.owner) == account_id.as_ref()) + .map(|channel_details| channel_details.deposit_channel.address) + .collect::>(); + + ChainAccounts { + btc_chain_accounts + } + } + + fn cf_tainted_transaction_events() -> crate::runtime_apis::TaintedTransactionEvents { + let btc_events = System::read_events_no_consensus().filter_map(|event_record| { + if let RuntimeEvent::BitcoinIngressEgress(btc_ie_event) = event_record.event { + match btc_ie_event { + pallet_cf_ingress_egress::Event::TaintedTransactionReportExpired{ account_id, tx_id } => + Some(TaintedTransactionEvent::TaintedTransactionReportExpired{ account_id, tx_id }), + pallet_cf_ingress_egress::Event::TaintedTransactionReportReceived{ account_id, tx_id, expires_at: _ } => + Some(TaintedTransactionEvent::TaintedTransactionReportReceived{account_id, tx_id }), + pallet_cf_ingress_egress::Event::TaintedTransactionRejected{ broadcast_id, tx_id } => + Some(TaintedTransactionEvent::TaintedTransactionRejected{ refund_broadcast_id: broadcast_id, tx_id: tx_id.id.tx_id }), + _ => None, + } + } else { + None + } + }).collect(); + + TaintedTransactionEvents { + btc_events + } + } } + impl monitoring_apis::MonitoringRuntimeApi for Runtime { fn cf_authorities() -> AuthoritiesInfo { diff --git a/state-chain/runtime/src/runtime_apis.rs b/state-chain/runtime/src/runtime_apis.rs index 5c0479a7397..7d8c19dd93a 100644 --- a/state-chain/runtime/src/runtime_apis.rs +++ b/state-chain/runtime/src/runtime_apis.rs @@ -4,7 +4,8 @@ use cf_amm::{ range_orders::Liquidity, }; use cf_chains::{ - assets::any::AssetMap, eth::Address as EthereumAddress, Chain, ForeignChainAddress, + self, assets::any::AssetMap, eth::Address as EthereumAddress, Chain, ChainCrypto, + ForeignChainAddress, }; use cf_primitives::{ AccountRole, Asset, AssetAmount, BlockNumber, BroadcastId, EpochIndex, FlipBalance, @@ -183,6 +184,39 @@ pub struct FailingWitnessValidators { pub validators: Vec<(cf_primitives::AccountId, String, bool)>, } +type ChainAccountFor = ::ChainAccount; + +#[derive(Serialize, Deserialize, Encode, Decode, Eq, PartialEq, TypeInfo, Debug, Clone)] +pub struct ChainAccounts { + pub btc_chain_accounts: Vec>, +} + +#[derive(Serialize, Deserialize, Encode, Decode, Eq, PartialEq, TypeInfo, Debug, Clone)] +pub enum TaintedTransactionEvent { + TaintedTransactionReportReceived { + account_id: ::AccountId, + tx_id: TxId, + }, + + TaintedTransactionReportExpired { + account_id: ::AccountId, + tx_id: TxId, + }, + + TaintedTransactionRejected { + refund_broadcast_id: BroadcastId, + tx_id: TxId, + }, +} + +type TaintedTransactionEventFor = + TaintedTransactionEvent<<::ChainCrypto as ChainCrypto>::TransactionInId>; + +#[derive(Serialize, Deserialize, Encode, Decode, Eq, PartialEq, TypeInfo, Debug, Clone)] +pub struct TaintedTransactionEvents { + pub btc_events: Vec>, +} + decl_runtime_apis!( /// Definition for all runtime API interfaces. pub trait CustomRuntimeApi { @@ -301,6 +335,8 @@ decl_runtime_apis!( fn cf_swap_limits() -> SwapLimits; fn cf_lp_events() -> Vec>; fn cf_minimum_chunk_size(asset: Asset) -> AssetAmount; + fn cf_get_open_deposit_channels(account_id: Option) -> ChainAccounts; + fn cf_tainted_transaction_events() -> TaintedTransactionEvents; } );