From f4b5f9cbb56b5350dde9e2225d9006fb18fe796d Mon Sep 17 00:00:00 2001 From: Georgi Zlatarev <45011053+ghzlatarev@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:03:38 +0200 Subject: [PATCH] [Manta] Add a `congested_chain_simulation` test in Calamari (#337) * Add a congested_chain_simulation test to calculate the associated costs more accurately * Clean up unused * Spelling correction * Merge Adam's fetch_kma_price code * Add sanity check that next fee multiplier should not reduce --- Cargo.lock | 158 +++++++++++++++++++++++++++++++++++- runtime/calamari/Cargo.toml | 4 + runtime/calamari/src/fee.rs | 138 ++++++++++++++++++++++++++----- 3 files changed, 278 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3280c4b49..6f44250e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -944,8 +944,10 @@ dependencies = [ "polkadot-parachain", "polkadot-primitives", "polkadot-runtime-common", + "reqwest", "scale-info", "serde", + "serde_json", "smallvec", "sp-api", "sp-block-builder", @@ -2023,6 +2025,15 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "encoding_rs" +version = "0.8.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "enum-as-inner" version = "0.3.3" @@ -2254,6 +2265,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "fork-tree" version = "3.0.0" @@ -2987,6 +3013,19 @@ dependencies = [ "webpki", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes 1.1.0", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "idna" version = "0.1.5" @@ -3163,7 +3202,7 @@ dependencies = [ "socket2 0.3.19", "widestring", "winapi 0.3.9", - "winreg", + "winreg 0.6.2", ] [[package]] @@ -4564,6 +4603,12 @@ dependencies = [ "thrift", ] +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4782,6 +4827,24 @@ dependencies = [ "rand 0.8.4", ] +[[package]] +name = "native-tls" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "net2" version = "0.2.37" @@ -4950,12 +5013,39 @@ dependencies = [ "syn", ] +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + [[package]] name = "openssl-probe" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +[[package]] +name = "openssl-sys" +version = "0.9.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-float" version = "1.1.1" @@ -7757,6 +7847,41 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "reqwest" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c4e0a76dc12a116108933f6301b95e83634e0c47b0afbed6abbaa0601e99258" +dependencies = [ + "base64", + "bytes 1.1.0", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding 2.1.0", + "pin-project-lite 0.2.7", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "url 2.2.2", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.7.0", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -9126,6 +9251,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha-1" version = "0.8.2" @@ -10426,6 +10563,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.22.0" @@ -11354,6 +11501,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/runtime/calamari/Cargo.toml b/runtime/calamari/Cargo.toml index 9e8aaf6cf..22bea3c52 100644 --- a/runtime/calamari/Cargo.toml +++ b/runtime/calamari/Cargo.toml @@ -88,6 +88,10 @@ targets = ['x86_64-unknown-linux-gnu'] [build-dependencies] substrate-wasm-builder = { git = 'https://github.com/paritytech/substrate.git', branch = "polkadot-v0.9.13" } +[dev-dependencies] +serde_json = "1.0" +reqwest = { version = "0.11", features = ["blocking"] } + [features] default = ['std'] try-runtime = [ diff --git a/runtime/calamari/src/fee.rs b/runtime/calamari/src/fee.rs index 0c48ec9da..7f6077427 100644 --- a/runtime/calamari/src/fee.rs +++ b/runtime/calamari/src/fee.rs @@ -38,27 +38,7 @@ pub struct WeightToFee; impl WeightToFeePolynomial for WeightToFee { type Balance = Balance; fn polynomial() -> WeightToFeeCoefficients { - // Consider the daily cost to fully congest our network to be defined as: - // daily_cost_to_fully_congest = inclusion_fee * txs_per_block * blocks_per_day * kma_price - // The weight fee is defined as: - // weight_fee = coeff_integer * (weight ^ degree) + coeff_friction * (weight ^ degree) - // The inclusion fee is defined as: - // inclusion_fee = base_fee + length_fee + [targeted_fee_adjustment * weight_fee] - // As of the day of writing this code a single `balances.transfer` is benchmarked at 156626000 weight. - // Let's assume worst case scenarios where the `length_fee` of a transfer is negligible, - // and that `targeted_fee_adjustment` is simply 1, as if the network is not congested. - // Furthermore we know the `base_fee` is 0.000125KMA defined in our runtime. So: - // inclusion_fee = 0.000125 * coeff + 0.000156626 * coeff = 0.000281626 * coeff - // We have profiled `txs_per_block` to be around 1134 and `blocks_per_day` is known to be 7200. - // KMA price in dollars can be checked daily but at the time of writing the code it was $0.02. So: - // daily_cost_to_fully_congest = 0.000281626 * coeff * 1134 * 7200 * 0.02 = 45.988399296 * coeff - // Assuming we want the daily cost to be around $250000 we get: - // 250000 = 45.988399296 * coeff - // coeff = ~5436 - - // Keep in mind this is a rough worst-case scenario calculation. - // The `length_fee` could not be negligible, and the `targeted_fee_adjustment` will hike the fees - // as the network gets more and more congested, which will further increase the costs. + // Refer to the congested_chain_simulation() test for how to come up with the coefficient. smallvec![WeightToFeeCoefficient { coeff_integer: 5000u32.into(), coeff_frac: Perbill::zero(), @@ -67,3 +47,119 @@ impl WeightToFeePolynomial for WeightToFee { }] } } + +#[cfg(test)] +mod multiplier_tests { + use crate::{Runtime, RuntimeBlockWeights as BlockWeights, System, TransactionPayment, KMA}; + use frame_support::weights::{DispatchClass, Weight, WeightToFeePolynomial}; + use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment}; + use polkadot_runtime_common::{AdjustmentVariable, MinimumMultiplier, TargetBlockFullness}; + use sp_runtime::{ + traits::{Convert, One}, + FixedPointNumber, + }; + + fn run_with_system_weight(w: Weight, assertions: F) + where + F: Fn() -> (), + { + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap() + .into(); + t.execute_with(|| { + System::set_block_consumed_resources(w, 0); + assertions() + }); + } + + // update based on runtime impl. + fn runtime_multiplier_update(fm: Multiplier) -> Multiplier { + TargetedFeeAdjustment::< + Runtime, + TargetBlockFullness, + AdjustmentVariable, + MinimumMultiplier, + >::convert(fm) + } + + fn fetch_kma_price() -> Result { + let body = reqwest::blocking::get( + "https://api.coingecko.com/api/v3/simple/price?ids=calamari-network&vs_currencies=usd", + ) + .unwrap(); + let json_reply: serde_json::Value = serde_json::from_reader(body).unwrap(); + if let Some(price) = json_reply["calamari-network"]["usd"].as_f64() { + // CG API return: {"calamari-network":{"usd": 0.01092173}} + Ok(price as f32) + } else { + Err("KMA price not found in reply from Coingecko. API changed? Check https://www.coingecko.com/en/api/documentation") + } + } + + // Consider the daily cost to fully congest our network to be defined as: + // `target_daily_congestion_cost_usd = inclusion_fee * blocks_per_day * kma_price` + // Where: + // `inclusion_fee = fee_adjustment * (weight_to_fee_coeff * (block_weight ^ degree)) + base_fee + length_fee` + // Where: + // `fee_adjustment` and `weight_to_fee_coeff` are configurable in a runtime via `FeeMultiplierUpdate` and `WeightToFee` + // `fee_adjustment` is also variable depending on previous block's fullness + // We are also assuming `length_fee` is negligible for small TXs like a remark or a transfer. + // This test loops 1 day of parachain blocks (7200) and calculates accumulated fee if every block is almost full + #[test] + fn congested_chain_simulation() { + // Configure the target cost depending on the current state of the network. + let target_daily_congestion_cost_usd = 250000; + let kma_price = fetch_kma_price().unwrap(); + println!("KMA/USD price as read from CoinGecko = {}", kma_price); + let target_daily_congestion_cost_kma = + (target_daily_congestion_cost_usd as f32 * kma_price * KMA as f32) as u128; + + // `cargo test --package calamari-runtime --lib -- fee::multiplier_tests::congested_chain_simulation --exact --nocapture` to get some insight. + // almost full. The entire quota of normal transactions is taken. + let block_weight = BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap() - 10; + + let base_fee = ::WeightToFee::calc( + &frame_support::weights::constants::ExtrinsicBaseWeight::get(), + ); + + run_with_system_weight(block_weight, || { + // initial value configured on module + let mut fee_adjustment = Multiplier::one(); + assert_eq!(fee_adjustment, TransactionPayment::next_fee_multiplier()); + let mut accumulated_fee: u128 = 0; + // Simulates 1 day of parachain blocks (12 seconds each) + for iteration in 0..7200 { + let next = runtime_multiplier_update(fee_adjustment); + // if no change or less, panic. This should never happen in this case. + if fee_adjustment >= next { + println!("final fee_adjustment: {}", fee_adjustment); + println!("final next: {}", next); + panic!("The fee should ever increase"); + } + fee_adjustment = next; + let fee = ::WeightToFee::calc( + &block_weight, + ); + // base_fee is not adjusted + let adjusted_fee = fee_adjustment.saturating_mul_acc_int(fee) + base_fee; + accumulated_fee += adjusted_fee; + println!( + "Iteration {}, New fee_adjustment = {:?}. Adjusted Fee: {} KMA, Total Fee: {} KMA, Dollar Vlaue: {}", + iteration, + fee_adjustment, + adjusted_fee / KMA, + accumulated_fee / KMA, + (accumulated_fee / KMA) as f32 * kma_price, + ); + } + + if accumulated_fee < target_daily_congestion_cost_kma { + panic!("The cost to fully congest our network should be over the target_daily_congestion_cost_kma after 1 day."); + } + }); + } +}