From c661a94014d351d1220f49bf7e0f34331cfef4d1 Mon Sep 17 00:00:00 2001 From: undercover-cactus Date: Tue, 2 Sep 2025 15:05:36 +0200 Subject: [PATCH] feat: add storagehub pallets to all the runtimes (#133) This PR add the storagehub pallets to the `testnet` and `mainnet` runtime. The storage hub pallets are only build with the runtime if the `storage-hub` feature is activated. It is also add minor fixes to the `stagenet` runtime (fix wrong path dependencies, index collision, etc...). --------- Co-authored-by: Steve Degosserie <723552+stiiifff@users.noreply.github.com> --- operator/Cargo.lock | 44 ++ operator/runtime/mainnet/Cargo.toml | 76 +++ operator/runtime/mainnet/src/configs/mod.rs | 2 + .../mainnet/src/configs/runtime_params.rs | 318 +++++++++ .../mainnet/src/configs/storagehub/mod.rs | 610 ++++++++++++++++++ operator/runtime/mainnet/src/lib.rs | 207 +++++- .../stagenet/src/configs/runtime_params.rs | 71 +- operator/runtime/stagenet/src/lib.rs | 3 + operator/runtime/testnet/Cargo.toml | 74 +++ operator/runtime/testnet/src/configs/mod.rs | 2 + .../testnet/src/configs/runtime_params.rs | 318 +++++++++ .../testnet/src/configs/storagehub/mod.rs | 610 ++++++++++++++++++ operator/runtime/testnet/src/lib.rs | 207 +++++- 13 files changed, 2509 insertions(+), 33 deletions(-) create mode 100644 operator/runtime/mainnet/src/configs/storagehub/mod.rs create mode 100644 operator/runtime/testnet/src/configs/storagehub/mod.rs diff --git a/operator/Cargo.lock b/operator/Cargo.lock index a5c56e90..d0899c13 100644 --- a/operator/Cargo.lock +++ b/operator/Cargo.lock @@ -2354,11 +2354,14 @@ dependencies = [ "hex", "hex-literal 0.3.4", "log", + "num-bigint", "pallet-authorship", "pallet-babe", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", + "pallet-bucket-nfts", + "pallet-cr-randomness", "pallet-datahaven-native-transfer", "pallet-ethereum", "pallet-evm", @@ -2366,19 +2369,29 @@ dependencies = [ "pallet-external-validators", "pallet-external-validators-rewards", "pallet-external-validators-rewards-runtime-api", + "pallet-file-system", + "pallet-file-system-runtime-api", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-message-queue", "pallet-mmr", "pallet-multisig", + "pallet-nfts", "pallet-offences", "pallet-outbound-commitment-store", "pallet-parameters", + "pallet-payment-streams", + "pallet-payment-streams-runtime-api", "pallet-preimage", + "pallet-proofs-dealer", + "pallet-proofs-dealer-runtime-api", "pallet-proxy", + "pallet-randomness", "pallet-scheduler", "pallet-session", + "pallet-storage-providers", + "pallet-storage-providers-runtime-api", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", @@ -2390,6 +2403,14 @@ dependencies = [ "polkadot-runtime-common", "scale-info", "serde_json", + "shp-constants", + "shp-data-price-updater", + "shp-file-key-verifier", + "shp-file-metadata", + "shp-forest-verifier", + "shp-traits", + "shp-treasury-funding", + "smallvec", "snowbridge-beacon-primitives 0.2.0", "snowbridge-core 0.2.0", "snowbridge-inbound-queue-primitives", @@ -2421,6 +2442,7 @@ dependencies = [ "sp-storage", "sp-tracing", "sp-transaction-pool", + "sp-trie", "sp-version", "staging-xcm", "staging-xcm-builder", @@ -2669,11 +2691,14 @@ dependencies = [ "hex", "hex-literal 0.3.4", "log", + "num-bigint", "pallet-authorship", "pallet-babe", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", + "pallet-bucket-nfts", + "pallet-cr-randomness", "pallet-datahaven-native-transfer", "pallet-ethereum", "pallet-evm", @@ -2681,19 +2706,29 @@ dependencies = [ "pallet-external-validators", "pallet-external-validators-rewards", "pallet-external-validators-rewards-runtime-api", + "pallet-file-system", + "pallet-file-system-runtime-api", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-message-queue", "pallet-mmr", "pallet-multisig", + "pallet-nfts", "pallet-offences", "pallet-outbound-commitment-store", "pallet-parameters", + "pallet-payment-streams", + "pallet-payment-streams-runtime-api", "pallet-preimage", + "pallet-proofs-dealer", + "pallet-proofs-dealer-runtime-api", "pallet-proxy", + "pallet-randomness", "pallet-scheduler", "pallet-session", + "pallet-storage-providers", + "pallet-storage-providers-runtime-api", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", @@ -2705,6 +2740,14 @@ dependencies = [ "polkadot-runtime-common", "scale-info", "serde_json", + "shp-constants", + "shp-data-price-updater", + "shp-file-key-verifier", + "shp-file-metadata", + "shp-forest-verifier", + "shp-traits", + "shp-treasury-funding", + "smallvec", "snowbridge-beacon-primitives 0.2.0", "snowbridge-core 0.2.0", "snowbridge-inbound-queue-primitives", @@ -2736,6 +2779,7 @@ dependencies = [ "sp-storage", "sp-tracing", "sp-transaction-pool", + "sp-trie", "sp-version", "staging-xcm", "staging-xcm-builder", diff --git a/operator/runtime/mainnet/Cargo.toml b/operator/runtime/mainnet/Cargo.toml index 68e25172..486324d0 100644 --- a/operator/runtime/mainnet/Cargo.toml +++ b/operator/runtime/mainnet/Cargo.toml @@ -32,6 +32,7 @@ frame-try-runtime = { workspace = true, optional = true } hex = { workspace = true } hex-literal = { workspace = true } log = { workspace = true } +num-bigint = { workspace = true, optional = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true, features = ["insecure_zero_ed"] } @@ -69,6 +70,7 @@ scale-info = { workspace = true, features = ["derive", "serde"] } serde_json = { workspace = true, default-features = false, features = [ "alloc", ] } +smallvec = { workspace = true } snowbridge-beacon-primitives = { workspace = true } snowbridge-core = { workspace = true } snowbridge-inbound-queue-primitives = { workspace = true } @@ -104,6 +106,30 @@ xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } + +# StorageHub +pallet-bucket-nfts = { workspace = true, optional = true } +pallet-nfts = { workspace = true, optional = true } +pallet-cr-randomness = { workspace = true, optional = true } +pallet-file-system = { workspace = true, optional = true } +pallet-file-system-runtime-api = { workspace = true, optional = true } +pallet-payment-streams = { workspace = true, optional = true } +pallet-payment-streams-runtime-api = { workspace = true, optional = true } +pallet-proofs-dealer = { workspace = true, optional = true } +pallet-proofs-dealer-runtime-api = { workspace = true, optional = true } +pallet-randomness = { workspace = true, optional = true } +pallet-storage-providers = { workspace = true, optional = true } +pallet-storage-providers-runtime-api = { workspace = true, optional = true } +shp-constants = { workspace = true, optional = true } +shp-file-metadata = { workspace = true, optional = true } +shp-traits = { workspace = true, optional = true } +shp-treasury-funding = { workspace = true, optional = true } +shp-forest-verifier = { workspace = true, optional = true } +shp-file-key-verifier = { workspace = true, optional = true } +shp-data-price-updater = { workspace = true, optional = true } +sp-trie = { workspace = true, optional = true } + + [build-dependencies] substrate-wasm-builder = { workspace = true, optional = true, default-features = true } @@ -120,6 +146,29 @@ snowbridge-pallet-system-v2 = { workspace = true } snowbridge-outbound-queue-primitives = { workspace = true } [features] +storage-hub = [ + "dep:num-bigint", + "dep:pallet-bucket-nfts", + "dep:pallet-nfts", + "dep:pallet-cr-randomness", + "dep:pallet-file-system", + "dep:pallet-file-system-runtime-api", + "dep:pallet-payment-streams", + "dep:pallet-payment-streams-runtime-api", + "dep:pallet-proofs-dealer", + "dep:pallet-proofs-dealer-runtime-api", + "dep:pallet-randomness", + "dep:pallet-storage-providers", + "dep:pallet-storage-providers-runtime-api", + "dep:shp-constants", + "dep:shp-file-metadata", + "dep:shp-traits", + "dep:shp-treasury-funding", + "dep:shp-forest-verifier", + "dep:shp-file-key-verifier", + "dep:shp-data-price-updater", + "dep:sp-trie" + ] default = ["std"] std = [ "codec/std", @@ -199,6 +248,33 @@ std = [ "substrate-wasm-builder", "pallet-outbound-commitment-store/std", "pallet-datahaven-native-transfer/std", + + # StorageHub + "pallet-authorship/std", + "pallet-balances/std", + "pallet-bucket-nfts/std", + "pallet-nfts/std", + "pallet-cr-randomness/std", + "pallet-file-system/std", + "pallet-file-system-runtime-api/std", + "pallet-payment-streams/std", + "pallet-payment-streams-runtime-api/std", + "pallet-proofs-dealer/std", + "pallet-proofs-dealer-runtime-api/std", + "pallet-randomness/std", + "pallet-session/std", + "pallet-storage-providers/std", + "pallet-storage-providers-runtime-api/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "shp-constants/std", + "shp-file-metadata/std", + "shp-forest-verifier/std", + "shp-traits/std", + "shp-treasury-funding/std", + "shp-file-key-verifier/std", ] runtime-benchmarks = [ diff --git a/operator/runtime/mainnet/src/configs/mod.rs b/operator/runtime/mainnet/src/configs/mod.rs index 6b3d7bec..bd581e0a 100644 --- a/operator/runtime/mainnet/src/configs/mod.rs +++ b/operator/runtime/mainnet/src/configs/mod.rs @@ -22,6 +22,8 @@ // OTHER DEALINGS IN THE SOFTWARE. // // For more information, please refer to +#[cfg(feature = "storage-hub")] +mod storagehub; pub mod runtime_params; diff --git a/operator/runtime/mainnet/src/configs/runtime_params.rs b/operator/runtime/mainnet/src/configs/runtime_params.rs index 9672fac9..48e197ab 100644 --- a/operator/runtime/mainnet/src/configs/runtime_params.rs +++ b/operator/runtime/mainnet/src/configs/runtime_params.rs @@ -5,6 +5,16 @@ use sp_core::{ConstU32, H160, H256}; use sp_runtime::{BoundedVec, Perbill}; use sp_std::vec; +#[cfg(feature = "storage-hub")] +use crate::currency::{GIGAWEI, HAVE}; + +#[cfg(feature = "storage-hub")] +use crate::configs::storagehub::{ChallengeTicksTolerance, ReplicationTargetType, SpMinDeposit}; + +#[cfg(feature = "storage-hub")] +use datahaven_runtime_common::{Balance, BlockNumber}; + +#[cfg(not(feature = "storage-hub"))] #[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] pub mod dynamic_params { use super::*; @@ -50,6 +60,314 @@ pub mod dynamic_params { } } +#[cfg(feature = "storage-hub")] +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod runtime_config { + + use super::*; + + #[codec(index = 0)] + #[allow(non_upper_case_globals)] + /// Set the initial address of the Snowbridge Gateway contract on Ethereum. + /// The fact that this is a parameter means that we can set it initially to the zero address, + /// and then change it later via governance, to the actual address of the deployed contract. + pub static EthereumGatewayAddress: H160 = H160::repeat_byte(0x0); + + #[codec(index = 1)] + #[allow(non_upper_case_globals)] + /// Set the initial address of the Rewards Registry contract on Ethereum. + /// The fact that this is a parameter means that we can set it initially to the zero address, + /// and then change it later via governance, to the actual address of the deployed contract. + pub static RewardsRegistryAddress: H160 = H160::repeat_byte(0x0); + + #[codec(index = 2)] + #[allow(non_upper_case_globals)] + /// The Selector is the first 4 bytes of the keccak256 hash of the function signature("updateRewardsMerkleRoot(bytes32)") + pub static RewardsUpdateSelector: BoundedVec> = + BoundedVec::truncate_from(vec![0xdc, 0x3d, 0x04, 0xec]); + + #[codec(index = 3)] + #[allow(non_upper_case_globals)] + /// The RewardsAgentOrigin is the hash of the string "external_validators_rewards" + /// TODO: Decide which agent origin we want to use. Currently for testing it's the zero hash + pub static RewardsAgentOrigin: H256 = H256::from_slice(&hex!( + "c505dfb2df107d106d08bd0f1a0acd10052ca9aa078629a4ccfd0c90c6e69b65" + )); + + // Proportion of fees allocated to the Treasury (remainder are burned). + // e.g. 20% to the treasury, 80% burned. + #[codec(index = 4)] + #[allow(non_upper_case_globals)] + pub static FeesTreasuryProportion: Perbill = Perbill::from_percent(20); + + // ╔══════════════════════ StorageHub Pallets ═══════════════════════╗ + + #[codec(index = 5)] + #[allow(non_upper_case_globals)] + /// 20 HAVEs + pub static SlashAmountPerMaxFileSize: Balance = 20 * HAVE; + + #[codec(index = 6)] + #[allow(non_upper_case_globals)] + /// 10k HAVEs * [`MinChallengePeriod`] = 10k HAVEs * 30 = 300k HAVEs + /// + /// This can be interpreted as "a Provider with 10k HAVEs of stake would get the minimum challenge period". + pub static StakeToChallengePeriod: Balance = + 10_000 * HAVE * Into::::into(MinChallengePeriod::get()); + + #[codec(index = 7)] + #[allow(non_upper_case_globals)] + /// The [`CheckpointChallengePeriod`] is set to be equal to the longest possible challenge period + /// (i.e. the [`StakeToChallengePeriod`] divided by the [`SpMinDeposit`]). + /// + // 300k HAVEs / 100 HAVEs + 50 + 1 = ~3k ticks (i.e. ~5 hours with 6 seconds per tick) + pub static CheckpointChallengePeriod: BlockNumber = (StakeToChallengePeriod::get() + / SpMinDeposit::get()).saturating_add(ChallengeTicksTolerance::get() as u128).saturating_add(1) + .try_into() + .expect( + "StakeToChallengePeriod / SpMinDeposit should be a number of ticks that can fit in BlockNumber numerical type", + ); + + #[codec(index = 8)] + #[allow(non_upper_case_globals)] + /// 30 ticks, or 3 minutes with 6 seconds per tick. + pub static MinChallengePeriod: BlockNumber = 30; + + #[codec(index = 9)] + #[allow(non_upper_case_globals)] + /// Price decreases when system utilisation is below 30%. + pub static SystemUtilisationLowerThresholdPercentage: Perbill = Perbill::from_percent(30); + + #[codec(index = 10)] + #[allow(non_upper_case_globals)] + /// Price increases when system utilisation is above 95%. + pub static SystemUtilisationUpperThresholdPercentage: Perbill = Perbill::from_percent(95); + + #[codec(index = 11)] + #[allow(non_upper_case_globals)] + /// 50 [`GIGAWEI`]s is the price per GB of data, per tick. + /// + /// With 6 seconds per tick, this means that over a month, the price of 1 GB is: + /// 50e-9 [`HAVE`]s * 10 ticks/min * 60 min/h * 24 h/day * 30 days/month = 21.6e-3 [`HAVE`]s + pub static MostlyStablePrice: Balance = 50 * GIGAWEI; + + #[codec(index = 12)] + #[allow(non_upper_case_globals)] + /// [`MostlyStablePrice`] * 10 = 500 [`GIGAWEI`]s + pub static MaxPrice: Balance = MostlyStablePrice::get() * 10; + + #[codec(index = 13)] + #[allow(non_upper_case_globals)] + /// [`MostlyStablePrice`] / 5 = 10 [`GIGAWEI`]s + pub static MinPrice: Balance = MostlyStablePrice::get() / 5; + + #[codec(index = 14)] + #[allow(non_upper_case_globals)] + /// u = [`UpperExponentFactor`] + /// system_utilisation = 1 + /// + /// [`MaxPrice`] = [`MostlyStablePrice`] + u * e ^ ( 1 - [`SystemUtilisationUpperThresholdPercentage`] ) + /// + /// 500 = 50 + u * (e ^ (1 - 0.95) - 1) + /// u = (500 - 50) / (e ^ (1 - 0.95) - 1) ≈ 8777 + pub static UpperExponentFactor: u32 = 8777; + + #[codec(index = 15)] + #[allow(non_upper_case_globals)] + /// l = [`LowerExponentFactor`] + /// system_utilisation = 0 + /// + /// [`MinPrice`] = [`MostlyStablePrice`] - u * e ^ ( [`SystemUtilisationLowerThresholdPercentage`] - 0 ) + /// + /// 10 = 50 - l * (e ^ (0.3 - 0) - 1) + /// l = (50 - 10) / (e ^ (0.3 - 0) - 1) ≈ 114 + pub static LowerExponentFactor: u32 = 114; + + #[codec(index = 16)] + #[allow(non_upper_case_globals)] + /// 0-size bucket fixed rate payment stream representing the price for 1 GB of data. + /// + /// Base rate for a new fixed payment stream established between an MSP and a user. + pub static ZeroSizeBucketFixedRate: Balance = 50 * GIGAWEI; + + #[codec(index = 17)] + #[allow(non_upper_case_globals)] + /// Ideal utilisation rate of the system + pub static IdealUtilisationRate: Perbill = Perbill::from_percent(85); + + #[codec(index = 18)] + #[allow(non_upper_case_globals)] + /// Decay rate of the power of two function that determines the percentage of funds that go to + /// the treasury for utilisation rates greater than the ideal. + pub static DecayRate: Perbill = Perbill::from_percent(5); + + #[codec(index = 19)] + #[allow(non_upper_case_globals)] + /// The minimum treasury cut that can be taken from the amount charged from a payment stream. + pub static MinimumTreasuryCut: Perbill = Perbill::from_percent(1); + + #[codec(index = 20)] + #[allow(non_upper_case_globals)] + /// The maximum treasury cut that can be taken from the amount charged from a payment stream. + pub static MaximumTreasuryCut: Perbill = Perbill::from_percent(5); + + #[codec(index = 21)] + #[allow(non_upper_case_globals)] + /// The penalty a BSP must pay when they forcefully stop storing a file. + /// We set this to be half of the `SlashAmountPerMaxFileSize` with the rationale that + /// for a BSP that has lost this file, it should be more convenient to voluntarily + /// show up and pay this penalty in good faith, rather than risking being slashed for + /// being unable to submit a proof that should include this file. + pub static BspStopStoringFilePenalty: Balance = SlashAmountPerMaxFileSize::get() / 2; + + /// Time-to-live for a provider to top up their deposit to cover a capacity deficit. + /// Set to 14_400 relay blocks = 1 day with 6 second timeslots. + #[codec(index = 22)] + #[allow(non_upper_case_globals)] + pub static ProviderTopUpTtl: BlockNumber = 14_400; + + /// The following parameters are the replication targets for the different security levels + /// that a storage request (and thus the file it represents) can have. + /// + /// These are associated with the probability that a malicious actor could hold the file hostage by controlling + /// all BSPs that volunteered and confirmed storing it. + /// The values were calculated from the probabilities derived using binomial distribution calculations, + /// where the total number of BSPs is set to 1000, the fraction of malicious BSPs is 1/3, and the target number of BSPs + /// is incremented until the probability of all selected BSPs being malicious falls below the required percentage. + /// + /// The formula used is: + /// num_bsps = 1000 + /// fraction_evil = 1/3 + /// n_evil = int(num_bsps * fraction_evil) // = 333 + /// target = range(1, num_bsps) + /// p_init = target / num_bsps + /// prob = binomial_cdf_at_least(n_evil, target, p_init) + /// + /// This ensures that the replication targets were selected optimally to balance security and storage efficiency. + /// -------------------------------------------------------------------------------------------------------------------- + /// The amount of BSPs that a basic security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~1%. + #[codec(index = 23)] + #[allow(non_upper_case_globals)] + pub static BasicReplicationTarget: ReplicationTargetType = 7; + + /// The amount of BSPs that a standard security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~0.1%. + #[codec(index = 24)] + #[allow(non_upper_case_globals)] + pub static StandardReplicationTarget: ReplicationTargetType = 12; + + /// The amount of BSPs that a high security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~0.01%. + #[codec(index = 25)] + #[allow(non_upper_case_globals)] + pub static HighSecurityReplicationTarget: ReplicationTargetType = 17; + + /// The amount of BSPs that a super high security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~0.001%. + #[codec(index = 26)] + #[allow(non_upper_case_globals)] + pub static SuperHighSecurityReplicationTarget: ReplicationTargetType = 22; + + /// The amount of BSPs that an ultra high security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~0.0001%. + #[codec(index = 27)] + #[allow(non_upper_case_globals)] + pub static UltraHighSecurityReplicationTarget: ReplicationTargetType = 26; + + /// The maximum amount of BSPs that a user can require a storage request to use as the replication target. + /// + /// This is a safety measure to prevent users from issuing storage requests that are too large and would + /// require a large number of BSPs to store the file. + #[codec(index = 28)] + #[allow(non_upper_case_globals)] + pub static MaxReplicationTarget: ReplicationTargetType = + UltraHighSecurityReplicationTarget::get() + .saturating_mul(150) + .saturating_div(100); + + /// The amount of ticks that have to pass for the threshold to volunteer for a specific storage request + /// to arrive at its maximum value. + /// + /// This is big enough so volunteering for a storage request is not open to everyone inmediatly, preventing + /// a select few BSPs from taking all the requests, while small enough so that storage requests don't take + /// too long to be filled. + #[codec(index = 29)] + #[allow(non_upper_case_globals)] + pub static TickRangeToMaximumThreshold: BlockNumber = 3600; // 6 hours with a 6 second block time + + /// The amount of ticks after which a storage request is considered expired and can be removed from storage. + /// + /// It's a function of the TickRangeToMaximumThreshold since it does not make sense for a storage request to + /// expire before arriving at its maximum threshold for volunteering. + #[codec(index = 30)] + #[allow(non_upper_case_globals)] + pub static StorageRequestTtl: BlockNumber = TickRangeToMaximumThreshold::get() + .saturating_mul(110) + .saturating_div(100); + + /// The minimum amount of ticks between a stop storing request from a BSP and that BSP being able to + /// confirm to stop storing that file key. + /// + /// It's a function of the checkpoint challenge period since this makes it so BSPs can't avoid checkpoint + /// challenges by stopping storing a file key right before the challenge period ends in case they lost it. + #[codec(index = 31)] + #[allow(non_upper_case_globals)] + pub static MinWaitForStopStoring: BlockNumber = CheckpointChallengePeriod::get() + .saturating_mul(110) + .saturating_div(100); + + #[codec(index = 32)] + #[allow(non_upper_case_globals)] + /// 20 ticks, or 2 minutes with 6 seconds per tick. + pub static MinSeedPeriod: BlockNumber = 20; + + #[codec(index = 33)] + #[allow(non_upper_case_globals)] + /// 10k HAVEs * [`MinSeedPeriod`] = 10k HAVEs * 20 = 200k HAVEs + /// + /// This can be interpreted as "a Provider with 10k HAVEs of stake would get the minimum seed period". + pub static StakeToSeedPeriod: Balance = + 10_000 * HAVE * Into::::into(MinSeedPeriod::get()); + + #[codec(index = 34)] + #[allow(non_upper_case_globals)] + /// The amount of ticks to charge a user upfront when it tries to issue a new storage request. + /// This is done as a deterrent to avoid users spamming the network with huge files but never + /// actually planning to store them longterm. + /// + /// 72k ticks = 5 days with 6 seconds per tick. + /// This means that a user must pay for 5 days of storage upfront, which gets transferred to the + /// treasury. Governance can then decide what to do with the accumulated funds. + /// + /// With a stable price (defined as `MostlyStablePrice` in this file) of 50 GIGAWEIs per gigabyte + /// per tick and a standard replication target (`StandardReplicationTarget`) of 12 BSPs, the upfront + /// cost for the user to issue a storage request for a 1 GB file would be: + /// 50 GIGAWEIs per gigabyte per tick * 12 BSPs * 72k ticks * 1 GB = 0.0432 HAVEs + pub static UpfrontTicksToPay: BlockNumber = 72_000; + // ╚══════════════════════ StorageHub Pallets ═══════════════════════╝ + } +} + #[cfg(feature = "runtime-benchmarks")] impl Default for RuntimeParameters { fn default() -> Self { diff --git a/operator/runtime/mainnet/src/configs/storagehub/mod.rs b/operator/runtime/mainnet/src/configs/storagehub/mod.rs new file mode 100644 index 00000000..636393e8 --- /dev/null +++ b/operator/runtime/mainnet/src/configs/storagehub/mod.rs @@ -0,0 +1,610 @@ +use super::{ + AccountId, Balance, Balances, BlockNumber, Hash, RuntimeEvent, RuntimeHoldReason, HAVE, +}; +use crate::configs::runtime_params::dynamic_params::runtime_config; +use crate::{ + BucketNfts, Nfts, PaymentStreams, ProofsDealer, Providers, Runtime, Signature, WeightToFee, + HOURS, +}; +use core::marker::PhantomData; +use datahaven_runtime_common::time::{DAYS, MINUTES}; +use frame_support::pallet_prelude::DispatchClass; +use frame_support::traits::AsEnsureOriginWithArg; +use frame_support::{ + parameter_types, + traits::{ConstU128, ConstU32, ConstU64}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::EnsureRoot; +use frame_system::EnsureSigned; +use num_bigint::BigUint; +use pallet_nfts::PalletFeatures; +use polkadot_runtime_common::prod_or_fast; +use shp_data_price_updater::{MostlyStablePriceIndexUpdater, MostlyStablePriceIndexUpdaterConfig}; +use shp_file_key_verifier::FileKeyVerifier; +use shp_file_metadata::{ChunkId, FileMetadata}; +use shp_forest_verifier::ForestVerifier; +use shp_treasury_funding::{ + LinearThenPowerOfTwoTreasuryCutCalculator, LinearThenPowerOfTwoTreasuryCutCalculatorConfig, +}; +use sp_core::Get; +use sp_core::Hasher; +use sp_core::H256; +use sp_runtime::traits::Convert; +use sp_runtime::traits::ConvertBack; +use sp_runtime::traits::Verify; +use sp_runtime::traits::Zero; +use sp_runtime::SaturatedConversion; +use sp_runtime::{traits::BlakeTwo256, Perbill}; +use sp_std::convert::{From, Into}; +use sp_std::vec; +use sp_trie::{LayoutV1, TrieConfiguration, TrieLayout}; + +/// Type representing the storage data units in StorageHub. +pub type StorageDataUnit = u64; + +pub type StorageProofsMerkleTrieLayout = LayoutV1; + +pub type Hashing = BlakeTwo256; + +// TODO: remove this and replace with pallet treasury +pub struct TreasuryAccount; +impl Get for TreasuryAccount { + fn get() -> AccountId { + AccountId::from([0; 32]) + } +} + +/****** NFTs pallet ******/ +parameter_types! { + pub const CollectionDeposit: Balance = 100 * HAVE; + pub const ItemDeposit: Balance = 1 * HAVE; + pub const MetadataDepositBase: Balance = 10 * HAVE; + pub const MetadataDepositPerByte: Balance = 1 * HAVE; + pub const ApprovalsLimit: u32 = 20; + pub const ItemAttributesApprovalsLimit: u32 = 20; + pub const MaxTips: u32 = 10; + pub const MaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; + pub const MaxAttributesPerCall: u32 = 10; + pub Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl pallet_nfts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type CollectionDeposit = CollectionDeposit; + type ItemDeposit = ItemDeposit; + type MetadataDepositBase = MetadataDepositBase; + type AttributeDepositBase = MetadataDepositBase; + type DepositPerByte = MetadataDepositPerByte; + type StringLimit = ConstU32<256>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<256>; + type ApprovalsLimit = ApprovalsLimit; + type ItemAttributesApprovalsLimit = ItemAttributesApprovalsLimit; + type MaxTips = MaxTips; + type MaxDeadlineDuration = MaxDeadlineDuration; + type MaxAttributesPerCall = MaxAttributesPerCall; + type Features = Features; + type OffchainSignature = Signature; + type OffchainPublic = ::Signer; + type WeightInfo = pallet_nfts::weights::SubstrateWeight; + type Locker = (); +} +/****** ****** ****** ******/ + +/****** Relay Randomness pallet ******/ +impl pallet_randomness::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type BabeDataGetter = BabeDataGetter; + type BabeBlockGetter = BlockNumberGetter; + type WeightInfo = (); + type BabeDataGetterBlockNumber = BlockNumber; +} + +pub struct BabeDataGetter; +impl pallet_randomness::GetBabeData for BabeDataGetter { + fn get_epoch_index() -> u64 { + todo!("implement `get_epoch_index`"); + } + fn get_epoch_randomness() -> Hash { + todo!("implement `get_epoch_randomness`"); + } + fn get_parent_randomness() -> Hash { + todo!("implement `get_parent_randomness`"); + } +} + +pub struct BlockNumberGetter {} +impl sp_runtime::traits::BlockNumberProvider for BlockNumberGetter { + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + frame_system::Pallet::::block_number() + } +} + +/****** ****** ****** ******/ + +/****** Storage Providers pallet ******/ +parameter_types! { + pub const SpMinDeposit: Balance = 100 * HAVE; + pub const BucketDeposit: Balance = 100 * HAVE; + pub const BspSignUpLockPeriod: BlockNumber = 90 * DAYS; // ~3 months + pub const MaxBlocksForRandomness: BlockNumber = prod_or_fast!(2 * HOURS, 2 * MINUTES); + // TODO: If the next line is uncommented (which should be eventually, replacing the line above), compilation breaks (most likely because of mismatched dependency issues) + // pub const MaxBlocksForRandomness: BlockNumber = prod_or_fast!(2 * runtime_constants::time::EPOCH_DURATION_IN_SLOTS, 2 * MINUTES); +} + +impl pallet_storage_providers::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_storage_providers::weights::SubstrateWeight; + type ProvidersRandomness = pallet_randomness::RandomnessFromOneEpochAgo; + type PaymentStreams = PaymentStreams; + type ProofDealer = ProofsDealer; + type FileMetadataManager = FileMetadata< + { shp_constants::H_LENGTH }, + { shp_constants::FILE_CHUNK_SIZE }, + { shp_constants::FILE_SIZE_TO_CHALLENGES }, + >; + type NativeBalance = Balances; + type CrRandomness = MockCrRandomness; + type RuntimeHoldReason = RuntimeHoldReason; + type StorageDataUnit = StorageDataUnit; + type StorageDataUnitAndBalanceConvert = StorageDataUnitAndBalanceConverter; + type SpCount = u32; + type BucketCount = u128; + type MerklePatriciaRoot = Hash; + type MerkleTrieHashing = Hashing; + type ProviderId = Hash; + type ProviderIdHashing = Hashing; + type ValuePropId = Hash; + type ValuePropIdHashing = Hashing; + type ReadAccessGroupId = ::CollectionId; + type ProvidersProofSubmitters = ProofsDealer; + type ReputationWeightType = u32; + type StorageHubTickGetter = ProofsDealer; + type Treasury = TreasuryAccount; + type SpMinDeposit = SpMinDeposit; + type SpMinCapacity = ConstU64<2>; + type DepositPerData = ConstU128<2>; + type MaxFileSize = ConstU64<{ u64::MAX }>; + type MaxMultiAddressSize = ConstU32<100>; + type MaxMultiAddressAmount = ConstU32<5>; + type MaxProtocols = ConstU32<100>; + type BucketDeposit = BucketDeposit; + type BucketNameLimit = ConstU32<100>; + type MaxBlocksForRandomness = MaxBlocksForRandomness; + type MinBlocksBetweenCapacityChanges = ConstU32<10>; + type DefaultMerkleRoot = DefaultMerkleRoot; + type SlashAmountPerMaxFileSize = runtime_config::SlashAmountPerMaxFileSize; + type StartingReputationWeight = ConstU32<1>; + type BspSignUpLockPeriod = BspSignUpLockPeriod; + type MaxCommitmentSize = ConstU32<1000>; + type ZeroSizeBucketFixedRate = runtime_config::ZeroSizeBucketFixedRate; + type ProviderTopUpTtl = runtime_config::ProviderTopUpTtl; + type MaxExpiredItemsInBlock = ConstU32<100>; +} + +pub struct StorageDataUnitAndBalanceConverter; +impl Convert for StorageDataUnitAndBalanceConverter { + fn convert(data_unit: StorageDataUnit) -> Balance { + data_unit.saturated_into() + } +} +impl ConvertBack for StorageDataUnitAndBalanceConverter { + fn convert_back(balance: Balance) -> StorageDataUnit { + balance.saturated_into() + } +} + +pub type HasherOutT = <::Hash as Hasher>::Out; +pub struct DefaultMerkleRoot(PhantomData); +impl Get> for DefaultMerkleRoot { + fn get() -> HasherOutT { + sp_trie::empty_trie_root::() + } +} + +/****** ****** ****** ******/ + +/****** Payment Streams pallet ******/ +parameter_types! { + pub const PaymentStreamHoldReason: RuntimeHoldReason = RuntimeHoldReason::PaymentStreams(pallet_payment_streams::HoldReason::PaymentStreamDeposit); + pub const UserWithoutFundsCooldown: BlockNumber = 100; +} + +impl pallet_payment_streams::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_payment_streams::weights::SubstrateWeight; + type NativeBalance = Balances; + type ProvidersPallet = Providers; + type RuntimeHoldReason = RuntimeHoldReason; + type UserWithoutFundsCooldown = UserWithoutFundsCooldown; // Amount of blocks that a user will have to wait before being able to clear the out of funds flag + type NewStreamDeposit = ConstU32<10>; // Amount of blocks that the deposit of a new stream should be able to pay for + type Units = StorageDataUnit; // Storage unit + type BlockNumberToBalance = BlockNumberToBalance; + type ProvidersProofSubmitters = ProofsDealer; + type TreasuryCutCalculator = LinearThenPowerOfTwoTreasuryCutCalculator; + type TreasuryAccount = TreasuryAccount; + type MaxUsersToCharge = ConstU32<10>; + type BaseDeposit = ConstU128<10>; +} + +// Converter from the BlockNumber type to the Balance type for math +pub struct BlockNumberToBalance; +impl Convert for BlockNumberToBalance { + fn convert(block_number: BlockNumber) -> Balance { + block_number.into() // In this converter we assume that the block number type is smaller in size than the balance type + } +} + +impl LinearThenPowerOfTwoTreasuryCutCalculatorConfig for Runtime { + type Balance = Balance; + type ProvidedUnit = StorageDataUnit; + type IdealUtilisationRate = runtime_config::IdealUtilisationRate; + type DecayRate = runtime_config::DecayRate; + type MinimumCut = runtime_config::MinimumTreasuryCut; + type MaximumCut = runtime_config::MaximumTreasuryCut; +} +/****** ****** ****** ******/ + +/****** Proofs Dealer pallet ******/ +const RANDOM_CHALLENGES_PER_BLOCK: u32 = 10; +const MAX_CUSTOM_CHALLENGES_PER_BLOCK: u32 = 10; +const TOTAL_MAX_CHALLENGES_PER_BLOCK: u32 = + RANDOM_CHALLENGES_PER_BLOCK + MAX_CUSTOM_CHALLENGES_PER_BLOCK; + +parameter_types! { + pub const RandomChallengesPerBlock: u32 = RANDOM_CHALLENGES_PER_BLOCK; + pub const MaxCustomChallengesPerBlock: u32 = MAX_CUSTOM_CHALLENGES_PER_BLOCK; + pub const TotalMaxChallengesPerBlock: u32 = TOTAL_MAX_CHALLENGES_PER_BLOCK; + pub const TargetTicksStorageOfSubmitters: u32 = 3; + pub const ChallengeHistoryLength: BlockNumber = 100; + pub const ChallengesQueueLength: u32 = 100; + pub const ChallengesFee: Balance = 1 * HAVE; + pub const ChallengeTicksTolerance: u32 = 50; +} + +impl pallet_proofs_dealer::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_proofs_dealer::weights::SubstrateWeight; + type ProvidersPallet = Providers; + type NativeBalance = Balances; + type MerkleTrieHash = Hash; + type MerkleTrieHashing = BlakeTwo256; + type ForestVerifier = ForestVerifier; + type KeyVerifier = FileKeyVerifier< + StorageProofsMerkleTrieLayout, + { shp_constants::H_LENGTH }, + { shp_constants::FILE_CHUNK_SIZE }, + { shp_constants::FILE_SIZE_TO_CHALLENGES }, + >; + type StakeToBlockNumber = SaturatingBalanceToBlockNumber; + type RandomChallengesPerBlock = RandomChallengesPerBlock; + type MaxCustomChallengesPerBlock = MaxCustomChallengesPerBlock; + type MaxSubmittersPerTick = MaxSubmittersPerTick; + type TargetTicksStorageOfSubmitters = TargetTicksStorageOfSubmitters; + type ChallengeHistoryLength = ChallengeHistoryLength; + type ChallengesQueueLength = ChallengesQueueLength; + type CheckpointChallengePeriod = runtime_config::CheckpointChallengePeriod; + type ChallengesFee = ChallengesFee; + type Treasury = TreasuryAccount; + // TODO: Once the client logic to keep track of CR randomness deadlines and execute their submissions is implemented + // AND after the chain has been live for enough time to have enough providers to avoid the commit-reveal randomness being + // gameable, the randomness provider should be CrRandomness + type RandomnessProvider = pallet_randomness::ParentBlockRandomness; + type StakeToChallengePeriod = runtime_config::StakeToChallengePeriod; + type MinChallengePeriod = runtime_config::MinChallengePeriod; + type ChallengeTicksTolerance = ChallengeTicksTolerance; + type BlockFullnessPeriod = ChallengeTicksTolerance; // We purposely set this to `ChallengeTicksTolerance` so that spamming of the chain is evaluated for the same blocks as the tolerance BSPs are given. + type BlockFullnessHeadroom = BlockFullnessHeadroom; + type MinNotFullBlocksRatio = MinNotFullBlocksRatio; + type MaxSlashableProvidersPerTick = MaxSlashableProvidersPerTick; +} + +// Converter from the Balance type to the BlockNumber type for math. +// It performs a saturated conversion, so that the result is always a valid BlockNumber. +pub struct SaturatingBalanceToBlockNumber; +impl Convert> for SaturatingBalanceToBlockNumber { + fn convert(block_number: Balance) -> BlockNumberFor { + block_number.saturated_into() + } +} + +pub struct MaxSubmittersPerTick; +impl Get for MaxSubmittersPerTick { + fn get() -> u32 { + let block_weights = ::BlockWeights::get(); + + // Not being able to get the `max_total` weight for the Normal dispatch class is considered + // a critical bug. So we set it to be zero, essentially allowing zero submitters per tick. + // This value can be read from the constants of a node, but with the current configuration, this is: + // + // max_total: { + // ref_time: 1,500,000,000,000 + // proof_size: 3,932,160 + // } + let max_weight_for_class = block_weights + .get(DispatchClass::Normal) + .max_total + .unwrap_or(Zero::zero()); + + // Get the minimum weight a `submit_proof` extrinsic can have. + // This would be the case where the proof is just made up of a single file key proof, that is a + // response to all the random challenges. And there are no checkpoint challenges. + // With the current benchmarking, this is: + // + // TODO: UPDATE THIS WITH THE FINAL BENCHMARKING + // min_weight_for_submit_proof: { + // ref_time: 2,980,252,675 + // proof_size: 16,056 + // } + let min_weight_for_submit_proof = + as pallet_proofs_dealer::weights::WeightInfo>::submit_proof_no_checkpoint_challenges_key_proofs(1); + + // Calculate the maximum number of submit proofs that is possible to have in a block/tick. + // With the current values, this would be: + // + // TODO: UPDATE THIS WITH THE FINAL BENCHMARKING + // 244 proof submissions per block (limited by `proof_size`) + let max_proof_submissions_per_tick = max_weight_for_class + .checked_div_per_component(&min_weight_for_submit_proof) + .unwrap_or(0); + + // Saturating u64 to u32 should be enough. + max_proof_submissions_per_tick.saturated_into() + } +} + +pub struct BlockFullnessHeadroom; +impl Get for BlockFullnessHeadroom { + fn get() -> Weight { + // The block headroom is set to be the maximum benchmarked weight that a `submit_proof` extrinsic can have. + // That is, when the proof includes two file key proofs for every single random challenge, and for the maximum + // number of checkpoint challenges as well. + as pallet_proofs_dealer::weights::WeightInfo>::submit_proof_with_checkpoint_challenges_key_proofs(TOTAL_MAX_CHALLENGES_PER_BLOCK * 2) + } +} + +pub struct MinNotFullBlocksRatio; +impl Get for MinNotFullBlocksRatio { + fn get() -> Perbill { + // This means that we tolerate at most 50% of misbehaving collators. + Perbill::from_percent(50) + } +} + +pub struct MaxSlashableProvidersPerTick; +impl Get for MaxSlashableProvidersPerTick { + fn get() -> u32 { + // With the maximum number of slashable providers per tick being `N`, the absolute maximum + // weight that the `on_poll` hook can have, with the current benchmarking, is: + // + // TODO: UPDATE THIS WITH THE FINAL BENCHMARKING + // new_challenges_round_weight: { + // ref_time: 576,000,000 + N * 551,601,146 + // proof_size: 8,523 + N * 3,158 + // } + // new_checkpoint_challenge_round_max_weight: { + // ref_time: 587,205,208 + ChallengesQueueLength * 225,083 = 610,554,678 + // proof_size: 4,787 + // } + // check_spamming_condition_weight: { + // ref_time: 313,000,000 + // proof_size: 6,012 + // } + // + // For `N` = 1000, this would be: + // max_on_poll_weight: { + // ref_time: 313,000,000 + 610,554,678 + 576,000,000 + N * 551,601,146 ≈ 553,100,700,678 + // proof_size: 6,012 + 4,787 + 8,523 + N * 3,158 ≈ 3,177,322 + // } + // + // Consider that the maximum block weight is: + // maxBlock: { + // ref_time: 2,000,000,000,000 + // proof_size: 5,242,880 + // } + // + // This `on_poll` hook would consume roughly 1/4 of the block `ref_time` and 3/5 of the block `proof_size`. + // This is naturally a lot. But it would be a very unlikely scenario. + // + // This would be the case where all `N` Providers have synchronised their challenge periods + // and have the same deadline, plus, all of them missed their proof submissions. + // The normal scenario would be that NONE (or just a small number) of the Providers have + // missed their proof submissions. + let max_slashable_providers_per_tick = 1000; + max_slashable_providers_per_tick + } +} +/****** ****** ****** ******/ + +/****** File System pallet ******/ +type ThresholdType = u32; +pub type ReplicationTargetType = u32; + +parameter_types! { + pub const BaseStorageRequestCreationDeposit: Balance = 1 * HAVE; + pub const FileDeletionRequestCreationDeposit: Balance = 1 * HAVE; + pub const FileSystemStorageRequestCreationHoldReason: RuntimeHoldReason = RuntimeHoldReason::FileSystem(pallet_file_system::HoldReason::StorageRequestCreationHold); + pub const FileSystemFileDeletionRequestHoldReason: RuntimeHoldReason = RuntimeHoldReason::FileSystem(pallet_file_system::HoldReason::FileDeletionRequestHold); +} + +impl pallet_file_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_file_system::weights::SubstrateWeight; + type Providers = Providers; + type ProofDealer = ProofsDealer; + type PaymentStreams = PaymentStreams; + // TODO: Replace the mocked CR randomness with the actual one when it's ready + // type CrRandomness = CrRandomness; + type CrRandomness = MockCrRandomness; + type UpdateStoragePrice = MostlyStablePriceIndexUpdater; + type UserSolvency = PaymentStreams; + type Fingerprint = Hash; + type ReplicationTargetType = ReplicationTargetType; + type ThresholdType = ThresholdType; + type ThresholdTypeToTickNumber = ThresholdTypeToBlockNumberConverter; + type HashToThresholdType = HashToThresholdTypeConverter; + type MerkleHashToRandomnessOutput = MerkleHashToRandomnessOutputConverter; + type ChunkIdToMerkleHash = ChunkIdToMerkleHashConverter; + type Currency = Balances; + type RuntimeHoldReason = RuntimeHoldReason; + type Nfts = Nfts; + type CollectionInspector = BucketNfts; + type BspStopStoringFilePenalty = runtime_config::BspStopStoringFilePenalty; + type TreasuryAccount = TreasuryAccount; + type MaxBatchConfirmStorageRequests = ConstU32<10>; + type MaxFilePathSize = ConstU32<512u32>; + type MaxPeerIdSize = ConstU32<100>; + type MaxNumberOfPeerIds = ConstU32<5>; + type MaxDataServerMultiAddresses = ConstU32<10>; + type MaxExpiredItemsInTick = ConstU32<100>; + type StorageRequestTtl = runtime_config::StorageRequestTtl; + type MoveBucketRequestTtl = ConstU32<40u32>; + type MaxUserPendingDeletionRequests = ConstU32<10u32>; + type MaxUserPendingMoveBucketRequests = ConstU32<10u32>; + type MinWaitForStopStoring = runtime_config::MinWaitForStopStoring; + type BaseStorageRequestCreationDeposit = BaseStorageRequestCreationDeposit; + type UpfrontTicksToPay = runtime_config::UpfrontTicksToPay; + type WeightToFee = WeightToFee; + type ReplicationTargetToBalance = ReplicationTargetToBalance; + type TickNumberToBalance = TickNumberToBalance; + type StorageDataUnitToBalance = StorageDataUnitToBalance; + type FileDeletionRequestDeposit = FileDeletionRequestCreationDeposit; + type BasicReplicationTarget = runtime_config::BasicReplicationTarget; + type StandardReplicationTarget = runtime_config::StandardReplicationTarget; + type HighSecurityReplicationTarget = runtime_config::HighSecurityReplicationTarget; + type SuperHighSecurityReplicationTarget = runtime_config::SuperHighSecurityReplicationTarget; + type UltraHighSecurityReplicationTarget = runtime_config::UltraHighSecurityReplicationTarget; + type MaxReplicationTarget = runtime_config::MaxReplicationTarget; + type TickRangeToMaximumThreshold = runtime_config::TickRangeToMaximumThreshold; +} + +impl MostlyStablePriceIndexUpdaterConfig for Runtime { + type Price = Balance; + type StorageDataUnit = StorageDataUnit; + type LowerThreshold = runtime_config::SystemUtilisationLowerThresholdPercentage; + type UpperThreshold = runtime_config::SystemUtilisationUpperThresholdPercentage; + type MostlyStablePrice = runtime_config::MostlyStablePrice; + type MaxPrice = runtime_config::MaxPrice; + type MinPrice = runtime_config::MinPrice; + type UpperExponentFactor = runtime_config::UpperExponentFactor; + type LowerExponentFactor = runtime_config::LowerExponentFactor; +} + +// Converter from the ThresholdType to the BlockNumber type and vice versa. +// It performs a saturated conversion, so that the result is always a valid BlockNumber. +pub struct ThresholdTypeToBlockNumberConverter; +impl Convert> for ThresholdTypeToBlockNumberConverter { + fn convert(threshold: ThresholdType) -> BlockNumberFor { + threshold.saturated_into() + } +} + +impl ConvertBack> for ThresholdTypeToBlockNumberConverter { + fn convert_back(block_number: BlockNumberFor) -> ThresholdType { + block_number.into() + } +} + +/// Converter from the [`Hash`] type to the [`ThresholdType`]. +pub struct HashToThresholdTypeConverter; +impl Convert<::Hash, ThresholdType> + for HashToThresholdTypeConverter +{ + fn convert(hash: ::Hash) -> ThresholdType { + // Get the hash as bytes + let hash_bytes = hash.as_ref(); + + // Get the 4 least significant bytes of the hash and interpret them as an u32 + let truncated_hash_bytes: [u8; 4] = + hash_bytes[28..].try_into().expect("Hash is 32 bytes; qed"); + + ThresholdType::from_be_bytes(truncated_hash_bytes) + } +} + +// Converter from the MerkleHash (H256) type to the RandomnessOutput (H256) type. +pub struct MerkleHashToRandomnessOutputConverter; +impl Convert for MerkleHashToRandomnessOutputConverter { + fn convert(hash: H256) -> H256 { + hash + } +} + +// Converter from the ChunkId type to the MerkleHash (H256) type. +pub struct ChunkIdToMerkleHashConverter; + +impl Convert for ChunkIdToMerkleHashConverter { + fn convert(chunk_id: ChunkId) -> H256 { + let chunk_id_biguint = BigUint::from(chunk_id.as_u64()); + let mut bytes = chunk_id_biguint.to_bytes_be(); + + // Ensure the byte slice is exactly 32 bytes long by padding with leading zeros + if bytes.len() < 32 { + let mut padded_bytes = vec![0u8; 32 - bytes.len()]; + padded_bytes.extend(bytes); + bytes = padded_bytes; + } + + H256::from_slice(&bytes) + } +} + +// Converter from the ReplicationTargetType type to the Balance type. +pub struct ReplicationTargetToBalance; +impl Convert for ReplicationTargetToBalance { + fn convert(replication_target: ReplicationTargetType) -> Balance { + replication_target.into() + } +} + +// Converter from the TickNumber type to the Balance type. +pub type TickNumber = BlockNumber; +pub struct TickNumberToBalance; +impl Convert for TickNumberToBalance { + fn convert(tick_number: TickNumber) -> Balance { + tick_number.into() + } +} + +// Converter from the StorageDataUnit type to the Balance type. +pub struct StorageDataUnitToBalance; +impl Convert for StorageDataUnitToBalance { + fn convert(storage_data_unit: StorageDataUnit) -> Balance { + storage_data_unit.into() + } +} +/****** ****** ****** ******/ + +/****** Bucket NFTs pallet ******/ +impl pallet_bucket_nfts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bucket_nfts::weights::SubstrateWeight; + type Buckets = Providers; +} +/****** ****** ****** ******/ + +/****** Commit-Reveal Randomness pallet ******/ +pub struct MockCrRandomness; +impl shp_traits::CommitRevealRandomnessInterface for MockCrRandomness { + type ProviderId = Hash; + + fn initialise_randomness_cycle( + _who: &Self::ProviderId, + ) -> frame_support::dispatch::DispatchResult { + Ok(()) + } + + fn stop_randomness_cycle(_who: &Self::ProviderId) -> frame_support::dispatch::DispatchResult { + Ok(()) + } +} +/****** ****** ****** ******/ diff --git a/operator/runtime/mainnet/src/lib.rs b/operator/runtime/mainnet/src/lib.rs index 7f78cb76..7150ea3b 100644 --- a/operator/runtime/mainnet/src/lib.rs +++ b/operator/runtime/mainnet/src/lib.rs @@ -19,7 +19,10 @@ use frame_support::{ pallet_prelude::{TransactionValidity, TransactionValidityError}, parameter_types, traits::{KeyOwnerProofSystem, OnFinalize}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, + weights::{ + constants::ExtrinsicBaseWeight, constants::WEIGHT_REF_TIME_PER_SECOND, Weight, + WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, + }, }; pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; @@ -28,6 +31,7 @@ use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, Runner} use pallet_external_validators::traits::EraIndex; use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId}; pub use pallet_timestamp::Call as TimestampCall; +use smallvec::smallvec; use snowbridge_core::AgentId; use snowbridge_merkle_tree::MerkleProof; use sp_api::impl_runtime_apis; @@ -230,8 +234,36 @@ where } } +/// Handles converting a weight scalar to a fee value, based on the scale and granularity of the +/// node's balance type. +/// +/// This should typically create a mapping between the following ranges: +/// - `[0, MAXIMUM_BLOCK_WEIGHT]` +/// - `[Balance::min, Balance::max]` +/// +/// Yet, it can be used for any other sort of change to weight-fee. Some examples being: +/// - Setting it to `0` will essentially disable the weight fee. +/// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. +pub struct WeightToFee; +impl WeightToFeePolynomial for WeightToFee { + type Balance = Balance; + fn polynomial() -> WeightToFeeCoefficients { + // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1 MILLIHAVE: + // in our template, we map to 1/10 of that, or 1/10 MILLIHAVE + let p = currency::MILLIHAVE / 10; + let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); + smallvec![WeightToFeeCoefficient { + degree: 1, + negative: false, + coeff_frac: Perbill::from_rational(p % q, q), + coeff_integer: p / q, + }] + } +} + // Create the runtime by composing the FRAME pallets that were previously configured. #[frame_support::runtime] +#[cfg(not(feature = "storage-hub"))] mod runtime { #[runtime::runtime] #[runtime::derive( @@ -383,6 +415,179 @@ mod runtime { // ╚═══════════════════ DataHaven-specific Pallets ══════════════════╝ } +#[frame_support::runtime] +#[cfg(feature = "storage-hub")] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct Runtime; + + // ╔══════════════════ System and Consensus Pallets ═════════════════╗ + #[runtime::pallet_index(0)] + pub type System = frame_system; + + // Babe must be before session. + #[runtime::pallet_index(1)] + pub type Babe = pallet_babe; + + #[runtime::pallet_index(2)] + pub type Timestamp = pallet_timestamp; + + #[runtime::pallet_index(3)] + pub type Balances = pallet_balances; + + // Consensus support. + // Authorship must be before session in order to note author in the correct session and era. + #[runtime::pallet_index(4)] + pub type Authorship = pallet_authorship; + + #[runtime::pallet_index(5)] + pub type Offences = pallet_offences; + + #[runtime::pallet_index(6)] + pub type Historical = pallet_session::historical; + + // External Validators must be before Session. + #[runtime::pallet_index(7)] + pub type ExternalValidators = pallet_external_validators; + + #[runtime::pallet_index(8)] + pub type Session = pallet_session; + + #[runtime::pallet_index(9)] + pub type ImOnline = pallet_im_online; + + #[runtime::pallet_index(10)] + pub type Grandpa = pallet_grandpa; + + #[runtime::pallet_index(11)] + pub type TransactionPayment = pallet_transaction_payment; + + #[runtime::pallet_index(12)] + pub type Beefy = pallet_beefy; + + #[runtime::pallet_index(13)] + pub type Mmr = pallet_mmr; + + #[runtime::pallet_index(14)] + pub type BeefyMmrLeaf = pallet_beefy_mmr; + // ╚═════════════════ System and Consensus Pallets ══════════════════╝ + + // ╔═════════════════ Polkadot SDK Utility Pallets ══════════════════╗ + #[runtime::pallet_index(30)] + pub type Utility = pallet_utility; + + #[runtime::pallet_index(31)] + pub type Scheduler = pallet_scheduler; + + #[runtime::pallet_index(32)] + pub type Preimage = pallet_preimage; + + #[runtime::pallet_index(33)] + pub type Identity = pallet_identity; + + #[runtime::pallet_index(34)] + pub type Multisig = pallet_multisig; + + #[runtime::pallet_index(35)] + pub type Parameters = pallet_parameters; + + #[runtime::pallet_index(36)] + pub type Sudo = pallet_sudo; + + #[runtime::pallet_index(37)] + pub type Treasury = pallet_treasury; + + #[runtime::pallet_index(38)] + pub type Proxy = pallet_proxy; + // ╚═════════════════ Polkadot SDK Utility Pallets ══════════════════╝ + + // ╔════════════════════ Frontier (EVM) Pallets ═════════════════════╗ + #[runtime::pallet_index(50)] + pub type Ethereum = pallet_ethereum; + + #[runtime::pallet_index(51)] + pub type Evm = pallet_evm; + + #[runtime::pallet_index(52)] + pub type EvmChainId = pallet_evm_chain_id; + // ╚════════════════════ Frontier (EVM) Pallets ═════════════════════╝ + + // ╔══════════════════════ Snowbridge Pallets ═══════════════════════╗ + #[runtime::pallet_index(60)] + pub type EthereumBeaconClient = snowbridge_pallet_ethereum_client; + + #[runtime::pallet_index(61)] + pub type EthereumInboundQueueV2 = snowbridge_pallet_inbound_queue_v2; + + #[runtime::pallet_index(62)] + pub type EthereumOutboundQueueV2 = snowbridge_pallet_outbound_queue_v2; + + #[runtime::pallet_index(63)] + pub type SnowbridgeSystem = snowbridge_pallet_system; + + #[runtime::pallet_index(64)] + pub type SnowbridgeSystemV2 = snowbridge_pallet_system_v2; + // ╚══════════════════════ Snowbridge Pallets ═══════════════════════╝ + + // ╔════════════ Polkadot SDK Utility Pallets - Block 2 ═════════════╗ + // The Message Queue pallet has to be after the Snowbridge Outbound + // Queue V2 pallet since the former processes messages in its + // `on_initialize` hook and the latter clears up messages in + // its `on_initialize` hook, so otherwise messages will be cleared + // up before they are processed. + #[runtime::pallet_index(70)] + pub type MessageQueue = pallet_message_queue; + // ╚════════════ Polkadot SDK Utility Pallets - Block 2 ═════════════╝ + + // ╔══════════════════════ StorageHub Pallets ═══════════════════════╗ + // Start with index 80 + #[runtime::pallet_index(80)] + pub type Providers = pallet_storage_providers; + + #[runtime::pallet_index(81)] + pub type FileSystem = pallet_file_system; + + #[runtime::pallet_index(82)] + pub type ProofsDealer = pallet_proofs_dealer; + + #[runtime::pallet_index(83)] + pub type Randomness = pallet_randomness; + + #[runtime::pallet_index(84)] + pub type PaymentStreams = pallet_payment_streams; + + #[runtime::pallet_index(85)] + pub type BucketNfts = pallet_bucket_nfts; + + #[runtime::pallet_index(90)] + pub type Nfts = pallet_nfts; + // ╚══════════════════════ StorageHub Pallets ═══════════════════════╝ + + // ╔═══════════════════ DataHaven-specific Pallets ══════════════════╗ + // Start with index 100 + #[runtime::pallet_index(100)] + pub type OutboundCommitmentStore = pallet_outbound_commitment_store; + + #[runtime::pallet_index(101)] + pub type ExternalValidatorsRewards = pallet_external_validators_rewards; + + #[runtime::pallet_index(102)] + pub type DataHavenNativeTransfer = pallet_datahaven_native_transfer; + + // ╚═══════════════════ DataHaven-specific Pallets ══════════════════╝ +} + /// MMR helper types. mod mmr { use super::Runtime; diff --git a/operator/runtime/stagenet/src/configs/runtime_params.rs b/operator/runtime/stagenet/src/configs/runtime_params.rs index 811322af..93808918 100644 --- a/operator/runtime/stagenet/src/configs/runtime_params.rs +++ b/operator/runtime/stagenet/src/configs/runtime_params.rs @@ -6,11 +6,14 @@ use sp_runtime::{BoundedVec, Perbill}; use sp_std::vec; #[cfg(feature = "storage-hub")] -use crate::currency::{Balance, GIGAWEI, HAVE}; +use crate::currency::{GIGAWEI, HAVE}; #[cfg(feature = "storage-hub")] use crate::configs::storagehub::{ChallengeTicksTolerance, ReplicationTargetType, SpMinDeposit}; +#[cfg(feature = "storage-hub")] +use datahaven_runtime_common::{Balance, BlockNumber}; + #[cfg(not(feature = "storage-hub"))] #[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] pub mod dynamic_params { @@ -99,14 +102,20 @@ pub mod dynamic_params { "c505dfb2df107d106d08bd0f1a0acd10052ca9aa078629a4ccfd0c90c6e69b65" )); + // Proportion of fees allocated to the Treasury (remainder are burned). + // e.g. 20% to the treasury, 80% burned. + #[codec(index = 4)] + #[allow(non_upper_case_globals)] + pub static FeesTreasuryProportion: Perbill = Perbill::from_percent(20); + // ╔══════════════════════ StorageHub Pallets ═══════════════════════╗ - #[codec(index = 4)] + #[codec(index = 5)] #[allow(non_upper_case_globals)] /// 20 HAVEs pub static SlashAmountPerMaxFileSize: Balance = 20 * HAVE; - #[codec(index = 5)] + #[codec(index = 6)] #[allow(non_upper_case_globals)] /// 10k HAVEs * [`MinChallengePeriod`] = 10k HAVEs * 30 = 300k HAVEs /// @@ -114,7 +123,7 @@ pub mod dynamic_params { pub static StakeToChallengePeriod: Balance = 10_000 * HAVE * Into::::into(MinChallengePeriod::get()); - #[codec(index = 6)] + #[codec(index = 7)] #[allow(non_upper_case_globals)] /// The [`CheckpointChallengePeriod`] is set to be equal to the longest possible challenge period /// (i.e. the [`StakeToChallengePeriod`] divided by the [`SpMinDeposit`]). @@ -127,22 +136,22 @@ pub mod dynamic_params { "StakeToChallengePeriod / SpMinDeposit should be a number of ticks that can fit in BlockNumber numerical type", ); - #[codec(index = 7)] + #[codec(index = 8)] #[allow(non_upper_case_globals)] /// 30 ticks, or 3 minutes with 6 seconds per tick. pub static MinChallengePeriod: BlockNumber = 30; - #[codec(index = 8)] + #[codec(index = 9)] #[allow(non_upper_case_globals)] /// Price decreases when system utilisation is below 30%. pub static SystemUtilisationLowerThresholdPercentage: Perbill = Perbill::from_percent(30); - #[codec(index = 9)] + #[codec(index = 10)] #[allow(non_upper_case_globals)] /// Price increases when system utilisation is above 95%. pub static SystemUtilisationUpperThresholdPercentage: Perbill = Perbill::from_percent(95); - #[codec(index = 10)] + #[codec(index = 11)] #[allow(non_upper_case_globals)] /// 50 [`GIGAWEI`]s is the price per GB of data, per tick. /// @@ -150,17 +159,17 @@ pub mod dynamic_params { /// 50e-9 [`HAVE`]s * 10 ticks/min * 60 min/h * 24 h/day * 30 days/month = 21.6e-3 [`HAVE`]s pub static MostlyStablePrice: Balance = 50 * GIGAWEI; - #[codec(index = 11)] + #[codec(index = 12)] #[allow(non_upper_case_globals)] /// [`MostlyStablePrice`] * 10 = 500 [`GIGAWEI`]s pub static MaxPrice: Balance = MostlyStablePrice::get() * 10; - #[codec(index = 12)] + #[codec(index = 13)] #[allow(non_upper_case_globals)] /// [`MostlyStablePrice`] / 5 = 10 [`GIGAWEI`]s pub static MinPrice: Balance = MostlyStablePrice::get() / 5; - #[codec(index = 13)] + #[codec(index = 14)] #[allow(non_upper_case_globals)] /// u = [`UpperExponentFactor`] /// system_utilisation = 1 @@ -171,7 +180,7 @@ pub mod dynamic_params { /// u = (500 - 50) / (e ^ (1 - 0.95) - 1) ≈ 8777 pub static UpperExponentFactor: u32 = 8777; - #[codec(index = 14)] + #[codec(index = 15)] #[allow(non_upper_case_globals)] /// l = [`LowerExponentFactor`] /// system_utilisation = 0 @@ -182,35 +191,35 @@ pub mod dynamic_params { /// l = (50 - 10) / (e ^ (0.3 - 0) - 1) ≈ 114 pub static LowerExponentFactor: u32 = 114; - #[codec(index = 15)] + #[codec(index = 16)] #[allow(non_upper_case_globals)] /// 0-size bucket fixed rate payment stream representing the price for 1 GB of data. /// /// Base rate for a new fixed payment stream established between an MSP and a user. pub static ZeroSizeBucketFixedRate: Balance = 50 * GIGAWEI; - #[codec(index = 16)] + #[codec(index = 17)] #[allow(non_upper_case_globals)] /// Ideal utilisation rate of the system pub static IdealUtilisationRate: Perbill = Perbill::from_percent(85); - #[codec(index = 17)] + #[codec(index = 18)] #[allow(non_upper_case_globals)] /// Decay rate of the power of two function that determines the percentage of funds that go to /// the treasury for utilisation rates greater than the ideal. pub static DecayRate: Perbill = Perbill::from_percent(5); - #[codec(index = 18)] + #[codec(index = 19)] #[allow(non_upper_case_globals)] /// The minimum treasury cut that can be taken from the amount charged from a payment stream. pub static MinimumTreasuryCut: Perbill = Perbill::from_percent(1); - #[codec(index = 19)] + #[codec(index = 20)] #[allow(non_upper_case_globals)] /// The maximum treasury cut that can be taken from the amount charged from a payment stream. pub static MaximumTreasuryCut: Perbill = Perbill::from_percent(5); - #[codec(index = 20)] + #[codec(index = 21)] #[allow(non_upper_case_globals)] /// The penalty a BSP must pay when they forcefully stop storing a file. /// We set this to be half of the `SlashAmountPerMaxFileSize` with the rationale that @@ -221,7 +230,7 @@ pub mod dynamic_params { /// Time-to-live for a provider to top up their deposit to cover a capacity deficit. /// Set to 14_400 relay blocks = 1 day with 6 second timeslots. - #[codec(index = 21)] + #[codec(index = 22)] #[allow(non_upper_case_globals)] pub static ProviderTopUpTtl: BlockNumber = 14_400; @@ -249,7 +258,7 @@ pub mod dynamic_params { /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its /// volunteered BSPs is ~1%. - #[codec(index = 22)] + #[codec(index = 23)] #[allow(non_upper_case_globals)] pub static BasicReplicationTarget: ReplicationTargetType = 7; @@ -258,7 +267,7 @@ pub mod dynamic_params { /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its /// volunteered BSPs is ~0.1%. - #[codec(index = 23)] + #[codec(index = 24)] #[allow(non_upper_case_globals)] pub static StandardReplicationTarget: ReplicationTargetType = 12; @@ -267,7 +276,7 @@ pub mod dynamic_params { /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its /// volunteered BSPs is ~0.01%. - #[codec(index = 24)] + #[codec(index = 25)] #[allow(non_upper_case_globals)] pub static HighSecurityReplicationTarget: ReplicationTargetType = 17; @@ -276,7 +285,7 @@ pub mod dynamic_params { /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its /// volunteered BSPs is ~0.001%. - #[codec(index = 25)] + #[codec(index = 26)] #[allow(non_upper_case_globals)] pub static SuperHighSecurityReplicationTarget: ReplicationTargetType = 22; @@ -285,7 +294,7 @@ pub mod dynamic_params { /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its /// volunteered BSPs is ~0.0001%. - #[codec(index = 26)] + #[codec(index = 27)] #[allow(non_upper_case_globals)] pub static UltraHighSecurityReplicationTarget: ReplicationTargetType = 26; @@ -293,7 +302,7 @@ pub mod dynamic_params { /// /// This is a safety measure to prevent users from issuing storage requests that are too large and would /// require a large number of BSPs to store the file. - #[codec(index = 27)] + #[codec(index = 28)] #[allow(non_upper_case_globals)] pub static MaxReplicationTarget: ReplicationTargetType = UltraHighSecurityReplicationTarget::get() @@ -306,7 +315,7 @@ pub mod dynamic_params { /// This is big enough so volunteering for a storage request is not open to everyone inmediatly, preventing /// a select few BSPs from taking all the requests, while small enough so that storage requests don't take /// too long to be filled. - #[codec(index = 28)] + #[codec(index = 29)] #[allow(non_upper_case_globals)] pub static TickRangeToMaximumThreshold: BlockNumber = 3600; // 6 hours with a 6 second block time @@ -314,7 +323,7 @@ pub mod dynamic_params { /// /// It's a function of the TickRangeToMaximumThreshold since it does not make sense for a storage request to /// expire before arriving at its maximum threshold for volunteering. - #[codec(index = 29)] + #[codec(index = 30)] #[allow(non_upper_case_globals)] pub static StorageRequestTtl: BlockNumber = TickRangeToMaximumThreshold::get() .saturating_mul(110) @@ -325,18 +334,18 @@ pub mod dynamic_params { /// /// It's a function of the checkpoint challenge period since this makes it so BSPs can't avoid checkpoint /// challenges by stopping storing a file key right before the challenge period ends in case they lost it. - #[codec(index = 30)] + #[codec(index = 31)] #[allow(non_upper_case_globals)] pub static MinWaitForStopStoring: BlockNumber = CheckpointChallengePeriod::get() .saturating_mul(110) .saturating_div(100); - #[codec(index = 31)] + #[codec(index = 32)] #[allow(non_upper_case_globals)] /// 20 ticks, or 2 minutes with 6 seconds per tick. pub static MinSeedPeriod: BlockNumber = 20; - #[codec(index = 32)] + #[codec(index = 33)] #[allow(non_upper_case_globals)] /// 10k HAVEs * [`MinSeedPeriod`] = 10k HAVEs * 20 = 200k HAVEs /// @@ -344,7 +353,7 @@ pub mod dynamic_params { pub static StakeToSeedPeriod: Balance = 10_000 * HAVE * Into::::into(MinSeedPeriod::get()); - #[codec(index = 33)] + #[codec(index = 34)] #[allow(non_upper_case_globals)] /// The amount of ticks to charge a user upfront when it tries to issue a new storage request. /// This is done as a deterrent to avoid users spamming the network with huge files but never diff --git a/operator/runtime/stagenet/src/lib.rs b/operator/runtime/stagenet/src/lib.rs index 0335770e..86c1da7e 100644 --- a/operator/runtime/stagenet/src/lib.rs +++ b/operator/runtime/stagenet/src/lib.rs @@ -527,6 +527,9 @@ mod runtime { #[runtime::pallet_index(36)] pub type Sudo = pallet_sudo; + #[runtime::pallet_index(37)] + pub type Treasury = pallet_treasury; + #[runtime::pallet_index(38)] pub type Proxy = pallet_proxy; // ╚═════════════════ Polkadot SDK Utility Pallets ══════════════════╝ diff --git a/operator/runtime/testnet/Cargo.toml b/operator/runtime/testnet/Cargo.toml index 29d9281e..0f5aca79 100644 --- a/operator/runtime/testnet/Cargo.toml +++ b/operator/runtime/testnet/Cargo.toml @@ -32,6 +32,7 @@ frame-try-runtime = { workspace = true, optional = true } hex = { workspace = true } hex-literal = { workspace = true } log = { workspace = true } +num-bigint = { workspace = true, optional = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true, features = ["insecure_zero_ed"] } @@ -69,6 +70,7 @@ scale-info = { workspace = true, features = ["derive", "serde"] } serde_json = { workspace = true, default-features = false, features = [ "alloc", ] } +smallvec = { workspace = true } snowbridge-beacon-primitives = { workspace = true } snowbridge-core = { workspace = true } snowbridge-inbound-queue-primitives = { workspace = true } @@ -104,6 +106,28 @@ xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } +# StorageHub +pallet-bucket-nfts = { workspace = true, optional = true } +pallet-nfts = { workspace = true, optional = true } +pallet-cr-randomness = { workspace = true, optional = true } +pallet-file-system = { workspace = true, optional = true } +pallet-file-system-runtime-api = { workspace = true, optional = true } +pallet-payment-streams = { workspace = true, optional = true } +pallet-payment-streams-runtime-api = { workspace = true, optional = true } +pallet-proofs-dealer = { workspace = true, optional = true } +pallet-proofs-dealer-runtime-api = { workspace = true, optional = true } +pallet-randomness = { workspace = true, optional = true } +pallet-storage-providers = { workspace = true, optional = true } +pallet-storage-providers-runtime-api = { workspace = true, optional = true } +shp-constants = { workspace = true, optional = true } +shp-file-metadata = { workspace = true, optional = true } +shp-traits = { workspace = true, optional = true } +shp-treasury-funding = { workspace = true, optional = true } +shp-forest-verifier = { workspace = true, optional = true } +shp-file-key-verifier = { workspace = true, optional = true } +shp-data-price-updater = { workspace = true, optional = true } +sp-trie = { workspace = true, optional = true } + [build-dependencies] substrate-wasm-builder = { workspace = true, optional = true, default-features = true } @@ -120,6 +144,29 @@ snowbridge-pallet-system = { workspace = true } snowbridge-pallet-system-v2 = { workspace = true } [features] +storage-hub = [ + "dep:num-bigint", + "dep:pallet-bucket-nfts", + "dep:pallet-nfts", + "dep:pallet-cr-randomness", + "dep:pallet-file-system", + "dep:pallet-file-system-runtime-api", + "dep:pallet-payment-streams", + "dep:pallet-payment-streams-runtime-api", + "dep:pallet-proofs-dealer", + "dep:pallet-proofs-dealer-runtime-api", + "dep:pallet-randomness", + "dep:pallet-storage-providers", + "dep:pallet-storage-providers-runtime-api", + "dep:shp-constants", + "dep:shp-file-metadata", + "dep:shp-traits", + "dep:shp-treasury-funding", + "dep:shp-forest-verifier", + "dep:shp-file-key-verifier", + "dep:shp-data-price-updater", + "dep:sp-trie" + ] default = ["std"] std = [ "codec/std", @@ -199,6 +246,33 @@ std = [ "pallet-external-validators-rewards/std", "pallet-external-validators-rewards-runtime-api/std", "pallet-datahaven-native-transfer/std", + + # StorageHub + "pallet-authorship/std", + "pallet-balances/std", + "pallet-bucket-nfts/std", + "pallet-nfts/std", + "pallet-cr-randomness/std", + "pallet-file-system/std", + "pallet-file-system-runtime-api/std", + "pallet-payment-streams/std", + "pallet-payment-streams-runtime-api/std", + "pallet-proofs-dealer/std", + "pallet-proofs-dealer-runtime-api/std", + "pallet-randomness/std", + "pallet-session/std", + "pallet-storage-providers/std", + "pallet-storage-providers-runtime-api/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "shp-constants/std", + "shp-file-metadata/std", + "shp-forest-verifier/std", + "shp-traits/std", + "shp-treasury-funding/std", + "shp-file-key-verifier/std", ] runtime-benchmarks = [ diff --git a/operator/runtime/testnet/src/configs/mod.rs b/operator/runtime/testnet/src/configs/mod.rs index 0bc3b45c..cc52bf98 100644 --- a/operator/runtime/testnet/src/configs/mod.rs +++ b/operator/runtime/testnet/src/configs/mod.rs @@ -22,6 +22,8 @@ // OTHER DEALINGS IN THE SOFTWARE. // // For more information, please refer to +#[cfg(feature = "storage-hub")] +mod storagehub; pub mod runtime_params; diff --git a/operator/runtime/testnet/src/configs/runtime_params.rs b/operator/runtime/testnet/src/configs/runtime_params.rs index e2978d99..9cb3c457 100644 --- a/operator/runtime/testnet/src/configs/runtime_params.rs +++ b/operator/runtime/testnet/src/configs/runtime_params.rs @@ -6,7 +6,17 @@ use sp_std::vec; use crate::Runtime; +#[cfg(feature = "storage-hub")] +use crate::currency::{GIGAWEI, HAVE}; + +#[cfg(feature = "storage-hub")] +use crate::configs::storagehub::{ChallengeTicksTolerance, ReplicationTargetType, SpMinDeposit}; + +#[cfg(feature = "storage-hub")] +use datahaven_runtime_common::{Balance, BlockNumber}; + #[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +#[cfg(not(feature = "storage-hub"))] pub mod dynamic_params { use super::*; #[dynamic_pallet_params] @@ -51,6 +61,314 @@ pub mod dynamic_params { } } +#[cfg(feature = "storage-hub")] +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod runtime_config { + + use super::*; + + #[codec(index = 0)] + #[allow(non_upper_case_globals)] + /// Set the initial address of the Snowbridge Gateway contract on Ethereum. + /// The fact that this is a parameter means that we can set it initially to the zero address, + /// and then change it later via governance, to the actual address of the deployed contract. + pub static EthereumGatewayAddress: H160 = H160::repeat_byte(0x0); + + #[codec(index = 1)] + #[allow(non_upper_case_globals)] + /// Set the initial address of the Rewards Registry contract on Ethereum. + /// The fact that this is a parameter means that we can set it initially to the zero address, + /// and then change it later via governance, to the actual address of the deployed contract. + pub static RewardsRegistryAddress: H160 = H160::repeat_byte(0x0); + + #[codec(index = 2)] + #[allow(non_upper_case_globals)] + /// The Selector is the first 4 bytes of the keccak256 hash of the function signature("updateRewardsMerkleRoot(bytes32)") + pub static RewardsUpdateSelector: BoundedVec> = + BoundedVec::truncate_from(vec![0xdc, 0x3d, 0x04, 0xec]); + + #[codec(index = 3)] + #[allow(non_upper_case_globals)] + /// The RewardsAgentOrigin is the hash of the string "external_validators_rewards" + /// TODO: Decide which agent origin we want to use. Currently for testing it's the zero hash + pub static RewardsAgentOrigin: H256 = H256::from_slice(&hex!( + "c505dfb2df107d106d08bd0f1a0acd10052ca9aa078629a4ccfd0c90c6e69b65" + )); + + // Proportion of fees allocated to the Treasury (remainder are burned). + // e.g. 20% to the treasury, 80% burned. + #[codec(index = 4)] + #[allow(non_upper_case_globals)] + pub static FeesTreasuryProportion: Perbill = Perbill::from_percent(20); + + // ╔══════════════════════ StorageHub Pallets ═══════════════════════╗ + + #[codec(index = 5)] + #[allow(non_upper_case_globals)] + /// 20 HAVEs + pub static SlashAmountPerMaxFileSize: Balance = 20 * HAVE; + + #[codec(index = 6)] + #[allow(non_upper_case_globals)] + /// 10k HAVEs * [`MinChallengePeriod`] = 10k HAVEs * 30 = 300k HAVEs + /// + /// This can be interpreted as "a Provider with 10k HAVEs of stake would get the minimum challenge period". + pub static StakeToChallengePeriod: Balance = + 10_000 * HAVE * Into::::into(MinChallengePeriod::get()); + + #[codec(index = 7)] + #[allow(non_upper_case_globals)] + /// The [`CheckpointChallengePeriod`] is set to be equal to the longest possible challenge period + /// (i.e. the [`StakeToChallengePeriod`] divided by the [`SpMinDeposit`]). + /// + // 300k HAVEs / 100 HAVEs + 50 + 1 = ~3k ticks (i.e. ~5 hours with 6 seconds per tick) + pub static CheckpointChallengePeriod: BlockNumber = (StakeToChallengePeriod::get() + / SpMinDeposit::get()).saturating_add(ChallengeTicksTolerance::get() as u128).saturating_add(1) + .try_into() + .expect( + "StakeToChallengePeriod / SpMinDeposit should be a number of ticks that can fit in BlockNumber numerical type", + ); + + #[codec(index = 8)] + #[allow(non_upper_case_globals)] + /// 30 ticks, or 3 minutes with 6 seconds per tick. + pub static MinChallengePeriod: BlockNumber = 30; + + #[codec(index = 9)] + #[allow(non_upper_case_globals)] + /// Price decreases when system utilisation is below 30%. + pub static SystemUtilisationLowerThresholdPercentage: Perbill = Perbill::from_percent(30); + + #[codec(index = 10)] + #[allow(non_upper_case_globals)] + /// Price increases when system utilisation is above 95%. + pub static SystemUtilisationUpperThresholdPercentage: Perbill = Perbill::from_percent(95); + + #[codec(index = 11)] + #[allow(non_upper_case_globals)] + /// 50 [`GIGAWEI`]s is the price per GB of data, per tick. + /// + /// With 6 seconds per tick, this means that over a month, the price of 1 GB is: + /// 50e-9 [`HAVE`]s * 10 ticks/min * 60 min/h * 24 h/day * 30 days/month = 21.6e-3 [`HAVE`]s + pub static MostlyStablePrice: Balance = 50 * GIGAWEI; + + #[codec(index = 12)] + #[allow(non_upper_case_globals)] + /// [`MostlyStablePrice`] * 10 = 500 [`GIGAWEI`]s + pub static MaxPrice: Balance = MostlyStablePrice::get() * 10; + + #[codec(index = 13)] + #[allow(non_upper_case_globals)] + /// [`MostlyStablePrice`] / 5 = 10 [`GIGAWEI`]s + pub static MinPrice: Balance = MostlyStablePrice::get() / 5; + + #[codec(index = 14)] + #[allow(non_upper_case_globals)] + /// u = [`UpperExponentFactor`] + /// system_utilisation = 1 + /// + /// [`MaxPrice`] = [`MostlyStablePrice`] + u * e ^ ( 1 - [`SystemUtilisationUpperThresholdPercentage`] ) + /// + /// 500 = 50 + u * (e ^ (1 - 0.95) - 1) + /// u = (500 - 50) / (e ^ (1 - 0.95) - 1) ≈ 8777 + pub static UpperExponentFactor: u32 = 8777; + + #[codec(index = 15)] + #[allow(non_upper_case_globals)] + /// l = [`LowerExponentFactor`] + /// system_utilisation = 0 + /// + /// [`MinPrice`] = [`MostlyStablePrice`] - u * e ^ ( [`SystemUtilisationLowerThresholdPercentage`] - 0 ) + /// + /// 10 = 50 - l * (e ^ (0.3 - 0) - 1) + /// l = (50 - 10) / (e ^ (0.3 - 0) - 1) ≈ 114 + pub static LowerExponentFactor: u32 = 114; + + #[codec(index = 16)] + #[allow(non_upper_case_globals)] + /// 0-size bucket fixed rate payment stream representing the price for 1 GB of data. + /// + /// Base rate for a new fixed payment stream established between an MSP and a user. + pub static ZeroSizeBucketFixedRate: Balance = 50 * GIGAWEI; + + #[codec(index = 17)] + #[allow(non_upper_case_globals)] + /// Ideal utilisation rate of the system + pub static IdealUtilisationRate: Perbill = Perbill::from_percent(85); + + #[codec(index = 18)] + #[allow(non_upper_case_globals)] + /// Decay rate of the power of two function that determines the percentage of funds that go to + /// the treasury for utilisation rates greater than the ideal. + pub static DecayRate: Perbill = Perbill::from_percent(5); + + #[codec(index = 19)] + #[allow(non_upper_case_globals)] + /// The minimum treasury cut that can be taken from the amount charged from a payment stream. + pub static MinimumTreasuryCut: Perbill = Perbill::from_percent(1); + + #[codec(index = 20)] + #[allow(non_upper_case_globals)] + /// The maximum treasury cut that can be taken from the amount charged from a payment stream. + pub static MaximumTreasuryCut: Perbill = Perbill::from_percent(5); + + #[codec(index = 21)] + #[allow(non_upper_case_globals)] + /// The penalty a BSP must pay when they forcefully stop storing a file. + /// We set this to be half of the `SlashAmountPerMaxFileSize` with the rationale that + /// for a BSP that has lost this file, it should be more convenient to voluntarily + /// show up and pay this penalty in good faith, rather than risking being slashed for + /// being unable to submit a proof that should include this file. + pub static BspStopStoringFilePenalty: Balance = SlashAmountPerMaxFileSize::get() / 2; + + /// Time-to-live for a provider to top up their deposit to cover a capacity deficit. + /// Set to 14_400 relay blocks = 1 day with 6 second timeslots. + #[codec(index = 22)] + #[allow(non_upper_case_globals)] + pub static ProviderTopUpTtl: BlockNumber = 14_400; + + /// The following parameters are the replication targets for the different security levels + /// that a storage request (and thus the file it represents) can have. + /// + /// These are associated with the probability that a malicious actor could hold the file hostage by controlling + /// all BSPs that volunteered and confirmed storing it. + /// The values were calculated from the probabilities derived using binomial distribution calculations, + /// where the total number of BSPs is set to 1000, the fraction of malicious BSPs is 1/3, and the target number of BSPs + /// is incremented until the probability of all selected BSPs being malicious falls below the required percentage. + /// + /// The formula used is: + /// num_bsps = 1000 + /// fraction_evil = 1/3 + /// n_evil = int(num_bsps * fraction_evil) // = 333 + /// target = range(1, num_bsps) + /// p_init = target / num_bsps + /// prob = binomial_cdf_at_least(n_evil, target, p_init) + /// + /// This ensures that the replication targets were selected optimally to balance security and storage efficiency. + /// -------------------------------------------------------------------------------------------------------------------- + /// The amount of BSPs that a basic security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~1%. + #[codec(index = 23)] + #[allow(non_upper_case_globals)] + pub static BasicReplicationTarget: ReplicationTargetType = 7; + + /// The amount of BSPs that a standard security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~0.1%. + #[codec(index = 24)] + #[allow(non_upper_case_globals)] + pub static StandardReplicationTarget: ReplicationTargetType = 12; + + /// The amount of BSPs that a high security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~0.01%. + #[codec(index = 25)] + #[allow(non_upper_case_globals)] + pub static HighSecurityReplicationTarget: ReplicationTargetType = 17; + + /// The amount of BSPs that a super high security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~0.001%. + #[codec(index = 26)] + #[allow(non_upper_case_globals)] + pub static SuperHighSecurityReplicationTarget: ReplicationTargetType = 22; + + /// The amount of BSPs that an ultra high security storage request should use as the replication target. + /// + /// This must be the lowest amount of BSPs that guarantee that the probability that a malicious + /// actor controlling 1/3 of the BSPs can hold the file hostage by controlling all its + /// volunteered BSPs is ~0.0001%. + #[codec(index = 27)] + #[allow(non_upper_case_globals)] + pub static UltraHighSecurityReplicationTarget: ReplicationTargetType = 26; + + /// The maximum amount of BSPs that a user can require a storage request to use as the replication target. + /// + /// This is a safety measure to prevent users from issuing storage requests that are too large and would + /// require a large number of BSPs to store the file. + #[codec(index = 28)] + #[allow(non_upper_case_globals)] + pub static MaxReplicationTarget: ReplicationTargetType = + UltraHighSecurityReplicationTarget::get() + .saturating_mul(150) + .saturating_div(100); + + /// The amount of ticks that have to pass for the threshold to volunteer for a specific storage request + /// to arrive at its maximum value. + /// + /// This is big enough so volunteering for a storage request is not open to everyone inmediatly, preventing + /// a select few BSPs from taking all the requests, while small enough so that storage requests don't take + /// too long to be filled. + #[codec(index = 29)] + #[allow(non_upper_case_globals)] + pub static TickRangeToMaximumThreshold: BlockNumber = 3600; // 6 hours with a 6 second block time + + /// The amount of ticks after which a storage request is considered expired and can be removed from storage. + /// + /// It's a function of the TickRangeToMaximumThreshold since it does not make sense for a storage request to + /// expire before arriving at its maximum threshold for volunteering. + #[codec(index = 30)] + #[allow(non_upper_case_globals)] + pub static StorageRequestTtl: BlockNumber = TickRangeToMaximumThreshold::get() + .saturating_mul(110) + .saturating_div(100); + + /// The minimum amount of ticks between a stop storing request from a BSP and that BSP being able to + /// confirm to stop storing that file key. + /// + /// It's a function of the checkpoint challenge period since this makes it so BSPs can't avoid checkpoint + /// challenges by stopping storing a file key right before the challenge period ends in case they lost it. + #[codec(index = 31)] + #[allow(non_upper_case_globals)] + pub static MinWaitForStopStoring: BlockNumber = CheckpointChallengePeriod::get() + .saturating_mul(110) + .saturating_div(100); + + #[codec(index = 32)] + #[allow(non_upper_case_globals)] + /// 20 ticks, or 2 minutes with 6 seconds per tick. + pub static MinSeedPeriod: BlockNumber = 20; + + #[codec(index = 33)] + #[allow(non_upper_case_globals)] + /// 10k HAVEs * [`MinSeedPeriod`] = 10k HAVEs * 20 = 200k HAVEs + /// + /// This can be interpreted as "a Provider with 10k HAVEs of stake would get the minimum seed period". + pub static StakeToSeedPeriod: Balance = + 10_000 * HAVE * Into::::into(MinSeedPeriod::get()); + + #[codec(index = 34)] + #[allow(non_upper_case_globals)] + /// The amount of ticks to charge a user upfront when it tries to issue a new storage request. + /// This is done as a deterrent to avoid users spamming the network with huge files but never + /// actually planning to store them longterm. + /// + /// 72k ticks = 5 days with 6 seconds per tick. + /// This means that a user must pay for 5 days of storage upfront, which gets transferred to the + /// treasury. Governance can then decide what to do with the accumulated funds. + /// + /// With a stable price (defined as `MostlyStablePrice` in this file) of 50 GIGAWEIs per gigabyte + /// per tick and a standard replication target (`StandardReplicationTarget`) of 12 BSPs, the upfront + /// cost for the user to issue a storage request for a 1 GB file would be: + /// 50 GIGAWEIs per gigabyte per tick * 12 BSPs * 72k ticks * 1 GB = 0.0432 HAVEs + pub static UpfrontTicksToPay: BlockNumber = 72_000; + // ╚══════════════════════ StorageHub Pallets ═══════════════════════╝ + } +} + #[cfg(feature = "runtime-benchmarks")] impl Default for RuntimeParameters { fn default() -> Self { diff --git a/operator/runtime/testnet/src/configs/storagehub/mod.rs b/operator/runtime/testnet/src/configs/storagehub/mod.rs new file mode 100644 index 00000000..636393e8 --- /dev/null +++ b/operator/runtime/testnet/src/configs/storagehub/mod.rs @@ -0,0 +1,610 @@ +use super::{ + AccountId, Balance, Balances, BlockNumber, Hash, RuntimeEvent, RuntimeHoldReason, HAVE, +}; +use crate::configs::runtime_params::dynamic_params::runtime_config; +use crate::{ + BucketNfts, Nfts, PaymentStreams, ProofsDealer, Providers, Runtime, Signature, WeightToFee, + HOURS, +}; +use core::marker::PhantomData; +use datahaven_runtime_common::time::{DAYS, MINUTES}; +use frame_support::pallet_prelude::DispatchClass; +use frame_support::traits::AsEnsureOriginWithArg; +use frame_support::{ + parameter_types, + traits::{ConstU128, ConstU32, ConstU64}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::EnsureRoot; +use frame_system::EnsureSigned; +use num_bigint::BigUint; +use pallet_nfts::PalletFeatures; +use polkadot_runtime_common::prod_or_fast; +use shp_data_price_updater::{MostlyStablePriceIndexUpdater, MostlyStablePriceIndexUpdaterConfig}; +use shp_file_key_verifier::FileKeyVerifier; +use shp_file_metadata::{ChunkId, FileMetadata}; +use shp_forest_verifier::ForestVerifier; +use shp_treasury_funding::{ + LinearThenPowerOfTwoTreasuryCutCalculator, LinearThenPowerOfTwoTreasuryCutCalculatorConfig, +}; +use sp_core::Get; +use sp_core::Hasher; +use sp_core::H256; +use sp_runtime::traits::Convert; +use sp_runtime::traits::ConvertBack; +use sp_runtime::traits::Verify; +use sp_runtime::traits::Zero; +use sp_runtime::SaturatedConversion; +use sp_runtime::{traits::BlakeTwo256, Perbill}; +use sp_std::convert::{From, Into}; +use sp_std::vec; +use sp_trie::{LayoutV1, TrieConfiguration, TrieLayout}; + +/// Type representing the storage data units in StorageHub. +pub type StorageDataUnit = u64; + +pub type StorageProofsMerkleTrieLayout = LayoutV1; + +pub type Hashing = BlakeTwo256; + +// TODO: remove this and replace with pallet treasury +pub struct TreasuryAccount; +impl Get for TreasuryAccount { + fn get() -> AccountId { + AccountId::from([0; 32]) + } +} + +/****** NFTs pallet ******/ +parameter_types! { + pub const CollectionDeposit: Balance = 100 * HAVE; + pub const ItemDeposit: Balance = 1 * HAVE; + pub const MetadataDepositBase: Balance = 10 * HAVE; + pub const MetadataDepositPerByte: Balance = 1 * HAVE; + pub const ApprovalsLimit: u32 = 20; + pub const ItemAttributesApprovalsLimit: u32 = 20; + pub const MaxTips: u32 = 10; + pub const MaxDeadlineDuration: BlockNumber = 12 * 30 * DAYS; + pub const MaxAttributesPerCall: u32 = 10; + pub Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl pallet_nfts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type CollectionDeposit = CollectionDeposit; + type ItemDeposit = ItemDeposit; + type MetadataDepositBase = MetadataDepositBase; + type AttributeDepositBase = MetadataDepositBase; + type DepositPerByte = MetadataDepositPerByte; + type StringLimit = ConstU32<256>; + type KeyLimit = ConstU32<64>; + type ValueLimit = ConstU32<256>; + type ApprovalsLimit = ApprovalsLimit; + type ItemAttributesApprovalsLimit = ItemAttributesApprovalsLimit; + type MaxTips = MaxTips; + type MaxDeadlineDuration = MaxDeadlineDuration; + type MaxAttributesPerCall = MaxAttributesPerCall; + type Features = Features; + type OffchainSignature = Signature; + type OffchainPublic = ::Signer; + type WeightInfo = pallet_nfts::weights::SubstrateWeight; + type Locker = (); +} +/****** ****** ****** ******/ + +/****** Relay Randomness pallet ******/ +impl pallet_randomness::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type BabeDataGetter = BabeDataGetter; + type BabeBlockGetter = BlockNumberGetter; + type WeightInfo = (); + type BabeDataGetterBlockNumber = BlockNumber; +} + +pub struct BabeDataGetter; +impl pallet_randomness::GetBabeData for BabeDataGetter { + fn get_epoch_index() -> u64 { + todo!("implement `get_epoch_index`"); + } + fn get_epoch_randomness() -> Hash { + todo!("implement `get_epoch_randomness`"); + } + fn get_parent_randomness() -> Hash { + todo!("implement `get_parent_randomness`"); + } +} + +pub struct BlockNumberGetter {} +impl sp_runtime::traits::BlockNumberProvider for BlockNumberGetter { + type BlockNumber = BlockNumber; + + fn current_block_number() -> Self::BlockNumber { + frame_system::Pallet::::block_number() + } +} + +/****** ****** ****** ******/ + +/****** Storage Providers pallet ******/ +parameter_types! { + pub const SpMinDeposit: Balance = 100 * HAVE; + pub const BucketDeposit: Balance = 100 * HAVE; + pub const BspSignUpLockPeriod: BlockNumber = 90 * DAYS; // ~3 months + pub const MaxBlocksForRandomness: BlockNumber = prod_or_fast!(2 * HOURS, 2 * MINUTES); + // TODO: If the next line is uncommented (which should be eventually, replacing the line above), compilation breaks (most likely because of mismatched dependency issues) + // pub const MaxBlocksForRandomness: BlockNumber = prod_or_fast!(2 * runtime_constants::time::EPOCH_DURATION_IN_SLOTS, 2 * MINUTES); +} + +impl pallet_storage_providers::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_storage_providers::weights::SubstrateWeight; + type ProvidersRandomness = pallet_randomness::RandomnessFromOneEpochAgo; + type PaymentStreams = PaymentStreams; + type ProofDealer = ProofsDealer; + type FileMetadataManager = FileMetadata< + { shp_constants::H_LENGTH }, + { shp_constants::FILE_CHUNK_SIZE }, + { shp_constants::FILE_SIZE_TO_CHALLENGES }, + >; + type NativeBalance = Balances; + type CrRandomness = MockCrRandomness; + type RuntimeHoldReason = RuntimeHoldReason; + type StorageDataUnit = StorageDataUnit; + type StorageDataUnitAndBalanceConvert = StorageDataUnitAndBalanceConverter; + type SpCount = u32; + type BucketCount = u128; + type MerklePatriciaRoot = Hash; + type MerkleTrieHashing = Hashing; + type ProviderId = Hash; + type ProviderIdHashing = Hashing; + type ValuePropId = Hash; + type ValuePropIdHashing = Hashing; + type ReadAccessGroupId = ::CollectionId; + type ProvidersProofSubmitters = ProofsDealer; + type ReputationWeightType = u32; + type StorageHubTickGetter = ProofsDealer; + type Treasury = TreasuryAccount; + type SpMinDeposit = SpMinDeposit; + type SpMinCapacity = ConstU64<2>; + type DepositPerData = ConstU128<2>; + type MaxFileSize = ConstU64<{ u64::MAX }>; + type MaxMultiAddressSize = ConstU32<100>; + type MaxMultiAddressAmount = ConstU32<5>; + type MaxProtocols = ConstU32<100>; + type BucketDeposit = BucketDeposit; + type BucketNameLimit = ConstU32<100>; + type MaxBlocksForRandomness = MaxBlocksForRandomness; + type MinBlocksBetweenCapacityChanges = ConstU32<10>; + type DefaultMerkleRoot = DefaultMerkleRoot; + type SlashAmountPerMaxFileSize = runtime_config::SlashAmountPerMaxFileSize; + type StartingReputationWeight = ConstU32<1>; + type BspSignUpLockPeriod = BspSignUpLockPeriod; + type MaxCommitmentSize = ConstU32<1000>; + type ZeroSizeBucketFixedRate = runtime_config::ZeroSizeBucketFixedRate; + type ProviderTopUpTtl = runtime_config::ProviderTopUpTtl; + type MaxExpiredItemsInBlock = ConstU32<100>; +} + +pub struct StorageDataUnitAndBalanceConverter; +impl Convert for StorageDataUnitAndBalanceConverter { + fn convert(data_unit: StorageDataUnit) -> Balance { + data_unit.saturated_into() + } +} +impl ConvertBack for StorageDataUnitAndBalanceConverter { + fn convert_back(balance: Balance) -> StorageDataUnit { + balance.saturated_into() + } +} + +pub type HasherOutT = <::Hash as Hasher>::Out; +pub struct DefaultMerkleRoot(PhantomData); +impl Get> for DefaultMerkleRoot { + fn get() -> HasherOutT { + sp_trie::empty_trie_root::() + } +} + +/****** ****** ****** ******/ + +/****** Payment Streams pallet ******/ +parameter_types! { + pub const PaymentStreamHoldReason: RuntimeHoldReason = RuntimeHoldReason::PaymentStreams(pallet_payment_streams::HoldReason::PaymentStreamDeposit); + pub const UserWithoutFundsCooldown: BlockNumber = 100; +} + +impl pallet_payment_streams::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_payment_streams::weights::SubstrateWeight; + type NativeBalance = Balances; + type ProvidersPallet = Providers; + type RuntimeHoldReason = RuntimeHoldReason; + type UserWithoutFundsCooldown = UserWithoutFundsCooldown; // Amount of blocks that a user will have to wait before being able to clear the out of funds flag + type NewStreamDeposit = ConstU32<10>; // Amount of blocks that the deposit of a new stream should be able to pay for + type Units = StorageDataUnit; // Storage unit + type BlockNumberToBalance = BlockNumberToBalance; + type ProvidersProofSubmitters = ProofsDealer; + type TreasuryCutCalculator = LinearThenPowerOfTwoTreasuryCutCalculator; + type TreasuryAccount = TreasuryAccount; + type MaxUsersToCharge = ConstU32<10>; + type BaseDeposit = ConstU128<10>; +} + +// Converter from the BlockNumber type to the Balance type for math +pub struct BlockNumberToBalance; +impl Convert for BlockNumberToBalance { + fn convert(block_number: BlockNumber) -> Balance { + block_number.into() // In this converter we assume that the block number type is smaller in size than the balance type + } +} + +impl LinearThenPowerOfTwoTreasuryCutCalculatorConfig for Runtime { + type Balance = Balance; + type ProvidedUnit = StorageDataUnit; + type IdealUtilisationRate = runtime_config::IdealUtilisationRate; + type DecayRate = runtime_config::DecayRate; + type MinimumCut = runtime_config::MinimumTreasuryCut; + type MaximumCut = runtime_config::MaximumTreasuryCut; +} +/****** ****** ****** ******/ + +/****** Proofs Dealer pallet ******/ +const RANDOM_CHALLENGES_PER_BLOCK: u32 = 10; +const MAX_CUSTOM_CHALLENGES_PER_BLOCK: u32 = 10; +const TOTAL_MAX_CHALLENGES_PER_BLOCK: u32 = + RANDOM_CHALLENGES_PER_BLOCK + MAX_CUSTOM_CHALLENGES_PER_BLOCK; + +parameter_types! { + pub const RandomChallengesPerBlock: u32 = RANDOM_CHALLENGES_PER_BLOCK; + pub const MaxCustomChallengesPerBlock: u32 = MAX_CUSTOM_CHALLENGES_PER_BLOCK; + pub const TotalMaxChallengesPerBlock: u32 = TOTAL_MAX_CHALLENGES_PER_BLOCK; + pub const TargetTicksStorageOfSubmitters: u32 = 3; + pub const ChallengeHistoryLength: BlockNumber = 100; + pub const ChallengesQueueLength: u32 = 100; + pub const ChallengesFee: Balance = 1 * HAVE; + pub const ChallengeTicksTolerance: u32 = 50; +} + +impl pallet_proofs_dealer::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_proofs_dealer::weights::SubstrateWeight; + type ProvidersPallet = Providers; + type NativeBalance = Balances; + type MerkleTrieHash = Hash; + type MerkleTrieHashing = BlakeTwo256; + type ForestVerifier = ForestVerifier; + type KeyVerifier = FileKeyVerifier< + StorageProofsMerkleTrieLayout, + { shp_constants::H_LENGTH }, + { shp_constants::FILE_CHUNK_SIZE }, + { shp_constants::FILE_SIZE_TO_CHALLENGES }, + >; + type StakeToBlockNumber = SaturatingBalanceToBlockNumber; + type RandomChallengesPerBlock = RandomChallengesPerBlock; + type MaxCustomChallengesPerBlock = MaxCustomChallengesPerBlock; + type MaxSubmittersPerTick = MaxSubmittersPerTick; + type TargetTicksStorageOfSubmitters = TargetTicksStorageOfSubmitters; + type ChallengeHistoryLength = ChallengeHistoryLength; + type ChallengesQueueLength = ChallengesQueueLength; + type CheckpointChallengePeriod = runtime_config::CheckpointChallengePeriod; + type ChallengesFee = ChallengesFee; + type Treasury = TreasuryAccount; + // TODO: Once the client logic to keep track of CR randomness deadlines and execute their submissions is implemented + // AND after the chain has been live for enough time to have enough providers to avoid the commit-reveal randomness being + // gameable, the randomness provider should be CrRandomness + type RandomnessProvider = pallet_randomness::ParentBlockRandomness; + type StakeToChallengePeriod = runtime_config::StakeToChallengePeriod; + type MinChallengePeriod = runtime_config::MinChallengePeriod; + type ChallengeTicksTolerance = ChallengeTicksTolerance; + type BlockFullnessPeriod = ChallengeTicksTolerance; // We purposely set this to `ChallengeTicksTolerance` so that spamming of the chain is evaluated for the same blocks as the tolerance BSPs are given. + type BlockFullnessHeadroom = BlockFullnessHeadroom; + type MinNotFullBlocksRatio = MinNotFullBlocksRatio; + type MaxSlashableProvidersPerTick = MaxSlashableProvidersPerTick; +} + +// Converter from the Balance type to the BlockNumber type for math. +// It performs a saturated conversion, so that the result is always a valid BlockNumber. +pub struct SaturatingBalanceToBlockNumber; +impl Convert> for SaturatingBalanceToBlockNumber { + fn convert(block_number: Balance) -> BlockNumberFor { + block_number.saturated_into() + } +} + +pub struct MaxSubmittersPerTick; +impl Get for MaxSubmittersPerTick { + fn get() -> u32 { + let block_weights = ::BlockWeights::get(); + + // Not being able to get the `max_total` weight for the Normal dispatch class is considered + // a critical bug. So we set it to be zero, essentially allowing zero submitters per tick. + // This value can be read from the constants of a node, but with the current configuration, this is: + // + // max_total: { + // ref_time: 1,500,000,000,000 + // proof_size: 3,932,160 + // } + let max_weight_for_class = block_weights + .get(DispatchClass::Normal) + .max_total + .unwrap_or(Zero::zero()); + + // Get the minimum weight a `submit_proof` extrinsic can have. + // This would be the case where the proof is just made up of a single file key proof, that is a + // response to all the random challenges. And there are no checkpoint challenges. + // With the current benchmarking, this is: + // + // TODO: UPDATE THIS WITH THE FINAL BENCHMARKING + // min_weight_for_submit_proof: { + // ref_time: 2,980,252,675 + // proof_size: 16,056 + // } + let min_weight_for_submit_proof = + as pallet_proofs_dealer::weights::WeightInfo>::submit_proof_no_checkpoint_challenges_key_proofs(1); + + // Calculate the maximum number of submit proofs that is possible to have in a block/tick. + // With the current values, this would be: + // + // TODO: UPDATE THIS WITH THE FINAL BENCHMARKING + // 244 proof submissions per block (limited by `proof_size`) + let max_proof_submissions_per_tick = max_weight_for_class + .checked_div_per_component(&min_weight_for_submit_proof) + .unwrap_or(0); + + // Saturating u64 to u32 should be enough. + max_proof_submissions_per_tick.saturated_into() + } +} + +pub struct BlockFullnessHeadroom; +impl Get for BlockFullnessHeadroom { + fn get() -> Weight { + // The block headroom is set to be the maximum benchmarked weight that a `submit_proof` extrinsic can have. + // That is, when the proof includes two file key proofs for every single random challenge, and for the maximum + // number of checkpoint challenges as well. + as pallet_proofs_dealer::weights::WeightInfo>::submit_proof_with_checkpoint_challenges_key_proofs(TOTAL_MAX_CHALLENGES_PER_BLOCK * 2) + } +} + +pub struct MinNotFullBlocksRatio; +impl Get for MinNotFullBlocksRatio { + fn get() -> Perbill { + // This means that we tolerate at most 50% of misbehaving collators. + Perbill::from_percent(50) + } +} + +pub struct MaxSlashableProvidersPerTick; +impl Get for MaxSlashableProvidersPerTick { + fn get() -> u32 { + // With the maximum number of slashable providers per tick being `N`, the absolute maximum + // weight that the `on_poll` hook can have, with the current benchmarking, is: + // + // TODO: UPDATE THIS WITH THE FINAL BENCHMARKING + // new_challenges_round_weight: { + // ref_time: 576,000,000 + N * 551,601,146 + // proof_size: 8,523 + N * 3,158 + // } + // new_checkpoint_challenge_round_max_weight: { + // ref_time: 587,205,208 + ChallengesQueueLength * 225,083 = 610,554,678 + // proof_size: 4,787 + // } + // check_spamming_condition_weight: { + // ref_time: 313,000,000 + // proof_size: 6,012 + // } + // + // For `N` = 1000, this would be: + // max_on_poll_weight: { + // ref_time: 313,000,000 + 610,554,678 + 576,000,000 + N * 551,601,146 ≈ 553,100,700,678 + // proof_size: 6,012 + 4,787 + 8,523 + N * 3,158 ≈ 3,177,322 + // } + // + // Consider that the maximum block weight is: + // maxBlock: { + // ref_time: 2,000,000,000,000 + // proof_size: 5,242,880 + // } + // + // This `on_poll` hook would consume roughly 1/4 of the block `ref_time` and 3/5 of the block `proof_size`. + // This is naturally a lot. But it would be a very unlikely scenario. + // + // This would be the case where all `N` Providers have synchronised their challenge periods + // and have the same deadline, plus, all of them missed their proof submissions. + // The normal scenario would be that NONE (or just a small number) of the Providers have + // missed their proof submissions. + let max_slashable_providers_per_tick = 1000; + max_slashable_providers_per_tick + } +} +/****** ****** ****** ******/ + +/****** File System pallet ******/ +type ThresholdType = u32; +pub type ReplicationTargetType = u32; + +parameter_types! { + pub const BaseStorageRequestCreationDeposit: Balance = 1 * HAVE; + pub const FileDeletionRequestCreationDeposit: Balance = 1 * HAVE; + pub const FileSystemStorageRequestCreationHoldReason: RuntimeHoldReason = RuntimeHoldReason::FileSystem(pallet_file_system::HoldReason::StorageRequestCreationHold); + pub const FileSystemFileDeletionRequestHoldReason: RuntimeHoldReason = RuntimeHoldReason::FileSystem(pallet_file_system::HoldReason::FileDeletionRequestHold); +} + +impl pallet_file_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_file_system::weights::SubstrateWeight; + type Providers = Providers; + type ProofDealer = ProofsDealer; + type PaymentStreams = PaymentStreams; + // TODO: Replace the mocked CR randomness with the actual one when it's ready + // type CrRandomness = CrRandomness; + type CrRandomness = MockCrRandomness; + type UpdateStoragePrice = MostlyStablePriceIndexUpdater; + type UserSolvency = PaymentStreams; + type Fingerprint = Hash; + type ReplicationTargetType = ReplicationTargetType; + type ThresholdType = ThresholdType; + type ThresholdTypeToTickNumber = ThresholdTypeToBlockNumberConverter; + type HashToThresholdType = HashToThresholdTypeConverter; + type MerkleHashToRandomnessOutput = MerkleHashToRandomnessOutputConverter; + type ChunkIdToMerkleHash = ChunkIdToMerkleHashConverter; + type Currency = Balances; + type RuntimeHoldReason = RuntimeHoldReason; + type Nfts = Nfts; + type CollectionInspector = BucketNfts; + type BspStopStoringFilePenalty = runtime_config::BspStopStoringFilePenalty; + type TreasuryAccount = TreasuryAccount; + type MaxBatchConfirmStorageRequests = ConstU32<10>; + type MaxFilePathSize = ConstU32<512u32>; + type MaxPeerIdSize = ConstU32<100>; + type MaxNumberOfPeerIds = ConstU32<5>; + type MaxDataServerMultiAddresses = ConstU32<10>; + type MaxExpiredItemsInTick = ConstU32<100>; + type StorageRequestTtl = runtime_config::StorageRequestTtl; + type MoveBucketRequestTtl = ConstU32<40u32>; + type MaxUserPendingDeletionRequests = ConstU32<10u32>; + type MaxUserPendingMoveBucketRequests = ConstU32<10u32>; + type MinWaitForStopStoring = runtime_config::MinWaitForStopStoring; + type BaseStorageRequestCreationDeposit = BaseStorageRequestCreationDeposit; + type UpfrontTicksToPay = runtime_config::UpfrontTicksToPay; + type WeightToFee = WeightToFee; + type ReplicationTargetToBalance = ReplicationTargetToBalance; + type TickNumberToBalance = TickNumberToBalance; + type StorageDataUnitToBalance = StorageDataUnitToBalance; + type FileDeletionRequestDeposit = FileDeletionRequestCreationDeposit; + type BasicReplicationTarget = runtime_config::BasicReplicationTarget; + type StandardReplicationTarget = runtime_config::StandardReplicationTarget; + type HighSecurityReplicationTarget = runtime_config::HighSecurityReplicationTarget; + type SuperHighSecurityReplicationTarget = runtime_config::SuperHighSecurityReplicationTarget; + type UltraHighSecurityReplicationTarget = runtime_config::UltraHighSecurityReplicationTarget; + type MaxReplicationTarget = runtime_config::MaxReplicationTarget; + type TickRangeToMaximumThreshold = runtime_config::TickRangeToMaximumThreshold; +} + +impl MostlyStablePriceIndexUpdaterConfig for Runtime { + type Price = Balance; + type StorageDataUnit = StorageDataUnit; + type LowerThreshold = runtime_config::SystemUtilisationLowerThresholdPercentage; + type UpperThreshold = runtime_config::SystemUtilisationUpperThresholdPercentage; + type MostlyStablePrice = runtime_config::MostlyStablePrice; + type MaxPrice = runtime_config::MaxPrice; + type MinPrice = runtime_config::MinPrice; + type UpperExponentFactor = runtime_config::UpperExponentFactor; + type LowerExponentFactor = runtime_config::LowerExponentFactor; +} + +// Converter from the ThresholdType to the BlockNumber type and vice versa. +// It performs a saturated conversion, so that the result is always a valid BlockNumber. +pub struct ThresholdTypeToBlockNumberConverter; +impl Convert> for ThresholdTypeToBlockNumberConverter { + fn convert(threshold: ThresholdType) -> BlockNumberFor { + threshold.saturated_into() + } +} + +impl ConvertBack> for ThresholdTypeToBlockNumberConverter { + fn convert_back(block_number: BlockNumberFor) -> ThresholdType { + block_number.into() + } +} + +/// Converter from the [`Hash`] type to the [`ThresholdType`]. +pub struct HashToThresholdTypeConverter; +impl Convert<::Hash, ThresholdType> + for HashToThresholdTypeConverter +{ + fn convert(hash: ::Hash) -> ThresholdType { + // Get the hash as bytes + let hash_bytes = hash.as_ref(); + + // Get the 4 least significant bytes of the hash and interpret them as an u32 + let truncated_hash_bytes: [u8; 4] = + hash_bytes[28..].try_into().expect("Hash is 32 bytes; qed"); + + ThresholdType::from_be_bytes(truncated_hash_bytes) + } +} + +// Converter from the MerkleHash (H256) type to the RandomnessOutput (H256) type. +pub struct MerkleHashToRandomnessOutputConverter; +impl Convert for MerkleHashToRandomnessOutputConverter { + fn convert(hash: H256) -> H256 { + hash + } +} + +// Converter from the ChunkId type to the MerkleHash (H256) type. +pub struct ChunkIdToMerkleHashConverter; + +impl Convert for ChunkIdToMerkleHashConverter { + fn convert(chunk_id: ChunkId) -> H256 { + let chunk_id_biguint = BigUint::from(chunk_id.as_u64()); + let mut bytes = chunk_id_biguint.to_bytes_be(); + + // Ensure the byte slice is exactly 32 bytes long by padding with leading zeros + if bytes.len() < 32 { + let mut padded_bytes = vec![0u8; 32 - bytes.len()]; + padded_bytes.extend(bytes); + bytes = padded_bytes; + } + + H256::from_slice(&bytes) + } +} + +// Converter from the ReplicationTargetType type to the Balance type. +pub struct ReplicationTargetToBalance; +impl Convert for ReplicationTargetToBalance { + fn convert(replication_target: ReplicationTargetType) -> Balance { + replication_target.into() + } +} + +// Converter from the TickNumber type to the Balance type. +pub type TickNumber = BlockNumber; +pub struct TickNumberToBalance; +impl Convert for TickNumberToBalance { + fn convert(tick_number: TickNumber) -> Balance { + tick_number.into() + } +} + +// Converter from the StorageDataUnit type to the Balance type. +pub struct StorageDataUnitToBalance; +impl Convert for StorageDataUnitToBalance { + fn convert(storage_data_unit: StorageDataUnit) -> Balance { + storage_data_unit.into() + } +} +/****** ****** ****** ******/ + +/****** Bucket NFTs pallet ******/ +impl pallet_bucket_nfts::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_bucket_nfts::weights::SubstrateWeight; + type Buckets = Providers; +} +/****** ****** ****** ******/ + +/****** Commit-Reveal Randomness pallet ******/ +pub struct MockCrRandomness; +impl shp_traits::CommitRevealRandomnessInterface for MockCrRandomness { + type ProviderId = Hash; + + fn initialise_randomness_cycle( + _who: &Self::ProviderId, + ) -> frame_support::dispatch::DispatchResult { + Ok(()) + } + + fn stop_randomness_cycle(_who: &Self::ProviderId) -> frame_support::dispatch::DispatchResult { + Ok(()) + } +} +/****** ****** ****** ******/ diff --git a/operator/runtime/testnet/src/lib.rs b/operator/runtime/testnet/src/lib.rs index 08069219..2405f6f4 100644 --- a/operator/runtime/testnet/src/lib.rs +++ b/operator/runtime/testnet/src/lib.rs @@ -19,7 +19,10 @@ use frame_support::{ pallet_prelude::{TransactionValidity, TransactionValidityError}, parameter_types, traits::{KeyOwnerProofSystem, OnFinalize}, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, + weights::{ + constants::ExtrinsicBaseWeight, constants::WEIGHT_REF_TIME_PER_SECOND, Weight, + WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, + }, }; pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; @@ -28,6 +31,7 @@ use pallet_evm::{Account as EVMAccount, FeeCalculator, GasWeightMapping, Runner} use pallet_external_validators::traits::EraIndex; use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId}; pub use pallet_timestamp::Call as TimestampCall; +use smallvec::smallvec; use snowbridge_core::AgentId; use snowbridge_merkle_tree::MerkleProof; use sp_api::impl_runtime_apis; @@ -230,8 +234,36 @@ where } } +/// Handles converting a weight scalar to a fee value, based on the scale and granularity of the +/// node's balance type. +/// +/// This should typically create a mapping between the following ranges: +/// - `[0, MAXIMUM_BLOCK_WEIGHT]` +/// - `[Balance::min, Balance::max]` +/// +/// Yet, it can be used for any other sort of change to weight-fee. Some examples being: +/// - Setting it to `0` will essentially disable the weight fee. +/// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. +pub struct WeightToFee; +impl WeightToFeePolynomial for WeightToFee { + type Balance = Balance; + fn polynomial() -> WeightToFeeCoefficients { + // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1 MILLIHAVE: + // in our template, we map to 1/10 of that, or 1/10 MILLIHAVE + let p = currency::MILLIHAVE / 10; + let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); + smallvec![WeightToFeeCoefficient { + degree: 1, + negative: false, + coeff_frac: Perbill::from_rational(p % q, q), + coeff_integer: p / q, + }] + } +} + // Create the runtime by composing the FRAME pallets that were previously configured. #[frame_support::runtime] +#[cfg(not(feature = "storage-hub"))] mod runtime { #[runtime::runtime] #[runtime::derive( @@ -383,6 +415,179 @@ mod runtime { // ╚═══════════════════ DataHaven-specific Pallets ══════════════════╝ } +#[frame_support::runtime] +#[cfg(feature = "storage-hub")] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct Runtime; + + // ╔══════════════════ System and Consensus Pallets ═════════════════╗ + #[runtime::pallet_index(0)] + pub type System = frame_system; + + // Babe must be before session. + #[runtime::pallet_index(1)] + pub type Babe = pallet_babe; + + #[runtime::pallet_index(2)] + pub type Timestamp = pallet_timestamp; + + #[runtime::pallet_index(3)] + pub type Balances = pallet_balances; + + // Consensus support. + // Authorship must be before session in order to note author in the correct session and era. + #[runtime::pallet_index(4)] + pub type Authorship = pallet_authorship; + + #[runtime::pallet_index(5)] + pub type Offences = pallet_offences; + + #[runtime::pallet_index(6)] + pub type Historical = pallet_session::historical; + + // External Validators must be before Session. + #[runtime::pallet_index(7)] + pub type ExternalValidators = pallet_external_validators; + + #[runtime::pallet_index(8)] + pub type Session = pallet_session; + + #[runtime::pallet_index(9)] + pub type ImOnline = pallet_im_online; + + #[runtime::pallet_index(10)] + pub type Grandpa = pallet_grandpa; + + #[runtime::pallet_index(11)] + pub type TransactionPayment = pallet_transaction_payment; + + #[runtime::pallet_index(12)] + pub type Beefy = pallet_beefy; + + #[runtime::pallet_index(13)] + pub type Mmr = pallet_mmr; + + #[runtime::pallet_index(14)] + pub type BeefyMmrLeaf = pallet_beefy_mmr; + // ╚═════════════════ System and Consensus Pallets ══════════════════╝ + + // ╔═════════════════ Polkadot SDK Utility Pallets ══════════════════╗ + #[runtime::pallet_index(30)] + pub type Utility = pallet_utility; + + #[runtime::pallet_index(31)] + pub type Scheduler = pallet_scheduler; + + #[runtime::pallet_index(32)] + pub type Preimage = pallet_preimage; + + #[runtime::pallet_index(33)] + pub type Identity = pallet_identity; + + #[runtime::pallet_index(34)] + pub type Multisig = pallet_multisig; + + #[runtime::pallet_index(35)] + pub type Parameters = pallet_parameters; + + #[runtime::pallet_index(36)] + pub type Sudo = pallet_sudo; + + #[runtime::pallet_index(37)] + pub type Treasury = pallet_treasury; + + #[runtime::pallet_index(38)] + pub type Proxy = pallet_proxy; + // ╚═════════════════ Polkadot SDK Utility Pallets ══════════════════╝ + + // ╔════════════════════ Frontier (EVM) Pallets ═════════════════════╗ + #[runtime::pallet_index(50)] + pub type Ethereum = pallet_ethereum; + + #[runtime::pallet_index(51)] + pub type Evm = pallet_evm; + + #[runtime::pallet_index(52)] + pub type EvmChainId = pallet_evm_chain_id; + // ╚════════════════════ Frontier (EVM) Pallets ═════════════════════╝ + + // ╔══════════════════════ Snowbridge Pallets ═══════════════════════╗ + #[runtime::pallet_index(60)] + pub type EthereumBeaconClient = snowbridge_pallet_ethereum_client; + + #[runtime::pallet_index(61)] + pub type EthereumInboundQueueV2 = snowbridge_pallet_inbound_queue_v2; + + #[runtime::pallet_index(62)] + pub type EthereumOutboundQueueV2 = snowbridge_pallet_outbound_queue_v2; + + #[runtime::pallet_index(63)] + pub type SnowbridgeSystem = snowbridge_pallet_system; + + #[runtime::pallet_index(64)] + pub type SnowbridgeSystemV2 = snowbridge_pallet_system_v2; + // ╚══════════════════════ Snowbridge Pallets ═══════════════════════╝ + + // ╔════════════ Polkadot SDK Utility Pallets - Block 2 ═════════════╗ + // The Message Queue pallet has to be after the Snowbridge Outbound + // Queue V2 pallet since the former processes messages in its + // `on_initialize` hook and the latter clears up messages in + // its `on_initialize` hook, so otherwise messages will be cleared + // up before they are processed. + #[runtime::pallet_index(70)] + pub type MessageQueue = pallet_message_queue; + // ╚════════════ Polkadot SDK Utility Pallets - Block 2 ═════════════╝ + + // ╔══════════════════════ StorageHub Pallets ═══════════════════════╗ + // Start with index 80 + #[runtime::pallet_index(80)] + pub type Providers = pallet_storage_providers; + + #[runtime::pallet_index(81)] + pub type FileSystem = pallet_file_system; + + #[runtime::pallet_index(82)] + pub type ProofsDealer = pallet_proofs_dealer; + + #[runtime::pallet_index(83)] + pub type Randomness = pallet_randomness; + + #[runtime::pallet_index(84)] + pub type PaymentStreams = pallet_payment_streams; + + #[runtime::pallet_index(85)] + pub type BucketNfts = pallet_bucket_nfts; + + #[runtime::pallet_index(90)] + pub type Nfts = pallet_nfts; + // ╚══════════════════════ StorageHub Pallets ═══════════════════════╝ + + // ╔═══════════════════ DataHaven-specific Pallets ══════════════════╗ + // Start with index 100 + #[runtime::pallet_index(100)] + pub type OutboundCommitmentStore = pallet_outbound_commitment_store; + + #[runtime::pallet_index(101)] + pub type ExternalValidatorsRewards = pallet_external_validators_rewards; + + #[runtime::pallet_index(102)] + pub type DataHavenNativeTransfer = pallet_datahaven_native_transfer; + + // ╚═══════════════════ DataHaven-specific Pallets ══════════════════╝ +} + /// MMR helper types. mod mmr { use super::Runtime;