diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6760aced37..a7b0242559 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,5 @@ /bouncer/ @martin-chainflip /state-chain/custom-rpc/ @chainflip-io/web +/api/bin/chainflip-ingress-egress-tracker @chainflip-io/web /engine/config/CI/config/Settings.toml @chainflip-io/platform -/localnet/ @chainflip-io/platform \ No newline at end of file +/localnet/ @chainflip-io/platform diff --git a/.github/workflows/_40_post_check.yml b/.github/workflows/_40_post_check.yml index 4c65d0342b..ecb58c723e 100644 --- a/.github/workflows/_40_post_check.yml +++ b/.github/workflows/_40_post_check.yml @@ -100,6 +100,16 @@ jobs: run: | cat /tmp/chainflip/*/chainflip-node.log + - name: Print chainflip-broker-api logs + if: failure() + run: | + cat /tmp/chainflip/chainflip-broker-api.log + + - name: Print chainflip-lp-api logs + if: failure() + run: | + cat /tmp/chainflip/chainflip-lp-api.log + - name: Upload Localnet Logs ๐Ÿ’พ if: always() continue-on-error: true @@ -108,6 +118,7 @@ jobs: name: localnet-logs path: | /tmp/chainflip/*/chainflip-*.log + /tmp/chainflip/chainflip-*.log - name: Clean Up docker containers ๐Ÿงน if: always() diff --git a/Cargo.lock b/Cargo.lock index 967a0a33fe..1b447b6c9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1466,7 +1466,7 @@ dependencies = [ "custom-rpc", "futures", "hex", - "jsonrpsee 0.20.3", + "jsonrpsee", "serde", "sp-rpc", "substrate-build-script-utils 3.0.0 (git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+3)", @@ -1530,7 +1530,7 @@ dependencies = [ "hex", "httparse", "itertools 0.11.0", - "jsonrpsee 0.16.3", + "jsonrpsee", "lazy_static", "mockall", "multisig", @@ -1602,14 +1602,17 @@ dependencies = [ "clap 3.2.25", "config", "futures", - "jsonrpsee 0.16.3", + "hex", + "jsonrpsee", + "pallet-cf-broadcast", "pallet-cf-environment", + "pallet-cf-ingress-egress", "parity-scale-codec", "reqwest", "serde", "sp-core 21.0.0 (git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+5)", "state-chain-runtime", - "substrate-build-script-utils 3.0.0 (git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+3)", + "substrate-build-script-utils 3.0.0 (git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+5)", "tempfile", "tokio", "tracing", @@ -1629,7 +1632,7 @@ dependencies = [ "frame-system", "futures", "hex", - "jsonrpsee 0.20.3", + "jsonrpsee", "pallet-cf-pools", "serde", "serde_json", @@ -1656,7 +1659,7 @@ dependencies = [ "futures", "hex", "hex-literal", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "pallet-transaction-payment", "pallet-transaction-payment-rpc", @@ -2414,7 +2417,7 @@ dependencies = [ "futures", "hex", "insta", - "jsonrpsee 0.16.3", + "jsonrpsee", "pallet-cf-governance", "pallet-cf-pools", "sc-client-api", @@ -3917,7 +3920,7 @@ dependencies = [ "async-recursion", "futures", "indicatif", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "parity-scale-codec", "serde", @@ -4382,28 +4385,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-sink", - "gloo-utils 0.1.7", - "js-sys", - "pin-project", - "serde", - "serde_json", - "thiserror", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "gloo-net" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "gloo-utils 0.2.0", - "http", + "gloo-utils", "js-sys", "pin-project", "serde", @@ -4439,19 +4421,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "gloo-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "group" version = "0.12.1" @@ -5187,32 +5156,14 @@ version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "367a292944c07385839818bb71c8d76611138e2dedb0677d035b8da21d29c78b" dependencies = [ - "jsonrpsee-client-transport 0.16.3", - "jsonrpsee-core 0.16.3", - "jsonrpsee-http-client 0.16.3", - "jsonrpsee-proc-macros 0.16.3", - "jsonrpsee-server 0.16.3", - "jsonrpsee-types 0.16.3", - "jsonrpsee-wasm-client 0.16.3", - "jsonrpsee-ws-client 0.16.3", - "tracing", -] - -[[package]] -name = "jsonrpsee" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc" -dependencies = [ - "jsonrpsee-client-transport 0.20.3", - "jsonrpsee-core 0.20.3", - "jsonrpsee-http-client 0.20.3", - "jsonrpsee-proc-macros 0.20.3", - "jsonrpsee-server 0.20.3", - "jsonrpsee-types 0.20.3", - "jsonrpsee-wasm-client 0.20.3", - "jsonrpsee-ws-client 0.20.3", - "tokio", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-http-client", + "jsonrpsee-proc-macros", + "jsonrpsee-server", + "jsonrpsee-types", + "jsonrpsee-wasm-client", + "jsonrpsee-ws-client", "tracing", ] @@ -5226,32 +5177,10 @@ dependencies = [ "futures-channel", "futures-timer", "futures-util", - "gloo-net 0.2.6", - "http", - "jsonrpsee-core 0.16.3", - "jsonrpsee-types 0.16.3", - "pin-project", - "rustls-native-certs", - "soketto", - "thiserror", - "tokio", - "tokio-rustls", - "tokio-util", - "tracing", - "webpki-roots 0.25.2", -] - -[[package]] -name = "jsonrpsee-client-transport" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935" -dependencies = [ - "futures-channel", - "futures-util", - "gloo-net 0.4.0", + "gloo-net", "http", - "jsonrpsee-core 0.20.3", + "jsonrpsee-core", + "jsonrpsee-types", "pin-project", "rustls-native-certs", "soketto", @@ -5260,7 +5189,6 @@ dependencies = [ "tokio-rustls", "tokio-util", "tracing", - "url", "webpki-roots 0.25.2", ] @@ -5280,33 +5208,7 @@ dependencies = [ "futures-util", "globset", "hyper", - "jsonrpsee-types 0.16.3", - "parking_lot 0.12.1", - "rand 0.8.5", - "rustc-hash", - "serde", - "serde_json", - "soketto", - "thiserror", - "tokio", - "tracing", - "wasm-bindgen-futures", -] - -[[package]] -name = "jsonrpsee-core" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b" -dependencies = [ - "anyhow", - "async-lock 2.8.0", - "async-trait", - "beef", - "futures-timer", - "futures-util", - "hyper", - "jsonrpsee-types 0.20.3", + "jsonrpsee-types", "parking_lot 0.12.1", "rand 0.8.5", "rustc-hash", @@ -5328,8 +5230,8 @@ dependencies = [ "async-trait", "hyper", "hyper-rustls", - "jsonrpsee-core 0.16.3", - "jsonrpsee-types 0.16.3", + "jsonrpsee-core", + "jsonrpsee-types", "rustc-hash", "serde", "serde_json", @@ -5338,26 +5240,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "jsonrpsee-http-client" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20" -dependencies = [ - "async-trait", - "hyper", - "hyper-rustls", - "jsonrpsee-core 0.20.3", - "jsonrpsee-types 0.20.3", - "serde", - "serde_json", - "thiserror", - "tokio", - "tower", - "tracing", - "url", -] - [[package]] name = "jsonrpsee-proc-macros" version = "0.16.3" @@ -5371,19 +5253,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "jsonrpsee-proc-macros" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515" -dependencies = [ - "heck", - "proc-macro-crate 1.1.3", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "jsonrpsee-server" version = "0.16.3" @@ -5394,8 +5263,8 @@ dependencies = [ "futures-util", "http", "hyper", - "jsonrpsee-core 0.16.3", - "jsonrpsee-types 0.16.3", + "jsonrpsee-core", + "jsonrpsee-types", "serde", "serde_json", "soketto", @@ -5406,29 +5275,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "jsonrpsee-server" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c" -dependencies = [ - "futures-util", - "http", - "hyper", - "jsonrpsee-core 0.20.3", - "jsonrpsee-types 0.20.3", - "route-recognizer", - "serde", - "serde_json", - "soketto", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tower", - "tracing", -] - [[package]] name = "jsonrpsee-types" version = "0.16.3" @@ -5443,40 +5289,15 @@ dependencies = [ "tracing", ] -[[package]] -name = "jsonrpsee-types" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9" -dependencies = [ - "anyhow", - "beef", - "serde", - "serde_json", - "thiserror", - "tracing", -] - [[package]] name = "jsonrpsee-wasm-client" version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18e5df77c8f625d36e4cfb583c5a674eccebe32403fcfe42f7ceff7fac9324dd" dependencies = [ - "jsonrpsee-client-transport 0.16.3", - "jsonrpsee-core 0.16.3", - "jsonrpsee-types 0.16.3", -] - -[[package]] -name = "jsonrpsee-wasm-client" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7cbb3447cf14fd4d2f407c3cc96e6c9634d5440aa1fbed868a31f3c02b27f0" -dependencies = [ - "jsonrpsee-client-transport 0.20.3", - "jsonrpsee-core 0.20.3", - "jsonrpsee-types 0.20.3", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", ] [[package]] @@ -5486,22 +5307,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e1b3975ed5d73f456478681a417128597acd6a2487855fdb7b4a3d4d195bf5e" dependencies = [ "http", - "jsonrpsee-client-transport 0.16.3", - "jsonrpsee-core 0.16.3", - "jsonrpsee-types 0.16.3", -] - -[[package]] -name = "jsonrpsee-ws-client" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0" -dependencies = [ - "http", - "jsonrpsee-client-transport 0.20.3", - "jsonrpsee-core 0.20.3", - "jsonrpsee-types 0.20.3", - "url", + "jsonrpsee-client-transport", + "jsonrpsee-core", + "jsonrpsee-types", ] [[package]] @@ -7723,7 +7531,7 @@ name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" source = "git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+5#7ed06344974340d3057663575503cb9aeab4a041" dependencies = [ - "jsonrpsee 0.16.3", + "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "sp-api", @@ -9039,12 +8847,6 @@ dependencies = [ "serde", ] -[[package]] -name = "route-recognizer" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" - [[package]] name = "rpassword" version = "7.2.0" @@ -9594,7 +9396,7 @@ source = "git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthl dependencies = [ "finality-grandpa", "futures", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "parity-scale-codec", "sc-client-api", @@ -9929,7 +9731,7 @@ version = "4.0.0-dev" source = "git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+5#7ed06344974340d3057663575503cb9aeab4a041" dependencies = [ "futures", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -9959,7 +9761,7 @@ name = "sc-rpc-api" version = "0.10.0-dev" source = "git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+5#7ed06344974340d3057663575503cb9aeab4a041" dependencies = [ - "jsonrpsee 0.16.3", + "jsonrpsee", "parity-scale-codec", "sc-chain-spec", "sc-transaction-pool-api", @@ -9979,7 +9781,7 @@ version = "4.0.0-dev" source = "git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+5#7ed06344974340d3057663575503cb9aeab4a041" dependencies = [ "http", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "serde_json", "substrate-prometheus-endpoint", @@ -9997,7 +9799,7 @@ dependencies = [ "futures", "futures-util", "hex", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -10024,7 +9826,7 @@ dependencies = [ "exit-future", "futures", "futures-timer", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -12267,7 +12069,7 @@ source = "git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthl dependencies = [ "frame-system-rpc-runtime-api", "futures", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "parity-scale-codec", "sc-rpc-api", @@ -12297,7 +12099,7 @@ version = "0.10.0-dev" source = "git+https://github.com/chainflip-io/substrate.git?tag=chainflip-monthly-2023-08+5#7ed06344974340d3057663575503cb9aeab4a041" dependencies = [ "async-trait", - "jsonrpsee 0.16.3", + "jsonrpsee", "log", "sc-rpc-api", "serde", @@ -12357,7 +12159,7 @@ dependencies = [ "futures", "hex", "impl-serde", - "jsonrpsee 0.16.3", + "jsonrpsee", "parity-scale-codec", "primitive-types", "scale-bits", @@ -12386,7 +12188,7 @@ dependencies = [ "frame-metadata 16.0.0", "heck", "hex", - "jsonrpsee 0.16.3", + "jsonrpsee", "parity-scale-codec", "proc-macro2", "quote", @@ -12897,10 +12699,6 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite 0.2.13", "tower-layer", "tower-service", "tracing", @@ -13417,7 +13215,7 @@ dependencies = [ "futures", "hex", "itertools 0.11.0", - "jsonrpsee 0.20.3", + "jsonrpsee", "lazy_format", "lazy_static", "mockall", diff --git a/api/bin/chainflip-broker-api/Cargo.toml b/api/bin/chainflip-broker-api/Cargo.toml index f71f4ebe06..31cad18358 100644 --- a/api/bin/chainflip-broker-api/Cargo.toml +++ b/api/bin/chainflip-broker-api/Cargo.toml @@ -27,7 +27,7 @@ anyhow = "1.0.66" clap = { version = "3.2.23", features = ["derive"] } futures = "0.3" hex = "0.4.3" -jsonrpsee = { version = "0.20", features = ["full"] } +jsonrpsee = { version = "0.16.2", features = ["full"] } serde = { version = '1.0', features = ['derive'] } sp-rpc = { git = "https://github.com/chainflip-io/substrate.git", tag = "chainflip-monthly-2023-08+5" } tokio = "1.20.1" diff --git a/api/bin/chainflip-broker-api/src/main.rs b/api/bin/chainflip-broker-api/src/main.rs index 39224cdff5..2a023c4537 100644 --- a/api/bin/chainflip-broker-api/src/main.rs +++ b/api/bin/chainflip-broker-api/src/main.rs @@ -1,7 +1,6 @@ use cf_utilities::{ rpc::NumberOrHex, task_scope::{task_scope, Scope}, - AnyhowRpcError, }; use chainflip_api::{ self, clean_foreign_chain_address, @@ -12,7 +11,11 @@ use chainflip_api::{ use clap::Parser; use custom_rpc::RpcAsset; use futures::FutureExt; -use jsonrpsee::{core::async_trait, proc_macros::rpc, server::ServerBuilder}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + server::ServerBuilder, +}; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use tracing::log; @@ -42,7 +45,7 @@ impl From for BrokerSwapDepositAddress { #[rpc(server, client, namespace = "broker")] pub trait Rpc { #[method(name = "register_account", aliases = ["broker_registerAccount"])] - async fn register_account(&self) -> Result; + async fn register_account(&self) -> RpcResult; #[method(name = "request_swap_deposit_address", aliases = ["broker_requestSwapDepositAddress"])] async fn request_swap_deposit_address( @@ -52,7 +55,7 @@ pub trait Rpc { destination_address: String, broker_commission_bps: BasisPoints, channel_metadata: Option, - ) -> Result; + ) -> RpcResult; } pub struct RpcServerImpl { @@ -73,7 +76,7 @@ impl RpcServerImpl { #[async_trait] impl RpcServer for RpcServerImpl { - async fn register_account(&self) -> Result { + async fn register_account(&self) -> RpcResult { Ok(self .api .operator_api() @@ -89,7 +92,7 @@ impl RpcServer for RpcServerImpl { destination_address: String, broker_commission_bps: BasisPoints, channel_metadata: Option, - ) -> Result { + ) -> RpcResult { let destination_asset = destination_asset.try_into()?; Ok(self .api @@ -142,7 +145,7 @@ async fn main() -> anyhow::Result<()> { async move { let server = ServerBuilder::default().build(format!("0.0.0.0:{}", opts.port)).await?; let server_addr = server.local_addr()?; - let server = server.start(RpcServerImpl::new(scope, opts).await?.into_rpc()); + let server = server.start(RpcServerImpl::new(scope, opts).await?.into_rpc())?; log::info!("๐ŸŽ™ Server is listening on {server_addr}."); diff --git a/api/bin/chainflip-ingress-egress-tracker/Cargo.toml b/api/bin/chainflip-ingress-egress-tracker/Cargo.toml index ab1784b19d..07f03f2097 100644 --- a/api/bin/chainflip-ingress-egress-tracker/Cargo.toml +++ b/api/bin/chainflip-ingress-egress-tracker/Cargo.toml @@ -10,6 +10,7 @@ anyhow = "1.0.72" async-trait = "0.1.73" bitcoin = { version = "0.30.0", features = ["serde"] } futures = "0.3.28" +hex = "0.4.3" jsonrpsee = { version = "0.16.2", features = ["server"] } reqwest = { version = "0.11.18", features = ["json"] } serde = "1.0.183" @@ -31,8 +32,10 @@ chainflip-engine = { path = "../../../engine/" } utilities = { path = "../../../utilities" } cf-primitives = { path = "../../../state-chain/primitives" } pallet-cf-environment = { path = "../../../state-chain/pallets/cf-environment" } +pallet-cf-ingress-egress = { path = "../../../state-chain/pallets/cf-ingress-egress" } +pallet-cf-broadcast = { path = "../../../state-chain/pallets/cf-broadcast" } state-chain-runtime = { path = "../../../state-chain/runtime" } cf-chains = { path = "../../../state-chain/chains" } [build-dependencies] -substrate-build-script-utils = { git = "https://github.com/chainflip-io/substrate.git", tag = 'chainflip-monthly-2023-08+3' } \ No newline at end of file +substrate-build-script-utils = { git = "https://github.com/chainflip-io/substrate.git", tag = 'chainflip-monthly-2023-08+5' } diff --git a/api/bin/chainflip-ingress-egress-tracker/src/main.rs b/api/bin/chainflip-ingress-egress-tracker/src/main.rs index dd542e3536..445e5f85c9 100644 --- a/api/bin/chainflip-ingress-egress-tracker/src/main.rs +++ b/api/bin/chainflip-ingress-egress-tracker/src/main.rs @@ -1,17 +1,149 @@ -use chainflip_engine::settings::{ - insert_command_line_option, CfSettings, HttpBasicAuthEndpoint, WsHttpEndpoints, +use cf_chains::{ + btc::BitcoinNetwork, evm::SchnorrVerificationComponents, AnyChain, Bitcoin, Chain, Ethereum, + Polkadot, +}; +use cf_primitives::{Asset, BroadcastId, ForeignChain}; +use chainflip_engine::{ + settings::{insert_command_line_option, CfSettings, HttpBasicAuthEndpoint, WsHttpEndpoints}, + state_chain_observer::client::{ + chain_api::ChainApi, storage_api::StorageApi, StateChainClient, + }, + witness::common::STATE_CHAIN_CONNECTION, }; use clap::Parser; use config::{Config, ConfigBuilder, ConfigError, Environment, Map, Source, Value}; use futures::FutureExt; use jsonrpsee::{core::Error, server::ServerBuilder, RpcModule}; -use serde::Deserialize; +use pallet_cf_broadcast::TransactionOutIdFor; +use pallet_cf_ingress_egress::DepositWitness; +use serde::{Deserialize, Serialize}; +use state_chain_runtime::PalletInstanceAlias; use std::{collections::HashMap, env, net::SocketAddr}; use tracing::log; -use utilities::task_scope; +use utilities::{rpc::NumberOrHex, task_scope}; mod witnessing; +#[derive(Serialize)] +struct WitnessAsset { + chain: ForeignChain, + asset: Asset, +} + +impl From for WitnessAsset { + fn from(asset: cf_chains::assets::eth::Asset) -> Self { + match asset { + cf_chains::assets::eth::Asset::Eth | + cf_chains::assets::eth::Asset::Flip | + cf_chains::assets::eth::Asset::Usdc => + Self { chain: ForeignChain::Ethereum, asset: asset.into() }, + } + } +} + +impl From for WitnessAsset { + fn from(asset: cf_chains::assets::dot::Asset) -> Self { + match asset { + cf_chains::assets::dot::Asset::Dot => + Self { chain: ForeignChain::Polkadot, asset: asset.into() }, + } + } +} + +impl From for WitnessAsset { + fn from(asset: cf_chains::assets::btc::Asset) -> Self { + match asset { + cf_chains::assets::btc::Asset::Btc => + Self { chain: ForeignChain::Bitcoin, asset: asset.into() }, + } + } +} + +#[derive(Serialize)] +#[serde(tag = "deposit_chain", rename_all = "snake_case")] +enum TransactionId { + Bitcoin { hash: String }, + Ethereum { signature: SchnorrVerificationComponents }, + Polkadot { signature: String }, +} + +#[derive(Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum WitnessInformation { + Deposit { + deposit_chain_block_height: ::ChainBlockNumber, + deposit_address: String, + amount: NumberOrHex, + asset: WitnessAsset, + }, + Broadcast { + broadcast_id: BroadcastId, + tx_out_id: TransactionId, + }, +} + +type DepositInfo = (DepositWitness, ::ChainBlockNumber); + +impl From> for WitnessInformation { + fn from((value, height): DepositInfo) -> Self { + Self::Deposit { + deposit_chain_block_height: height, + deposit_address: value.deposit_address.to_string(), + amount: value.amount.into(), + asset: value.asset.into(), + } + } +} + +type BitcoinDepositInfo = + (DepositWitness, ::ChainBlockNumber, BitcoinNetwork); + +impl From for WitnessInformation { + fn from((value, height, network): BitcoinDepositInfo) -> Self { + Self::Deposit { + deposit_chain_block_height: height, + deposit_address: value.deposit_address.to_address(&network), + amount: value.amount.into(), + asset: value.asset.into(), + } + } +} + +impl From> for WitnessInformation { + fn from((value, height): DepositInfo) -> Self { + Self::Deposit { + deposit_chain_block_height: height as u64, + deposit_address: format!("0x{}", hex::encode(value.deposit_address.aliased_ref())), + amount: value.amount.into(), + asset: value.asset.into(), + } + } +} + +async fn get_broadcast_id( + state_chain_client: &StateChainClient<()>, + tx_out_id: &TransactionOutIdFor, +) -> Option +where + state_chain_runtime::Runtime: pallet_cf_broadcast::Config, + I: PalletInstanceAlias + 'static, +{ + let id = state_chain_client + .storage_map_entry::>(state_chain_client.latest_unfinalized_block().hash, tx_out_id) + .await + .expect(STATE_CHAIN_CONNECTION) + .map(|(broadcast_id, _)| broadcast_id); + + if id.is_none() { + log::warn!("Broadcast ID not found for {:?}", tx_out_id); + } + + id +} + #[derive(Clone, Deserialize, Debug)] pub struct DepositTrackerSettings { eth: WsHttpEndpoints, @@ -134,7 +266,9 @@ async fn start( let (witness_sender, _) = tokio::sync::broadcast::channel::(EVENT_BUFFER_SIZE); - witnessing::start(scope, settings, witness_sender.clone()).await?; + let (state_chain_client, env_params) = + witnessing::start(scope, settings, witness_sender.clone()).await?; + let btc_network = env_params.btc_network; module.register_subscription( "subscribe_witnessing", @@ -142,14 +276,135 @@ async fn start( "unsubscribe_witnessing", move |_params, mut sink, _context| { let mut witness_receiver = witness_sender.subscribe(); + let state_chain_client = state_chain_client.clone(); tokio::spawn(async move { while let Ok(event) = witness_receiver.recv().await { - use codec::Encode; - if let Ok(false) = sink.send(&event.encode()) { - log::debug!("Subscription is closed"); - break + use pallet_cf_broadcast::Call as BroadcastCall; + use pallet_cf_ingress_egress::Call as IngressEgressCall; + use state_chain_runtime::RuntimeCall::*; + + // rustfmt chokes when formatting this macro. + // See: https://github.com/rust-lang/rustfmt/issues/5404 + #[rustfmt::skip] + macro_rules! send { + ($value:expr) => { + if let Ok(false) = sink.send($value) { + log::debug!("Subscription is closed"); + return + } + }; } + + match event { + EthereumIngressEgress(IngressEgressCall::process_deposits { + deposit_witnesses, + block_height, + }) => + for witness in deposit_witnesses as Vec> { + let info = WitnessInformation::from((witness, block_height)); + send!(&info); + }, + BitcoinIngressEgress(IngressEgressCall::process_deposits { + deposit_witnesses, + block_height, + }) => + for witness in deposit_witnesses as Vec> { + let info = + WitnessInformation::from((witness, block_height, btc_network)); + send!(&info); + }, + PolkadotIngressEgress(IngressEgressCall::process_deposits { + deposit_witnesses, + block_height, + }) => + for witness in deposit_witnesses as Vec> { + let info = WitnessInformation::from((witness, block_height)); + send!(&info); + }, + EthereumBroadcaster(BroadcastCall::transaction_succeeded { + tx_out_id, + .. + }) => { + let broadcast_id = + get_broadcast_id::(&state_chain_client, &tx_out_id).await; + + if let Some(broadcast_id) = broadcast_id { + send!(&WitnessInformation::Broadcast { + broadcast_id, + tx_out_id: TransactionId::Ethereum { signature: tx_out_id } + }) + } + }, + BitcoinBroadcaster(BroadcastCall::transaction_succeeded { + tx_out_id, + .. + }) => { + let broadcast_id = + get_broadcast_id::(&state_chain_client, &tx_out_id).await; + + if let Some(broadcast_id) = broadcast_id { + send!(&WitnessInformation::Broadcast { + broadcast_id, + tx_out_id: TransactionId::Bitcoin { + hash: format!("0x{}", hex::encode(tx_out_id)) + } + }) + } + }, + PolkadotBroadcaster(BroadcastCall::transaction_succeeded { + tx_out_id, + .. + }) => { + let broadcast_id = + get_broadcast_id::(&state_chain_client, &tx_out_id).await; + + if let Some(broadcast_id) = broadcast_id { + send!(&WitnessInformation::Broadcast { + broadcast_id, + tx_out_id: TransactionId::Polkadot { + signature: format!( + "0x{}", + hex::encode(tx_out_id.aliased_ref()) + ) + } + }) + } + }, + + EthereumIngressEgress(_) | + BitcoinIngressEgress(_) | + PolkadotIngressEgress(_) | + System(_) | + Timestamp(_) | + Environment(_) | + Flip(_) | + Emissions(_) | + Funding(_) | + AccountRoles(_) | + Witnesser(_) | + Validator(_) | + Session(_) | + Grandpa(_) | + Governance(_) | + Reputation(_) | + TokenholderGovernance(_) | + EthereumChainTracking(_) | + BitcoinChainTracking(_) | + PolkadotChainTracking(_) | + EthereumVault(_) | + PolkadotVault(_) | + BitcoinVault(_) | + EthereumThresholdSigner(_) | + PolkadotThresholdSigner(_) | + BitcoinThresholdSigner(_) | + EthereumBroadcaster(_) | + PolkadotBroadcaster(_) | + BitcoinBroadcaster(_) | + Swapping(_) | + LiquidityProvider(_) | + LiquidityPools(_) => {}, + }; } }); Ok(()) diff --git a/api/bin/chainflip-ingress-egress-tracker/src/witnessing.rs b/api/bin/chainflip-ingress-egress-tracker/src/witnessing.rs index 84c6aee343..3c1d392c1c 100644 --- a/api/bin/chainflip-ingress-egress-tracker/src/witnessing.rs +++ b/api/bin/chainflip-ingress-egress-tracker/src/witnessing.rs @@ -3,7 +3,7 @@ pub mod btc_mempool; mod dot; mod eth; -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use cf_chains::dot::PolkadotHash; use cf_primitives::chains::assets::eth::Asset; @@ -20,7 +20,7 @@ use utilities::task_scope; use crate::DepositTrackerSettings; #[derive(Clone)] -struct EnvironmentParameters { +pub(super) struct EnvironmentParameters { eth_chain_id: u64, eth_vault_address: H160, eth_address_checker_address: H160, @@ -28,7 +28,7 @@ struct EnvironmentParameters { usdc_contract_address: H160, supported_erc20_tokens: HashMap, dot_genesis_hash: PolkadotHash, - btc_network: cf_chains::btc::BitcoinNetwork, + pub btc_network: cf_chains::btc::BitcoinNetwork, } async fn get_env_parameters(state_chain_client: &StateChainClient<()>) -> EnvironmentParameters { @@ -104,7 +104,7 @@ pub(super) async fn start( scope: &task_scope::Scope<'_, anyhow::Error>, settings: DepositTrackerSettings, witness_sender: tokio::sync::broadcast::Sender, -) -> anyhow::Result<()> { +) -> anyhow::Result<(Arc>, EnvironmentParameters)> { let (state_chain_stream, unfinalized_chain_stream, state_chain_client) = { state_chain_observer::client::StateChainClient::connect_without_account( scope, @@ -159,12 +159,12 @@ pub(super) async fn start( scope, witness_call, settings, - env_params, - state_chain_client, + env_params.clone(), + state_chain_client.clone(), unfinalized_chain_stream, epoch_source, ) .await?; - Ok(()) + Ok((state_chain_client, env_params)) } diff --git a/api/bin/chainflip-ingress-egress-tracker/src/witnessing/eth.rs b/api/bin/chainflip-ingress-egress-tracker/src/witnessing/eth.rs index 287f6e70a3..d997c52796 100644 --- a/api/bin/chainflip-ingress-egress-tracker/src/witnessing/eth.rs +++ b/api/bin/chainflip-ingress-egress-tracker/src/witnessing/eth.rs @@ -1,12 +1,14 @@ -use std::sync::Arc; - +use anyhow::Context; use cf_primitives::chains::assets::eth::Asset; +use std::sync::Arc; use utilities::task_scope; use chainflip_engine::{ eth::{retry_rpc::EthRetryRpcClient, rpc::EthRpcClient}, settings::NodeContainer, - state_chain_observer::client::{StateChainClient, StateChainStreamApi}, + state_chain_observer::client::{ + chain_api::ChainApi, storage_api::StorageApi, StateChainClient, StateChainStreamApi, + }, witness::{ common::{chain_source::extension::ChainSourceExt, epoch_source::EpochSourceBuilder}, eth::{ @@ -103,5 +105,18 @@ where .logging("witnessing Vault") .spawn(scope); + let key_manager_address = state_chain_client + .storage_value::>( + state_chain_client.latest_unfinalized_block().hash, + ) + .await + .context("Failed to get KeyManager address from SC")?; + + eth_source + .clone() + .key_manager_witnessing(witness_call.clone(), eth_client.clone(), key_manager_address) + .logging("witnessing KeyManager") + .spawn(scope); + Ok(()) } diff --git a/api/bin/chainflip-lp-api/Cargo.toml b/api/bin/chainflip-lp-api/Cargo.toml index f6d61a3581..4172808ff2 100644 --- a/api/bin/chainflip-lp-api/Cargo.toml +++ b/api/bin/chainflip-lp-api/Cargo.toml @@ -23,7 +23,7 @@ anyhow = "1.0" clap = { version = "3.2.23", features = ["derive", "env"] } futures = "0.3" hex = "0.4.3" -jsonrpsee = { version = "0.20", features = ["full"] } +jsonrpsee = { version = "0.16.2", features = ["full"] } serde = { version = '1.0', features = ['derive'] } serde_json = "1.0" sp-rpc = { git = "https://github.com/chainflip-io/substrate.git", tag = "chainflip-monthly-2023-08+5" } diff --git a/api/bin/chainflip-lp-api/src/main.rs b/api/bin/chainflip-lp-api/src/main.rs index b4354463b1..3869b7f2c1 100644 --- a/api/bin/chainflip-lp-api/src/main.rs +++ b/api/bin/chainflip-lp-api/src/main.rs @@ -2,7 +2,7 @@ use cf_primitives::{AccountId, BlockNumber, EgressId}; use cf_utilities::{ rpc::NumberOrHex, task_scope::{task_scope, Scope}, - try_parse_number_or_hex, AnyhowRpcError, + try_parse_number_or_hex, }; use chainflip_api::{ self, @@ -15,16 +15,17 @@ use chainflip_api::{ AccountRole, Asset, ForeignChain, Hash, RedemptionAmount, }, settings::StateChain, - ChainApi, EthereumAddress, OperatorApi, StateChainApi, StorageApi, WaitFor, + BlockInfo, ChainApi, EthereumAddress, OperatorApi, StateChainApi, StorageApi, WaitFor, }; use clap::Parser; use custom_rpc::RpcAsset; -use futures::{FutureExt, StreamExt}; +use futures::{try_join, FutureExt, StreamExt}; use jsonrpsee::{ - core::{async_trait, SubscriptionResult}, + core::{async_trait, RpcResult}, proc_macros::rpc, server::ServerBuilder, - PendingSubscriptionSink, SubscriptionMessage, + types::SubscriptionResult, + SubscriptionSink, }; use pallet_cf_pools::{AssetPair, AssetsMap, IncreaseOrDecrease, OrderId, RangeOrderSize}; use rpc_types::{AssetBalance, OpenSwapChannels, OrderIdJson, RangeOrderSizeJson}; @@ -33,6 +34,7 @@ use std::{ collections::{BTreeMap, HashMap, HashSet}, ops::Range, path::PathBuf, + sync::Arc, }; use tracing::log; @@ -100,21 +102,21 @@ pub mod rpc_types { #[rpc(server, client, namespace = "lp")] pub trait Rpc { #[method(name = "register_account")] - async fn register_account(&self) -> Result; + async fn register_account(&self) -> RpcResult; #[method(name = "liquidity_deposit")] async fn request_liquidity_deposit_address( &self, asset: RpcAsset, wait_for: Option, - ) -> Result, AnyhowRpcError>; + ) -> RpcResult>; #[method(name = "register_liquidity_refund_address")] async fn register_liquidity_refund_address( &self, chain: ForeignChain, address: &str, - ) -> Result; + ) -> RpcResult; #[method(name = "withdraw_asset")] async fn withdraw_asset( @@ -123,7 +125,7 @@ pub trait Rpc { asset: RpcAsset, destination_address: &str, wait_for: Option, - ) -> Result, AnyhowRpcError>; + ) -> RpcResult>; #[method(name = "update_range_order")] async fn update_range_order( @@ -134,7 +136,7 @@ pub trait Rpc { tick_range: Option>, size_change: IncreaseOrDecrease, wait_for: Option, - ) -> Result>, AnyhowRpcError>; + ) -> RpcResult>>; #[method(name = "set_range_order")] async fn set_range_order( @@ -145,7 +147,7 @@ pub trait Rpc { tick_range: Option>, size: RangeOrderSizeJson, wait_for: Option, - ) -> Result>, AnyhowRpcError>; + ) -> RpcResult>>; #[method(name = "update_limit_order")] async fn update_limit_order( @@ -158,7 +160,7 @@ pub trait Rpc { amount_change: IncreaseOrDecrease, dispatch_at: Option, wait_for: Option, - ) -> Result>, AnyhowRpcError>; + ) -> RpcResult>>; #[method(name = "set_limit_order")] async fn set_limit_order( @@ -171,15 +173,13 @@ pub trait Rpc { sell_amount: NumberOrHex, dispatch_at: Option, wait_for: Option, - ) -> Result>, AnyhowRpcError>; + ) -> RpcResult>>; #[method(name = "asset_balances")] - async fn asset_balances( - &self, - ) -> Result>, AnyhowRpcError>; + async fn asset_balances(&self) -> RpcResult>>; #[method(name = "get_open_swap_channels")] - async fn get_open_swap_channels(&self) -> Result; + async fn get_open_swap_channels(&self) -> RpcResult; #[method(name = "request_redemption")] async fn request_redemption( @@ -187,10 +187,13 @@ pub trait Rpc { redeem_address: EthereumAddress, exact_amount: Option, executor_address: Option, - ) -> Result; + ) -> RpcResult; #[subscription(name = "subscribe_order_fills", item = OrderFills)] - async fn subscribe_order_fills(&self) -> SubscriptionResult; + fn subscribe_order_fills(&self); + + #[method(name = "order_fills")] + async fn order_fills(&self, at: Option) -> RpcResult; } pub struct RpcServerImpl { @@ -209,14 +212,14 @@ impl RpcServerImpl { } } -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(serde::Serialize, serde::Deserialize, Clone)] pub struct OrderFills { block_hash: Hash, block_number: BlockNumber, fills: Vec, } -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(serde::Serialize, serde::Deserialize, Clone)] #[serde(rename_all = "snake_case")] pub enum OrderFilled { LimitOrder { @@ -249,7 +252,7 @@ impl RpcServer for RpcServerImpl { &self, asset: RpcAsset, wait_for: Option, - ) -> Result, AnyhowRpcError> { + ) -> RpcResult> { Ok(self .api .lp_api() @@ -262,7 +265,7 @@ impl RpcServer for RpcServerImpl { &self, chain: ForeignChain, address: &str, - ) -> Result { + ) -> RpcResult { let ewa_address = chainflip_api::clean_foreign_chain_address(chain, address)?; Ok(self.api.lp_api().register_liquidity_refund_address(ewa_address).await?) } @@ -274,7 +277,7 @@ impl RpcServer for RpcServerImpl { asset: RpcAsset, destination_address: &str, wait_for: Option, - ) -> Result, AnyhowRpcError> { + ) -> RpcResult> { let asset: Asset = asset.try_into()?; let destination_address = @@ -293,9 +296,7 @@ impl RpcServer for RpcServerImpl { } /// Returns a list of all assets and their free balance in json format - async fn asset_balances( - &self, - ) -> Result>, AnyhowRpcError> { + async fn asset_balances(&self) -> RpcResult>> { let mut balances = BTreeMap::<_, Vec<_>>::new(); for (asset, balance) in self.api.query_api().get_balances(None).await? { balances @@ -314,7 +315,7 @@ impl RpcServer for RpcServerImpl { tick_range: Option>, size_change: IncreaseOrDecrease, wait_for: Option, - ) -> Result>, AnyhowRpcError> { + ) -> RpcResult>> { Ok(self .api .lp_api() @@ -337,7 +338,7 @@ impl RpcServer for RpcServerImpl { tick_range: Option>, size: RangeOrderSizeJson, wait_for: Option, - ) -> Result>, AnyhowRpcError> { + ) -> RpcResult>> { Ok(self .api .lp_api() @@ -362,7 +363,7 @@ impl RpcServer for RpcServerImpl { amount_change: IncreaseOrDecrease, dispatch_at: Option, wait_for: Option, - ) -> Result>, AnyhowRpcError> { + ) -> RpcResult>> { Ok(self .api .lp_api() @@ -389,7 +390,7 @@ impl RpcServer for RpcServerImpl { sell_amount: NumberOrHex, dispatch_at: Option, wait_for: Option, - ) -> Result>, AnyhowRpcError> { + ) -> RpcResult>> { Ok(self .api .lp_api() @@ -407,7 +408,7 @@ impl RpcServer for RpcServerImpl { } /// Returns the tx hash that the account role was set - async fn register_account(&self) -> Result { + async fn register_account(&self) -> RpcResult { Ok(self .api .operator_api() @@ -415,7 +416,7 @@ impl RpcServer for RpcServerImpl { .await?) } - async fn get_open_swap_channels(&self) -> Result { + async fn get_open_swap_channels(&self) -> RpcResult { let api = self.api.query_api(); let (ethereum, bitcoin, polkadot) = tokio::try_join!( @@ -431,7 +432,7 @@ impl RpcServer for RpcServerImpl { redeem_address: EthereumAddress, exact_amount: Option, executor_address: Option, - ) -> Result { + ) -> RpcResult { let redeem_amount = if let Some(number_or_hex) = exact_amount { RedemptionAmount::Exact(try_parse_number_or_hex(number_or_hex)?) } else { @@ -445,103 +446,186 @@ impl RpcServer for RpcServerImpl { .await?) } - async fn subscribe_order_fills(&self, sink: PendingSubscriptionSink) -> SubscriptionResult { - let sink = sink.accept().await?; + fn subscribe_order_fills(&self, mut sink: SubscriptionSink) -> SubscriptionResult { + sink.accept()?; let state_chain_client = self.api.state_chain_client.clone(); - let mut finalized_block_stream = state_chain_client.finalized_block_stream().await; - let mut previous_pools = state_chain_client.storage_map::, HashMap<_, _>>(finalized_block_stream.cache().hash).await?; - tokio::spawn(async move { + let mut finalized_block_stream = state_chain_client.finalized_block_stream().await; while let Some(block) = finalized_block_stream.next().await { - let events = state_chain_client - .storage_value::>( - block.hash, - ) + if let Err(option_error) = order_fills(state_chain_client.clone(), block) .await - .unwrap(); - sink.send(SubscriptionMessage::from_json(&OrderFills { block_hash: block.hash, block_number: block.number, fills: { - let updated_range_orders = events.iter().filter_map(|event_record| { - match &event_record.event { - chainflip_api::primitives::state_chain_runtime::RuntimeEvent::LiquidityPools(pallet_cf_pools::Event::RangeOrderUpdated { - lp, - base_asset, - quote_asset, - id, - .. - }) => { - Some((lp.clone(), AssetPair::new(*base_asset, *quote_asset).unwrap(), *id)) - }, - _ => { - None - } - } - }).collect::>(); - - let updated_limit_orders = events.iter().filter_map(|event_record| { - match &event_record.event { - chainflip_api::primitives::state_chain_runtime::RuntimeEvent::LiquidityPools(pallet_cf_pools::Event::LimitOrderUpdated { - lp, - base_asset, - quote_asset, - side, - id, - .. - }) => { - Some((lp.clone(), AssetPair::new(*base_asset, *quote_asset).unwrap(), *side, *id)) - }, - _ => { - None - } - } - }).collect::>(); - - let pools = state_chain_client.storage_map::, HashMap<_, _>>(block.hash).await.unwrap(); - - let order_fills = pools.iter().flat_map(|(asset_pair, pool)| { - let updated_range_orders = &updated_range_orders; - let updated_limit_orders = &updated_limit_orders; - let previous_pools = &previous_pools; - [Order::Sell, Order::Buy].into_iter().flat_map(move |side| { - pool.pool_state.limit_orders(side).filter_map(move |((lp, id), tick, collected, position_info)| { - let (fees, sold, bought) = { - let option_previous_order_state = if updated_limit_orders.contains(&(lp.clone(), *asset_pair, side, id)) { - None - } else { - previous_pools.get(asset_pair).and_then(|pool| pool.pool_state.limit_order(&(lp.clone(), id), side, tick).ok()) + .map_err(Some) + .and_then(|order_fills| match sink.send(&order_fills) { + Ok(true) => Ok(()), + Ok(false) => Err(None), + Err(error) => Err(Some(jsonrpsee::core::Error::ParseError(error))), + }) { + if let Some(error) = option_error { + sink.close(error); + } + break + } + } + }); + + Ok(()) + } + + async fn order_fills(&self, at: Option) -> RpcResult { + let state_chain_client = &self.api.state_chain_client; + + let block = if let Some(at) = at { + state_chain_client.block(at).await? + } else { + state_chain_client.latest_finalized_block() + }; + + Ok(order_fills(state_chain_client.clone(), block).await?) + } +} + +async fn order_fills( + state_chain_client: Arc, + block: BlockInfo, +) -> Result +where + StateChainClient: StorageApi, +{ + Ok(OrderFills { + block_hash: block.hash, + block_number: block.number, + fills: { + let (previous_pools, pools, events) = try_join!( + state_chain_client.storage_map::, HashMap<_, _>>(block.parent_hash), + state_chain_client.storage_map::, HashMap<_, _>>(block.hash), + state_chain_client.storage_value::>(block.hash) + )?; + + let updated_range_orders = events.iter().filter_map(|event_record| { + match &event_record.event { + chainflip_api::primitives::state_chain_runtime::RuntimeEvent::LiquidityPools(pallet_cf_pools::Event::RangeOrderUpdated { + lp, + base_asset, + quote_asset, + id, + .. + }) => { + Some((lp.clone(), AssetPair::new(*base_asset, *quote_asset).unwrap(), *id)) + }, + _ => { + None + } + } + }).collect::>(); + + let updated_limit_orders = events.iter().filter_map(|event_record| { + match &event_record.event { + chainflip_api::primitives::state_chain_runtime::RuntimeEvent::LiquidityPools(pallet_cf_pools::Event::LimitOrderUpdated { + lp, + base_asset, + quote_asset, + side, + id, + .. + }) => { + Some((lp.clone(), AssetPair::new(*base_asset, *quote_asset).unwrap(), *side, *id)) + }, + _ => { + None + } + } + }).collect::>(); + + let order_fills = pools + .iter() + .flat_map(|(asset_pair, pool)| { + let updated_range_orders = &updated_range_orders; + let updated_limit_orders = &updated_limit_orders; + let previous_pools = &previous_pools; + [Order::Sell, Order::Buy] + .into_iter() + .flat_map(move |side| { + pool.pool_state.limit_orders(side).filter_map( + move |((lp, id), tick, collected, position_info)| { + let (fees, sold, bought) = { + let option_previous_order_state = if updated_limit_orders + .contains(&(lp.clone(), *asset_pair, side, id)) + { + None + } else { + previous_pools.get(asset_pair).and_then(|pool| { + pool.pool_state + .limit_order(&(lp.clone(), id), side, tick) + .ok() + }) + }; + + if let Some((previous_collected, _)) = + option_previous_order_state + { + ( + collected.fees - previous_collected.fees, + collected.sold_amount - + previous_collected.sold_amount, + collected.bought_amount - + previous_collected.bought_amount, + ) + } else { + ( + collected.fees, + collected.sold_amount, + collected.bought_amount, + ) + } }; - if let Some((previous_collected, _)) = option_previous_order_state { - ( - collected.fees - previous_collected.fees, - collected.sold_amount - previous_collected.sold_amount, - collected.bought_amount - previous_collected.bought_amount, - ) + if fees.is_zero() && sold.is_zero() && bought.is_zero() { + None } else { - ( - collected.fees, - collected.sold_amount, - collected.bought_amount, - ) + Some(OrderFilled::LimitOrder { + lp, + base_asset: asset_pair.assets().base, + quote_asset: asset_pair.assets().quote, + side, + id: id.into(), + tick, + sold, + bought, + fees, + remaining: position_info.amount, + }) } - }; - - if fees.is_zero() && sold.is_zero() && bought.is_zero() { - None - } else { - Some(OrderFilled::LimitOrder { lp, base_asset: asset_pair.assets().base, quote_asset: asset_pair.assets().quote, side, id: id.into(), tick, sold, bought, fees, remaining: position_info.amount }) - } - }) - }).chain( - pool.pool_state.range_orders().filter_map(move |((lp, id), range, collected, position_info)| { + }, + ) + }) + .chain(pool.pool_state.range_orders().filter_map( + move |((lp, id), range, collected, position_info)| { let fees = { - let option_previous_order_state = if updated_range_orders.contains(&(lp.clone(), *asset_pair, id)) { + let option_previous_order_state = if updated_range_orders + .contains(&(lp.clone(), *asset_pair, id)) + { None } else { - previous_pools.get(asset_pair).and_then(|pool| pool.pool_state.range_order(&(lp.clone(), id), range.clone()).ok()) + previous_pools.get(asset_pair).and_then(|pool| { + pool.pool_state + .range_order(&(lp.clone(), id), range.clone()) + .ok() + }) }; - if let Some((previous_collected, _)) = option_previous_order_state { - collected.fees.zip(previous_collected.fees).map(|_, (fees, previous_fees)| fees - previous_fees) + if let Some((previous_collected, _)) = + option_previous_order_state + { + collected + .fees + .zip(previous_collected.fees) + .map(|_, (fees, previous_fees)| fees - previous_fees) } else { collected.fees } @@ -557,22 +641,17 @@ impl RpcServer for RpcServerImpl { id: id.into(), range: range.clone(), fees: fees.map(|_, fees| fees).into(), - liquidity: position_info.liquidity.into() + liquidity: position_info.liquidity.into(), }) } - }) - ) - }).collect::>(); - - previous_pools = pools; - - order_fills - } }).unwrap()).await.unwrap(); - } - }); + }, + )) + }) + .collect::>(); - Ok(()) - } + order_fills + }, + }) } #[derive(Parser, Debug, Clone, Default)] @@ -617,7 +696,7 @@ async fn main() -> anyhow::Result<()> { async move { let server = ServerBuilder::default().build(format!("0.0.0.0:{}", opts.port)).await?; let server_addr = server.local_addr()?; - let server = server.start(RpcServerImpl::new(scope, opts).await?.into_rpc()); + let server = server.start(RpcServerImpl::new(scope, opts).await?.into_rpc())?; log::info!("๐ŸŽ™ Server is listening on {server_addr}."); diff --git a/api/lib/src/lib.rs b/api/lib/src/lib.rs index de8097fb3b..84b44667d4 100644 --- a/api/lib/src/lib.rs +++ b/api/lib/src/lib.rs @@ -34,6 +34,7 @@ pub use chainflip_engine::state_chain_observer::client::{ chain_api::ChainApi, extrinsic_api::signed::{SignedExtrinsicApi, UntilFinalized, WaitFor, WaitForResult}, storage_api::StorageApi, + BlockInfo, }; pub mod lp; diff --git a/bouncer/commands/read_workspace_tomls.ts b/bouncer/commands/read_workspace_tomls.ts index 03a94c9434..969b9848bd 100755 --- a/bouncer/commands/read_workspace_tomls.ts +++ b/bouncer/commands/read_workspace_tomls.ts @@ -36,6 +36,7 @@ const runtimeTomlVersion = await tomlVersion(`${projectRoot}/state-chain/runtime const nodeTomlVersion = await tomlVersion(`${projectRoot}/state-chain/node/Cargo.toml`); const cliTomlVersion = await tomlVersion(`${projectRoot}/api/bin/chainflip-cli/Cargo.toml`); const lpApiTomlVersion = await tomlVersion(`${projectRoot}/api/bin/chainflip-lp-api/Cargo.toml`); +const apiLibTomlVersion = await tomlVersion(`${projectRoot}/api/lib/Cargo.toml`); const brokerTomlVersion = await tomlVersion( `${projectRoot}/api/bin/chainflip-broker-api/Cargo.toml`, ); @@ -46,7 +47,8 @@ if ( runtimeTomlVersion === nodeTomlVersion && nodeTomlVersion === cliTomlVersion && cliTomlVersion === lpApiTomlVersion && - lpApiTomlVersion === brokerTomlVersion + lpApiTomlVersion === brokerTomlVersion && + apiLibTomlVersion === brokerTomlVersion ) ) { throw Error('All versions should be the same'); diff --git a/engine/src/btc/rpc.rs b/engine/src/btc/rpc.rs index 181160d080..c9457b9a63 100644 --- a/engine/src/btc/rpc.rs +++ b/engine/src/btc/rpc.rs @@ -86,15 +86,12 @@ impl BtcRpcClient { match get_bitcoin_network(&client, &endpoint).await { Ok(network) if network == expected_btc_network => break, Ok(network) => { - error!( - "Connected to Bitcoin node but with incorrect network name `{network}`, expected `{expected_btc_network}` on endpoint {}. Please check your CFE - configuration file...", - endpoint.http_endpoint - ); + error!("Connected to Bitcoin node but with incorrect network name `{network}`, expected `{expected_btc_network}` on endpoint {}. \ + Please check your CFE configuration file...", endpoint.http_endpoint); }, Err(e) => error!( - "Failure connecting to Bitcoin node at {} with error: {e}. Please check your CFE - configuration file. Retrying in {:?}...", + "Failure connecting to Bitcoin node at {} with error: {e}. \ + Please check your CFE configuration file. Retrying in {:?}...", endpoint.http_endpoint, poll_interval.period() ), diff --git a/engine/src/dot/http_rpc.rs b/engine/src/dot/http_rpc.rs index 84c81e79a5..dfbe92ff2a 100644 --- a/engine/src/dot/http_rpc.rs +++ b/engine/src/dot/http_rpc.rs @@ -7,7 +7,7 @@ use jsonrpsee::{ core::{client::ClientT, traits::ToRpcParams, Error as JsonRpseeError}, http_client::{HttpClient, HttpClientBuilder}, }; -use reqwest::header::{HeaderMap, AUTHORIZATION}; +use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION}; use serde_json::value::RawValue; use sp_core::H256; use subxt::{ @@ -22,7 +22,7 @@ use subxt::{ use anyhow::Result; use tracing::{error, warn}; -use utilities::{make_periodic_tick, redact_endpoint_secret::SecretUrl}; +use utilities::{const_eval, make_periodic_tick, redact_endpoint_secret::SecretUrl}; use crate::constants::RPC_RETRY_CONNECTION_INTERVAL; @@ -32,12 +32,14 @@ pub struct PolkadotHttpClient(HttpClient); impl PolkadotHttpClient { pub fn new(url: &SecretUrl) -> Result { - let token = format!("Bearer {}", "TOKEN"); - let mut headers = HeaderMap::new(); - headers.insert(AUTHORIZATION, token.parse().unwrap()); - let client = HttpClientBuilder::default().set_headers(headers).build(url)?; - - Ok(Self(client)) + Ok(Self( + HttpClientBuilder::default() + .set_headers(HeaderMap::from_iter([( + AUTHORIZATION, + const_eval!(HeaderValue, HeaderValue::from_static("Bearer TOKEN")), + )])) + .build(url)?, + )) } } @@ -115,10 +117,10 @@ impl DotHttpRpcClient { }, Err(e) => { error!( - "Failed to connect to Polkadot node at {url} with error: {e}. Please check your CFE - configuration file. Retrying in {:?}...", - poll_interval.period() - ); + "Failed to connect to Polkadot node at {url} with error: {e}. \ + Please check your CFE configuration file. Retrying in {:?}...", + poll_interval.period() + ); }, } }; diff --git a/engine/src/eth/mod.rs b/engine/src/eth/mod.rs index 56d83c9421..4dc346f549 100644 --- a/engine/src/eth/mod.rs +++ b/engine/src/eth/mod.rs @@ -2,7 +2,7 @@ pub mod event; pub mod retry_rpc; pub mod rpc; -use anyhow::Result; +use anyhow::{Context, Result}; use futures::FutureExt; @@ -25,6 +25,19 @@ pub struct ConscientiousEthWebsocketBlockHeaderStream { >, } +impl ConscientiousEthWebsocketBlockHeaderStream { + pub async fn new(web3: web3::Web3) -> Result { + Ok(Self { + stream: Some( + web3.eth_subscribe() + .subscribe_new_heads() + .await + .context("Failed to subscribe to new heads with WS Client")?, + ), + }) + } +} + impl Drop for ConscientiousEthWebsocketBlockHeaderStream { fn drop(&mut self) { tracing::warn!("Dropping the ETH WS connection"); diff --git a/engine/src/eth/rpc.rs b/engine/src/eth/rpc.rs index cba14a5ebd..c2dc4b4a08 100644 --- a/engine/src/eth/rpc.rs +++ b/engine/src/eth/rpc.rs @@ -43,15 +43,15 @@ impl EthRpcClient { Ok(chain_id) if chain_id == expected_chain_id.into() => break client, Ok(chain_id) => { tracing::error!( - "Connected to Ethereum node but with incorrect chain_id {chain_id}, expected {expected_chain_id} from {http_endpoint}. Please check your CFE - configuration file...", + "Connected to Ethereum node but with incorrect chain_id {chain_id}, expected {expected_chain_id} from {http_endpoint}. \ + Please check your CFE configuration file...", ); }, Err(e) => tracing::error!( - "Cannot connect to an Ethereum node at {http_endpoint} with error: {e}. Please check your CFE - configuration file. Retrying in {:?}...", - poll_interval.period() - ), + "Cannot connect to an Ethereum node at {http_endpoint} with error: {e}. \ + Please check your CFE configuration file. Retrying in {:?}...", + poll_interval.period() + ), } } }) @@ -318,14 +318,7 @@ impl ReconnectSubscribeApi for ReconnectSubscriptionClient { bail!("Expected chain id {}, eth ws client returned {client_chain_id}.", self.chain_id) } - Ok(ConscientiousEthWebsocketBlockHeaderStream { - stream: Some( - web3.eth_subscribe() - .subscribe_new_heads() - .await - .context("Failed to subscribe to new heads with WS Client")?, - ), - }) + ConscientiousEthWebsocketBlockHeaderStream::new(web3).await } } diff --git a/engine/src/retrier.rs b/engine/src/retrier.rs index 94abc3d75c..36bc0ee724 100644 --- a/engine/src/retrier.rs +++ b/engine/src/retrier.rs @@ -405,7 +405,13 @@ where // We avoid small delays by always having a time of at least half. let half_max = max_sleep_duration(initial_request_timeout, attempt) / 2; let sleep_duration = half_max + rand::thread_rng().gen_range(Duration::default()..half_max); - tracing::error!("Retrier {name}: Error for request `{request_log}` with id `{request_id}`, attempt `{attempt}`: {e}. Delaying for {}ms", sleep_duration.as_millis()); + + let error_message = format!("Retrier {name}: Error for request `{request_log}` with id `{request_id}`, attempt `{attempt}`: {e}. Delaying for {:?}", sleep_duration); + if attempt == 0 && !matches!(retry_limit, RetryLimit::Limit(1)) { + tracing::warn!(error_message); + } else { + tracing::error!(error_message); + } // Delay the request before the next retry. retry_delays.push(Box::pin( diff --git a/engine/src/state_chain_observer/client/chain_api.rs b/engine/src/state_chain_observer/client/chain_api.rs index 696fd0e3db..2c6ca54406 100644 --- a/engine/src/state_chain_observer/client/chain_api.rs +++ b/engine/src/state_chain_observer/client/chain_api.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use jsonrpsee::core::RpcResult; use super::StateChainStreamApi; @@ -9,4 +10,6 @@ pub trait ChainApi { async fn finalized_block_stream(&self) -> Box; async fn unfinalized_block_stream(&self) -> Box>; + + async fn block(&self, hash: state_chain_runtime::Hash) -> RpcResult; } diff --git a/engine/src/state_chain_observer/client/mod.rs b/engine/src/state_chain_observer/client/mod.rs index 2b0510ef4a..e389fad654 100644 --- a/engine/src/state_chain_observer/client/mod.rs +++ b/engine/src/state_chain_observer/client/mod.rs @@ -13,6 +13,7 @@ use futures::{StreamExt, TryStreamExt}; use futures_core::Stream; use futures_util::FutureExt; +use jsonrpsee::core::RpcResult; use sp_core::{Pair, H256}; use state_chain_runtime::AccountId; use std::{sync::Arc, time::Duration}; @@ -948,6 +949,10 @@ impl< .expect(OR_CANCEL); receiver.await.expect(OR_CANCEL) } + + async fn block(&self, block_hash: state_chain_runtime::Hash) -> RpcResult { + self.base_rpc_client.block_header(block_hash).await.map(|header| header.into()) + } } #[cfg(test)] @@ -1038,6 +1043,8 @@ pub mod mocks { async fn finalized_block_stream(&self) -> Box; async fn unfinalized_block_stream(&self) -> Box>; + + async fn block(&self, block_hash: state_chain_runtime::Hash) -> RpcResult; } #[async_trait] impl StorageApi for StateChainClient { diff --git a/engine/src/witness/btc/btc_deposits.rs b/engine/src/witness/btc/btc_deposits.rs index 2fd0ff7e83..36a284d938 100644 --- a/engine/src/witness/btc/btc_deposits.rs +++ b/engine/src/witness/btc/btc_deposits.rs @@ -156,7 +156,7 @@ pub mod tests { VerboseTransaction { txid, version: Version::from_consensus(2), - locktime: LockTime::Blocks(Height::from_consensus(0).unwrap()), + locktime: LockTime::Blocks(Height::ZERO), vin: vec![], vout: tx_outs, fee, diff --git a/state-chain/chains/src/btc.rs b/state-chain/chains/src/btc.rs index 7b7b6eaf9c..ccf76b45a4 100644 --- a/state-chain/chains/src/btc.rs +++ b/state-chain/chains/src/btc.rs @@ -23,6 +23,7 @@ use cf_utilities::SliceToArray; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ sp_io::hashing::sha2_256, + sp_runtime::{FixedPointNumber, FixedU128}, traits::{ConstBool, ConstU32}, BoundedVec, RuntimeDebug, }; @@ -118,21 +119,30 @@ impl Default for BitcoinTrackedData { } } +/// A constant multiplier applied to the fees. +/// +/// TODO: Allow this value to adjust based on the current fee deficit/surplus. +const BTC_FEE_MULTIPLIER: FixedU128 = FixedU128::from_rational(3, 2); + impl FeeEstimationApi for BitcoinTrackedData { fn estimate_ingress_fee( &self, _asset: ::ChainAsset, ) -> ::ChainAmount { - // Include the min fee so we over-estimate the cost. - self.btc_fee_info.min_fee_required_per_tx + self.btc_fee_info.fee_per_input_utxo + BTC_FEE_MULTIPLIER + .checked_mul_int(self.btc_fee_info.fee_per_input_utxo) + .expect("fee is a u64, multiplier is 1.5, so this should never overflow") } fn estimate_egress_fee( &self, _asset: ::ChainAsset, ) -> ::ChainAmount { - // Include the min fee so we over-estimate the cost. - self.btc_fee_info.min_fee_required_per_tx + self.btc_fee_info.fee_per_output_utxo + BTC_FEE_MULTIPLIER + .checked_mul_int( + self.btc_fee_info.min_fee_required_per_tx + self.btc_fee_info.fee_per_output_utxo, + ) + .expect("fee is a u64, multiplier is 1.5, so this should never overflow") } } @@ -175,15 +185,18 @@ impl BitcoinFeeInfo { /// We ensure that a minimum of 1 sat per vByte is set for each of the fees. pub fn new(sats_per_kilo_byte: BtcAmount) -> Self { Self { - // Our input utxos are approximately 178 bytes each in the Btc transaction + // Our input utxos are approximately INPUT_UTXO_SIZE_IN_BYTES vbytes each in the Btc + // transaction fee_per_input_utxo: max(sats_per_kilo_byte, BYTES_PER_KILOBYTE) .saturating_mul(INPUT_UTXO_SIZE_IN_BYTES) / BYTES_PER_KILOBYTE, - // Our output utxos are approximately 34 bytes each in the Btc transaction + // Our output utxos are approximately OUTPUT_UTXO_SIZE_IN_BYTES vbytes each in the Btc + // transaction fee_per_output_utxo: max(BYTES_PER_KILOBYTE, sats_per_kilo_byte) .saturating_mul(OUTPUT_UTXO_SIZE_IN_BYTES) / BYTES_PER_KILOBYTE, - // Minimum size of tx that does not scale with input and output utxos is 12 bytes + // Minimum size of tx that does not scale with input and output utxos is + // MINIMUM_BTC_TX_SIZE_IN_BYTES bytes min_fee_required_per_tx: max(BYTES_PER_KILOBYTE, sats_per_kilo_byte) .saturating_mul(MINIMUM_BTC_TX_SIZE_IN_BYTES) / BYTES_PER_KILOBYTE, diff --git a/state-chain/chains/src/evm.rs b/state-chain/chains/src/evm.rs index 05489be01a..0cd0bc24dd 100644 --- a/state-chain/chains/src/evm.rs +++ b/state-chain/chains/src/evm.rs @@ -341,7 +341,7 @@ impl Tokenizable for AggKey { } } -#[derive(Encode, Decode, TypeInfo, Copy, Clone, RuntimeDebug, PartialEq, Eq)] +#[derive(Encode, Decode, TypeInfo, Copy, Clone, RuntimeDebug, PartialEq, Eq, Serialize)] pub struct SchnorrVerificationComponents { /// Scalar component pub s: [u8; 32], diff --git a/state-chain/pallets/cf-environment/src/tests.rs b/state-chain/pallets/cf-environment/src/tests.rs index 9fd0500d6d..450b1ca83a 100644 --- a/state-chain/pallets/cf-environment/src/tests.rs +++ b/state-chain/pallets/cf-environment/src/tests.rs @@ -53,23 +53,24 @@ fn test_btc_utxo_selection() { add_utxo_amount(dust_amount); // select some utxos for a tx + const EXPECTED_CHANGE_AMOUNT: crate::BtcAmount = 24770; assert_eq!( Environment::select_and_take_bitcoin_utxos(UtxoSelectionType::Some { output_amount: 12000, number_of_outputs: 2 }) .unwrap(), - (vec![utxo(5000), utxo(10000), utxo(25000), utxo(100000)], 120080) + (vec![utxo(5000), utxo(10000), utxo(25000)], EXPECTED_CHANGE_AMOUNT) ); // add the change utxo back to the available utxo list - add_utxo_amount(120080); + add_utxo_amount(EXPECTED_CHANGE_AMOUNT); // select all remaining utxos assert_eq!( Environment::select_and_take_bitcoin_utxos(UtxoSelectionType::SelectAllForRotation) .unwrap(), - (vec![utxo(5000000), utxo(120080),], 5116060) + (vec![utxo(5000000), utxo(100000), utxo(EXPECTED_CHANGE_AMOUNT),], 5121970) ); // add some more utxos to the list @@ -87,7 +88,7 @@ fn test_btc_utxo_selection() { assert_eq!( Environment::select_and_take_bitcoin_utxos(UtxoSelectionType::SelectAllForRotation) .unwrap(), - (vec![utxo(5000), utxo(15000),], 15980) + (vec![utxo(5000), utxo(15000),], 17950) ); }); } @@ -144,7 +145,7 @@ fn test_btc_utxo_consolidation() { // Should select two UTXOs, with all funds (minus fees) going back to us as change assert_eq!( Environment::select_and_take_bitcoin_utxos(UtxoSelectionType::SelectForConsolidation), - Some((vec![utxo(10000), utxo(20000)], 25980)) + Some((vec![utxo(10000), utxo(20000)], 27950)) ); // Any utxo that didn't get consolidated should still be available: diff --git a/state-chain/pallets/cf-ingress-egress/src/lib.rs b/state-chain/pallets/cf-ingress-egress/src/lib.rs index 5e7047dd1d..eb32a3ae93 100644 --- a/state-chain/pallets/cf-ingress-egress/src/lib.rs +++ b/state-chain/pallets/cf-ingress-egress/src/lib.rs @@ -64,6 +64,15 @@ impl FetchOrTransfer { } } +#[derive(RuntimeDebug, Eq, PartialEq, Clone, Encode, Decode, TypeInfo)] +pub enum DepositIgnoredReason { + BelowMinimumDeposit, + + /// The deposit was ignored because the amount provided was not high enough to pay for the fees + /// required to process the requisite transactions. + NotEnoughToPayFees, +} + /// Cross-chain messaging requests. #[derive(RuntimeDebug, Eq, PartialEq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] pub(crate) struct CrossChainMessage { @@ -423,6 +432,7 @@ pub mod pallet { asset: TargetChainAsset, amount: TargetChainAmount, deposit_details: ::DepositDetails, + reason: DepositIgnoredReason, }, TransferFallbackRequested { asset: TargetChainAsset, @@ -968,6 +978,7 @@ impl, I: 'static> Pallet { asset, amount: deposit_amount, deposit_details, + reason: DepositIgnoredReason::BelowMinimumDeposit, }); return Ok(()) } @@ -986,82 +997,82 @@ impl, I: 'static> Pallet { deposit_amount, ); - // TODO: Consider updating the event with a reason explaining why the deposit was ignored. + // Add the deposit to the balance. + T::DepositHandler::on_deposit_made( + deposit_details.clone(), + deposit_amount, + deposit_channel_details.deposit_channel, + ); + DepositBalances::::mutate(asset, |deposits| { + deposits.register_deposit(net_deposit_amount) + }); + if net_deposit_amount.is_zero() { Self::deposit_event(Event::::DepositIgnored { deposit_address, asset, amount: deposit_amount, deposit_details, + reason: DepositIgnoredReason::NotEnoughToPayFees, }); - return Ok(()) - } - - match deposit_channel_details.action { - ChannelAction::LiquidityProvision { lp_account, .. } => - T::LpBalance::try_credit_account( - &lp_account, + } else { + match deposit_channel_details.action { + ChannelAction::LiquidityProvision { lp_account, .. } => + T::LpBalance::try_credit_account( + &lp_account, + asset.into(), + net_deposit_amount.into(), + )?, + ChannelAction::Swap { + destination_address, + destination_asset, + broker_id, + broker_commission_bps, + .. + } => T::SwapDepositHandler::schedule_swap_from_channel( + deposit_address.clone().into(), + block_height.into(), asset.into(), + destination_asset, net_deposit_amount.into(), - )?, - ChannelAction::Swap { - destination_address, - destination_asset, - broker_id, - broker_commission_bps, - .. - } => T::SwapDepositHandler::schedule_swap_from_channel( - deposit_address.clone().into(), - block_height.into(), - asset.into(), - destination_asset, - net_deposit_amount.into(), - destination_address, - broker_id, - broker_commission_bps, - channel_id, - ), - ChannelAction::CcmTransfer { - destination_asset, - destination_address, - channel_metadata, - .. - } => T::CcmHandler::on_ccm_deposit( - asset.into(), - net_deposit_amount.into(), - destination_asset, - destination_address, - CcmDepositMetadata { - source_chain: asset.into(), - source_address: None, - channel_metadata, - }, - SwapOrigin::DepositChannel { - deposit_address: T::AddressConverter::to_encoded_address( - deposit_address.clone().into(), - ), + destination_address, + broker_id, + broker_commission_bps, channel_id, - deposit_block_height: block_height.into(), - }, - ), - }; + ), + ChannelAction::CcmTransfer { + destination_asset, + destination_address, + channel_metadata, + .. + } => T::CcmHandler::on_ccm_deposit( + asset.into(), + net_deposit_amount.into(), + destination_asset, + destination_address, + CcmDepositMetadata { + source_chain: asset.into(), + source_address: None, + channel_metadata, + }, + SwapOrigin::DepositChannel { + deposit_address: T::AddressConverter::to_encoded_address( + deposit_address.clone().into(), + ), + channel_id, + deposit_block_height: block_height.into(), + }, + ), + }; - // Add the deposit to the balance. - T::DepositHandler::on_deposit_made( - deposit_details.clone(), - deposit_amount, - deposit_channel_details.deposit_channel, - ); - DepositBalances::::mutate(asset, |deposits| { - deposits.register_deposit(deposit_amount) - }); + Self::deposit_event(Event::DepositReceived { + deposit_address, + asset, + amount: deposit_amount, + deposit_details, + }); + } - Self::deposit_event(Event::DepositReceived { - deposit_address, - asset, - amount: deposit_amount, - deposit_details, - }); Ok(()) } diff --git a/state-chain/pallets/cf-ingress-egress/src/tests.rs b/state-chain/pallets/cf-ingress-egress/src/tests.rs index 20fcdfb461..4f443322f4 100644 --- a/state-chain/pallets/cf-ingress-egress/src/tests.rs +++ b/state-chain/pallets/cf-ingress-egress/src/tests.rs @@ -1,8 +1,9 @@ use crate::{ mock::*, Call as PalletCall, ChannelAction, ChannelIdCounter, CrossChainMessage, - DepositChannelLookup, DepositChannelPool, DepositWitness, DisabledEgressAssets, - Event as PalletEvent, FailedForeignChainCall, FailedForeignChainCalls, FetchOrTransfer, - MinimumDeposit, Pallet, ScheduledEgressCcm, ScheduledEgressFetchOrTransfer, TargetChainAccount, + DepositChannelLookup, DepositChannelPool, DepositIgnoredReason, DepositWitness, + DisabledEgressAssets, Event as PalletEvent, FailedForeignChainCall, FailedForeignChainCalls, + FetchOrTransfer, MinimumDeposit, Pallet, ScheduledEgressCcm, ScheduledEgressFetchOrTransfer, + TargetChainAccount, }; use cf_chains::{ address::AddressConverter, evm::EvmFetchId, mocks::MockEthereum, CcmChannelMetadata, @@ -874,6 +875,7 @@ fn deposits_below_minimum_are_rejected() { asset: eth, amount: default_deposit_amount, deposit_details: Default::default(), + reason: DepositIgnoredReason::BelowMinimumDeposit, }, )); diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 4c8e1d8468..ec5cf3e5d7 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -242,7 +242,7 @@ impl From> for SideMap { } } -pub const PALLET_VERSION: StorageVersion = StorageVersion::new(1); +pub const PALLET_VERSION: StorageVersion = StorageVersion::new(2); #[frame_support::pallet] pub mod pallet { @@ -1559,10 +1559,14 @@ impl Pallet { input_amount: AssetAmount, ) -> Result { Ok(match (from, to) { - (_, STABLE_ASSET) | (STABLE_ASSET, _) => { + (_, STABLE_ASSET) => { let output = Self::take_network_fee(Self::swap_single_leg(from, to, input_amount)?); SwapOutput { intermediary: None, output } }, + (STABLE_ASSET, _) => { + let output = Self::swap_single_leg(from, to, Self::take_network_fee(input_amount))?; + SwapOutput { intermediary: None, output } + }, _ => { let intermediary = Self::swap_single_leg(from, STABLE_ASSET, input_amount)?; let output = diff --git a/state-chain/pallets/cf-pools/src/migrations.rs b/state-chain/pallets/cf-pools/src/migrations.rs index 3a777a7fde..98b8dc1f38 100644 --- a/state-chain/pallets/cf-pools/src/migrations.rs +++ b/state-chain/pallets/cf-pools/src/migrations.rs @@ -1,5 +1,9 @@ pub mod v1; +pub mod v2; use cf_runtime_upgrade_utilities::VersionedMigration; -pub type PalletMigration = (VersionedMigration, v1::Migration, 0, 1>,); +pub type PalletMigration = ( + VersionedMigration, v1::Migration, 0, 1>, + VersionedMigration, v2::Migration, 1, 2>, +); diff --git a/state-chain/pallets/cf-pools/src/migrations/v2.rs b/state-chain/pallets/cf-pools/src/migrations/v2.rs new file mode 100644 index 0000000000..89a53e53b2 --- /dev/null +++ b/state-chain/pallets/cf-pools/src/migrations/v2.rs @@ -0,0 +1,20 @@ +use crate::*; +use frame_support::traits::OnRuntimeUpgrade; +use sp_std::marker::PhantomData; + +/// Resets the CollectedFees. +pub struct Migration(PhantomData); + +impl OnRuntimeUpgrade for Migration { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + log::info!("Deleting CollectedNetworkFee"); + CollectedNetworkFee::::kill(); + Zero::zero() + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), frame_support::sp_runtime::TryRuntimeError> { + ensure!(CollectedNetworkFee::::get() == 0, "CollectedNetworkFee should be zero"); + Ok(()) + } +} diff --git a/state-chain/pallets/cf-pools/src/tests.rs b/state-chain/pallets/cf-pools/src/tests.rs index 9707686d7c..6e2d4ac94e 100644 --- a/state-chain/pallets/cf-pools/src/tests.rs +++ b/state-chain/pallets/cf-pools/src/tests.rs @@ -3,12 +3,13 @@ use crate::{ CollectedNetworkFee, Error, Event, FlipBuyInterval, FlipToBurn, LimitOrder, PoolInfo, PoolOrders, Pools, RangeOrder, RangeOrderSize, ScheduledLimitOrderUpdates, STABLE_ASSET, }; -use cf_amm::common::{price_at_tick, Order, Tick}; +use cf_amm::common::{price_at_tick, tick_at_price, Order, Tick, PRICE_FRACTIONAL_BITS}; use cf_primitives::{chains::assets::any::Asset, AssetAmount, SwapOutput}; use cf_test_utilities::{assert_events_match, assert_has_event, last_event}; use cf_traits::AssetConverter; use frame_support::{assert_noop, assert_ok, traits::Hooks}; use frame_system::pallet_prelude::BlockNumberFor; +use sp_core::U256; use sp_runtime::Permill; #[test] @@ -187,7 +188,7 @@ fn test_sweeping() { fn test_buy_back_flip() { new_test_ext().execute_with(|| { const INTERVAL: BlockNumberFor = 5; - const POSITION: core::ops::Range = -100_000..100_000; + const FLIP_PRICE_IN_USDC: u128 = 10; const FLIP: Asset = Asset::Flip; // Create a new pool. @@ -198,21 +199,30 @@ fn test_buy_back_flip() { Default::default(), price_at_tick(0).unwrap(), )); - assert_ok!(LiquidityPools::set_range_order( - RuntimeOrigin::signed(ALICE), - FLIP, - STABLE_ASSET, - 0, - Some(POSITION), - RangeOrderSize::Liquidity { liquidity: 1_000_000 }, - )); + for side in [Order::Buy, Order::Sell] { + assert_ok!(LiquidityPools::set_limit_order( + RuntimeOrigin::signed(ALICE), + FLIP, + STABLE_ASSET, + side, + 0, + Some( + tick_at_price(U256::from(FLIP_PRICE_IN_USDC) << PRICE_FRACTIONAL_BITS).unwrap() + ), + 1_000_000_000, + )); + } // Swapping should cause the network fee to be collected. - LiquidityPools::swap_with_network_fee(FLIP, STABLE_ASSET, 1000).unwrap(); - LiquidityPools::swap_with_network_fee(STABLE_ASSET, FLIP, 1000).unwrap(); + // Do two swaps of equivalent value. + const USDC_SWAP_VALUE: u128 = 100_000; + const FLIP_SWAP_VALUE: u128 = USDC_SWAP_VALUE / FLIP_PRICE_IN_USDC; + LiquidityPools::swap_with_network_fee(FLIP, STABLE_ASSET, FLIP_SWAP_VALUE).unwrap(); + LiquidityPools::swap_with_network_fee(STABLE_ASSET, FLIP, USDC_SWAP_VALUE).unwrap(); - let collected_fee = CollectedNetworkFee::::get(); - assert!(collected_fee > 0); + // 2 swaps of 100_000 USDC, 0.2% fee + const EXPECTED_COLLECTED_FEES: AssetAmount = 400; + assert_eq!(CollectedNetworkFee::::get(), EXPECTED_COLLECTED_FEES); // The default buy interval is zero, and this means we don't buy back. assert_eq!(FlipBuyInterval::::get(), 0); @@ -225,12 +235,14 @@ fn test_buy_back_flip() { // Nothing is bought if we're not at the interval. LiquidityPools::on_initialize(INTERVAL * 3 - 1); assert_eq!(0, FlipToBurn::::get()); - assert_eq!(collected_fee, CollectedNetworkFee::::get()); + assert_eq!(EXPECTED_COLLECTED_FEES, CollectedNetworkFee::::get()); // If we're at an interval, we should buy flip. LiquidityPools::on_initialize(INTERVAL * 3); assert_eq!(0, CollectedNetworkFee::::get()); - assert!(FlipToBurn::::get() > 0); + assert!( + FlipToBurn::::get().abs_diff(EXPECTED_COLLECTED_FEES / FLIP_PRICE_IN_USDC) <= 1 + ); }); } @@ -369,7 +381,7 @@ fn can_update_pool_liquidity_fee_and_collect_for_limit_order() { // Do some swaps to collect fees. assert_eq!( LiquidityPools::swap_with_network_fee(STABLE_ASSET, Asset::Eth, 10_000).unwrap(), - SwapOutput { intermediary: None, output: 5_988u128 } + SwapOutput { intermediary: None, output: 5_987u128 } ); assert_eq!( LiquidityPools::swap_with_network_fee(Asset::Eth, STABLE_ASSET, 10_000).unwrap(), @@ -387,9 +399,9 @@ fn can_update_pool_liquidity_fee_and_collect_for_limit_order() { // All Lpers' fees and bought amount are Collected and accredited. // Fee and swaps are calculated proportional to the liquidity amount. assert_eq!(AliceCollectedEth::get(), 908u128); - assert_eq!(AliceCollectedUsdc::get(), 3_333u128); + assert_eq!(AliceCollectedUsdc::get(), 3_325u128); assert_eq!(BobCollectedEth::get(), 9090u128); - assert_eq!(BobCollectedUsdc::get(), 6_666u128); + assert_eq!(BobCollectedUsdc::get(), 6_651u128); // New pool fee is set and event emitted. assert_eq!( @@ -416,8 +428,8 @@ fn can_update_pool_liquidity_fee_and_collect_for_limit_order() { lp: ALICE, id: 0.into(), tick: 0, - sell_amount: 3000.into(), - fees_earned: 1333.into(), + sell_amount: 3004.into(), + fees_earned: 1330.into(), original_sell_amount: 5000.into() }], bids: vec![LimitOrder { @@ -440,8 +452,8 @@ fn can_update_pool_liquidity_fee_and_collect_for_limit_order() { lp: BOB, id: 0.into(), tick: 0, - sell_amount: 6_000u128.into(), - fees_earned: 2666.into(), + sell_amount: 6_008u128.into(), + fees_earned: 2660.into(), original_sell_amount: 10000.into() }], bids: vec![LimitOrder { @@ -589,9 +601,9 @@ fn pallet_limit_order_is_in_sync_with_pool() { id: 0, tick: 100, sell_amount_change: None, - sell_amount_total: 5, - collected_fees: 100998, - bought_amount: 100998, + sell_amount_total: 205, + collected_fees: 100796, + bought_amount: 100796, })); assert_has_event::(RuntimeEvent::LiquidityPools(Event::::LimitOrderUpdated { lp: BOB, @@ -679,7 +691,7 @@ fn update_pool_liquidity_fee_collects_fees_for_range_order() { id: 0.into(), range: range.clone(), liquidity: 1_000_000, - fees_earned: AssetsMap { base: 999.into(), quote: 999.into() } + fees_earned: AssetsMap { base: 999.into(), quote: 997.into() } }] }) ); @@ -692,7 +704,7 @@ fn update_pool_liquidity_fee_collects_fees_for_range_order() { id: 0.into(), range: range.clone(), liquidity: 1_000_000, - fees_earned: AssetsMap { base: 999.into(), quote: 999.into() } + fees_earned: AssetsMap { base: 999.into(), quote: 997.into() } }] }) ); @@ -717,13 +729,13 @@ fn update_pool_liquidity_fee_collects_fees_for_range_order() { // Earned liquidity pool fees are paid out. // Total of ~ 4_000 fee were paid, evenly split between Alice and Bob. - assert_eq!(AliceCollectedEth::get(), 5_988u128); - assert_eq!(AliceCollectedUsdc::get(), 5_984u128); + assert_eq!(AliceCollectedEth::get(), 5_991u128); + assert_eq!(AliceCollectedUsdc::get(), 5_979u128); assert_eq!(AliceDebitedEth::get(), 4_988u128); assert_eq!(AliceDebitedUsdc::get(), 4_988u128); - assert_eq!(BobCollectedEth::get(), 5_988u128); - assert_eq!(BobCollectedUsdc::get(), 5_984u128); + assert_eq!(BobCollectedEth::get(), 5_991u128); + assert_eq!(BobCollectedUsdc::get(), 5_979u128); assert_eq!(BobDebitedEth::get(), 4_988u128); assert_eq!(BobDebitedUsdc::get(), 4_988u128); }); diff --git a/state-chain/primitives/src/lib.rs b/state-chain/primitives/src/lib.rs index 1040859deb..2d139c5fc3 100644 --- a/state-chain/primitives/src/lib.rs +++ b/state-chain/primitives/src/lib.rs @@ -58,8 +58,8 @@ pub const FLIPPERINOS_PER_FLIP: FlipBalance = 10u128.pow(FLIP_DECIMALS); pub const DEFAULT_FEE_SATS_PER_KILO_BYTE: u64 = 102400; // Approximate values calculated -pub const INPUT_UTXO_SIZE_IN_BYTES: u64 = 178; -pub const OUTPUT_UTXO_SIZE_IN_BYTES: u64 = 34; +pub const INPUT_UTXO_SIZE_IN_BYTES: u64 = 75; +pub const OUTPUT_UTXO_SIZE_IN_BYTES: u64 = 43; pub const MINIMUM_BTC_TX_SIZE_IN_BYTES: u64 = 12; /// This determines the average expected block time that we are targeting. diff --git a/state-chain/runtime-utilities/src/lib.rs b/state-chain/runtime-utilities/src/lib.rs index 4de942d34a..d2348b51ad 100644 --- a/state-chain/runtime-utilities/src/lib.rs +++ b/state-chain/runtime-utilities/src/lib.rs @@ -63,16 +63,18 @@ where /// Logs if running in release, panics if running in test. #[macro_export] macro_rules! log_or_panic { - ($($arg:tt)*) => { - #[cfg(not(debug_assertions))] - { - log::error!($($arg)*); - } - #[cfg(debug_assertions)] - { - panic!($($arg)*); - }; - }; + ($($arg:tt)*) => { + #[cfg(not(debug_assertions))] + { + use scale_info::prelude::format; + log::error!("log_or_panic: {}", format_args!($($arg)*)); + } + #[cfg(debug_assertions)] + { + use scale_info::prelude::format; + panic!("log_or_panic: {}", format_args!($($arg)*)); + }; + }; } #[cfg(test)] diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml index c684d6cfed..1c6e9ed0f9 100644 --- a/utilities/Cargo.toml +++ b/utilities/Cargo.toml @@ -37,7 +37,7 @@ num-traits = { version = "0.2", optional = true } scopeguard = { version = "1.2.0" } prometheus = { version = "0.13.0", default-features = false } lazy_static = "1.4" -jsonrpsee = { version = "0.20", features = [ +jsonrpsee = { version = "0.16.2", features = [ "jsonrpsee-types", ], optional = true } regex = { version = "1", optional = true } diff --git a/utilities/src/lib.rs b/utilities/src/lib.rs index 2149b2855f..d57d2e1ec6 100644 --- a/utilities/src/lib.rs +++ b/utilities/src/lib.rs @@ -30,6 +30,15 @@ macro_rules! assert_err { }; } +/// Forces compile evaluation of expression +#[macro_export] +macro_rules! const_eval { + ($t:ty, $e:expr) => {{ + static X: $t = $e; + X.clone() + }}; +} + /// Note that the resulting `threshold` is the maximum number /// of parties *not* enough to generate a signature, /// i.e. at least `t+1` parties are required. diff --git a/utilities/src/with_std.rs b/utilities/src/with_std.rs index 4e6c3d9a6f..73d7487f7e 100644 --- a/utilities/src/with_std.rs +++ b/utilities/src/with_std.rs @@ -1,7 +1,6 @@ use anyhow::{anyhow, Context}; use core::time::Duration; use futures::{stream, Stream}; -use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; #[doc(hidden)] pub use lazy_format::lazy_format as internal_lazy_format; use rpc::NumberOrHex; @@ -25,24 +24,6 @@ pub use cached_stream::{CachedStream, InnerCachedStream, MakeCachedStream}; mod try_cached_stream; pub use try_cached_stream::{MakeTryCachedStream, TryCachedStream}; -/// A wrapper around `anyhow::Error` to allow conversion to `jsonrpsee::types::ErrorObjectOwned` -/// including context and source. -pub struct AnyhowRpcError { - pub error: anyhow::Error, -} - -impl> From for AnyhowRpcError { - fn from(error: E) -> Self { - Self { error: error.into() } - } -} - -impl From for ErrorObjectOwned { - fn from(e: AnyhowRpcError) -> Self { - ErrorObjectOwned::owned(CALL_EXECUTION_FAILED_CODE, format!("{:#}", e.error), None::<()>) - } -} - pub fn clean_hex_address>>(address_str: &str) -> Result { let address_hex_str = match address_str.strip_prefix("0x") { Some(address_stripped) => address_stripped,