From 131d06b4bf1dc1bcac17c0a452982f7255046eef Mon Sep 17 00:00:00 2001 From: yahortsaryk Date: Tue, 24 Dec 2024 18:12:47 +0100 Subject: [PATCH] feat: DAC v5 Inspection + Payouts prototype --- CHANGELOG.md | 6 +- Cargo.lock | 249 +- Cargo.toml | 2 +- pallets/ddc-customers/src/lib.rs | 6 +- pallets/ddc-customers/src/tests.rs | 4 +- pallets/ddc-payouts/src/lib.rs | 705 +- pallets/ddc-payouts/src/migrations.rs | 314 +- pallets/ddc-payouts/src/mock.rs | 10 +- pallets/ddc-payouts/src/tests.rs | 803 +-- pallets/ddc-verification/Cargo.toml | 1 + .../ddc-verification/src/aggregate_tree.rs | 93 + .../ddc-verification/src/aggregator_client.rs | 721 +- pallets/ddc-verification/src/benchmarking.rs | 580 +- pallets/ddc-verification/src/lib.rs | 6072 +++++++++-------- pallets/ddc-verification/src/migrations.rs | 61 + pallets/ddc-verification/src/mock.rs | 120 +- pallets/ddc-verification/src/tests.rs | 249 +- pallets/ddc-verification/src/weights.rs | 357 +- primitives/src/lib.rs | 187 +- primitives/src/traits/customer.rs | 4 +- primitives/src/traits/payout.rs | 53 +- runtime/cere-dev/src/lib.rs | 5 +- runtime/cere/src/lib.rs | 14 +- 23 files changed, 5638 insertions(+), 4978 deletions(-) create mode 100644 pallets/ddc-verification/src/aggregate_tree.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa0d5c35..9d06da06e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [C] Changes is `Cere` Runtime - [D] Changes is `Cere Dev` Runtime +## [7.2.0] + +- [C,D] DAC v5 Inspection + Payouts prototype + ## [7.1.0] - [C,D] Update Substrate from `stable2407` to `stable2409`. @@ -197,7 +201,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [C,D] Updated Substrate to polkadot-v0.9.38 - [C] Added pallet-preimage to support democracy functionality. -- Changes in `pallet-ddc-payouts::begin_billing_report` crate to accept start and end of the era. +- Changes in `pallet-ddc-payouts::begin_payout` crate to accept start and end of the era. - More explicit events in `pallet-ddc-payouts` and `pallet-ddc-customers` ## [4.8.6] diff --git a/Cargo.lock b/Cargo.lock index 74ea06c4e..e169e3d3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,7 +189,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -391,7 +391,7 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "synstructure 0.13.1", ] @@ -414,7 +414,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -472,7 +472,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -589,7 +589,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -842,9 +842,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.6" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -853,7 +853,7 @@ dependencies = [ [[package]] name = "cere-cli" -version = "7.1.0" +version = "7.2.0" dependencies = [ "cere-client", "cere-service", @@ -871,7 +871,7 @@ dependencies = [ [[package]] name = "cere-client" -version = "7.1.0" +version = "7.2.0" dependencies = [ "cere-dev-runtime", "cere-runtime", @@ -904,7 +904,7 @@ dependencies = [ [[package]] name = "cere-dev-runtime" -version = "7.1.0" +version = "7.2.0" dependencies = [ "cere-runtime-common", "ddc-primitives", @@ -1000,7 +1000,7 @@ dependencies = [ [[package]] name = "cere-rpc" -version = "7.1.0" +version = "7.2.0" dependencies = [ "jsonrpsee", "node-primitives", @@ -1029,7 +1029,7 @@ dependencies = [ [[package]] name = "cere-runtime" -version = "7.1.0" +version = "7.2.0" dependencies = [ "cere-runtime-common", "ddc-primitives", @@ -1125,7 +1125,7 @@ dependencies = [ [[package]] name = "cere-runtime-common" -version = "7.1.0" +version = "7.2.0" dependencies = [ "frame-support", "frame-system", @@ -1146,7 +1146,7 @@ dependencies = [ [[package]] name = "cere-service" -version = "7.1.0" +version = "7.2.0" dependencies = [ "cere-client", "cere-dev-runtime", @@ -1360,7 +1360,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -1750,14 +1750,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] name = "cxx" -version = "1.0.136" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad7c7515609502d316ab9a24f67dc045132d93bfd3f00713389e90d9898bf30d" +checksum = "4d44ff199ff93242c3afe480ab588d544dd08d72e92885e152ffebc670f076ad" dependencies = [ "cc", "cxxbridge-cmd", @@ -1769,47 +1769,47 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.136" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bfd16fca6fd420aebbd80d643c201ee4692114a0de208b790b9cd02ceae65fb" +checksum = "66fd8f17ad454fc1e4f4ab83abffcc88a532e90350d3ffddcb73030220fcbd52" dependencies = [ "cc", "codespan-reporting", "proc-macro2", "quote", "scratch", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] name = "cxxbridge-cmd" -version = "1.0.136" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c33fd49f5d956a1b7ee5f7a9768d58580c6752838d92e39d0d56439efdedc35" +checksum = "4717c9c806a9e07fdcb34c84965a414ea40fafe57667187052cf1eb7f5e8a8a9" dependencies = [ "clap", "codespan-reporting", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] name = "cxxbridge-flags" -version = "1.0.136" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0f1077278fac36299cce8446effd19fe93a95eedb10d39265f3bf67b3036c9" +checksum = "2f6515329bf3d98f4073101c7866ff2bec4e635a13acb82e3f3753fff0bf43cb" [[package]] name = "cxxbridge-macro" -version = "1.0.136" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da7e4d6e74af6b79031d264b2f13c3ea70af1978083741c41ffce9308f1f24f" +checksum = "fb93e6a7ce8ec985c02bbb758237a31598b340acbbc3c19c5a4fa6adaaac92ab" dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -1833,7 +1833,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -1844,7 +1844,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -1888,7 +1888,7 @@ dependencies = [ [[package]] name = "ddc-primitives" -version = "7.1.0" +version = "7.2.0" dependencies = [ "blake2 0.10.6", "frame-support", @@ -1972,7 +1972,7 @@ checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -1985,7 +1985,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2005,7 +2005,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2094,7 +2094,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2118,7 +2118,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.93", + "syn 2.0.91", "termcolor", "toml 0.8.19", "walkdir", @@ -2277,7 +2277,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2297,7 +2297,7 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2383,7 +2383,7 @@ dependencies = [ "prettyplease 0.2.25", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2647,7 +2647,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2754,7 +2754,7 @@ dependencies = [ "proc-macro2", "quote", "sp-crypto-hashing", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2766,7 +2766,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2776,7 +2776,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409 dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -2936,7 +2936,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -3084,9 +3084,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "governor" @@ -3626,7 +3626,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -3755,7 +3755,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -3991,7 +3991,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4460,7 +4460,7 @@ dependencies = [ "proc-macro-warning 0.4.2", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4837,7 +4837,7 @@ dependencies = [ "macro_magic_core", "macro_magic_macros", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4851,7 +4851,7 @@ dependencies = [ "macro_magic_core_macros", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4862,7 +4862,7 @@ checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -4873,7 +4873,7 @@ checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" dependencies = [ "macro_magic_core", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -5072,7 +5072,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -5418,7 +5418,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -5560,7 +5560,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -5727,7 +5727,7 @@ dependencies = [ [[package]] name = "pallet-chainbridge" -version = "7.1.0" +version = "7.2.0" dependencies = [ "frame-support", "frame-system", @@ -5814,7 +5814,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409 dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -5847,7 +5847,7 @@ dependencies = [ [[package]] name = "pallet-ddc-clusters" -version = "7.1.0" +version = "7.2.0" dependencies = [ "ddc-primitives", "frame-benchmarking", @@ -5873,7 +5873,7 @@ dependencies = [ [[package]] name = "pallet-ddc-clusters-gov" -version = "7.1.0" +version = "7.2.0" dependencies = [ "ddc-primitives", "frame-benchmarking", @@ -5908,7 +5908,7 @@ dependencies = [ [[package]] name = "pallet-ddc-customers" -version = "7.1.0" +version = "7.2.0" dependencies = [ "ddc-primitives", "frame-benchmarking", @@ -5931,7 +5931,7 @@ dependencies = [ [[package]] name = "pallet-ddc-nodes" -version = "7.1.0" +version = "7.2.0" dependencies = [ "ddc-primitives", "frame-benchmarking", @@ -5954,7 +5954,7 @@ dependencies = [ [[package]] name = "pallet-ddc-payouts" -version = "7.1.0" +version = "7.2.0" dependencies = [ "byte-unit", "chrono", @@ -5981,7 +5981,7 @@ dependencies = [ [[package]] name = "pallet-ddc-staking" -version = "7.1.0" +version = "7.2.0" dependencies = [ "ddc-primitives", "frame-benchmarking", @@ -6009,10 +6009,11 @@ dependencies = [ [[package]] name = "pallet-ddc-verification" -version = "7.1.0" +version = "7.2.0" dependencies = [ "array-bytes", "base64ct", + "byte-unit", "ddc-primitives", "frame-benchmarking", "frame-election-provider-support", @@ -6132,7 +6133,7 @@ dependencies = [ [[package]] name = "pallet-erc20" -version = "7.1.0" +version = "7.2.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -6151,7 +6152,7 @@ dependencies = [ [[package]] name = "pallet-erc721" -version = "7.1.0" +version = "7.2.0" dependencies = [ "frame-benchmarking", "frame-support", @@ -6390,7 +6391,7 @@ dependencies = [ [[package]] name = "pallet-origins" -version = "7.1.0" +version = "7.2.0" dependencies = [ "cere-runtime-common", "frame-benchmarking", @@ -6550,7 +6551,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -6915,7 +6916,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -6956,7 +6957,7 @@ checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7073,7 +7074,7 @@ dependencies = [ "polkavm-common", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7083,7 +7084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" dependencies = [ "polkavm-derive-impl", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7223,7 +7224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7290,7 +7291,7 @@ checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7301,7 +7302,7 @@ checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7347,7 +7348,7 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7419,7 +7420,7 @@ dependencies = [ "prost 0.12.6", "prost-types 0.12.6", "regex", - "syn 2.0.93", + "syn 2.0.91", "tempfile", ] @@ -7439,7 +7440,7 @@ dependencies = [ "prost 0.13.4", "prost-types 0.13.4", "regex", - "syn 2.0.93", + "syn 2.0.91", "tempfile", ] @@ -7466,7 +7467,7 @@ dependencies = [ "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7479,7 +7480,7 @@ dependencies = [ "itertools 0.13.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -7660,9 +7661,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -7815,7 +7816,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -8126,9 +8127,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "rw-stream-sink" @@ -8287,7 +8288,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -9196,7 +9197,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -9280,7 +9281,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -9465,9 +9466,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -9483,13 +9484,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -9515,9 +9516,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.12.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", @@ -9531,14 +9532,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -9798,7 +9799,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -9995,7 +9996,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409 dependencies = [ "quote", "sp-crypto-hashing", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -10014,7 +10015,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk?tag=polkadot-stable2409 dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -10227,7 +10228,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -10412,7 +10413,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -10663,7 +10664,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -10788,9 +10789,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.93" +version = "2.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" dependencies = [ "proc-macro2", "quote", @@ -10817,7 +10818,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -10917,7 +10918,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -10928,7 +10929,7 @@ checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -11057,7 +11058,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -11217,7 +11218,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -11650,7 +11651,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "wasm-bindgen-shared", ] @@ -11685,7 +11686,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -12460,14 +12461,14 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] name = "xml-rs" -version = "0.8.24" +version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8b391c9a790b496184c29f7f93b9ed5b16abb306c05415b68bcc16e4d06432" +checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" [[package]] name = "xmltree" @@ -12522,7 +12523,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "synstructure 0.13.1", ] @@ -12544,7 +12545,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -12564,7 +12565,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", "synstructure 0.13.1", ] @@ -12585,7 +12586,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] @@ -12607,7 +12608,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.93", + "syn 2.0.91", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 95d9f731c..1b791baab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "7.1.0" +version = "7.2.0" authors = ["Cerebellum-Network"] edition = "2021" homepage = "https://cere.network/" diff --git a/pallets/ddc-customers/src/lib.rs b/pallets/ddc-customers/src/lib.rs index 1ac0b287f..91547c0a5 100644 --- a/pallets/ddc-customers/src/lib.rs +++ b/pallets/ddc-customers/src/lib.rs @@ -754,9 +754,9 @@ pub mod pallet { } impl CustomerCharger for Pallet { - fn charge_bucket_owner( + fn charge_customer( bucket_owner: T::AccountId, - billing_vault: T::AccountId, + payout_vault: T::AccountId, amount: u128, ) -> Result { let actually_charged: BalanceOf; @@ -793,7 +793,7 @@ pub mod pallet { ::Currency::transfer( &Self::account_id(), - &billing_vault, + &payout_vault, actually_charged, ExistenceRequirement::AllowDeath, )?; diff --git a/pallets/ddc-customers/src/tests.rs b/pallets/ddc-customers/src/tests.rs index 27fc85bb2..f347e3d51 100644 --- a/pallets/ddc-customers/src/tests.rs +++ b/pallets/ddc-customers/src/tests.rs @@ -216,7 +216,7 @@ fn charge_bucket_owner_works() { // successful transfer let charge1 = 10; - let charged = DdcCustomers::charge_bucket_owner(account_3, vault, charge1).unwrap(); + let charged = DdcCustomers::charge_customer(account_3, vault, charge1).unwrap(); assert_eq!(charge1, charged); let vault_balance = Balances::free_balance(vault); @@ -246,7 +246,7 @@ fn charge_bucket_owner_works() { // failed transfer let charge2 = 100u128; - let charge_result = DdcCustomers::charge_bucket_owner(account_3, vault, charge2).unwrap(); + let charge_result = DdcCustomers::charge_customer(account_3, vault, charge2).unwrap(); assert_eq!( Ledger::::get(account_3), Some(AccountsLedger { diff --git a/pallets/ddc-payouts/src/lib.rs b/pallets/ddc-payouts/src/lib.rs index 12df628fc..3d296d872 100644 --- a/pallets/ddc-payouts/src/lib.rs +++ b/pallets/ddc-payouts/src/lib.rs @@ -14,10 +14,11 @@ #![cfg_attr(not(feature = "std"), no_std)] #![recursion_limit = "256"] -#[cfg(test)] -pub(crate) mod mock; -#[cfg(test)] -mod tests; +// todo(yahortsaryk) tests for DAC v4 payments should be completely revised +// #[cfg(test)] +// pub(crate) mod mock; +// #[cfg(test)] +// mod tests; pub mod migrations; @@ -27,10 +28,10 @@ use ddc_primitives::{ customer::CustomerCharger as CustomerChargerType, node::NodeManager, pallet::PalletVisitor as PalletVisitorType, payout::PayoutProcessor, ClusterValidator, }, - BatchIndex, BillingFingerprintParams, BillingReportParams, BucketId, BucketUsage, ClusterId, - CustomerCharge, DdcEra, Fingerprint, MMRProof, MergeMMRHash, NodePubKey, NodeUsage, - PayableUsageHash, PayoutError, PayoutState, ProviderReward, AVG_SECONDS_MONTH, - MAX_PAYOUT_BATCH_COUNT, MAX_PAYOUT_BATCH_SIZE, MILLICENTS, + BatchIndex, ClusterId, CustomerCharge, DdcEra, EHDId, EhdEra, Fingerprint, MMRProof, + MergeMMRHash, NodePubKey, NodeUsage, PayableUsageHash, PaymentEra, PayoutError, + PayoutFingerprintParams, PayoutReceiptParams, PayoutState, MAX_PAYOUT_BATCH_COUNT, + MAX_PAYOUT_BATCH_SIZE, MILLICENTS, }; use frame_election_provider_support::SortedListProvider; use frame_support::{ @@ -51,16 +52,9 @@ use scale_info::prelude::string::String; use sp_core::H256; use sp_runtime::{ traits::{Convert, Hash}, - AccountId32, PerThing, Percent, Perquintill, + AccountId32, Percent, Perquintill, Saturating, }; use sp_std::{collections::btree_set::BTreeSet, prelude::*}; -#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, Default, Clone)] -pub struct BillingReportDebt { - pub cluster_id: ClusterId, - pub era: DdcEra, - pub batch_index: BatchIndex, - pub amount: u128, -} /// The balance type of this pallet. pub type BalanceOf = @@ -79,7 +73,7 @@ parameter_types! { #[frame_support::pallet] pub mod pallet { - use ddc_primitives::{traits::ValidatorVisitor, ClusterPricingParams}; + use ddc_primitives::traits::ValidatorVisitor; use frame_support::PalletId; use sp_io::hashing::blake2_128; use sp_runtime::traits::{AccountIdConversion, Zero}; @@ -88,7 +82,7 @@ pub mod pallet { /// The current storage version. const STORAGE_VERSION: frame_support::traits::StorageVersion = - frame_support::traits::StorageVersion::new(2); + frame_support::traits::StorageVersion::new(3); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -119,7 +113,7 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - BillingReportInitialized { + PayoutInitialized { cluster_id: ClusterId, era: DdcEra, }, @@ -132,15 +126,13 @@ pub mod pallet { era: DdcEra, batch_index: BatchIndex, customer_id: T::AccountId, - bucket_id: BucketId, amount: u128, }, - ChargeFailed { + ChargedPartially { cluster_id: ClusterId, era: DdcEra, batch_index: BatchIndex, customer_id: T::AccountId, - bucket_id: BucketId, charged: u128, expected_to_charge: u128, }, @@ -149,7 +141,6 @@ pub mod pallet { era: DdcEra, batch_index: BatchIndex, customer_id: T::AccountId, - bucket_id: BucketId, amount: u128, }, ChargingFinished { @@ -201,13 +192,13 @@ pub mod pallet { cluster_id: ClusterId, era: DdcEra, expected_reward: u128, - total_distributed_reward: u128, + total_distributed_rewards: u128, }, RewardingFinished { cluster_id: ClusterId, era: DdcEra, }, - BillingReportFinalized { + PayoutReceiptFinalized { cluster_id: ClusterId, era: DdcEra, }, @@ -219,12 +210,10 @@ pub mod pallet { amount: u128, error: DispatchError, }, - BillingFingerprintCommited { + PayoutFingerprintCommited { validator_id: T::AccountId, cluster_id: ClusterId, - era_id: DdcEra, - start_era: i64, - end_era: i64, + era_id: EhdEra, payers_merkle_root: PayableUsageHash, payees_merkle_root: PayableUsageHash, }, @@ -233,7 +222,7 @@ pub mod pallet { #[pallet::error] #[derive(PartialEq)] pub enum Error { - BillingReportDoesNotExist, + PayoutReceiptDoesNotExist, NotExpectedState, Unauthorized, BatchIndexAlreadyProcessed, @@ -253,21 +242,21 @@ pub mod pallet { IncorrectClusterId, ClusterProtocolParamsNotSet, TotalStoredBytesLessThanZero, - BillingFingerprintIsCommitted, - BillingFingerprintDoesNotExist, - NoQuorumOnBillingFingerprint, + PayoutFingerprintCommitted, + PayoutFingerprintDoesNotExist, + NoQuorumOnPayoutFingerprint, FailedToCreateMerkleRoot, FailedToVerifyMerkleProof, } #[pallet::storage] - pub type ActiveBillingReports = StorageDoubleMap< + pub type PayoutReceipts = StorageDoubleMap< _, Blake2_128Concat, ClusterId, Blake2_128Concat, - DdcEra, - BillingReport, + PaymentEra, + PayoutReceipt, >; #[pallet::storage] @@ -278,30 +267,32 @@ pub mod pallet { pub type OwingProviders = StorageDoubleMap<_, Blake2_128Concat, ClusterId, Blake2_128Concat, T::AccountId, u128>; - /// The Billing report is used as a synchronization object during the multi-step payout process + /// The Payout Receipt is used as a synchronization object during the multi-step payout process /// and contains overall information about the payout for a cluster in an era. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] #[scale_info(skip_type_params(T))] - pub struct BillingReport { + pub struct PayoutReceipt { pub state: PayoutState, pub vault: T::AccountId, pub fingerprint: Fingerprint, - pub total_customer_charge: CustomerCharge, - pub total_distributed_reward: u128, + pub total_collected_charges: u128, + pub total_distributed_rewards: u128, + pub total_settled_fees: u128, pub charging_max_batch_index: BatchIndex, pub charging_processed_batches: BoundedBTreeSet, pub rewarding_max_batch_index: BatchIndex, pub rewarding_processed_batches: BoundedBTreeSet, } - impl Default for BillingReport { + impl Default for PayoutReceipt { fn default() -> Self { Self { state: PayoutState::default(), vault: T::PalletId::get().into_account_truncating(), fingerprint: Default::default(), - total_customer_charge: CustomerCharge::default(), - total_distributed_reward: Zero::zero(), + total_collected_charges: Zero::zero(), + total_distributed_rewards: Zero::zero(), + total_settled_fees: Zero::zero(), charging_max_batch_index: Zero::zero(), charging_processed_batches: BoundedBTreeSet::default(), rewarding_max_batch_index: Zero::zero(), @@ -324,41 +315,35 @@ pub mod pallet { Finalized = 7, } - /// Billing fingerprint includes payment-sensitive data used to validate the payouts for a + /// Payout Fingerprint includes payment-sensitive data used to validate the payouts for a /// cluster in an era. The required quorum of validators must agree on the same payout /// fingerprint and commit it to let the payout process begin. The `payers_merkle_root` and /// `payees_merkle_root` hashes are being used to verify batches of customers and providers /// during the payout. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] - pub struct BillingFingerprint { + pub struct PayoutFingerprint { + pub ehd_id: String, pub cluster_id: ClusterId, - pub era_id: DdcEra, - pub start_era: i64, - pub end_era: i64, pub payers_merkle_root: PayableUsageHash, pub payees_merkle_root: PayableUsageHash, - pub cluster_usage: NodeUsage, pub validators: BTreeSet, } - impl BillingFingerprint { + impl PayoutFingerprint { pub fn selective_hash(&self) -> Fingerprint { let mut data = self.cluster_id.encode(); - data.extend_from_slice(&self.era_id.encode()); - data.extend_from_slice(&self.start_era.encode()); - data.extend_from_slice(&self.end_era.encode()); + data.extend_from_slice(&self.ehd_id.encode()); data.extend_from_slice(&self.payers_merkle_root.encode()); data.extend_from_slice(&self.payees_merkle_root.encode()); - data.extend_from_slice(&self.cluster_usage.encode()); // we truncate the `validators` field on purpose as it's appendable collection that is - // used for reaching the quorum on the billing fingerprint + // used for reaching the quorum on the payout fingerprint T::Hasher::hash(&data) } } #[pallet::storage] - pub type BillingFingerprints = - StorageMap<_, Blake2_128Concat, Fingerprint, BillingFingerprint>; + pub type PayoutFingerprints = + StorageMap<_, Blake2_128Concat, Fingerprint, PayoutFingerprint>; #[pallet::call] impl Pallet {} @@ -442,78 +427,6 @@ pub mod pallet { Ok(()) } - fn get_provider_reward( - payable_usage: &NodeUsage, - cluster_usage: &NodeUsage, - total_customer_charge: &CustomerCharge, - ) -> Option { - let mut total_reward = ProviderReward::default(); - - let mut ratio = Perquintill::from_rational( - payable_usage.transferred_bytes as u128, - cluster_usage.transferred_bytes as u128, - ); - - // ratio multiplied by X will be > 0, < X no overflow - total_reward.transfer = ratio * total_customer_charge.transfer; - - ratio = Perquintill::from_rational( - payable_usage.stored_bytes as u128, - cluster_usage.stored_bytes as u128, - ); - total_reward.storage = ratio * total_customer_charge.storage; - - ratio = - Perquintill::from_rational(payable_usage.number_of_puts, cluster_usage.number_of_puts); - total_reward.puts = ratio * total_customer_charge.puts; - - ratio = - Perquintill::from_rational(payable_usage.number_of_gets, cluster_usage.number_of_gets); - total_reward.gets = ratio * total_customer_charge.gets; - - Some(total_reward) - } - - #[allow(clippy::field_reassign_with_default)] - fn get_customer_charge( - pricing: &ClusterPricingParams, - payable_usage: &BucketUsage, - start_era: i64, - end_era: i64, - ) -> Result { - let mut total_charge = CustomerCharge::default(); - - total_charge.transfer = (|| -> Option { - (payable_usage.transferred_bytes as u128) - .checked_mul(pricing.unit_per_mb_streamed)? - .checked_div(byte_unit::MEBIBYTE) - })() - .ok_or(Error::::ArithmeticOverflow)?; - - // Calculate the duration of the period in seconds - let duration_seconds = end_era - start_era; - let fraction_of_month = - Perquintill::from_rational(duration_seconds as u64, AVG_SECONDS_MONTH as u64); - - total_charge.storage = fraction_of_month * - (|| -> Option { - (payable_usage.stored_bytes as u128) - .checked_mul(pricing.unit_per_mb_stored)? - .checked_div(byte_unit::MEBIBYTE) - })() - .ok_or(Error::::ArithmeticOverflow)?; - - total_charge.gets = (payable_usage.number_of_gets as u128) - .checked_mul(pricing.unit_per_get_request) - .ok_or(Error::::ArithmeticOverflow)?; - - total_charge.puts = (payable_usage.number_of_puts as u128) - .checked_mul(pricing.unit_per_put_request) - .ok_or(Error::::ArithmeticOverflow)?; - - Ok(total_charge) - } - #[pallet::genesis_config] pub struct GenesisConfig { pub feeder_account: Option, @@ -599,17 +512,14 @@ pub mod pallet { payers_merkle_root: PayableUsageHash, batch_index: BatchIndex, max_batch_index: BatchIndex, - payers: &[(BucketId, BucketUsage)], + payers: &[(T::AccountId, u128)], batch_proof: &MMRProof, ) -> Result { let payers_batch = payers .iter() - .map(|(bucket_id, usage)| { - let mut data = bucket_id.encode(); - data.extend_from_slice(&usage.stored_bytes.encode()); - data.extend_from_slice(&usage.transferred_bytes.encode()); - data.extend_from_slice(&usage.number_of_puts.encode()); - data.extend_from_slice(&usage.number_of_gets.encode()); + .map(|(customer_id, amount)| { + let mut data = customer_id.encode(); + data.extend_from_slice(&amount.encode()); T::Hasher::hash(&data) }) .collect::>(); @@ -631,17 +541,14 @@ pub mod pallet { payees_merkle_root: PayableUsageHash, batch_index: BatchIndex, max_batch_index: BatchIndex, - payees: &[(NodePubKey, NodeUsage)], + payees: &[(T::AccountId, u128)], batch_proof: &MMRProof, ) -> Result { let payees_batch = payees .iter() - .map(|(node_key, usage)| { - let mut data = node_key.encode(); - data.extend_from_slice(&usage.stored_bytes.encode()); - data.extend_from_slice(&usage.transferred_bytes.encode()); - data.extend_from_slice(&usage.number_of_puts.encode()); - data.extend_from_slice(&usage.number_of_gets.encode()); + .map(|(provider_id, amount)| { + let mut data = provider_id.encode(); + data.extend_from_slice(&amount.encode()); T::Hasher::hash(&data) }) .collect::>(); @@ -710,55 +617,47 @@ pub mod pallet { } impl PayoutProcessor for Pallet { - fn commit_billing_fingerprint( + fn commit_payout_fingerprint( validator: T::AccountId, cluster_id: ClusterId, - era_id: DdcEra, - start_era: i64, - end_era: i64, + ehd_id: String, payers_merkle_root: PayableUsageHash, payees_merkle_root: PayableUsageHash, - cluster_usage: NodeUsage, ) -> DispatchResult { - ensure!(end_era > start_era, Error::::BadRequest); ensure!(payers_merkle_root != Default::default(), Error::::BadRequest); ensure!(payees_merkle_root != Default::default(), Error::::BadRequest); let last_paid_era = T::ClusterValidator::get_last_paid_era(&cluster_id)?; + let era_id = EHDId::try_from(ehd_id.clone()).map_err(|_| Error::::BadRequest)?.2; ensure!(era_id > last_paid_era, Error::::BadRequest); - let inited_billing_fingerprint = BillingFingerprint:: { + let inited_payout_fingerprint = PayoutFingerprint:: { cluster_id, - era_id, - start_era, - end_era, + ehd_id, payers_merkle_root, payees_merkle_root, - cluster_usage, validators: Default::default(), }; - let fingerprint = inited_billing_fingerprint.selective_hash::(); + let fingerprint = inited_payout_fingerprint.selective_hash::(); - let mut billing_fingerprint = if let Some(commited_billing_fingerprint) = - BillingFingerprints::::get(fingerprint) + let mut payout_fingerprint = if let Some(commited_payout_fingerprint) = + PayoutFingerprints::::get(fingerprint) { - commited_billing_fingerprint + commited_payout_fingerprint } else { - inited_billing_fingerprint + inited_payout_fingerprint }; ensure!( - billing_fingerprint.validators.insert(validator.clone()), - Error::::BillingFingerprintIsCommitted + payout_fingerprint.validators.insert(validator.clone()), + Error::::PayoutFingerprintCommitted ); - BillingFingerprints::::insert(fingerprint, billing_fingerprint); - Self::deposit_event(Event::::BillingFingerprintCommited { + PayoutFingerprints::::insert(fingerprint, payout_fingerprint); + Self::deposit_event(Event::::PayoutFingerprintCommited { validator_id: validator, cluster_id, era_id, - start_era, - end_era, payers_merkle_root, payees_merkle_root, }); @@ -766,55 +665,55 @@ pub mod pallet { Ok(()) } - fn begin_billing_report( + fn begin_payout( cluster_id: ClusterId, - era: DdcEra, + era: PaymentEra, fingerprint: Fingerprint, ) -> DispatchResult { ensure!( - ActiveBillingReports::::try_get(cluster_id, era).is_err(), + PayoutReceipts::::try_get(cluster_id, era).is_err(), Error::::NotExpectedState ); - let billing_fingerprint = BillingFingerprints::::try_get(fingerprint) - .map_err(|_| Error::::BillingFingerprintDoesNotExist)?; + let payout_fingerprint = PayoutFingerprints::::try_get(fingerprint) + .map_err(|_| Error::::PayoutFingerprintDoesNotExist)?; ensure!( T::ValidatorVisitor::is_quorum_reached( T::ValidatorsQuorum::get(), - billing_fingerprint.validators.len(), + payout_fingerprint.validators.len(), ), - Error::::NoQuorumOnBillingFingerprint + Error::::NoQuorumOnPayoutFingerprint ); - let billing_report = BillingReport:: { + let payout_receipt = PayoutReceipt:: { vault: Self::account_id(), fingerprint, state: PayoutState::Initialized, ..Default::default() }; - ActiveBillingReports::::insert(cluster_id, era, billing_report); + PayoutReceipts::::insert(cluster_id, era, payout_receipt); - Self::deposit_event(Event::::BillingReportInitialized { cluster_id, era }); + Self::deposit_event(Event::::PayoutInitialized { cluster_id, era }); Ok(()) } fn begin_charging_customers( cluster_id: ClusterId, - era: DdcEra, + era: PaymentEra, max_batch_index: BatchIndex, ) -> DispatchResult { ensure!(max_batch_index < MaxBatchesCount::get(), Error::::BatchIndexOverflow); - let mut billing_report = ActiveBillingReports::::try_get(cluster_id, era) - .map_err(|_| Error::::BillingReportDoesNotExist)?; + let mut payout_receipt = PayoutReceipts::::try_get(cluster_id, era) + .map_err(|_| Error::::PayoutReceiptDoesNotExist)?; - ensure!(billing_report.state == PayoutState::Initialized, Error::::NotExpectedState); + ensure!(payout_receipt.state == PayoutState::Initialized, Error::::NotExpectedState); - billing_report.charging_max_batch_index = max_batch_index; - billing_report.state = PayoutState::ChargingCustomers; - ActiveBillingReports::::insert(cluster_id, era, billing_report); + payout_receipt.charging_max_batch_index = max_batch_index; + payout_receipt.state = PayoutState::ChargingCustomers; + PayoutReceipts::::insert(cluster_id, era, payout_receipt); Self::deposit_event(Event::::ChargingStarted { cluster_id, era }); @@ -823,9 +722,9 @@ pub mod pallet { fn send_charging_customers_batch( cluster_id: ClusterId, - era: DdcEra, + era: PaymentEra, batch_index: BatchIndex, - payers: &[(BucketId, BucketUsage)], + payers: &[(T::AccountId, u128)], batch_proof: MMRProof, ) -> DispatchResult { ensure!( @@ -833,87 +732,66 @@ pub mod pallet { Error::::BatchSizeIsOutOfBounds ); - let billing_report = ActiveBillingReports::::try_get(cluster_id, era) - .map_err(|_| Error::::BillingReportDoesNotExist)?; + let payout_receipt = PayoutReceipts::::try_get(cluster_id, era) + .map_err(|_| Error::::PayoutReceiptDoesNotExist)?; ensure!( - billing_report.state == PayoutState::ChargingCustomers, + payout_receipt.state == PayoutState::ChargingCustomers, Error::::NotExpectedState ); ensure!( - billing_report.charging_max_batch_index >= batch_index, + payout_receipt.charging_max_batch_index >= batch_index, Error::::BatchIndexIsOutOfRange ); ensure!( - !billing_report.charging_processed_batches.contains(&batch_index), + !payout_receipt.charging_processed_batches.contains(&batch_index), Error::::BatchIndexAlreadyProcessed ); - let billing_fingerprint = BillingFingerprints::::try_get(billing_report.fingerprint) - .map_err(|_| Error::::BillingFingerprintDoesNotExist)?; + let payout_fingerprint = PayoutFingerprints::::try_get(payout_receipt.fingerprint) + .map_err(|_| Error::::PayoutFingerprintDoesNotExist)?; let is_batch_verifed = Self::is_customers_batch_valid( - billing_fingerprint.payers_merkle_root, + payout_fingerprint.payers_merkle_root, batch_index, - billing_report.charging_max_batch_index, + payout_receipt.charging_max_batch_index, payers, &batch_proof, )?; ensure!(is_batch_verifed, Error::::BatchValidationFailed); - let pricing = T::ClusterProtocol::get_pricing_params(&cluster_id) - .map_err(|_| Error::::NotExpectedClusterState)?; - - let mut updated_billing_report = billing_report; - for (bucket_ref, payable_usage) in payers { - let bucket_id = *bucket_ref; - let customer_id = T::BucketManager::get_bucket_owner_id(bucket_id)?; - - let mut customer_charge = get_customer_charge::( - &pricing, - payable_usage, - billing_fingerprint.start_era, - billing_fingerprint.end_era, - )?; - let total_customer_charge = (|| -> Option { - customer_charge - .transfer - .checked_add(customer_charge.storage)? - .checked_add(customer_charge.puts)? - .checked_add(customer_charge.gets) - })() - .ok_or(Error::::ArithmeticOverflow)?; - - let amount_actually_charged = match T::CustomerCharger::charge_bucket_owner( + let mut updated_payout_receipt = payout_receipt; + for (customer_id, total_charge) in payers { + let actual_charge = match T::CustomerCharger::charge_customer( customer_id.clone(), - updated_billing_report.vault.clone(), - total_customer_charge, + updated_payout_receipt.vault.clone(), + *total_charge, ) { - Ok(actually_charged) => actually_charged, + Ok(actual_charge) => actual_charge, Err(e) => { Self::deposit_event(Event::::ChargeError { cluster_id, era, batch_index, customer_id: customer_id.clone(), - amount: total_customer_charge, + amount: *total_charge, error: e, }); 0 }, }; - if amount_actually_charged < total_customer_charge { + if actual_charge < *total_charge { // debt let mut customer_debt = DebtorCustomers::::try_get(cluster_id, customer_id.clone()) .unwrap_or_else(|_| Zero::zero()); - let debt = total_customer_charge - .checked_sub(amount_actually_charged) + let debt = total_charge + .checked_sub(actual_charge) .ok_or(Error::::ArithmeticOverflow)?; customer_debt = @@ -926,122 +804,80 @@ pub mod pallet { era, batch_index, customer_id: customer_id.clone(), - bucket_id, amount: debt, }); - Self::deposit_event(Event::::ChargeFailed { - cluster_id, - era, - batch_index, - customer_id: customer_id.clone(), - bucket_id, - charged: amount_actually_charged, - expected_to_charge: total_customer_charge, - }); - - // something was charged and should be added - // calculate ratio - let ratio = - Perquintill::from_rational(amount_actually_charged, total_customer_charge); - - customer_charge.storage = ratio * customer_charge.storage; - customer_charge.transfer = ratio * customer_charge.transfer; - customer_charge.gets = ratio * customer_charge.gets; - customer_charge.puts = ratio * customer_charge.puts; + if actual_charge > 0 { + Self::deposit_event(Event::::ChargedPartially { + cluster_id, + era, + batch_index, + customer_id: customer_id.clone(), + charged: actual_charge, + expected_to_charge: *total_charge, + }); + } } else { Self::deposit_event(Event::::Charged { cluster_id, era, batch_index, customer_id: customer_id.clone(), - bucket_id, - amount: total_customer_charge, + amount: *total_charge, }); } - updated_billing_report.total_customer_charge.storage = updated_billing_report - .total_customer_charge - .storage - .checked_add(customer_charge.storage) - .ok_or(Error::::ArithmeticOverflow)?; - - updated_billing_report.total_customer_charge.transfer = updated_billing_report - .total_customer_charge - .transfer - .checked_add(customer_charge.transfer) - .ok_or(Error::::ArithmeticOverflow)?; - - updated_billing_report.total_customer_charge.puts = updated_billing_report - .total_customer_charge - .puts - .checked_add(customer_charge.puts) + updated_payout_receipt.total_collected_charges = updated_payout_receipt + .total_collected_charges + .checked_add(actual_charge.saturated_into::()) .ok_or(Error::::ArithmeticOverflow)?; - - updated_billing_report.total_customer_charge.gets = updated_billing_report - .total_customer_charge - .gets - .checked_add(customer_charge.gets) - .ok_or(Error::::ArithmeticOverflow)?; - - T::BucketManager::update_total_bucket_usage( - &cluster_id, - bucket_id, - customer_id, - payable_usage, - )?; } - updated_billing_report + updated_payout_receipt .charging_processed_batches .try_insert(batch_index) .map_err(|_| Error::::BoundedVecOverflow)?; - ActiveBillingReports::::insert(cluster_id, era, updated_billing_report); + PayoutReceipts::::insert(cluster_id, era, updated_payout_receipt); Ok(()) } - fn end_charging_customers(cluster_id: ClusterId, era: DdcEra) -> DispatchResult { - let mut billing_report = ActiveBillingReports::::try_get(cluster_id, era) - .map_err(|_| Error::::BillingReportDoesNotExist)?; + fn end_charging_customers(cluster_id: ClusterId, era: PaymentEra) -> DispatchResult { + let mut payout_receipt = PayoutReceipts::::try_get(cluster_id, era) + .map_err(|_| Error::::PayoutReceiptDoesNotExist)?; ensure!( - billing_report.state == PayoutState::ChargingCustomers, + payout_receipt.state == PayoutState::ChargingCustomers, Error::::NotExpectedState ); Self::validate_batches( - &billing_report.charging_processed_batches, - &billing_report.charging_max_batch_index, + &payout_receipt.charging_processed_batches, + &payout_receipt.charging_max_batch_index, )?; Self::deposit_event(Event::::ChargingFinished { cluster_id, era }); - // deduct fees let fees = T::ClusterProtocol::get_fees_params(&cluster_id) .map_err(|_| Error::::ClusterProtocolParamsNotSet)?; - let total_customer_charge = (|| -> Option { - billing_report - .total_customer_charge - .transfer - .checked_add(billing_report.total_customer_charge.storage)? - .checked_add(billing_report.total_customer_charge.puts)? - .checked_add(billing_report.total_customer_charge.gets) - })() - .ok_or(Error::::ArithmeticOverflow)?; - - let treasury_fee = fees.treasury_share * total_customer_charge; - let validators_fee = fees.validators_share * total_customer_charge; - let cluster_reserve_fee = fees.cluster_reserve_share * total_customer_charge; + let treasury_fee = fees.treasury_share * payout_receipt.total_collected_charges; + let validators_fee = fees.validators_share * payout_receipt.total_collected_charges; + let cluster_reserve_fee = + fees.cluster_reserve_share * payout_receipt.total_collected_charges; if treasury_fee > 0 { charge_treasury_fees::( treasury_fee, - &billing_report.vault, + &payout_receipt.vault, &T::TreasuryVisitor::get_account_id(), )?; + payout_receipt.total_settled_fees = payout_receipt + .total_settled_fees + .checked_add(treasury_fee.saturated_into::()) + .ok_or(Error::::ArithmeticOverflow)?; + Self::deposit_event(Event::::TreasuryFeesCollected { cluster_id, era, @@ -1052,10 +888,16 @@ pub mod pallet { if cluster_reserve_fee > 0 { charge_cluster_reserve_fees::( cluster_reserve_fee, - &billing_report.vault, + &payout_receipt.vault, &T::ClusterProtocol::get_reserve_account_id(&cluster_id) .map_err(|_| Error::::NotExpectedClusterState)?, )?; + + payout_receipt.total_settled_fees = payout_receipt + .total_settled_fees + .checked_add(cluster_reserve_fee.saturated_into::()) + .ok_or(Error::::ArithmeticOverflow)?; + Self::deposit_event(Event::::ClusterReserveFeesCollected { cluster_id, era, @@ -1064,7 +906,13 @@ pub mod pallet { } if validators_fee > 0 { - charge_validator_fees::(validators_fee, &billing_report.vault, cluster_id, era)?; + charge_validator_fees::(validators_fee, &payout_receipt.vault, cluster_id, era)?; + + payout_receipt.total_settled_fees = payout_receipt + .total_settled_fees + .checked_add(validators_fee.saturated_into::()) + .ok_or(Error::::ArithmeticOverflow)?; + Self::deposit_event(Event::::ValidatorFeesCollected { cluster_id, era, @@ -1072,47 +920,30 @@ pub mod pallet { }); } - // 1 - (X + Y + Z) > 0, 0 < X + Y + Z < 1 - let total_left_from_one = - (fees.treasury_share + fees.validators_share + fees.cluster_reserve_share) - .left_from_one(); - - if !total_left_from_one.is_zero() { - // X * Z < X, 0 < Z < 1 - billing_report.total_customer_charge.transfer = - total_left_from_one * billing_report.total_customer_charge.transfer; - billing_report.total_customer_charge.storage = - total_left_from_one * billing_report.total_customer_charge.storage; - billing_report.total_customer_charge.puts = - total_left_from_one * billing_report.total_customer_charge.puts; - billing_report.total_customer_charge.gets = - total_left_from_one * billing_report.total_customer_charge.gets; - } - - billing_report.state = PayoutState::CustomersChargedWithFees; - ActiveBillingReports::::insert(cluster_id, era, billing_report); + payout_receipt.state = PayoutState::CustomersChargedWithFees; + PayoutReceipts::::insert(cluster_id, era, payout_receipt); Ok(()) } fn begin_rewarding_providers( cluster_id: ClusterId, - era: DdcEra, + era: PaymentEra, max_batch_index: BatchIndex, ) -> DispatchResult { ensure!(max_batch_index < MaxBatchesCount::get(), Error::::BatchIndexOverflow); - let mut billing_report = ActiveBillingReports::::try_get(cluster_id, era) - .map_err(|_| Error::::BillingReportDoesNotExist)?; + let mut payout_receipt = PayoutReceipts::::try_get(cluster_id, era) + .map_err(|_| Error::::PayoutReceiptDoesNotExist)?; ensure!( - billing_report.state == PayoutState::CustomersChargedWithFees, + payout_receipt.state == PayoutState::CustomersChargedWithFees, Error::::NotExpectedState ); - billing_report.rewarding_max_batch_index = max_batch_index; - billing_report.state = PayoutState::RewardingProviders; - ActiveBillingReports::::insert(cluster_id, era, billing_report); + payout_receipt.rewarding_max_batch_index = max_batch_index; + payout_receipt.state = PayoutState::RewardingProviders; + PayoutReceipts::::insert(cluster_id, era, payout_receipt); Self::deposit_event(Event::::RewardingStarted { cluster_id, era }); @@ -1121,9 +952,9 @@ pub mod pallet { fn send_rewarding_providers_batch( cluster_id: ClusterId, - era: DdcEra, + era: PaymentEra, batch_index: BatchIndex, - payees: &[(NodePubKey, NodeUsage)], + payees: &[(T::AccountId, u128)], batch_proof: MMRProof, ) -> DispatchResult { ensure!( @@ -1131,29 +962,29 @@ pub mod pallet { Error::::BatchSizeIsOutOfBounds ); - let billing_report = ActiveBillingReports::::try_get(cluster_id, era) - .map_err(|_| Error::::BillingReportDoesNotExist)?; + let payout_receipt = PayoutReceipts::::try_get(cluster_id, era) + .map_err(|_| Error::::PayoutReceiptDoesNotExist)?; ensure!( - billing_report.state == PayoutState::RewardingProviders, + payout_receipt.state == PayoutState::RewardingProviders, Error::::NotExpectedState ); ensure!( - billing_report.rewarding_max_batch_index >= batch_index, + payout_receipt.rewarding_max_batch_index >= batch_index, Error::::BatchIndexIsOutOfRange ); ensure!( - !billing_report.rewarding_processed_batches.contains(&batch_index), + !payout_receipt.rewarding_processed_batches.contains(&batch_index), Error::::BatchIndexAlreadyProcessed ); - let billing_fingerprint = BillingFingerprints::::try_get(billing_report.fingerprint) - .map_err(|_| Error::::BillingFingerprintDoesNotExist)?; + let payout_fingerprint = PayoutFingerprints::::try_get(payout_receipt.fingerprint) + .map_err(|_| Error::::PayoutFingerprintDoesNotExist)?; let is_batch_verified = Self::is_providers_batch_valid( - billing_fingerprint.payees_merkle_root, + payout_fingerprint.payees_merkle_root, batch_index, - billing_report.rewarding_max_batch_index, + payout_receipt.rewarding_max_batch_index, payees, &batch_proof, )?; @@ -1161,63 +992,41 @@ pub mod pallet { ensure!(is_batch_verified, Error::::BatchValidationFailed); let max_dust = MaxDust::get().saturated_into::>(); - let mut updated_billing_report = billing_report.clone(); - for (node_key, payable_usage) in payees { - let provider_id = T::NodeManager::get_node_provider_id(node_key)?; - - let provider_reward = get_provider_reward( - payable_usage, - &billing_fingerprint.cluster_usage, - &billing_report.total_customer_charge, - ) - .ok_or(Error::::ArithmeticOverflow)?; - - let total_provider_reward = (|| -> Option { - provider_reward - .transfer - .checked_add(provider_reward.storage)? - .checked_add(provider_reward.puts)? - .checked_add(provider_reward.gets) - })() - .ok_or(Error::::ArithmeticOverflow)?; - - let mut reward_ = total_provider_reward; - let mut reward: BalanceOf = - total_provider_reward.saturated_into::>(); - - if total_provider_reward > 0 { - let vault_balance = ::Currency::free_balance( - &updated_billing_report.vault, - ) - ::Currency::minimum_balance(); + let mut updated_payout_receipt = payout_receipt.clone(); + for (provider_id, total_reward) in payees { + let mut actual_reward: BalanceOf = + (*total_reward).saturated_into::>(); + + if *total_reward > 0 { + let vault_balance = T::Currency::free_balance(&updated_payout_receipt.vault) + .saturating_sub(T::Currency::minimum_balance()); // 10000000000001 > 10000000000000 but is still ok - if reward > vault_balance { - if reward - vault_balance > max_dust { + if actual_reward > vault_balance { + if actual_reward - vault_balance > max_dust { Self::deposit_event(Event::::NotDistributedReward { cluster_id, era, batch_index, node_provider_id: provider_id.clone(), - expected_reward: total_provider_reward, + expected_reward: *total_reward, distributed_reward: vault_balance, }); } - reward = vault_balance; + actual_reward = vault_balance; } - ::Currency::transfer( - &updated_billing_report.vault, - &provider_id, - reward, + T::Currency::transfer( + &updated_payout_receipt.vault, + provider_id, + actual_reward, ExistenceRequirement::AllowDeath, )?; - reward_ = reward.saturated_into::(); - - updated_billing_report.total_distributed_reward = updated_billing_report - .total_distributed_reward - .checked_add(reward_) + updated_payout_receipt.total_distributed_rewards = updated_payout_receipt + .total_distributed_rewards + .checked_add(actual_reward.saturated_into::()) .ok_or(Error::::ArithmeticOverflow)?; } @@ -1225,128 +1034,122 @@ pub mod pallet { cluster_id, era, batch_index, - node_provider_id: provider_id, - rewarded: reward_, - expected_to_reward: total_provider_reward, + node_provider_id: provider_id.clone(), + rewarded: actual_reward.saturated_into(), + expected_to_reward: *total_reward, }); - - T::NodeManager::update_total_node_usage(node_key, payable_usage)?; } - updated_billing_report + updated_payout_receipt .rewarding_processed_batches .try_insert(batch_index) .map_err(|_| Error::::BoundedVecOverflow)?; - ActiveBillingReports::::insert(cluster_id, era, updated_billing_report); + PayoutReceipts::::insert(cluster_id, era, updated_payout_receipt); Ok(()) } - fn end_rewarding_providers(cluster_id: ClusterId, era: DdcEra) -> DispatchResult { - let mut billing_report = ActiveBillingReports::::try_get(cluster_id, era) - .map_err(|_| Error::::BillingReportDoesNotExist)?; + fn end_rewarding_providers(cluster_id: ClusterId, era: PaymentEra) -> DispatchResult { + let mut payout_receipt = PayoutReceipts::::try_get(cluster_id, era) + .map_err(|_| Error::::PayoutReceiptDoesNotExist)?; ensure!( - billing_report.state == PayoutState::RewardingProviders, + payout_receipt.state == PayoutState::RewardingProviders, Error::::NotExpectedState ); Self::validate_batches( - &billing_report.rewarding_processed_batches, - &billing_report.rewarding_max_batch_index, + &payout_receipt.rewarding_processed_batches, + &payout_receipt.rewarding_max_batch_index, )?; - let expected_amount_to_reward = (|| -> Option { - billing_report - .total_customer_charge - .transfer - .checked_add(billing_report.total_customer_charge.storage)? - .checked_add(billing_report.total_customer_charge.puts)? - .checked_add(billing_report.total_customer_charge.gets) - })() - .ok_or(Error::::ArithmeticOverflow)?; - - if expected_amount_to_reward - billing_report.total_distributed_reward > MaxDust::get() + if payout_receipt + .total_collected_charges + .saturating_sub(payout_receipt.total_distributed_rewards) + .saturating_sub(payout_receipt.total_settled_fees) > + MaxDust::get() { Self::deposit_event(Event::::NotDistributedOverallReward { cluster_id, era, - expected_reward: expected_amount_to_reward, - total_distributed_reward: billing_report.total_distributed_reward, + expected_reward: payout_receipt + .total_collected_charges + .saturating_sub(payout_receipt.total_settled_fees), + total_distributed_rewards: payout_receipt.total_distributed_rewards, }); } - billing_report.state = PayoutState::ProvidersRewarded; - ActiveBillingReports::::insert(cluster_id, era, billing_report); + payout_receipt.state = PayoutState::ProvidersRewarded; + PayoutReceipts::::insert(cluster_id, era, payout_receipt); Self::deposit_event(Event::::RewardingFinished { cluster_id, era }); Ok(()) } - fn end_billing_report(cluster_id: ClusterId, era: DdcEra) -> DispatchResult { - let mut billing_report = ActiveBillingReports::::try_get(cluster_id, era) - .map_err(|_| Error::::BillingReportDoesNotExist)?; + fn end_payout(cluster_id: ClusterId, era: PaymentEra) -> DispatchResult { + let mut payout_receipt = PayoutReceipts::::try_get(cluster_id, era) + .map_err(|_| Error::::PayoutReceiptDoesNotExist)?; ensure!( - billing_report.state == PayoutState::ProvidersRewarded, + payout_receipt.state == PayoutState::ProvidersRewarded, Error::::NotExpectedState ); - billing_report.charging_processed_batches.clear(); - billing_report.rewarding_processed_batches.clear(); - billing_report.state = PayoutState::Finalized; + payout_receipt.charging_processed_batches.clear(); + payout_receipt.rewarding_processed_batches.clear(); + payout_receipt.state = PayoutState::Finalized; - ActiveBillingReports::::insert(cluster_id, era, billing_report); - Self::deposit_event(Event::::BillingReportFinalized { cluster_id, era }); + PayoutReceipts::::insert(cluster_id, era, payout_receipt); + Self::deposit_event(Event::::PayoutReceiptFinalized { cluster_id, era }); Ok(()) } - fn get_billing_report_status(cluster_id: &ClusterId, era: DdcEra) -> PayoutState { - let billing_report = ActiveBillingReports::::get(cluster_id, era); - match billing_report { + fn get_payout_state(cluster_id: &ClusterId, era: PaymentEra) -> PayoutState { + let payout_receipt = PayoutReceipts::::get(cluster_id, era); + match payout_receipt { Some(report) => report.state, None => PayoutState::NotInitialized, } } - fn all_customer_batches_processed(cluster_id: &ClusterId, era_id: DdcEra) -> bool { - let billing_report = match ActiveBillingReports::::try_get(cluster_id, era_id) { + fn is_customers_charging_finished(cluster_id: &ClusterId, era_id: PaymentEra) -> bool { + let payout_receipt = match PayoutReceipts::::try_get(cluster_id, era_id) { Ok(report) => report, Err(_) => return false, }; Self::validate_batches( - &billing_report.charging_processed_batches, - &billing_report.charging_max_batch_index, + &payout_receipt.charging_processed_batches, + &payout_receipt.charging_max_batch_index, ) .is_ok() } - fn all_provider_batches_processed(cluster_id: &ClusterId, era_id: DdcEra) -> bool { - let billing_report = match ActiveBillingReports::::try_get(cluster_id, era_id) { + fn is_providers_rewarding_finished(cluster_id: &ClusterId, era_id: PaymentEra) -> bool { + let payout_receipt = match PayoutReceipts::::try_get(cluster_id, era_id) { Ok(report) => report, Err(_) => return false, }; Self::validate_batches( - &billing_report.rewarding_processed_batches, - &billing_report.rewarding_max_batch_index, + &payout_receipt.rewarding_processed_batches, + &payout_receipt.rewarding_max_batch_index, ) .is_ok() } - fn get_next_customer_batch_for_payment( + fn get_next_customers_batch( cluster_id: &ClusterId, - era_id: DdcEra, + era_id: PaymentEra, ) -> Result, PayoutError> { - let billing_report = ActiveBillingReports::::try_get(cluster_id, era_id) - .map_err(|_| PayoutError::BillingReportDoesNotExist)?; + let payout_receipt = PayoutReceipts::::try_get(cluster_id, era_id) + .map_err(|_| PayoutError::PayoutReceiptDoesNotExist)?; - for batch_index in 0..=billing_report.charging_max_batch_index { - if !billing_report.charging_processed_batches.contains(&batch_index) { + for batch_index in 0..=payout_receipt.charging_max_batch_index { + if !payout_receipt.charging_processed_batches.contains(&batch_index) { return Ok(Some(batch_index)); } } @@ -1354,15 +1157,15 @@ pub mod pallet { Ok(None) } - fn get_next_provider_batch_for_payment( + fn get_next_providers_batch( cluster_id: &ClusterId, - era_id: DdcEra, + era_id: PaymentEra, ) -> Result, PayoutError> { - let billing_report = ActiveBillingReports::::try_get(cluster_id, era_id) - .map_err(|_| PayoutError::BillingReportDoesNotExist)?; + let payout_receipt = PayoutReceipts::::try_get(cluster_id, era_id) + .map_err(|_| PayoutError::PayoutReceiptDoesNotExist)?; - for batch_index in 0..=billing_report.rewarding_max_batch_index { - if !billing_report.rewarding_processed_batches.contains(&batch_index) { + for batch_index in 0..=payout_receipt.rewarding_max_batch_index { + if !payout_receipt.rewarding_processed_batches.contains(&batch_index) { return Ok(Some(batch_index)); } } @@ -1370,7 +1173,7 @@ pub mod pallet { Ok(None) } - fn create_billing_report(vault: T::AccountId, params: BillingReportParams) { + fn create_payout_receipt(vault: T::AccountId, params: PayoutReceiptParams) { let mut charging_processed_batches = BoundedBTreeSet::::new(); for batch in params.charging_processed_batches { @@ -1387,37 +1190,33 @@ pub mod pallet { .expect("Rewarding batch to be inserted"); } - let billing_report = BillingReport:: { + let payout_receipt = PayoutReceipt:: { vault, state: params.state, fingerprint: params.fingerprint, - total_customer_charge: params.total_customer_charge, - total_distributed_reward: params.total_distributed_reward, + total_collected_charges: params.total_collected_charges, + total_distributed_rewards: params.total_distributed_rewards, + total_settled_fees: params.total_settled_fees, charging_max_batch_index: params.charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index: params.rewarding_max_batch_index, rewarding_processed_batches, }; - ActiveBillingReports::::insert(params.cluster_id, params.era, billing_report); + PayoutReceipts::::insert(params.cluster_id, params.era, payout_receipt); } - fn create_billing_fingerprint( - params: BillingFingerprintParams, - ) -> Fingerprint { - let billing_fingerprint = BillingFingerprint:: { + fn create_payout_fingerprint(params: PayoutFingerprintParams) -> Fingerprint { + let payout_fingerprint = PayoutFingerprint:: { cluster_id: params.cluster_id, - era_id: params.era, - start_era: params.start_era, - end_era: params.end_era, + ehd_id: params.ehd_id, payers_merkle_root: params.payers_merkle_root, payees_merkle_root: params.payees_merkle_root, - cluster_usage: params.cluster_usage, validators: params.validators, }; - let fingerprint = billing_fingerprint.selective_hash::(); - BillingFingerprints::::insert(fingerprint, billing_fingerprint); + let fingerprint = payout_fingerprint.selective_hash::(); + PayoutFingerprints::::insert(fingerprint, payout_fingerprint); fingerprint } diff --git a/pallets/ddc-payouts/src/migrations.rs b/pallets/ddc-payouts/src/migrations.rs index f347c8d79..a0c3fa67c 100644 --- a/pallets/ddc-payouts/src/migrations.rs +++ b/pallets/ddc-payouts/src/migrations.rs @@ -1,4 +1,4 @@ -use frame_support::{migration, storage_alias, traits::OnRuntimeUpgrade}; +use frame_support::{migration, storage::unhashed, storage_alias, traits::OnRuntimeUpgrade}; use log; use sp_runtime::Saturating; @@ -106,7 +106,7 @@ pub mod v2 { pub state: PayoutState, pub vault: T::AccountId, pub fingerprint: Fingerprint, - pub total_customer_charge: CustomerCharge, + pub total_customer_charge: CustomerCharge, // removed field pub total_distributed_reward: u128, pub charging_max_batch_index: BatchIndex, pub charging_processed_batches: BoundedBTreeSet, @@ -124,6 +124,41 @@ pub mod v2 { BillingReport, >; + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] + pub struct PayoutFingerprint { + pub cluster_id: ClusterId, + pub era_id: DdcEra, // removed field + pub start_era: i64, // removed field + pub end_era: i64, // removed field + pub payers_merkle_root: PayableUsageHash, + pub payees_merkle_root: PayableUsageHash, + pub cluster_usage: NodeUsage, + pub validators: BTreeSet, + } + + impl PayoutFingerprint { + pub fn selective_hash(&self) -> Fingerprint { + let mut data = self.cluster_id.encode(); + data.extend_from_slice(&self.era_id.encode()); + data.extend_from_slice(&self.start_era.encode()); + data.extend_from_slice(&self.end_era.encode()); + data.extend_from_slice(&self.payers_merkle_root.encode()); + data.extend_from_slice(&self.payees_merkle_root.encode()); + data.extend_from_slice(&self.cluster_usage.encode()); + // we truncate the `validators` field on purpose as it's appendable collection that is + // used for reaching the quorum on the billing fingerprint + T::Hasher::hash(&data) + } + } + + #[storage_alias] + pub type BillingFingerprints = StorageMap< + crate::Pallet, + Blake2_128Concat, + Fingerprint, + PayoutFingerprint<::AccountId>, + >; + pub fn migrate_to_v2() -> Weight { let on_chain_version = Pallet::::on_chain_storage_version(); let current_version = Pallet::::in_code_storage_version(); @@ -143,46 +178,46 @@ pub mod v2 { " >>> Updating Billing Reports. Migrating {} reports...", count ); - let mut billing_fingerprints: Vec> = Vec::new(); + let mut billing_fingerprints: Vec> = Vec::new(); v2::ActiveBillingReports::::translate::, _>( - |cluster_id: ClusterId, era_id: DdcEra, billing_report: v1::BillingReport| { + |cluster_id: ClusterId, era_id: DdcEra, payout_receipt: v1::BillingReport| { log::info!(target: LOG_TARGET, "Migrating Billing Report for cluster_id {:?} era_id {:?}", cluster_id, era_id); translated.saturating_inc(); - let billing_fingerprint = BillingFingerprint { + let payout_fingerprint = v2::PayoutFingerprint { cluster_id, era_id, - start_era: billing_report.start_era, - end_era: billing_report.end_era, + start_era: payout_receipt.start_era, + end_era: payout_receipt.end_era, payers_merkle_root: Default::default(), payees_merkle_root: Default::default(), - cluster_usage: billing_report.total_node_usage, + cluster_usage: payout_receipt.total_node_usage, validators: Default::default(), }; - let fingerprint = billing_fingerprint.selective_hash::(); + let fingerprint = payout_fingerprint.selective_hash::(); - billing_fingerprints.push(billing_fingerprint); + billing_fingerprints.push(payout_fingerprint); Some(v2::BillingReport { - state: billing_report.state, - vault: billing_report.vault, + state: payout_receipt.state, + vault: payout_receipt.vault, fingerprint, - total_customer_charge: billing_report.total_customer_charge, - total_distributed_reward: billing_report.total_distributed_reward, - charging_max_batch_index: billing_report.charging_max_batch_index, - charging_processed_batches: billing_report.charging_processed_batches, - rewarding_max_batch_index: billing_report.rewarding_max_batch_index, - rewarding_processed_batches: billing_report.rewarding_processed_batches, + total_customer_charge: payout_receipt.total_customer_charge, + total_distributed_reward: payout_receipt.total_distributed_reward, + charging_max_batch_index: payout_receipt.charging_max_batch_index, + charging_processed_batches: payout_receipt.charging_processed_batches, + rewarding_max_batch_index: payout_receipt.rewarding_max_batch_index, + rewarding_processed_batches: payout_receipt.rewarding_processed_batches, }) }, ); let fingerprints_count: u64 = billing_fingerprints.len() as u64; - for billing_fingerprint in billing_fingerprints { - let fingerprint = billing_fingerprint.selective_hash::(); - v2::BillingFingerprints::::insert(fingerprint, billing_fingerprint); + for payout_fingerprint in billing_fingerprints { + let fingerprint = payout_fingerprint.selective_hash::(); + v2::BillingFingerprints::::insert(fingerprint, payout_fingerprint); } StorageVersion::new(2).put::>(); @@ -261,3 +296,240 @@ pub mod v2 { } } } + +pub mod v3 { + use frame_support::pallet_prelude::*; + + use super::*; + + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] + #[scale_info(skip_type_params(T))] + pub struct BillingReport { + pub state: PayoutState, + pub vault: T::AccountId, + pub fingerprint: Fingerprint, + pub total_collected_charges: u128, + pub total_distributed_rewards: u128, + pub total_settled_fees: u128, + pub charging_max_batch_index: BatchIndex, + pub charging_processed_batches: BoundedBTreeSet, + pub rewarding_max_batch_index: BatchIndex, + pub rewarding_processed_batches: BoundedBTreeSet, + } + + #[storage_alias] + pub type ActiveBillingReports = StorageDoubleMap< + crate::Pallet, + Blake2_128Concat, + ClusterId, + Blake2_128Concat, + DdcEra, + BillingReport, + >; + + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] + pub struct PayoutFingerprint { + pub ehd_id: String, + pub cluster_id: ClusterId, + pub payers_merkle_root: PayableUsageHash, + pub payees_merkle_root: PayableUsageHash, + pub validators: BTreeSet, + } + + impl PayoutFingerprint { + pub fn selective_hash(&self) -> Fingerprint { + let mut data = self.cluster_id.encode(); + data.extend_from_slice(&self.ehd_id.encode()); + data.extend_from_slice(&self.payers_merkle_root.encode()); + data.extend_from_slice(&self.payees_merkle_root.encode()); + // we truncate the `validators` field on purpose as it's appendable collection that is + // used for reaching the quorum on the billing fingerprint + T::Hasher::hash(&data) + } + } + + #[storage_alias] + pub type BillingFingerprints = StorageMap< + crate::Pallet, + Blake2_128Concat, + Fingerprint, + PayoutFingerprint<::AccountId>, + >; + + pub fn migrate_to_v3() -> Weight { + let on_chain_version = Pallet::::on_chain_storage_version(); + let current_version = Pallet::::in_code_storage_version(); + + log::info!( + target: LOG_TARGET, + "Running migration with current storage version {:?} / onchain {:?}", + current_version, + on_chain_version + ); + + if on_chain_version == 2 && current_version == 3 { + let mut translated = 0u64; + let reports_count = v2::ActiveBillingReports::::iter().count(); + log::info!( + target: LOG_TARGET, + " >>> Updating Billing Reports. Migrating {} reports...", reports_count + ); + + v3::ActiveBillingReports::::translate::, _>( + |cluster_id: ClusterId, era_id: EhdEra, payout_receipt: v2::BillingReport| { + log::info!(target: LOG_TARGET, "Migrating Billing Report for cluster_id {:?} era_id {:?}", cluster_id, era_id); + translated.saturating_inc(); + + let total_collected_charges = payout_receipt + .total_customer_charge + .transfer + .saturating_add(payout_receipt.total_customer_charge.storage) + .saturating_add(payout_receipt.total_customer_charge.gets) + .saturating_add(payout_receipt.total_customer_charge.puts); + + let total_distributed_rewards = payout_receipt.total_distributed_reward; + let total_settled_fees = + total_collected_charges.saturating_sub(total_distributed_rewards); + + Some(v3::BillingReport { + state: payout_receipt.state, + vault: payout_receipt.vault, + fingerprint: payout_receipt.fingerprint, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, + charging_max_batch_index: payout_receipt.charging_max_batch_index, + charging_processed_batches: payout_receipt.charging_processed_batches, + rewarding_max_batch_index: payout_receipt.rewarding_max_batch_index, + rewarding_processed_batches: payout_receipt.rewarding_processed_batches, + }) + }, + ); + + let fingerprints_count = v2::BillingFingerprints::::iter().count(); + log::info!( + target: LOG_TARGET, + " >>> Updating Billing Fingerprints. Migrating {} fingerprints...", fingerprints_count + ); + + v3::BillingFingerprints::::translate::< + v2::PayoutFingerprint<::AccountId>, + _, + >( + |_key: Fingerprint, + payout_fingerprint: v2::PayoutFingerprint< + ::AccountId, + >| { + log::info!(target: LOG_TARGET, "Migrating Billing Fingerprint for cluster_id {:?} era_id {:?}", payout_fingerprint.cluster_id, payout_fingerprint.era_id); + translated.saturating_inc(); + + Some(v3::PayoutFingerprint { + cluster_id: payout_fingerprint.cluster_id, + ehd_id: EHDId( + payout_fingerprint.cluster_id, + NodePubKey::StoragePubKey(sp_runtime::AccountId32::from([0u8; 32])), + payout_fingerprint.era_id, + ) + .into(), + payers_merkle_root: payout_fingerprint.payers_merkle_root, + payees_merkle_root: payout_fingerprint.payees_merkle_root, + validators: Default::default(), + }) + }, + ); + + let payouts_old_prefix = + storage::storage_prefix(>::name().as_bytes(), b"ActiveBillingReports"); + let payouts_new_prefix = + storage::storage_prefix(>::name().as_bytes(), b"PayoutReceipts"); + migration::move_prefix(&payouts_old_prefix, &payouts_new_prefix); + + if let Some(value) = unhashed::get_raw(&payouts_old_prefix) { + unhashed::put_raw(&payouts_new_prefix, &value); + unhashed::kill(&payouts_old_prefix); + } + + let fingerprints_old_prefix = + storage::storage_prefix(>::name().as_bytes(), b"BillingFingerprints"); + let fingerprints_new_prefix = + storage::storage_prefix(>::name().as_bytes(), b"PayoutFingerprints"); + migration::move_prefix(&fingerprints_old_prefix, &fingerprints_new_prefix); + + if let Some(value) = unhashed::get_raw(&fingerprints_old_prefix) { + unhashed::put_raw(&fingerprints_new_prefix, &value); + unhashed::kill(&fingerprints_old_prefix); + } + + StorageVersion::new(3).put::>(); + log::info!( + target: LOG_TARGET, + "Upgraded {} records, storage to version {:?}", + translated, + current_version + ); + + T::DbWeight::get().reads_writes(translated * 2 + 1, translated * 2 + 1) + } else { + log::info!(target: LOG_TARGET, " >>> Unused migration!"); + T::DbWeight::get().reads(1) + } + } + + pub struct MigrateToV3(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV3 { + fn on_runtime_upgrade() -> Weight { + migrate_to_v3::() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, DispatchError> { + let pre_reports_count = v2::ActiveBillingReports::::iter().count(); + let pre_fingerprints_count = v2::BillingFingerprints::::iter().count(); + + log::info!( + target: LOG_TARGET, + "BEFORE the migration - pre_reports_count: {:?} pre_fingerprints_count: {:?}", + pre_reports_count, + pre_fingerprints_count + ); + + Ok((pre_reports_count as u64, pre_fingerprints_count as u64).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_state: Vec) -> Result<(), DispatchError> { + let (pre_reports_count, pre_fingerprints_count): (u64, u64) = + Decode::decode(&mut &prev_state[..]) + .expect("pre_upgrade provides a valid state; qed"); + + let post_receipts_count = PayoutReceipts::::iter().count() as u64; + let post_fingerprints_count = PayoutFingerprints::::iter().count() as u64; + + log::info!( + target: LOG_TARGET, + "AFTER the migration - post_receipts_count: {:?} post_fingerprints_count: {:?}", + post_receipts_count, + post_fingerprints_count + ); + + ensure!( + pre_reports_count == post_receipts_count, + "Billing report count before and after the migration should be the same" + ); + ensure!( + pre_fingerprints_count == post_fingerprints_count, + "Billing report fingerprints count after the migration should be equal to billing reports count." + ); + + let current_version = Pallet::::in_code_storage_version(); + let on_chain_version = Pallet::::on_chain_storage_version(); + + frame_support::ensure!(current_version == 3, "must_upgrade"); + ensure!( + current_version == on_chain_version, + "after migration, the current_version and on_chain_version should be the same" + ); + Ok(()) + } + } +} diff --git a/pallets/ddc-payouts/src/mock.rs b/pallets/ddc-payouts/src/mock.rs index d4df17952..222964514 100644 --- a/pallets/ddc-payouts/src/mock.rs +++ b/pallets/ddc-payouts/src/mock.rs @@ -9,8 +9,8 @@ use ddc_primitives::{ bucket::BucketManager, cluster::ClusterProtocol, customer::CustomerCharger, node::NodeManager, pallet::PalletVisitor, ClusterQuery, ValidatorVisitor, }, - ClusterBondingParams, ClusterFeesParams, ClusterPricingParams, ClusterProtocolParams, - ClusterStatus, NodeParams, NodePubKey, NodeType, DOLLARS, + BucketUsage, ClusterBondingParams, ClusterFeesParams, ClusterPricingParams, + ClusterProtocolParams, ClusterStatus, NodeParams, NodePubKey, NodeType, DOLLARS, }; use frame_election_provider_support::SortedListProvider; use frame_support::{ @@ -270,9 +270,9 @@ impl CustomerCharger for TestCustomerCharger where ::AccountId: From, { - fn charge_bucket_owner( + fn charge_customer( content_owner: T::AccountId, - billing_vault: T::AccountId, + payout_vault: T::AccountId, amount: u128, ) -> Result { let mut amount_to_charge = amount; @@ -303,7 +303,7 @@ where let charge = amount_to_charge.saturated_into::>(); ::Currency::transfer( &content_owner, - &billing_vault, + &payout_vault, charge, ExistenceRequirement::AllowDeath, )?; diff --git a/pallets/ddc-payouts/src/tests.rs b/pallets/ddc-payouts/src/tests.rs index e40bc3ac3..9e163bf8d 100644 --- a/pallets/ddc-payouts/src/tests.rs +++ b/pallets/ddc-payouts/src/tests.rs @@ -16,28 +16,22 @@ use super::{mock::*, *}; fn get_fingerprint( cluster_id: &ClusterId, - era_id: DdcEra, - start_era: i64, - end_era: i64, + ehd_id: String, payers_merkle_root: PayableUsageHash, payees_merkle_root: PayableUsageHash, - cluster_usage: &NodeUsage, ) -> Fingerprint { - let fingerprint = BillingFingerprint:: { + let fingerprint = PayoutFingerprint:: { cluster_id: *cluster_id, - era_id, - start_era, - end_era, + ehd_id, payers_merkle_root, payees_merkle_root, - cluster_usage: cluster_usage.clone(), validators: Default::default(), }; fingerprint.selective_hash::() } -fn hash_bucket_payable_usage_batch(usages: Vec<(BucketId, BucketUsage)>) -> (H256, MMRProof, H256) { +fn hash_bucket_payable_usage_batch(usages: Vec<(AccountId, u128)>) -> (H256, MMRProof, H256) { if usages.len() > MAX_PAYOUT_BATCH_SIZE.into() { panic!("Batch size is reached") } @@ -46,12 +40,14 @@ fn hash_bucket_payable_usage_batch(usages: Vec<(BucketId, BucketUsage)>) -> (H25 let mut mmr1: MMR> = MemMMR::::new(0, &store1); - for usage in usages { - let mut data = usage.0.encode(); // bucket_id - data.extend_from_slice(&usage.1.stored_bytes.encode()); - data.extend_from_slice(&usage.1.transferred_bytes.encode()); - data.extend_from_slice(&usage.1.number_of_puts.encode()); - data.extend_from_slice(&usage.1.number_of_gets.encode()); + for (customer_id, amount) in usages { + // let mut data = usage.0.encode(); // bucket_id + // data.extend_from_slice(&usage.1.stored_bytes.encode()); + // data.extend_from_slice(&usage.1.transferred_bytes.encode()); + // data.extend_from_slice(&usage.1.number_of_puts.encode()); + // data.extend_from_slice(&usage.1.number_of_gets.encode()); + let mut data = customer_id.encode(); + data.extend_from_slice(&amount.encode()); let hash = blake2_256(&data); let _pos: u64 = mmr1.push(H256(hash)).unwrap(); } @@ -71,7 +67,7 @@ fn hash_bucket_payable_usage_batch(usages: Vec<(BucketId, BucketUsage)>) -> (H25 (payers_batch_root, payers_batch_proof, payers_root) } -fn hash_node_payable_usage_batch(usages: Vec<(NodePubKey, NodeUsage)>) -> (H256, MMRProof, H256) { +fn hash_node_payable_usage_batch(usages: Vec<(AccountId, u128)>) -> (H256, MMRProof, H256) { if usages.len() > MAX_PAYOUT_BATCH_SIZE.into() { panic!("Batch size is reached") } @@ -80,12 +76,15 @@ fn hash_node_payable_usage_batch(usages: Vec<(NodePubKey, NodeUsage)>) -> (H256, let mut mmr1: MMR> = MemMMR::::new(0, &store1); - for usage in usages { - let mut data = usage.0.encode(); // node_key - data.extend_from_slice(&usage.1.stored_bytes.encode()); - data.extend_from_slice(&usage.1.transferred_bytes.encode()); - data.extend_from_slice(&usage.1.number_of_puts.encode()); - data.extend_from_slice(&usage.1.number_of_gets.encode()); + for (provider_id, amount) in usages { + // let mut data = usage.0.encode(); // node_key + // data.extend_from_slice(&usage.1.stored_bytes.encode()); + // data.extend_from_slice(&usage.1.transferred_bytes.encode()); + // data.extend_from_slice(&usage.1.number_of_puts.encode()); + // data.extend_from_slice(&usage.1.number_of_gets.encode()); + let mut data = provider_id.encode(); + data.extend_from_slice(&amount.encode()); + let hash = blake2_256(&data); let _pos: u64 = mmr1.push(H256(hash)).unwrap(); } @@ -139,7 +138,6 @@ fn begin_billing_report_works() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let fingerprint = get_fingerprint( &cluster_id, era, @@ -147,10 +145,9 @@ fn begin_billing_report_works() { end_era, DEFAULT_PAYERS_ROOT, DEFAULT_PAYEES_ROOT, - &cluster_usage, ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -158,21 +155,20 @@ fn begin_billing_report_works() { end_era, DEFAULT_PAYERS_ROOT, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint )); - System::assert_last_event(Event::BillingReportInitialized { cluster_id, era }.into()); + System::assert_last_event(Event::PayoutInitialized { cluster_id, era }.into()); - let report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report.state, PayoutState::Initialized); - let fingerprint = BillingFingerprints::::get(fingerprint).unwrap(); + let fingerprint = DdcPayouts::billing_fingerprints(fingerprint).unwrap(); assert_eq!(fingerprint.start_era, start_era); assert_eq!(fingerprint.end_era, end_era); @@ -192,7 +188,7 @@ fn begin_charging_customers_fails_uninitialised() { era, max_charging_batch_index, ), - Error::::BillingReportDoesNotExist + Error::::PayoutReceiptDoesNotExist ); }) } @@ -210,7 +206,6 @@ fn begin_charging_customers_works() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let fingerprint = get_fingerprint( &cluster_id, era, @@ -218,10 +213,9 @@ fn begin_charging_customers_works() { end_era, DEFAULT_PAYERS_ROOT, DEFAULT_PAYEES_ROOT, - &cluster_usage, ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -229,10 +223,9 @@ fn begin_charging_customers_works() { end_era, DEFAULT_PAYERS_ROOT, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -246,7 +239,7 @@ fn begin_charging_customers_works() { System::assert_last_event(Event::ChargingStarted { cluster_id, era }.into()); - let report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report.state, PayoutState::ChargingCustomers); assert_eq!(report.charging_max_batch_index, max_charging_batch_index); }) @@ -273,24 +266,16 @@ fn send_charging_customers_batch_fails_uninitialised() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); let (payers2_batch_root, _, _) = hash_bucket_payable_usage_batch(payers2.clone()); let (payers_root, payers_proofs) = get_root_with_proofs(vec![payers1_batch_root, payers2_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -298,7 +283,6 @@ fn send_charging_customers_batch_fails_uninitialised() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); assert_noop!( @@ -309,10 +293,10 @@ fn send_charging_customers_batch_fails_uninitialised() { &payers1.clone(), payers_proofs.get(0).unwrap().clone(), ), - Error::::BillingReportDoesNotExist + Error::::PayoutReceiptDoesNotExist ); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -373,8 +357,8 @@ fn calculate_charge_parts_for_day(cluster_id: ClusterId, usage: BucketUsage) -> let fraction_of_month = Perquintill::from_rational(duration_seconds as u64, seconds_in_month as u64); - let storage = fraction_of_month * - (|| -> Option { + let storage = fraction_of_month + * (|| -> Option { (usage.stored_bytes as u128) .checked_mul(pricing_params.unit_per_mb_stored)? .checked_div(byte_unit::MEBIBYTE) @@ -382,8 +366,8 @@ fn calculate_charge_parts_for_day(cluster_id: ClusterId, usage: BucketUsage) -> .unwrap(); CustomerCharge { - transfer: pricing_params.unit_per_mb_streamed * (usage.transferred_bytes as u128) / - byte_unit::MEBIBYTE, + transfer: pricing_params.unit_per_mb_streamed * (usage.transferred_bytes as u128) + / byte_unit::MEBIBYTE, storage, puts: pricing_params.unit_per_put_request * (usage.number_of_puts as u128), gets: pricing_params.unit_per_get_request * (usage.number_of_gets as u128), @@ -399,8 +383,8 @@ fn calculate_charge_parts_for_month(cluster_id: ClusterId, usage: BucketUsage) - let pricing_params = get_pricing(&cluster_id); let fraction_of_month = Perquintill::one(); - let storage = fraction_of_month * - (|| -> Option { + let storage = fraction_of_month + * (|| -> Option { (usage.stored_bytes as u128) .checked_mul(pricing_params.unit_per_mb_stored)? .checked_div(byte_unit::MEBIBYTE) @@ -408,8 +392,8 @@ fn calculate_charge_parts_for_month(cluster_id: ClusterId, usage: BucketUsage) - .unwrap(); CustomerCharge { - transfer: pricing_params.unit_per_mb_streamed * (usage.transferred_bytes as u128) / - byte_unit::MEBIBYTE, + transfer: pricing_params.unit_per_mb_streamed * (usage.transferred_bytes as u128) + / byte_unit::MEBIBYTE, storage, puts: pricing_params.unit_per_put_request * (usage.number_of_puts as u128), gets: pricing_params.unit_per_get_request * (usage.number_of_gets as u128), @@ -423,8 +407,8 @@ fn calculate_charge_parts_for_hour(cluster_id: ClusterId, usage: BucketUsage) -> let seconds_in_month = 30.44 * 24.0 * 3600.0; let fraction_of_hour = Perquintill::from_rational(duration_seconds as u64, seconds_in_month as u64); - let storage = fraction_of_hour * - (|| -> Option { + let storage = fraction_of_hour + * (|| -> Option { (usage.stored_bytes as u128) .checked_mul(pricing_params.unit_per_mb_stored)? .checked_div(byte_unit::MEBIBYTE) @@ -432,8 +416,8 @@ fn calculate_charge_parts_for_hour(cluster_id: ClusterId, usage: BucketUsage) -> .unwrap(); CustomerCharge { - transfer: pricing_params.unit_per_mb_streamed * (usage.transferred_bytes as u128) / - byte_unit::MEBIBYTE, + transfer: pricing_params.unit_per_mb_streamed * (usage.transferred_bytes as u128) + / byte_unit::MEBIBYTE, storage, puts: pricing_params.unit_per_put_request * (usage.number_of_puts as u128), gets: pricing_params.unit_per_get_request * (usage.number_of_gets as u128), @@ -505,7 +489,6 @@ fn send_charging_customers_batch_works() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); let (payers2_batch_root, _, _) = hash_bucket_payable_usage_batch(payers2.clone()); @@ -514,17 +497,10 @@ fn send_charging_customers_batch_works() { let (payers_root, payers_proofs) = get_root_with_proofs(vec![payers1_batch_root, payers2_batch_root, payers3_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -532,10 +508,9 @@ fn send_charging_customers_batch_works() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint, @@ -558,7 +533,7 @@ fn send_charging_customers_batch_works() { )); let usage4_charge = calculate_charge_for_month(cluster_id, usage4.clone()); - let user2_debt = DebtorCustomers::::get(cluster_id, user2_debtor.clone()).unwrap(); + let user2_debt = DdcPayouts::debtor_customers(cluster_id, user2_debtor.clone()).unwrap(); let expected_charge2 = calculate_charge_for_month(cluster_id, usage2.clone()); let mut debt = expected_charge2 - CUSTOMER2_BALANCE; assert_eq!(user2_debt, debt); @@ -570,7 +545,7 @@ fn send_charging_customers_batch_works() { charge2.gets = ratio * charge2.gets; charge2.puts = ratio * charge2.puts; - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge4 = calculate_charge_parts_for_month(cluster_id, usage4); assert_eq!(charge2.puts + charge4.puts, report.total_customer_charge.puts); assert_eq!(charge2.gets + charge4.gets, report.total_customer_charge.gets); @@ -578,7 +553,7 @@ fn send_charging_customers_batch_works() { assert_eq!(charge2.transfer + charge4.transfer, report.total_customer_charge.transfer); System::assert_has_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, customer_id: user2_debtor.clone(), @@ -638,7 +613,7 @@ fn send_charging_customers_batch_works() { .into(), ); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge1 = calculate_charge_parts_for_month(cluster_id, usage1); assert_eq!( charge1.puts + before_total_customer_charge.puts, @@ -658,7 +633,7 @@ fn send_charging_customers_batch_works() { ); assert_eq!(report.state, PayoutState::ChargingCustomers); - let user1_debt = DebtorCustomers::::get(cluster_id, user1); + let user1_debt = DdcPayouts::debtor_customers(cluster_id, user1); assert_eq!(user1_debt, None); let balance_before = Balances::free_balance(DdcPayouts::account_id()); @@ -677,7 +652,7 @@ fn send_charging_customers_batch_works() { let user3_charge = calculate_charge_for_month(cluster_id, usage3.clone()); let charge3 = calculate_charge_parts_for_month(cluster_id, usage3); let ratio = Perquintill::from_rational(PARTIAL_CHARGE, user3_charge); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!( ratio * charge3.puts + before_total_customer_charge.puts, report.total_customer_charge.puts @@ -698,7 +673,7 @@ fn send_charging_customers_batch_works() { let balance = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance, balance_before + PARTIAL_CHARGE); - let user3_debt = DebtorCustomers::::get(cluster_id, user3_debtor.clone()).unwrap(); + let user3_debt = DdcPayouts::debtor_customers(cluster_id, user3_debtor.clone()).unwrap(); debt = user3_charge - PARTIAL_CHARGE; assert_eq!(user3_debt, debt); @@ -715,7 +690,7 @@ fn send_charging_customers_batch_works() { ); System::assert_last_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, batch_index, @@ -761,7 +736,6 @@ fn end_charging_customers_works_small_usage_1_hour() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (1.0 * 1.0 * 3600.0) as i64; // 1 hour - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); @@ -772,10 +746,9 @@ fn end_charging_customers_works_small_usage_1_hour() { end_era, payers1_batch_root, DEFAULT_PAYEES_ROOT, - &cluster_usage, ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -783,10 +756,9 @@ fn end_charging_customers_works_small_usage_1_hour() { end_era, payers1_batch_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -808,7 +780,7 @@ fn end_charging_customers_works_small_usage_1_hour() { MMRProof::default(), )); - let report_before = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let usage6_charge = calculate_charge_for_hour(cluster_id, usage6.clone()); let usage7_charge = calculate_charge_for_hour(cluster_id, usage7.clone()); let charge6 = calculate_charge_parts_for_hour(cluster_id, usage6); @@ -867,7 +839,7 @@ fn end_charging_customers_works_small_usage_1_hour() { assert_ok!(>::end_charging_customers(cluster_id, era,)); System::assert_has_event(Event::ChargingFinished { cluster_id, era }.into()); - let report_after = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report_after.state, PayoutState::CustomersChargedWithFees); let fees = get_fees(&cluster_id); @@ -877,10 +849,10 @@ fn end_charging_customers_works_small_usage_1_hour() { assert_eq!( total_left_from_one, - Perquintill::one() - - (PRICING_FEES_HIGH.treasury_share + - PRICING_FEES_HIGH.validators_share + - PRICING_FEES_HIGH.cluster_reserve_share) + Perquintill::one() + - (PRICING_FEES_HIGH.treasury_share + + PRICING_FEES_HIGH.validators_share + + PRICING_FEES_HIGH.cluster_reserve_share) ); assert_eq!(fees.treasury_share, PRICING_FEES_HIGH.treasury_share); assert_eq!(fees.validators_share, PRICING_FEES_HIGH.validators_share); @@ -993,7 +965,6 @@ fn send_charging_customers_batch_works_for_day() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (1.0 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); let (payers2_batch_root, _, _) = hash_bucket_payable_usage_batch(payers2.clone()); @@ -1001,17 +972,10 @@ fn send_charging_customers_batch_works_for_day() { let (payers_root, payers_proofs) = get_root_with_proofs(vec![payers1_batch_root, payers2_batch_root, payers3_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -1019,10 +983,9 @@ fn send_charging_customers_batch_works_for_day() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -1045,7 +1008,7 @@ fn send_charging_customers_batch_works_for_day() { )); let usage4_charge = calculate_charge_for_day(cluster_id, usage4.clone()); - let user2_debt = DebtorCustomers::::get(cluster_id, user2_debtor.clone()).unwrap(); + let user2_debt = DdcPayouts::debtor_customers(cluster_id, user2_debtor.clone()).unwrap(); let expected_charge2 = calculate_charge_for_day(cluster_id, usage2.clone()); let mut debt = expected_charge2 - CUSTOMER2_BALANCE; assert_eq!(user2_debt, debt); @@ -1057,7 +1020,7 @@ fn send_charging_customers_batch_works_for_day() { charge2.gets = ratio * charge2.gets; charge2.puts = ratio * charge2.puts; - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge4 = calculate_charge_parts_for_day(cluster_id, usage4); assert_eq!(charge2.puts + charge4.puts, report.total_customer_charge.puts); assert_eq!(charge2.gets + charge4.gets, report.total_customer_charge.gets); @@ -1065,7 +1028,7 @@ fn send_charging_customers_batch_works_for_day() { assert_eq!(charge2.transfer + charge4.transfer, report.total_customer_charge.transfer); System::assert_has_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, customer_id: user2_debtor.clone(), @@ -1125,7 +1088,7 @@ fn send_charging_customers_batch_works_for_day() { .into(), ); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge1 = calculate_charge_parts_for_day(cluster_id, usage1); assert_eq!( charge1.puts + before_total_customer_charge.puts, @@ -1145,7 +1108,7 @@ fn send_charging_customers_batch_works_for_day() { ); assert_eq!(report.state, PayoutState::ChargingCustomers); - let user1_debt = DebtorCustomers::::get(cluster_id, user1); + let user1_debt = DdcPayouts::debtor_customers(cluster_id, user1); assert_eq!(user1_debt, None); let balance_before = Balances::free_balance(DdcPayouts::account_id()); @@ -1164,7 +1127,7 @@ fn send_charging_customers_batch_works_for_day() { let user3_charge = calculate_charge_for_day(cluster_id, usage3.clone()); let charge3 = calculate_charge_parts_for_day(cluster_id, usage3); let ratio = Perquintill::from_rational(PARTIAL_CHARGE, user3_charge); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!( ratio * charge3.puts + before_total_customer_charge.puts, report.total_customer_charge.puts @@ -1185,7 +1148,7 @@ fn send_charging_customers_batch_works_for_day() { let balance = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance, balance_before + PARTIAL_CHARGE); - let user3_debt = DebtorCustomers::::get(cluster_id, user3_debtor.clone()).unwrap(); + let user3_debt = DdcPayouts::debtor_customers(cluster_id, user3_debtor.clone()).unwrap(); debt = user3_charge - PARTIAL_CHARGE; assert_eq!(user3_debt, debt); @@ -1202,7 +1165,7 @@ fn send_charging_customers_batch_works_for_day() { ); System::assert_last_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, batch_index, @@ -1277,18 +1240,10 @@ fn send_charging_customers_batch_works_for_day_free_storage() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (1.0 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -1296,10 +1251,9 @@ fn send_charging_customers_batch_works_for_day_free_storage() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -1322,7 +1276,7 @@ fn send_charging_customers_batch_works_for_day_free_storage() { )); let usage4_charge = calculate_charge_for_day(cluster_id, usage4.clone()); - let user2_debt = DebtorCustomers::::get(cluster_id, user2_debtor.clone()).unwrap(); + let user2_debt = DdcPayouts::debtor_customers(cluster_id, user2_debtor.clone()).unwrap(); let expected_charge2 = calculate_charge_for_day(cluster_id, usage2.clone()); let mut debt = expected_charge2 - CUSTOMER2_BALANCE; assert_eq!(user2_debt, debt); @@ -1334,7 +1288,7 @@ fn send_charging_customers_batch_works_for_day_free_storage() { charge2.gets = ratio * charge2.gets; charge2.puts = ratio * charge2.puts; - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge4 = calculate_charge_parts_for_day(cluster_id, usage4); assert_eq!(charge2.puts + charge4.puts, report.total_customer_charge.puts); assert_eq!(charge2.gets + charge4.gets, report.total_customer_charge.gets); @@ -1342,7 +1296,7 @@ fn send_charging_customers_batch_works_for_day_free_storage() { assert_eq!(charge2.transfer + charge4.transfer, report.total_customer_charge.transfer); System::assert_has_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, customer_id: user2_debtor.clone(), @@ -1402,7 +1356,7 @@ fn send_charging_customers_batch_works_for_day_free_storage() { .into(), ); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge1 = calculate_charge_parts_for_day(cluster_id, usage1); assert_eq!( charge1.puts + before_total_customer_charge.puts, @@ -1422,7 +1376,7 @@ fn send_charging_customers_batch_works_for_day_free_storage() { ); assert_eq!(report.state, PayoutState::ChargingCustomers); - let user1_debt = DebtorCustomers::::get(cluster_id, user1); + let user1_debt = DdcPayouts::debtor_customers(cluster_id, user1); assert_eq!(user1_debt, None); let balance_before = Balances::free_balance(DdcPayouts::account_id()); @@ -1441,7 +1395,7 @@ fn send_charging_customers_batch_works_for_day_free_storage() { let user3_charge = calculate_charge_for_day(cluster_id, usage3.clone()); let charge3 = calculate_charge_parts_for_day(cluster_id, usage3); let ratio = Perquintill::from_rational(PARTIAL_CHARGE, user3_charge); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!( ratio * charge3.puts + before_total_customer_charge.puts, report.total_customer_charge.puts @@ -1462,7 +1416,7 @@ fn send_charging_customers_batch_works_for_day_free_storage() { let balance = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance, balance_before + PARTIAL_CHARGE); - let user3_debt = DebtorCustomers::::get(cluster_id, user3_debtor.clone()).unwrap(); + let user3_debt = DdcPayouts::debtor_customers(cluster_id, user3_debtor.clone()).unwrap(); debt = user3_charge - PARTIAL_CHARGE; assert_eq!(user3_debt, debt); @@ -1479,7 +1433,7 @@ fn send_charging_customers_batch_works_for_day_free_storage() { ); System::assert_last_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, batch_index, @@ -1547,7 +1501,6 @@ fn send_charging_customers_batch_works_for_day_free_stream() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (1.0 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); let (payers2_batch_root, _, _) = hash_bucket_payable_usage_batch(payers2.clone()); @@ -1555,17 +1508,10 @@ fn send_charging_customers_batch_works_for_day_free_stream() { let (payers_root, payers_proofs) = get_root_with_proofs(vec![payers1_batch_root, payers2_batch_root, payers3_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -1573,10 +1519,9 @@ fn send_charging_customers_batch_works_for_day_free_stream() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -1599,7 +1544,7 @@ fn send_charging_customers_batch_works_for_day_free_stream() { )); let usage4_charge = calculate_charge_for_day(cluster_id, usage4.clone()); - let user2_debt = DebtorCustomers::::get(cluster_id, user2_debtor.clone()).unwrap(); + let user2_debt = DdcPayouts::debtor_customers(cluster_id, user2_debtor.clone()).unwrap(); let expected_charge2 = calculate_charge_for_day(cluster_id, usage2.clone()); let mut debt = expected_charge2 - CUSTOMER2_BALANCE; assert_eq!(user2_debt, debt); @@ -1611,7 +1556,7 @@ fn send_charging_customers_batch_works_for_day_free_stream() { charge2.gets = ratio * charge2.gets; charge2.puts = ratio * charge2.puts; - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge4 = calculate_charge_parts_for_day(cluster_id, usage4); assert_eq!(charge2.puts + charge4.puts, report.total_customer_charge.puts); assert_eq!(charge2.gets + charge4.gets, report.total_customer_charge.gets); @@ -1619,7 +1564,7 @@ fn send_charging_customers_batch_works_for_day_free_stream() { assert_eq!(charge2.transfer + charge4.transfer, report.total_customer_charge.transfer); System::assert_has_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, customer_id: user2_debtor.clone(), @@ -1679,7 +1624,7 @@ fn send_charging_customers_batch_works_for_day_free_stream() { .into(), ); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge1 = calculate_charge_parts_for_day(cluster_id, usage1); assert_eq!( charge1.puts + before_total_customer_charge.puts, @@ -1699,7 +1644,7 @@ fn send_charging_customers_batch_works_for_day_free_stream() { ); assert_eq!(report.state, PayoutState::ChargingCustomers); - let user1_debt = DebtorCustomers::::get(cluster_id, user1); + let user1_debt = DdcPayouts::debtor_customers(cluster_id, user1); assert_eq!(user1_debt, None); let balance_before = Balances::free_balance(DdcPayouts::account_id()); @@ -1718,7 +1663,7 @@ fn send_charging_customers_batch_works_for_day_free_stream() { let user3_charge = calculate_charge_for_day(cluster_id, usage3.clone()); let charge3 = calculate_charge_parts_for_day(cluster_id, usage3); let ratio = Perquintill::from_rational(PARTIAL_CHARGE, user3_charge); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!( ratio * charge3.puts + before_total_customer_charge.puts, report.total_customer_charge.puts @@ -1739,7 +1684,7 @@ fn send_charging_customers_batch_works_for_day_free_stream() { let balance = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance, balance_before + PARTIAL_CHARGE); - let user3_debt = DebtorCustomers::::get(cluster_id, user3_debtor.clone()).unwrap(); + let user3_debt = DdcPayouts::debtor_customers(cluster_id, user3_debtor.clone()).unwrap(); debt = user3_charge - PARTIAL_CHARGE; assert_eq!(user3_debt, debt); @@ -1756,7 +1701,7 @@ fn send_charging_customers_batch_works_for_day_free_stream() { ); System::assert_last_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, batch_index, @@ -1826,7 +1771,6 @@ fn send_charging_customers_batch_works_for_day_free_get() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (1.0 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); let (payers2_batch_root, _, _) = hash_bucket_payable_usage_batch(payers2.clone()); @@ -1834,17 +1778,10 @@ fn send_charging_customers_batch_works_for_day_free_get() { let (payers_root, payers_proofs) = get_root_with_proofs(vec![payers1_batch_root, payers2_batch_root, payers3_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -1852,10 +1789,9 @@ fn send_charging_customers_batch_works_for_day_free_get() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -1878,7 +1814,7 @@ fn send_charging_customers_batch_works_for_day_free_get() { )); let usage4_charge = calculate_charge_for_day(cluster_id, usage4.clone()); - let user2_debt = DebtorCustomers::::get(cluster_id, user2_debtor.clone()).unwrap(); + let user2_debt = DdcPayouts::debtor_customers(cluster_id, user2_debtor.clone()).unwrap(); let expected_charge2 = calculate_charge_for_day(cluster_id, usage2.clone()); let mut debt = expected_charge2 - CUSTOMER2_BALANCE; assert_eq!(user2_debt, debt); @@ -1890,7 +1826,7 @@ fn send_charging_customers_batch_works_for_day_free_get() { charge2.gets = ratio * charge2.gets; charge2.puts = ratio * charge2.puts; - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge4 = calculate_charge_parts_for_day(cluster_id, usage4); assert_eq!(charge2.puts + charge4.puts, report.total_customer_charge.puts); assert_eq!(charge2.gets + charge4.gets, report.total_customer_charge.gets); @@ -1898,7 +1834,7 @@ fn send_charging_customers_batch_works_for_day_free_get() { assert_eq!(charge2.transfer + charge4.transfer, report.total_customer_charge.transfer); System::assert_has_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, customer_id: user2_debtor.clone(), @@ -1958,7 +1894,7 @@ fn send_charging_customers_batch_works_for_day_free_get() { .into(), ); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge1 = calculate_charge_parts_for_day(cluster_id, usage1); assert_eq!( charge1.puts + before_total_customer_charge.puts, @@ -1978,7 +1914,7 @@ fn send_charging_customers_batch_works_for_day_free_get() { ); assert_eq!(report.state, PayoutState::ChargingCustomers); - let user1_debt = DebtorCustomers::::get(cluster_id, user1); + let user1_debt = DdcPayouts::debtor_customers(cluster_id, user1); assert_eq!(user1_debt, None); let balance_before = Balances::free_balance(DdcPayouts::account_id()); @@ -1997,7 +1933,7 @@ fn send_charging_customers_batch_works_for_day_free_get() { let user3_charge = calculate_charge_for_day(cluster_id, usage3.clone()); let charge3 = calculate_charge_parts_for_day(cluster_id, usage3); let ratio = Perquintill::from_rational(PARTIAL_CHARGE, user3_charge); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!( ratio * charge3.puts + before_total_customer_charge.puts, report.total_customer_charge.puts @@ -2018,7 +1954,7 @@ fn send_charging_customers_batch_works_for_day_free_get() { let balance = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance, balance_before + PARTIAL_CHARGE); - let user3_debt = DebtorCustomers::::get(cluster_id, user3_debtor.clone()).unwrap(); + let user3_debt = DdcPayouts::debtor_customers(cluster_id, user3_debtor.clone()).unwrap(); debt = user3_charge - PARTIAL_CHARGE; assert_eq!(user3_debt, debt); @@ -2035,7 +1971,7 @@ fn send_charging_customers_batch_works_for_day_free_get() { ); System::assert_last_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, batch_index, @@ -2104,7 +2040,6 @@ fn send_charging_customers_batch_works_for_day_free_put() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (1.0 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); let (payers2_batch_root, _, _) = hash_bucket_payable_usage_batch(payers2.clone()); @@ -2112,17 +2047,10 @@ fn send_charging_customers_batch_works_for_day_free_put() { let (payers_root, payers_proofs) = get_root_with_proofs(vec![payers1_batch_root, payers2_batch_root, payers3_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -2130,10 +2058,9 @@ fn send_charging_customers_batch_works_for_day_free_put() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -2156,7 +2083,7 @@ fn send_charging_customers_batch_works_for_day_free_put() { )); let usage4_charge = calculate_charge_for_day(cluster_id, usage4.clone()); - let user2_debt = DebtorCustomers::::get(cluster_id, user2_debtor.clone()).unwrap(); + let user2_debt = DdcPayouts::debtor_customers(cluster_id, user2_debtor.clone()).unwrap(); let expected_charge2 = calculate_charge_for_day(cluster_id, usage2.clone()); let mut debt = expected_charge2 - CUSTOMER2_BALANCE; assert_eq!(user2_debt, debt); @@ -2168,7 +2095,7 @@ fn send_charging_customers_batch_works_for_day_free_put() { charge2.gets = ratio * charge2.gets; charge2.puts = ratio * charge2.puts; - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge4 = calculate_charge_parts_for_day(cluster_id, usage4); assert_eq!(charge2.puts + charge4.puts, report.total_customer_charge.puts); assert_eq!(charge2.gets + charge4.gets, report.total_customer_charge.gets); @@ -2176,7 +2103,7 @@ fn send_charging_customers_batch_works_for_day_free_put() { assert_eq!(charge2.transfer + charge4.transfer, report.total_customer_charge.transfer); System::assert_has_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, customer_id: user2_debtor.clone(), @@ -2236,7 +2163,7 @@ fn send_charging_customers_batch_works_for_day_free_put() { .into(), ); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge1 = calculate_charge_parts_for_day(cluster_id, usage1); assert_eq!( charge1.puts + before_total_customer_charge.puts, @@ -2256,7 +2183,7 @@ fn send_charging_customers_batch_works_for_day_free_put() { ); assert_eq!(report.state, PayoutState::ChargingCustomers); - let user1_debt = DebtorCustomers::::get(cluster_id, user1); + let user1_debt = DdcPayouts::debtor_customers(cluster_id, user1); assert_eq!(user1_debt, None); let balance_before = Balances::free_balance(DdcPayouts::account_id()); @@ -2275,7 +2202,7 @@ fn send_charging_customers_batch_works_for_day_free_put() { let user3_charge = calculate_charge_for_day(cluster_id, usage3.clone()); let charge3 = calculate_charge_parts_for_day(cluster_id, usage3); let ratio = Perquintill::from_rational(PARTIAL_CHARGE, user3_charge); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!( ratio * charge3.puts + before_total_customer_charge.puts, report.total_customer_charge.puts @@ -2296,7 +2223,7 @@ fn send_charging_customers_batch_works_for_day_free_put() { let balance = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance, balance_before + PARTIAL_CHARGE); - let user3_debt = DebtorCustomers::::get(cluster_id, user3_debtor.clone()).unwrap(); + let user3_debt = DdcPayouts::debtor_customers(cluster_id, user3_debtor.clone()).unwrap(); debt = user3_charge - PARTIAL_CHARGE; assert_eq!(user3_debt, debt); @@ -2313,7 +2240,7 @@ fn send_charging_customers_batch_works_for_day_free_put() { ); System::assert_last_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, batch_index, @@ -2388,18 +2315,10 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (1.0 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -2407,10 +2326,9 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -2433,7 +2351,7 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { )); let usage4_charge = calculate_charge_for_day(cluster_id, usage4.clone()); - let user2_debt = DebtorCustomers::::get(cluster_id, user2_debtor.clone()).unwrap(); + let user2_debt = DdcPayouts::debtor_customers(cluster_id, user2_debtor.clone()).unwrap(); let expected_charge2 = calculate_charge_for_day(cluster_id, usage2.clone()); let mut debt = expected_charge2 - CUSTOMER2_BALANCE; assert_eq!(user2_debt, debt); @@ -2445,7 +2363,7 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { charge2.gets = ratio * charge2.gets; charge2.puts = ratio * charge2.puts; - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge4 = calculate_charge_parts_for_day(cluster_id, usage4); assert_eq!(charge2.puts + charge4.puts, report.total_customer_charge.puts); assert_eq!(charge2.gets + charge4.gets, report.total_customer_charge.gets); @@ -2453,7 +2371,7 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { assert_eq!(charge2.transfer + charge4.transfer, report.total_customer_charge.transfer); System::assert_has_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, customer_id: user2_debtor.clone(), @@ -2513,7 +2431,7 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { .into(), ); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge1 = calculate_charge_parts_for_day(cluster_id, usage1); assert_eq!( charge1.puts + before_total_customer_charge.puts, @@ -2533,7 +2451,7 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { ); assert_eq!(report.state, PayoutState::ChargingCustomers); - let user1_debt = DebtorCustomers::::get(cluster_id, user1); + let user1_debt = DdcPayouts::debtor_customers(cluster_id, user1); assert_eq!(user1_debt, None); let balance_before = Balances::free_balance(DdcPayouts::account_id()); @@ -2552,7 +2470,7 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { let user3_charge = calculate_charge_for_day(cluster_id, usage3.clone()); let charge3 = calculate_charge_parts_for_day(cluster_id, usage3); let ratio = Perquintill::from_rational(PARTIAL_CHARGE, user3_charge); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!( ratio * charge3.puts + before_total_customer_charge.puts, report.total_customer_charge.puts @@ -2573,7 +2491,7 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { let balance = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance, balance_before + PARTIAL_CHARGE); - let user3_debt = DebtorCustomers::::get(cluster_id, user3_debtor.clone()).unwrap(); + let user3_debt = DdcPayouts::debtor_customers(cluster_id, user3_debtor.clone()).unwrap(); debt = user3_charge - PARTIAL_CHARGE; assert_eq!(user3_debt, debt); @@ -2590,7 +2508,7 @@ fn send_charging_customers_batch_works_for_day_free_storage_stream() { ); System::assert_last_event( - Event::ChargeFailed { + Event::ChargedPartially { cluster_id, era, batch_index, @@ -2627,20 +2545,12 @@ fn send_charging_customers_batch_works_zero_fees() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (_, payers_batch_proof, payers_root) = hash_bucket_payable_usage_batch(payers1.clone()); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -2648,10 +2558,9 @@ fn send_charging_customers_batch_works_zero_fees() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -2665,7 +2574,7 @@ fn send_charging_customers_batch_works_zero_fees() { assert_eq!(System::events().len(), 3); // batch 1 - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let before_total_customer_charge = report.total_customer_charge; let balance_before = Balances::free_balance(DdcPayouts::account_id()); assert_ok!(>::send_charging_customers_batch( @@ -2679,7 +2588,7 @@ fn send_charging_customers_batch_works_zero_fees() { let usage5_charge = calculate_charge_for_month(cluster_id, usage1.clone()); let charge5 = calculate_charge_parts_for_month(cluster_id, usage1); let balance = Balances::free_balance(DdcPayouts::account_id()); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(balance, usage5_charge + balance_before); assert_eq!( charge5.puts + before_total_customer_charge.puts, @@ -2731,29 +2640,21 @@ fn end_charging_customers_fails_uninitialised() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); let (payers2_batch_root, _, _) = hash_bucket_payable_usage_batch(payers2.clone()); let (payers_root, payers_proofs) = get_root_with_proofs(vec![payers1_batch_root, payers2_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); assert_noop!( >::end_charging_customers(cluster_id, era,), - Error::::BillingReportDoesNotExist + Error::::PayoutReceiptDoesNotExist ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -2761,10 +2662,9 @@ fn end_charging_customers_fails_uninitialised() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -2825,7 +2725,6 @@ fn end_charging_customers_works() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers_batch_root, _, _) = hash_bucket_payable_usage_batch(payers.clone()); @@ -2836,10 +2735,9 @@ fn end_charging_customers_works() { end_era, payers_batch_root, DEFAULT_PAYEES_ROOT, - &cluster_usage, ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -2847,10 +2745,9 @@ fn end_charging_customers_works() { end_era, payers_batch_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -2870,7 +2767,7 @@ fn end_charging_customers_works() { MMRProof::default(), )); - let report_before = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge = calculate_charge_for_month(cluster_id, usage1); System::assert_last_event( Event::Charged { @@ -2911,12 +2808,12 @@ fn end_charging_customers_works() { let transfers = 3 + 3 + 3 * 3; // for Currency::transfer assert_eq!(System::events().len(), 7 + 2 + 3 + transfers); - let report_after = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report_after.state, PayoutState::CustomersChargedWithFees); - let total_left_from_one = (get_fees(&cluster_id).treasury_share + - get_fees(&cluster_id).validators_share + - get_fees(&cluster_id).cluster_reserve_share) + let total_left_from_one = (get_fees(&cluster_id).treasury_share + + get_fees(&cluster_id).validators_share + + get_fees(&cluster_id).cluster_reserve_share) .left_from_one(); balance = Balances::free_balance(AccountId::from(TREASURY_ACCOUNT_ID)); @@ -3021,7 +2918,6 @@ fn end_charging_customers_works_zero_fees() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers_batch_root, _, _) = hash_bucket_payable_usage_batch(payers.clone()); @@ -3032,10 +2928,9 @@ fn end_charging_customers_works_zero_fees() { end_era, payers_batch_root, DEFAULT_PAYEES_ROOT, - &cluster_usage, ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -3043,10 +2938,9 @@ fn end_charging_customers_works_zero_fees() { end_era, payers_batch_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -3066,7 +2960,7 @@ fn end_charging_customers_works_zero_fees() { MMRProof::default(), )); - let report_before = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let charge = calculate_charge_for_month(cluster_id, usage1); System::assert_last_event( Event::Charged { @@ -3089,7 +2983,7 @@ fn end_charging_customers_works_zero_fees() { System::assert_has_event(Event::ChargingFinished { cluster_id, era }.into()); assert_eq!(System::events().len(), 5 + 1); - let report_after = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report_after.state, PayoutState::CustomersChargedWithFees); let fees = get_fees(&cluster_id); @@ -3152,19 +3046,11 @@ fn begin_rewarding_providers_fails_uninitialised() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (_, payers_batch_proof, payers_root) = hash_bucket_payable_usage_batch(payers.clone()); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); assert_noop!( >::begin_rewarding_providers( @@ -3172,10 +3058,10 @@ fn begin_rewarding_providers_fails_uninitialised() { era, max_batch_index, ), - Error::::BillingReportDoesNotExist + Error::::PayoutReceiptDoesNotExist ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -3183,10 +3069,9 @@ fn begin_rewarding_providers_fails_uninitialised() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -3261,21 +3146,13 @@ fn begin_rewarding_providers_works() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (_, payers_batch_proof, payers_root) = hash_bucket_payable_usage_batch(payers.clone()); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -3283,16 +3160,15 @@ fn begin_rewarding_providers_works() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint )); - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report.state, PayoutState::Initialized); assert_ok!(>::begin_charging_customers( @@ -3319,7 +3195,7 @@ fn begin_rewarding_providers_works() { System::assert_last_event(Event::RewardingStarted { cluster_id, era }.into()); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report.state, PayoutState::RewardingProviders); }) } @@ -3344,7 +3220,6 @@ fn send_rewarding_providers_batch_fails_uninitialised() { let start_era: i64 = DateTime::::from_naive_utc_and_offset(start_date.and_time(time), Utc).timestamp(); let end_era: i64 = start_era + (30.44 * 24.0 * 3600.0) as i64; - let cluster_usage = NodeUsage::default(); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); let (payers2_batch_root, _, _) = hash_bucket_payable_usage_batch(payers2.clone()); @@ -3353,15 +3228,8 @@ fn send_rewarding_providers_batch_fails_uninitialised() { let (_, payees1_batch_proof, payees_root) = hash_node_payable_usage_batch(payees1.clone()); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); assert_noop!( >::send_rewarding_providers_batch( @@ -3371,10 +3239,10 @@ fn send_rewarding_providers_batch_fails_uninitialised() { &payees1, payees1_batch_proof.clone(), ), - Error::::BillingReportDoesNotExist + Error::::PayoutReceiptDoesNotExist ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -3382,10 +3250,9 @@ fn send_rewarding_providers_batch_fails_uninitialised() { end_era, payers_root, payees_root, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -3514,18 +3381,18 @@ fn send_rewarding_providers_batch_works() { }; let cluster_usage = NodeUsage { - transferred_bytes: node_usage1.transferred_bytes + - node_usage2.transferred_bytes + - node_usage3.transferred_bytes, - stored_bytes: node_usage1.stored_bytes + - node_usage2.stored_bytes + - node_usage3.stored_bytes, - number_of_puts: node_usage1.number_of_puts + - node_usage2.number_of_puts + - node_usage3.number_of_puts, - number_of_gets: node_usage1.number_of_gets + - node_usage2.number_of_gets + - node_usage3.number_of_gets, + transferred_bytes: node_usage1.transferred_bytes + + node_usage2.transferred_bytes + + node_usage3.transferred_bytes, + stored_bytes: node_usage1.stored_bytes + + node_usage2.stored_bytes + + node_usage3.stored_bytes, + number_of_puts: node_usage1.number_of_puts + + node_usage2.number_of_puts + + node_usage3.number_of_puts, + number_of_gets: node_usage1.number_of_gets + + node_usage2.number_of_gets + + node_usage3.number_of_gets, }; let payers = vec![(bucket_id1, usage1)]; @@ -3549,17 +3416,10 @@ fn send_rewarding_providers_batch_works() { let (payees_root, payees_proofs) = get_root_with_proofs(vec![payees1_batch_root, payees2_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -3567,10 +3427,9 @@ fn send_rewarding_providers_batch_works() { end_era, payers_root, payees_root, - cluster_usage.clone(), )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -3590,13 +3449,13 @@ fn send_rewarding_providers_batch_works() { payers1_batch_proof, )); - let report_before = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_ok!(>::end_charging_customers(cluster_id, era,)); - let report_after = ActiveBillingReports::::get(cluster_id, era).unwrap(); - let total_left_from_one = (get_fees(&cluster_id).treasury_share + - get_fees(&cluster_id).validators_share + - get_fees(&cluster_id).cluster_reserve_share) + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); + let total_left_from_one = (get_fees(&cluster_id).treasury_share + + get_fees(&cluster_id).validators_share + + get_fees(&cluster_id).cluster_reserve_share) .left_from_one(); assert_eq!( @@ -3652,7 +3511,7 @@ fn send_rewarding_providers_batch_works() { let balance_node1 = Balances::free_balance(NODE_PROVIDER1_KEY_32.clone()); assert_eq!(balance_node1, transfer_charge + storage_charge + puts_charge + gets_charge); - let mut report_reward = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report_reward = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); System::assert_has_event( Event::Rewarded { @@ -3688,7 +3547,7 @@ fn send_rewarding_providers_batch_works() { let balance_node2 = Balances::free_balance(NODE_PROVIDER2_KEY_32.clone()); assert_eq!(balance_node2, transfer_charge + storage_charge + puts_charge + gets_charge); - assert_eq!(report_reward.total_distributed_reward, balance_node1 + balance_node2); + assert_eq!(report_reward.total_distributed_rewards, balance_node1 + balance_node2); System::assert_has_event( Event::Rewarded { @@ -3731,7 +3590,7 @@ fn send_rewarding_providers_batch_works() { Perquintill::from_rational(node_usage3.number_of_gets, cluster_usage.number_of_gets); gets_charge = ratio3_gets * report_after.total_customer_charge.gets; - report_reward = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report_reward = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let balance_node3 = Balances::free_balance(NODE_PROVIDER3_KEY_32.clone()); assert_eq!(balance_node3, transfer_charge + storage_charge + puts_charge + gets_charge); @@ -3748,16 +3607,16 @@ fn send_rewarding_providers_batch_works() { ); assert_eq!( - report_reward.total_distributed_reward, + report_reward.total_distributed_rewards, balance_node1 + balance_node2 + balance_node3 ); - let expected_amount_to_reward = report_reward.total_customer_charge.transfer + - report_reward.total_customer_charge.storage + - report_reward.total_customer_charge.puts + - report_reward.total_customer_charge.gets; + let expected_amount_to_reward = report_reward.total_customer_charge.transfer + + report_reward.total_customer_charge.storage + + report_reward.total_customer_charge.puts + + report_reward.total_customer_charge.gets; - assert!(expected_amount_to_reward - report_reward.total_distributed_reward <= 20000); + assert!(expected_amount_to_reward - report_reward.total_distributed_rewards <= 20000); assert_ok!( >::end_rewarding_providers(cluster_id, era,) @@ -3929,17 +3788,10 @@ fn send_rewarding_providers_batch_100_nodes_small_usage_works() { } let (payers_root, payers_proofs) = get_root_with_proofs(payers_batch_roots); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -3947,10 +3799,9 @@ fn send_rewarding_providers_batch_100_nodes_small_usage_works() { end_era, payers_root, payees_root, - cluster_usage.clone(), )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -3990,7 +3841,7 @@ fn send_rewarding_providers_batch_100_nodes_small_usage_works() { batch_user_index += 1; } - let report_before = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let balance1 = Balances::free_balance(report_before.vault.clone()); let balance2 = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance1, balance2); @@ -3999,16 +3850,16 @@ fn send_rewarding_providers_batch_100_nodes_small_usage_works() { assert_ok!(>::end_charging_customers(cluster_id, era,)); - let report_after = ActiveBillingReports::::get(cluster_id, era).unwrap(); - let total_left_from_one = (get_fees(&cluster_id).treasury_share + - get_fees(&cluster_id).validators_share + - get_fees(&cluster_id).cluster_reserve_share) + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); + let total_left_from_one = (get_fees(&cluster_id).treasury_share + + get_fees(&cluster_id).validators_share + + get_fees(&cluster_id).cluster_reserve_share) .left_from_one(); - let total_charge = report_after.total_customer_charge.transfer + - report_before.total_customer_charge.storage + - report_before.total_customer_charge.puts + - report_before.total_customer_charge.gets; + let total_charge = report_after.total_customer_charge.transfer + + report_before.total_customer_charge.storage + + report_before.total_customer_charge.puts + + report_before.total_customer_charge.gets; let balance_after = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(total_charge, balance_after - Balances::minimum_balance()); @@ -4075,8 +3926,8 @@ fn send_rewarding_providers_batch_100_nodes_small_usage_works() { providers_accounts.get(i).expect("Node provider to be found"), ); assert!( - (transfer_charge + storage_charge + puts_charge + gets_charge) - balance_node1 < - MAX_DUST.into() + (transfer_charge + storage_charge + puts_charge + gets_charge) - balance_node1 + < MAX_DUST.into() ); batch_charge += transfer_charge + storage_charge + puts_charge + gets_charge; @@ -4251,7 +4102,7 @@ fn send_rewarding_providers_batch_100_nodes_large_usage_works() { } let (payers_root, payers_proofs) = get_root_with_proofs(payers_batch_roots); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -4259,20 +4110,12 @@ fn send_rewarding_providers_batch_100_nodes_large_usage_works() { end_era, payers_root, DEFAULT_PAYEES_ROOT, - cluster_usage.clone(), )); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - DEFAULT_PAYEES_ROOT, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, DEFAULT_PAYEES_ROOT); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -4312,7 +4155,7 @@ fn send_rewarding_providers_batch_100_nodes_large_usage_works() { batch_user_index += 1; } - let report_before = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let balance1 = Balances::free_balance(report_before.vault.clone()); let balance2 = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance1, balance2); @@ -4321,16 +4164,16 @@ fn send_rewarding_providers_batch_100_nodes_large_usage_works() { assert_ok!(>::end_charging_customers(cluster_id, era,)); - let report_after = ActiveBillingReports::::get(cluster_id, era).unwrap(); - let total_left_from_one = (get_fees(&cluster_id).treasury_share + - get_fees(&cluster_id).validators_share + - get_fees(&cluster_id).cluster_reserve_share) + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); + let total_left_from_one = (get_fees(&cluster_id).treasury_share + + get_fees(&cluster_id).validators_share + + get_fees(&cluster_id).cluster_reserve_share) .left_from_one(); - let total_charge = report_after.total_customer_charge.transfer + - report_before.total_customer_charge.storage + - report_before.total_customer_charge.puts + - report_before.total_customer_charge.gets; + let total_charge = report_after.total_customer_charge.transfer + + report_before.total_customer_charge.storage + + report_before.total_customer_charge.puts + + report_before.total_customer_charge.gets; let balance_after = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(total_charge, balance_after - Balances::minimum_balance()); @@ -4536,17 +4379,10 @@ fn send_rewarding_providers_batch_100_nodes_small_large_usage_works() { } let (payers_root, payers_proofs) = get_root_with_proofs(payers_batch_roots); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -4554,10 +4390,9 @@ fn send_rewarding_providers_batch_100_nodes_small_large_usage_works() { end_era, payers_root, payees_root, - cluster_usage.clone(), )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -4597,7 +4432,7 @@ fn send_rewarding_providers_batch_100_nodes_small_large_usage_works() { batch_user_index += 1; } - let report_before = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let balance1 = Balances::free_balance(report_before.vault.clone()); let balance2 = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance1, balance2); @@ -4606,16 +4441,16 @@ fn send_rewarding_providers_batch_100_nodes_small_large_usage_works() { assert_ok!(>::end_charging_customers(cluster_id, era,)); - let report_after = ActiveBillingReports::::get(cluster_id, era).unwrap(); - let total_left_from_one = (get_fees(&cluster_id).treasury_share + - get_fees(&cluster_id).validators_share + - get_fees(&cluster_id).cluster_reserve_share) + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); + let total_left_from_one = (get_fees(&cluster_id).treasury_share + + get_fees(&cluster_id).validators_share + + get_fees(&cluster_id).cluster_reserve_share) .left_from_one(); - let total_charge = report_after.total_customer_charge.transfer + - report_before.total_customer_charge.storage + - report_before.total_customer_charge.puts + - report_before.total_customer_charge.gets; + let total_charge = report_after.total_customer_charge.transfer + + report_before.total_customer_charge.storage + + report_before.total_customer_charge.puts + + report_before.total_customer_charge.gets; let balance_after = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(total_charge, balance_after - Balances::minimum_balance()); @@ -4682,8 +4517,8 @@ fn send_rewarding_providers_batch_100_nodes_small_large_usage_works() { providers_accounts.get(i).expect("Node provider to be found"), ); assert!( - (transfer_charge + storage_charge + puts_charge + gets_charge) - balance_node1 < - MAX_DUST.into() + (transfer_charge + storage_charge + puts_charge + gets_charge) - balance_node1 + < MAX_DUST.into() ); batch_charge += transfer_charge + storage_charge + puts_charge + gets_charge; @@ -4838,7 +4673,7 @@ fn send_rewarding_providers_batch_100_nodes_random_usage_works() { } let (payers_root, payers_proofs) = get_root_with_proofs(payers_batch_roots); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -4846,20 +4681,12 @@ fn send_rewarding_providers_batch_100_nodes_random_usage_works() { end_era, payers_root, payees_root, - cluster_usage.clone(), )); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -4899,7 +4726,7 @@ fn send_rewarding_providers_batch_100_nodes_random_usage_works() { batch_user_index += 1; } - let report_before = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_before = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); let balance1 = Balances::free_balance(report_before.vault.clone()); let balance2 = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(balance1, balance2); @@ -4908,16 +4735,16 @@ fn send_rewarding_providers_batch_100_nodes_random_usage_works() { assert_ok!(>::end_charging_customers(cluster_id, era,)); - let report_after = ActiveBillingReports::::get(cluster_id, era).unwrap(); - let total_left_from_one = (get_fees(&cluster_id).treasury_share + - get_fees(&cluster_id).validators_share + - get_fees(&cluster_id).cluster_reserve_share) + let report_after = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); + let total_left_from_one = (get_fees(&cluster_id).treasury_share + + get_fees(&cluster_id).validators_share + + get_fees(&cluster_id).cluster_reserve_share) .left_from_one(); - let total_charge = report_after.total_customer_charge.transfer + - report_before.total_customer_charge.storage + - report_before.total_customer_charge.puts + - report_before.total_customer_charge.gets; + let total_charge = report_after.total_customer_charge.transfer + + report_before.total_customer_charge.storage + + report_before.total_customer_charge.puts + + report_before.total_customer_charge.gets; let balance_after = Balances::free_balance(DdcPayouts::account_id()); assert_eq!(total_charge, balance_after - Balances::minimum_balance()); @@ -4984,8 +4811,8 @@ fn send_rewarding_providers_batch_100_nodes_random_usage_works() { providers_accounts.get(i).expect("Node provider to be found"), ); assert!( - (transfer_charge + storage_charge + puts_charge + gets_charge) - balance_node1 < - MAX_DUST.into() + (transfer_charge + storage_charge + puts_charge + gets_charge) - balance_node1 + < MAX_DUST.into() ); batch_charge += transfer_charge + storage_charge + puts_charge + gets_charge; @@ -5025,7 +4852,7 @@ fn end_rewarding_providers_fails_uninitialised() { assert_noop!( >::end_rewarding_providers(cluster_id, era,), - Error::::BillingReportDoesNotExist + Error::::PayoutReceiptDoesNotExist ); let (payers1_batch_root, _, _) = hash_bucket_payable_usage_batch(payers1.clone()); @@ -5039,17 +4866,10 @@ fn end_rewarding_providers_fails_uninitialised() { let (payees_root, payees_proofs) = get_root_with_proofs(vec![payees1_batch_root, payees2_batch_root]); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -5057,10 +4877,9 @@ fn end_rewarding_providers_fails_uninitialised() { end_era, payers_root, payees_root, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint @@ -5188,17 +5007,10 @@ fn end_rewarding_providers_works() { let cluster_usage = NodeUsage::default(); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -5206,16 +5018,15 @@ fn end_rewarding_providers_works() { end_era, payers_root, payees_root, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint )); - let mut report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let mut report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report.state, PayoutState::Initialized); assert_ok!(>::begin_charging_customers( @@ -5254,7 +5065,7 @@ fn end_rewarding_providers_works() { System::assert_last_event(Event::RewardingFinished { cluster_id, era }.into()); - report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report.state, PayoutState::ProvidersRewarded); }) } @@ -5312,22 +5123,15 @@ fn end_billing_report_fails_uninitialised() { let (_, payees1_batch_proof, payees_root) = hash_node_payable_usage_batch(payees1.clone()); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); assert_noop!( - >::end_billing_report(cluster_id, era,), - Error::::BillingReportDoesNotExist + >::end_payout(cluster_id, era,), + Error::::PayoutReceiptDoesNotExist ); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -5335,17 +5139,16 @@ fn end_billing_report_fails_uninitialised() { end_era, payers_root, payees_root, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint )); assert_noop!( - >::end_billing_report(cluster_id, era,), + >::end_payout(cluster_id, era,), Error::::NotExpectedState ); @@ -5356,7 +5159,7 @@ fn end_billing_report_fails_uninitialised() { )); assert_noop!( - >::end_billing_report(cluster_id, era), + >::end_payout(cluster_id, era), Error::::NotExpectedState ); @@ -5369,7 +5172,7 @@ fn end_billing_report_fails_uninitialised() { )); assert_noop!( - >::end_billing_report(cluster_id, era), + >::end_payout(cluster_id, era), Error::::NotExpectedState ); @@ -5382,14 +5185,14 @@ fn end_billing_report_fails_uninitialised() { )); assert_noop!( - >::end_billing_report(cluster_id, era), + >::end_payout(cluster_id, era), Error::::NotExpectedState ); assert_ok!(>::end_charging_customers(cluster_id, era)); assert_noop!( - >::end_billing_report(cluster_id, era), + >::end_payout(cluster_id, era), Error::::NotExpectedState ); @@ -5400,7 +5203,7 @@ fn end_billing_report_fails_uninitialised() { )); assert_noop!( - >::end_billing_report(cluster_id, era), + >::end_payout(cluster_id, era), Error::::NotExpectedState ); @@ -5413,12 +5216,12 @@ fn end_billing_report_fails_uninitialised() { )); assert_noop!( - >::end_billing_report(cluster_id, era), + >::end_payout(cluster_id, era), Error::::NotExpectedState ); assert_noop!( - >::end_billing_report(cluster_id, era), + >::end_payout(cluster_id, era), Error::::NotExpectedState ); }) @@ -5447,17 +5250,10 @@ fn end_billing_report_works() { let (_, payers_batch_proof, payers_root) = hash_bucket_payable_usage_batch(payers.clone()); let (_, payees_batch_proof, payees_root) = hash_node_payable_usage_batch(payees.clone()); - let fingerprint = get_fingerprint( - &cluster_id, - era, - start_era, - end_era, - payers_root, - payees_root, - &cluster_usage, - ); + let fingerprint = + get_fingerprint(&cluster_id, era, start_era, end_era, payers_root, payees_root); - assert_ok!(>::commit_billing_fingerprint( + assert_ok!(>::commit_payout_fingerprint( VALIDATOR1_ACCOUNT_ID.into(), cluster_id, era, @@ -5465,16 +5261,15 @@ fn end_billing_report_works() { end_era, payers_root, payees_root, - cluster_usage, )); - assert_ok!(>::begin_billing_report( + assert_ok!(>::begin_payout( cluster_id, era, fingerprint )); - let report = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert_eq!(report.state, PayoutState::Initialized); assert_ok!(>::begin_charging_customers( @@ -5511,11 +5306,11 @@ fn end_billing_report_works() { >::end_rewarding_providers(cluster_id, era,) ); - assert_ok!(>::end_billing_report(cluster_id, era,)); + assert_ok!(>::end_payout(cluster_id, era,)); - System::assert_last_event(Event::BillingReportFinalized { cluster_id, era }.into()); + System::assert_last_event(Event::PayoutReceiptFinalized { cluster_id, era }.into()); - let report_end = ActiveBillingReports::::get(cluster_id, era).unwrap(); + let report_end = DdcPayouts::active_billing_reports(cluster_id, era).unwrap(); assert!(report_end.rewarding_processed_batches.is_empty()); assert!(report_end.charging_processed_batches.is_empty()); assert_eq!(report_end.state, PayoutState::Finalized); diff --git a/pallets/ddc-verification/Cargo.toml b/pallets/ddc-verification/Cargo.toml index ac14acf38..fa94d435d 100644 --- a/pallets/ddc-verification/Cargo.toml +++ b/pallets/ddc-verification/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] array-bytes = { workspace = true } base64ct = { workspace = true } +byte-unit = { workspace = true } # 3rd-party dependencies codec = { workspace = true } # Cere dependencies diff --git a/pallets/ddc-verification/src/aggregate_tree.rs b/pallets/ddc-verification/src/aggregate_tree.rs new file mode 100644 index 000000000..fec586b5a --- /dev/null +++ b/pallets/ddc-verification/src/aggregate_tree.rs @@ -0,0 +1,93 @@ +use sp_runtime::ArithmeticError; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +pub fn get_node_id(level: u32, node_idx: u64) -> u64 { + 2_u64.pow(level) + node_idx +} + +pub fn get_leaves_ids(leaves_count: u64) -> Vec { + let levels = get_levels(leaves_count).expect("Tree levels to be defined"); + let leaf_level = levels.last().expect("Leaf level to be defined"); + let mut leaf_ids = Vec::new(); + for leaf_idx in 0..leaves_count { + leaf_ids.push(get_node_id(*leaf_level, leaf_idx)); + } + leaf_ids +} + +pub fn get_levels(leaves_count: u64) -> Result, &'static str> { + if leaves_count == 0 { + return Err("Tree must have at least one leaf."); + } + + // The last level is based on the log2 calculation + let last_level = leaves_count.ilog2(); + + // If the tree is not a perfect binary tree, we need one more level + if (1 << last_level) < leaves_count { + return Ok((0..=last_level + 1).collect()); + } + + // If the tree is a perfect binary tree + Ok((0..=last_level).collect()) +} + +pub const D_099: u64 = 9900; // 0.99 scaled by 10^4 +pub const P_001: u64 = 100; // 0.01 scaled by 10^4 + +pub fn get_n0_values() -> BTreeMap<(u64, u64), u64> { + let mut n0_map = BTreeMap::new(); + + // Formula: n0 = ln(1 - d) / ln(1 - p) + + // todo: add more pairs + n0_map.insert((D_099, P_001), 4582106); // ~458.2106 scaled by 10^4 + + n0_map +} + +pub fn calculate_sample_size_inf(d: u64, p: u64) -> u64 { + let n0_values = get_n0_values(); + + let n0 = n0_values.get(&(d, p)).expect("Sample size to be defined"); + *n0 +} + +pub fn calculate_sample_size_fin( + n0_scaled: u64, + population_size: u64, +) -> Result { + const SCALE: u64 = 10000; // Reduced scale factor for fixed-point precision (10^4) + + // Ensure that population_size > 1 to avoid division by zero or invalid calculations + if population_size <= 1 { + return Err(ArithmeticError::Underflow); + } + + // Formula: n = n_0 / (1 + (n_0 - 1) / N) + + // First calculate (n0 - 1) + let numerator = n0_scaled + .checked_sub(SCALE) // subtract 1 * SCALE to keep consistent scaling + .ok_or(ArithmeticError::Underflow)?; + + // Scale the numerator before dividing to increase precision + let numerator_scaled = numerator.checked_mul(SCALE).ok_or(ArithmeticError::Overflow)?; + + // Calculate (n0 - 1) / N using integer division, but scale numerator first + // Population size is unscaled, so we scale it before the division to maintain precision + let population_scaled = population_size.checked_mul(SCALE).ok_or(ArithmeticError::Overflow)?; + + let correction_term = numerator_scaled + .checked_div(population_scaled) + .ok_or(ArithmeticError::Underflow)?; + + // Now calculate 1 + correction_term, but scale 1 by SCALE to ensure consistency + let denominator = SCALE.checked_add(correction_term).ok_or(ArithmeticError::Overflow)?; // SCALE is used to scale the 1 + + // Finally calculate n = n0 / denominator, and return the result (floored automatically by + // integer division) + let n = n0_scaled.checked_div(denominator).ok_or(ArithmeticError::Underflow)?; + + Ok(n) +} diff --git a/pallets/ddc-verification/src/aggregator_client.rs b/pallets/ddc-verification/src/aggregator_client.rs index f68de000f..7f18b73e6 100644 --- a/pallets/ddc-verification/src/aggregator_client.rs +++ b/pallets/ddc-verification/src/aggregator_client.rs @@ -3,6 +3,7 @@ use ddc_primitives::{AggregatorInfo, BucketId, DdcEra}; use prost::Message; use serde_with::{base64::Base64, serde_as}; +use sp_core::crypto::AccountId32; use sp_io::offchain::timestamp; use sp_runtime::offchain::{http, Duration}; @@ -16,6 +17,47 @@ pub struct AggregatorClient<'a> { verify_sig: bool, } +macro_rules! fetch_and_parse { + ( + // Self reference (the aggregator client) + $self:expr, + // URL string variable (mutable) + $url:expr, + // The type of the JSON response if not signed + $unsigned_ty:ty, + // The type of the JSON response if signed + $signed_ty:ty + ) => {{ + if $self.verify_sig { + if $url.contains('?') { + $url = format!("{}&sign=true", $url); + } else { + $url = format!("{}?sign=true", $url); + } + } + + let response = $self.get(&$url, Accept::Any)?; + + let body = response.body().collect::>(); + + if $self.verify_sig { + let json_response: json::SignedJsonResponse<$signed_ty> = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + + if !json_response.verify() { + log::debug!("Bad signature, req: {:?}, resp: {:?}", $url, json_response); + return Err(http::Error::Unknown); + } + + Ok(json_response.payload) + } else { + let json_response: $unsigned_ty = + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + Ok(json_response) + } + }}; +} + impl<'a> AggregatorClient<'a> { pub fn new(base_url: &'a str, timeout: Duration, retries: u32, verify_sig: bool) -> Self { Self { base_url, timeout, retries, verify_sig } @@ -28,71 +70,44 @@ impl<'a> AggregatorClient<'a> { prev_token: Option, ) -> Result, http::Error> { let mut url = format!("{}/activity/buckets?eraId={}", self.base_url, era_id); + if let Some(limit) = limit { url = format!("{}&limit={}", url, limit); } if let Some(prev_token) = prev_token { url = format!("{}&prevToken={}", url, prev_token); } - if self.verify_sig { - url = format!("{}&sign=true", url); - } - let response = self.get(&url, Accept::Any)?; - let body = response.body().collect::>(); - if self.verify_sig { - let json_response: json::SignedJsonResponse> = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - - if !json_response.verify() { - log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } - - Ok(json_response.payload) - } else { - let json_response: Vec = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - - Ok(json_response) - } + // Now let the macro do the rest + fetch_and_parse!( + self, + url, + Vec, + Vec + ) } pub fn nodes_aggregates( &self, era_id: DdcEra, limit: Option, - prev_token: Option, // node_id hex string + prev_token: Option, ) -> Result, http::Error> { let mut url = format!("{}/activity/nodes?eraId={}", self.base_url, era_id); + if let Some(limit) = limit { url = format!("{}&limit={}", url, limit); } if let Some(prev_token) = prev_token { url = format!("{}&prevToken={}", url, prev_token); } - if self.verify_sig { - url = format!("{}&sign=true", url); - } - let response = self.get(&url, Accept::Any)?; - let body = response.body().collect::>(); - if self.verify_sig { - let json_response: json::SignedJsonResponse> = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - - if !json_response.verify() { - log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } - - Ok(json_response.payload) - } else { - let json_response: Vec = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - - Ok(json_response) - } + fetch_and_parse!( + self, + url, + Vec, + Vec + ) } pub fn challenge_bucket_sub_aggregate( @@ -100,7 +115,7 @@ impl<'a> AggregatorClient<'a> { era_id: DdcEra, bucket_id: BucketId, node_id: &str, - merkle_tree_node_id: Vec, + merkle_tree_node_id: Vec, ) -> Result { let url = format!( "{}/activity/buckets/{}/challenge?eraId={}&nodeId={}&merkleTreeNodeId={}", @@ -122,7 +137,7 @@ impl<'a> AggregatorClient<'a> { &self, era_id: DdcEra, node_id: &str, - merkle_tree_node_id: Vec, + merkle_tree_node_id: Vec, ) -> Result { let url = format!( "{}/activity/nodes/{}/challenge?eraId={}&merkleTreeNodeId={}", @@ -141,28 +156,75 @@ impl<'a> AggregatorClient<'a> { pub fn eras(&self) -> Result, http::Error> { let mut url = format!("{}/activity/eras", self.base_url); - if self.verify_sig { - url = format!("{}?sign=true", url); - } - let response = self.get(&url, Accept::Any)?; + fetch_and_parse!( + self, + url, + Vec, + Vec + ) + } - let body = response.body().collect::>(); - if self.verify_sig { - let json_response: json::SignedJsonResponse> = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + pub fn payment_eras(&self) -> Result, http::Error> { + let mut url = format!("{}/activity/payment-eras", self.base_url); + fetch_and_parse!(self, url, Vec, Vec) + } - if !json_response.verify() { - log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } + pub fn era_historical_document( + &self, + era_id: DdcEra, + ) -> Result { + let mut url = format!("{}/activity/ehd/{}", self.base_url, era_id); + fetch_and_parse!(self, url, json::EHDResponse, json::EHDResponse) + } - Ok(json_response.payload) - } else { - let json_response: Vec = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; + pub fn partial_historical_document( + &self, + phd_id: String, + ) -> Result { + let mut url = format!("{}/activity/phd/{}", self.base_url, phd_id); + fetch_and_parse!(self, url, json::PHDResponse, json::PHDResponse) + } - Ok(json_response) - } + pub fn traverse_era_historical_document( + &self, + ehd_id: EHDId, + tree_node_id: u32, + tree_levels_count: u32, + ) -> Result, http::Error> { + let mut url = format!( + "{}/activity/ehds/{}/traverse?merkleTreeNodeId={}&levels={}", + self.base_url, + >::into(ehd_id), + tree_node_id, + tree_levels_count + ); + fetch_and_parse!( + self, + url, + Vec, + Vec + ) + } + + pub fn traverse_partial_historical_document( + &self, + phd_id: PHDId, + tree_node_id: u32, + tree_levels_count: u32, + ) -> Result, http::Error> { + let mut url = format!( + "{}/activity/phds/{}/traverse?merkleTreeNodeId={}&levels={}", + self.base_url, + >::into(phd_id), + tree_node_id, + tree_levels_count + ); + fetch_and_parse!( + self, + url, + Vec, + Vec + ) } pub fn traverse_bucket_sub_aggregate( @@ -177,29 +239,7 @@ impl<'a> AggregatorClient<'a> { "{}/activity/buckets/{}/traverse?eraId={}&nodeId={}&merkleTreeNodeId={}&levels={}", self.base_url, bucket_id, era_id, node_id, merkle_tree_node_id, levels, ); - if self.verify_sig { - url = format!("{}&sign=true", url); - } - - let response = self.get(&url, Accept::Any)?; - - let body = response.body().collect::>(); - if self.verify_sig { - let json_response: json::SignedJsonResponse = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - - if !json_response.verify() { - log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } - - Ok(json_response.payload) - } else { - let json_response: json::MerkleTreeNodeResponse = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - - Ok(json_response) - } + fetch_and_parse!(self, url, json::MerkleTreeNodeResponse, json::MerkleTreeNodeResponse) } pub fn traverse_node_aggregate( @@ -213,31 +253,10 @@ impl<'a> AggregatorClient<'a> { "{}/activity/nodes/{}/traverse?eraId={}&merkleTreeNodeId={}&levels={}", self.base_url, node_id, era_id, merkle_tree_node_id, levels, ); - if self.verify_sig { - url = format!("{}&sign=true", url); - } - let response = self.get(&url, Accept::Any)?; - - let body = response.body().collect::>(); - if self.verify_sig { - let json_response: json::SignedJsonResponse = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - - if !json_response.verify() { - log::debug!("bad signature, req: {:?}, resp: {:?}", url, json_response); - return Err(http::Error::Unknown); // TODO (khssnv): more specific error. - } - - Ok(json_response.payload) - } else { - let json_response: json::MerkleTreeNodeResponse = - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown)?; - - Ok(json_response) - } + fetch_and_parse!(self, url, json::MerkleTreeNodeResponse, json::MerkleTreeNodeResponse) } - fn merkle_tree_node_id_param(merkle_tree_node_id: &[u32]) -> String { + fn merkle_tree_node_id_param(merkle_tree_node_id: &[u64]) -> String { merkle_tree_node_id .iter() .map(|x| format!("{}", x.clone())) @@ -245,6 +264,25 @@ impl<'a> AggregatorClient<'a> { .join(",") } + pub fn send_inspection_receipt( + &self, + receipt: json::InspectionReceipt, + ) -> Result { + let url = format!("{}/activity/inspection-receipts", self.base_url); + let body = serde_json::to_vec(&receipt).expect("Inspection receipt to be encoded"); + self.post(&url, body) + } + + pub fn fetch_grouped_inspection_receipts( + &self, + ehd_id: EHDId, + ) -> Result, http::Error> { + let ehd_param: String = ehd_id.into(); + let mut url = format!("{}/activity/inspection-receipts?ehdId={}", self.base_url, ehd_param); + + fetch_and_parse!(self, url, BTreeMap, BTreeMap) + } + fn get(&self, url: &str, accept: Accept) -> Result { let mut maybe_response = None; @@ -294,6 +332,51 @@ impl<'a> AggregatorClient<'a> { Ok(response) } + + fn post(&self, url: &str, request_body: Vec) -> Result { + let mut maybe_response = None; + + let deadline = timestamp().add(self.timeout); + let mut error = None; + + for _ in 0..self.retries { + let request = http::Request::post(url, vec![request_body.clone()]) + .add_header("content-type", "application/json"); + + let pending = request + .deadline(deadline) + .body(vec![request_body.clone()]) + .send() + .map_err(|_| http::Error::IoError)?; + + match pending.try_wait(deadline) { + Ok(Ok(r)) => { + maybe_response = Some(r); + error = None; + break; + }, + Ok(Err(_)) | Err(_) => { + error = Some(http::Error::DeadlineReached); + continue; + }, + } + } + + if let Some(e) = error { + return Err(e); + } + + let response = match maybe_response { + Some(r) => r, + None => return Err(http::Error::Unknown), + }; + + if response.code != 201 { + return Err(http::Error::Unknown); + } + + Ok(response) + } } enum Accept { @@ -530,4 +613,424 @@ pub(crate) mod json { #[serde_as(as = "Base64")] pub signature: Vec, } + + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Encode, Decode)] + pub struct EHDResponse { + #[serde(rename = "ehdId")] + pub ehd_id: String, + #[serde(rename = "clusterId")] + pub cluster_id: String, + #[serde(rename = "eraId")] + pub era_id: DdcEra, + #[serde(rename = "gCollector")] + pub g_collector: String, + #[serde(rename = "pdhIds")] + pub pdh_ids: Vec, + pub hash: String, + pub status: String, + pub customers: Vec, + pub providers: Vec, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct EHDUsage { + #[serde(rename = "storedBytes")] + pub stored_bytes: i64, + #[serde(rename = "transferredBytes")] + pub transferred_bytes: u64, + #[serde(rename = "puts")] + pub number_of_puts: u64, + #[serde(rename = "gets")] + pub number_of_gets: u64, + } + + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Encode, Decode)] + pub struct EHDUsagePercent { + #[serde(rename = "storedBytesPercent")] + pub stored_bytes: f64, + #[serde(rename = "transferredBytesPercent")] + pub transferred_bytes: f64, + #[serde(rename = "putsPercent")] + pub number_of_puts: f64, + #[serde(rename = "getsPercent")] + pub number_of_gets: f64, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct EHDCustomer { + #[serde(rename = "customerId")] + pub customer_id: String, + #[serde(rename = "consumedUsage")] + pub consumed_usage: EHDUsage, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct EHDProvider { + #[serde(rename = "providerId")] + pub provider_id: String, + #[serde(rename = "providedUsage")] + pub provided_usage: EHDUsage, + pub nodes: Vec, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct EHDNodeResources { + #[serde(rename = "nodeId")] + pub node_key: String, + #[serde(rename = "storedBytes")] + pub stored_bytes: i64, + #[serde(rename = "transferredBytes")] + pub transferred_bytes: u64, + #[serde(rename = "puts")] + pub number_of_puts: u64, + #[serde(rename = "gets")] + pub number_of_gets: u64, + #[serde(rename = "avgLatency")] + pub avg_latency: u64, + #[serde(rename = "avgBandwidth")] + pub avg_bandwidth: u64, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct PHDNodeAggregate { + #[serde(rename = "nodeId")] + pub node_key: String, + #[serde(rename = "storedBytes")] + pub stored_bytes: i64, + #[serde(rename = "transferredBytes")] + pub transferred_bytes: u64, + #[serde(rename = "numberOfPuts")] + pub number_of_puts: u64, + #[serde(rename = "numberOfGets")] + pub number_of_gets: u64, + pub metrics: PHDNodeMetrics, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct PHDNodeMetrics { + latency: LatencyMetrics, + availability: AvailabilityMetrics, + bandwidth: BandwidthMetrics, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct LatencyMetrics { + #[serde(rename = "p50")] + p_50: u64, + #[serde(rename = "p95")] + p_95: u64, + #[serde(rename = "p99")] + p_99: u64, + #[serde(rename = "p999")] + p_999: u64, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct AvailabilityMetrics { + #[serde(rename = "totalRequests")] + total_requests: u64, + #[serde(rename = "failedRequests")] + failed_requests: u64, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct BandwidthMetrics { + #[serde(rename = "100kbps")] + kbps_100: u64, + #[serde(rename = "1Mbps")] + mbps_1: u64, + #[serde(rename = "10Mbps")] + mbps_10: u64, + #[serde(rename = "100Mbps")] + mbps_100: u64, + inf: u64, + } + + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Encode, Decode)] + pub struct PHDResponse { + #[serde(rename = "phdId")] + pub phd_id: String, + #[serde(rename = "merkleTreeNodeId")] + pub tree_node_id: u32, + #[serde(rename = "NodesAggregates")] + pub nodes_aggregates: Vec, + #[serde(rename = "BucketsAggregates")] + pub buckets_aggregates: Vec, + pub hash: String, + pub collector: String, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct PHDBucketAggregate { + #[serde(rename = "bucketId")] + pub bucket_id: BucketId, + #[serde(rename = "storedBytes")] + pub stored_bytes: i64, + #[serde(rename = "transferredBytes")] + pub transferred_bytes: u64, + #[serde(rename = "numberOfPuts")] + pub number_of_puts: u64, + #[serde(rename = "numberOfGets")] + pub number_of_gets: u64, + #[serde(rename = "subAggregates")] + pub sub_aggregates: Vec, + + // todo: remove from top-level bucket aggregation + #[serde(rename = "nodeId")] + pub node_key: String, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct PHDBucketSubAggregate { + #[serde(rename = "bucketId")] + pub bucket_id: BucketId, + #[serde(rename = "nodeId")] + pub node_key: String, + #[serde(rename = "storedBytes")] + pub stored_bytes: i64, + #[serde(rename = "transferredBytes")] + pub transferred_bytes: u64, + #[serde(rename = "numberOfPuts")] + pub number_of_puts: u64, + #[serde(rename = "numberOfGets")] + pub number_of_gets: u64, + } + + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Encode, Decode)] + pub struct TraversedEHDResponse { + #[serde(rename = "ehdId")] + pub ehd_id: String, + #[serde(rename = "groupingCollector")] + pub g_collector: String, + #[serde(rename = "merkleTreeNodeId")] + pub tree_node_id: u32, + #[serde(rename = "merkleTreeNodeHash")] + pub tree_node_hash: String, + #[serde(rename = "pdhIds")] + pub pdh_ids: Vec, + pub status: String, + pub customers: Vec, + pub providers: Vec, + } + + impl TraversedEHDResponse { + pub fn get_cluster_usage(&self) -> EHDUsage { + self.providers.iter().fold( + EHDUsage { + stored_bytes: 0, + transferred_bytes: 0, + number_of_puts: 0, + number_of_gets: 0, + }, + |mut acc, provider| { + acc.stored_bytes += provider.provided_usage.stored_bytes; + acc.transferred_bytes += provider.provided_usage.transferred_bytes; + acc.number_of_puts += provider.provided_usage.number_of_puts; + acc.number_of_gets += provider.provided_usage.number_of_gets; + acc + }, + ) + } + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct TraversedEHDCustomer { + #[serde(rename = "customerId")] + pub customer_id: String, + #[serde(rename = "consumedUsage")] + pub consumed_usage: EHDUsage, + } + + impl TraversedEHDCustomer { + pub fn parse_customer_id(&self) -> Result { + if !self.customer_id.starts_with("0x") || self.customer_id.len() != 66 { + return Err(()); + } + + let hex_str = &self.customer_id[2..]; // skip '0x' + let hex_bytes = match hex::decode(hex_str) { + Ok(bytes) => bytes, + Err(_) => return Err(()), + }; + if hex_bytes.len() != 32 { + return Err(()); + } + let mut pub_key = [0u8; 32]; + pub_key.copy_from_slice(&hex_bytes[..32]); + + Ok(AccountId32::from(pub_key)) + } + } + + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Encode, Decode)] + pub struct TraversedEHDProvider { + #[serde(rename = "providerId")] + pub provider_id: String, + #[serde(rename = "providedUsage")] + pub provided_usage: EHDUsage, + pub nodes: Vec, + } + + impl TraversedEHDProvider { + pub fn parse_provider_id(&self) -> Result { + if !self.provider_id.starts_with("0x") || self.provider_id.len() != 66 { + return Err(()); + } + + let hex_str = &self.provider_id[2..]; // skip '0x' + let hex_bytes = match hex::decode(hex_str) { + Ok(bytes) => bytes, + Err(_) => return Err(()), + }; + if hex_bytes.len() != 32 { + return Err(()); + } + let mut pub_key = [0u8; 32]; + pub_key.copy_from_slice(&hex_bytes[..32]); + + Ok(AccountId32::from(pub_key)) + } + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct TraversedEHDNodeResources { + #[serde(rename = "nodeId")] + pub node_key: String, + #[serde(rename = "storedBytes")] + pub stored_bytes: i64, + #[serde(rename = "transferredBytes")] + pub transferred_bytes: u64, + #[serde(rename = "puts")] + pub number_of_puts: u64, + #[serde(rename = "gets")] + pub number_of_gets: u64, + } + + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Encode, Decode)] + pub struct TraversedPHDResponse { + #[serde(rename = "phdId")] + pub phd_id: String, + #[serde(rename = "collectorId")] + pub collector: String, + #[serde(rename = "merkleTreeNodeId")] + pub tree_node_id: u32, + #[serde(rename = "merkleTreeNodeHash")] + pub tree_node_hash: String, + #[serde(rename = "nodesAggregates")] + pub nodes_aggregates: Vec, + #[serde(rename = "bucketsAggregates")] + pub buckets_aggregates: Vec, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct TraversedPHDNodeAggregate { + #[serde(rename = "nodeId")] + pub node_key: String, + #[serde(rename = "storedBytes")] + pub stored_bytes: i64, + #[serde(rename = "transferredBytes")] + pub transferred_bytes: u64, + #[serde(rename = "numberOfPuts")] + pub number_of_puts: u64, + #[serde(rename = "numberOfGets")] + pub number_of_gets: u64, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, + )] + pub struct TraversedPHDBucketAggregate { + #[serde(rename = "bucketId")] + pub bucket_id: BucketId, + #[serde(rename = "storedBytes")] + pub stored_bytes: i64, + #[serde(rename = "transferredBytes")] + pub transferred_bytes: u64, + #[serde(rename = "numberOfPuts")] + pub number_of_puts: u64, + #[serde(rename = "numberOfGets")] + pub number_of_gets: u64, + } + + #[derive(Debug, Serialize, Deserialize, Clone, Hash, Encode, Decode)] + pub struct InspectionReceipt { + #[serde(rename = "ehdId")] + pub ehd_id: String, + pub inspector: String, + pub signature: String, + #[serde(rename = "nodesInspection")] + pub nodes_inspection: InspectionResult, + #[serde(rename = "bucketsInspection")] + pub buckets_inspection: InspectionResult, + } + + #[derive(Debug, Serialize, Deserialize, Clone, Hash, Encode, Decode)] + pub struct InspectionResult { + #[serde(rename = "unverifiedBranches")] + pub unverified_branches: Vec, + #[serde(rename = "verifiedBranches")] + pub verified_branches: Vec, + } + + #[derive(Debug, Serialize, Deserialize, Clone, Hash, Encode, Decode)] + pub struct InspectedTreePart { + pub collector: String, + pub aggregate: AggregateKey, + pub nodes: Vec, + pub leafs: BTreeMap>, + } + + #[derive( + Debug, Serialize, Deserialize, Clone, Hash, Encode, Decode, Ord, PartialOrd, PartialEq, Eq, + )] + pub struct EHDEra { + pub id: u32, + pub status: String, + pub era_start: Option, + pub era_end: Option, + pub time_start: Option, + pub time_end: Option, + } + + #[derive(Debug, Serialize, Deserialize, Clone, Hash, Encode, Decode)] + pub struct GroupedInspectionReceipt { + #[serde(rename = "ehdId")] + pub ehd_id: String, + pub inspector: String, + pub signature: String, + #[serde(rename = "nodesInspection")] + pub nodes_inspection: Option, /* todo(yahortsaryk): remove optional + * after fix in DDC api */ + #[serde(rename = "bucketsInspection")] + pub buckets_inspection: Option, /* todo(yahortsaryk): remove optional + * after fix in DDC api */ + } } diff --git a/pallets/ddc-verification/src/benchmarking.rs b/pallets/ddc-verification/src/benchmarking.rs index 6677e554a..c4bca621d 100644 --- a/pallets/ddc-verification/src/benchmarking.rs +++ b/pallets/ddc-verification/src/benchmarking.rs @@ -1,8 +1,7 @@ use ddc_primitives::{ - BillingFingerprintParams, BillingReportParams, BucketId, BucketParams, ClusterId, - ClusterParams, ClusterProtocolParams, CustomerCharge, DeltaUsageHash, EraValidation, - EraValidationStatus, MergeMMRHash, NodeParams, NodePubKey, NodeUsage, PayoutState, - StorageNodeMode, StorageNodeParams, AVG_SECONDS_MONTH, DOLLARS as CERE, MAX_PAYOUT_BATCH_SIZE, + ClusterId, ClusterParams, ClusterProtocolParams, DeltaUsageHash, MergeMMRHash, NodePubKey, + PayoutFingerprintParams, PayoutReceiptParams, PayoutState, DOLLARS as CERE, + MAX_PAYOUT_BATCH_SIZE, }; use frame_benchmarking::{account, v2::*, whitelist_account}; use frame_system::RawOrigin; @@ -14,7 +13,6 @@ use sp_runtime::{ use sp_std::{collections::btree_set::BTreeSet, vec}; use super::*; -use crate::EraActivity; #[allow(unused)] use crate::Pallet as DdcVerification; @@ -99,47 +97,14 @@ mod benchmarks { ); } - fn setup_validation_era( - cluster_id: ClusterId, - era_id: DdcEra, - validators: Vec, - payers_merkle_root_hash: DeltaUsageHash, - payees_merkle_root_hash: DeltaUsageHash, - status: EraValidationStatus, - ) { - let mut validations_map = BTreeMap::new(); - for validator in validators { - validations_map.insert( - (payers_merkle_root_hash, payees_merkle_root_hash), - vec![validator.clone()], - ); - } - - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; - - let era_validation = EraValidation:: { - validators: validations_map, - start_era, - end_era, - payers_merkle_root_hash, - payees_merkle_root_hash, - status, - }; - - >::insert(cluster_id, era_id, era_validation); - } - #[allow(clippy::too_many_arguments)] - fn create_billing_report( + fn create_payout_receipt( cluster_id: ClusterId, - era_id: DdcEra, - start_era: i64, - end_era: i64, + ehd_id: EHDId, state: PayoutState, - total_customer_charge: CustomerCharge, - total_distributed_reward: u128, - cluster_usage: NodeUsage, + total_collected_charges: u128, + total_distributed_rewards: u128, + total_settled_fees: u128, charging_max_batch_index: BatchIndex, charging_processed_batches: Vec, rewarding_max_batch_index: BatchIndex, @@ -151,39 +116,31 @@ mod benchmarks { let hash = blake2_128(&0.encode()); let vault = T::PalletId::get().into_sub_account_truncating(hash); - let total_customer_charge_amount = total_customer_charge.transfer + - total_customer_charge.storage + - total_customer_charge.gets + - total_customer_charge.puts; - - endow_account::(&vault, total_customer_charge_amount); + endow_account::(&vault, total_collected_charges); let mut validators_set = BTreeSet::new(); for validator in validators { validators_set.insert(validator); } - let fingerprint = - T::PayoutProcessor::create_billing_fingerprint(BillingFingerprintParams { - cluster_id, - era: era_id, - start_era, - end_era, - payers_merkle_root, - payees_merkle_root, - cluster_usage, - validators: validators_set, - }); + let fingerprint = T::PayoutProcessor::create_payout_fingerprint(PayoutFingerprintParams { + cluster_id, + ehd_id: ehd_id.clone().into(), + payers_merkle_root, + payees_merkle_root, + validators: validators_set, + }); - T::PayoutProcessor::create_billing_report( + T::PayoutProcessor::create_payout_receipt( vault.clone(), - BillingReportParams { + PayoutReceiptParams { cluster_id, - era: era_id, + era: ehd_id.2, state, fingerprint, - total_customer_charge, - total_distributed_reward, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index, @@ -192,38 +149,6 @@ mod benchmarks { ); } - #[benchmark] - fn set_prepare_era_for_payout(b: Linear<1, 5>) { - let validator = create_validator_account::(); - let cluster_id = ClusterId::from([1; 20]); - let era = EraActivity { id: 1, start: 1000, end: 2000 }; - - let payers_merkle_root_hash = H256(blake2_256(&1.encode())); - let payees_merkle_root_hash = H256(blake2_256(&2.encode())); - - let mut payers_batch_merkle_root_hashes = vec![]; - let mut payees_batch_merkle_root_hashes = vec![]; - - for i in 0..b { - payers_batch_merkle_root_hashes.push(H256(blake2_256(&(i + 10).encode()))); - payees_batch_merkle_root_hashes.push(H256(blake2_256(&(i + 100).encode()))) - } - - #[extrinsic_call] - set_prepare_era_for_payout( - RawOrigin::Signed(validator), - cluster_id, - era, - payers_merkle_root_hash, - payees_merkle_root_hash, - payers_batch_merkle_root_hashes, - payees_batch_merkle_root_hashes, - ); - - assert!(>::contains_key(cluster_id, era.id)); - assert_has_event::(Event::EraValidationReady { cluster_id, era_id: era.id }.into()); - } - #[benchmark] fn set_validator_key() { let validator = create_account::("new_validator_account", 0, 0); @@ -244,94 +169,72 @@ mod benchmarks { } #[benchmark] - fn commit_billing_fingerprint() { + fn commit_payout_fingerprint() { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); let payers_merkle_root = H256(blake2_256(&3.encode())); let payees_merkle_root = H256(blake2_256(&4.encode())); - let cluster_usage = NodeUsage::default(); create_default_cluster::(cluster_id); let validator = create_validator_account::(); whitelist_account!(validator); - setup_validation_era::( - cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::ReadyForPayout, - ); #[extrinsic_call] - commit_billing_fingerprint( + commit_payout_fingerprint( RawOrigin::Signed(validator), cluster_id, - era_id, - start_era, - end_era, + ehd_id.into(), payers_merkle_root, payees_merkle_root, - cluster_usage, ); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::NotInitialized); } #[benchmark] - fn begin_billing_report() { + fn begin_payout() { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); + let payers_merkle_root = H256(blake2_256(&3.encode())); + let payees_merkle_root = H256(blake2_256(&4.encode())); create_default_cluster::(cluster_id); let validator = create_validator_account::(); whitelist_account!(validator); - setup_validation_era::( - cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::ReadyForPayout, - ); let mut validators = BTreeSet::new(); validators.insert(validator.clone()); - let fingerprint = - T::PayoutProcessor::create_billing_fingerprint(BillingFingerprintParams { - cluster_id, - era: era_id, - start_era, - end_era, - payers_merkle_root: H256(blake2_256(&3.encode())), - payees_merkle_root: H256(blake2_256(&4.encode())), - cluster_usage: NodeUsage::default(), - validators, - }); + let fingerprint = T::PayoutProcessor::create_payout_fingerprint(PayoutFingerprintParams { + cluster_id, + ehd_id: ehd_id.into(), + payers_merkle_root, + payees_merkle_root, + validators, + }); #[extrinsic_call] - begin_billing_report(RawOrigin::Signed(validator), cluster_id, era_id, fingerprint); + begin_payout(RawOrigin::Signed(validator), cluster_id, payment_era, fingerprint); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::Initialized); } #[benchmark] fn begin_charging_customers() { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); let state = PayoutState::Initialized; - let total_customer_charge = Default::default(); - let total_distributed_reward: u128 = 0; - let cluster_usage = Default::default(); + let total_collected_charges: u128 = 0; + let total_distributed_rewards: u128 = 0; + let total_settled_fees: u128 = 0; let charging_max_batch_index = 10; let charging_processed_batches = Default::default(); let rewarding_max_batch_index = Default::default(); @@ -344,24 +247,13 @@ mod benchmarks { let validator = create_validator_account::(); whitelist_account!(validator); - setup_validation_era::( - cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::ReadyForPayout, - ); - - create_billing_report::( + create_payout_receipt::( cluster_id, - era_id, - start_era, - end_era, + ehd_id, state, - total_customer_charge, - total_distributed_reward, - cluster_usage, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index, @@ -375,24 +267,24 @@ mod benchmarks { begin_charging_customers( RawOrigin::Signed(validator), cluster_id, - era_id, + payment_era, charging_max_batch_index, ); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::ChargingCustomers); } #[benchmark] fn send_charging_customers_batch(b: Linear<1, { MAX_PAYOUT_BATCH_SIZE.into() }>) { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era: DdcEra = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); let state = PayoutState::ChargingCustomers; - let total_customer_charge = Default::default(); - let total_distributed_reward: u128 = 0; - let cluster_usage = Default::default(); + let total_collected_charges: u128 = 0; + let total_distributed_rewards: u128 = 0; + let total_settled_fees: u128 = 0; let charging_max_batch_index = 0; let charging_processed_batches = Default::default(); let rewarding_max_batch_index = Default::default(); @@ -404,7 +296,7 @@ mod benchmarks { whitelist_account!(validator); let batch_index: BatchIndex = 0; - let mut payers_batch: Vec<(BucketId, BucketUsage)> = vec![]; + let mut payers_batch: Vec<(T::AccountId, u128)> = vec![]; for i in 0..b { let customer = create_account::("customer", i, i); @@ -416,34 +308,23 @@ mod benchmarks { endow_customer::(&customer, 10 * CERE); } - let customer_usage = BucketUsage { - transferred_bytes: 200000000, // 200 mb - stored_bytes: 100000000, // 100 mb - number_of_gets: 10, // 10 gets - number_of_puts: 5, // 5 puts + let charge_amount = if b % 2 == 0 { + // no customer debt path + 900_000 * CERE + } else { + // customer debt path + 20 * CERE }; - let bucket_id: BucketId = (i + 1).into(); - T::BucketManager::create_bucket( - &cluster_id, - bucket_id, - customer, - BucketParams { is_public: true }, - ) - .expect("Bucket to be created"); - - payers_batch.push((bucket_id, customer_usage)); + payers_batch.push((customer, charge_amount)); } let activity_hashes = payers_batch .clone() .into_iter() - .map(|(bucket_id, usage)| { - let mut data = bucket_id.encode(); - data.extend_from_slice(&usage.stored_bytes.encode()); - data.extend_from_slice(&usage.transferred_bytes.encode()); - data.extend_from_slice(&usage.number_of_puts.encode()); - data.extend_from_slice(&usage.number_of_gets.encode()); + .map(|(customer_id, amount)| { + let mut data = customer_id.encode(); + data.extend_from_slice(&amount.encode()); H256(blake2_256(&data)) }) .collect::>(); @@ -467,24 +348,13 @@ mod benchmarks { let payees_merkle_root_hash = DeltaUsageHash::default(); - setup_validation_era::( + create_payout_receipt::( cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::PayoutInProgress, - ); - - create_billing_report::( - cluster_id, - era_id, - start_era, - end_era, + ehd_id, state, - total_customer_charge, - total_distributed_reward, - cluster_usage, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index, @@ -498,32 +368,27 @@ mod benchmarks { send_charging_customers_batch( RawOrigin::Signed(validator), cluster_id, - era_id, + payment_era, batch_index, payers_batch, MMRProof { proof }, ); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::ChargingCustomers); - assert!(T::PayoutProcessor::all_customer_batches_processed(&cluster_id, era_id)); + assert!(T::PayoutProcessor::is_customers_charging_finished(&cluster_id, payment_era)); } #[benchmark] fn end_charging_customers() { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); let state = PayoutState::ChargingCustomers; - let total_customer_charge = CustomerCharge { - transfer: 200 * CERE, // price for 200 mb - storage: 100 * CERE, // price for 100 mb - gets: 10 * CERE, // price for 10 gets - puts: 5 * CERE, // price for 5 puts - }; - let total_distributed_reward: u128 = 0; - let cluster_usage = Default::default(); + let total_collected_charges: u128 = 315 * CERE; + let total_distributed_rewards: u128 = 0; + let total_settled_fees: u128 = 0; let charging_max_batch_index = 0; let charging_processed_batches = vec![0]; let rewarding_max_batch_index = Default::default(); @@ -535,23 +400,13 @@ mod benchmarks { let validator = create_validator_account::(); whitelist_account!(validator); - setup_validation_era::( + create_payout_receipt::( cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::PayoutInProgress, - ); - create_billing_report::( - cluster_id, - era_id, - start_era, - end_era, + ehd_id, state, - total_customer_charge.clone(), - total_distributed_reward, - cluster_usage, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index, @@ -562,28 +417,23 @@ mod benchmarks { ); #[extrinsic_call] - end_charging_customers(RawOrigin::Signed(validator), cluster_id, era_id); + end_charging_customers(RawOrigin::Signed(validator), cluster_id, payment_era); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::CustomersChargedWithFees); - assert!(T::PayoutProcessor::all_customer_batches_processed(&cluster_id, era_id)); + assert!(T::PayoutProcessor::is_customers_charging_finished(&cluster_id, payment_era)); } #[benchmark] fn begin_rewarding_providers() { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); let state = PayoutState::CustomersChargedWithFees; - let total_customer_charge = CustomerCharge { - transfer: 200 * CERE, // price for 200 mb - storage: 100 * CERE, // price for 100 mb - gets: 10 * CERE, // price for 10 gets - puts: 5 * CERE, // price for 5 puts - }; - let total_distributed_reward: u128 = 0; - let cluster_usage = Default::default(); + let total_collected_charges: u128 = 315 * CERE; + let total_distributed_rewards: u128 = 0; + let total_settled_fees: u128 = 0; let charging_max_batch_index = 0; let charging_processed_batches = vec![0]; let rewarding_max_batch_index = 10; @@ -595,23 +445,13 @@ mod benchmarks { let validator = create_validator_account::(); whitelist_account!(validator); - setup_validation_era::( - cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::PayoutInProgress, - ); - create_billing_report::( + create_payout_receipt::( cluster_id, - era_id, - start_era, - end_era, + ehd_id, state, - total_customer_charge, - total_distributed_reward, - cluster_usage, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index, @@ -625,34 +465,24 @@ mod benchmarks { begin_rewarding_providers( RawOrigin::Signed(validator), cluster_id, - era_id, + payment_era, rewarding_max_batch_index, ); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::RewardingProviders); } #[benchmark] fn send_rewarding_providers_batch(b: Linear<1, { MAX_PAYOUT_BATCH_SIZE.into() }>) { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); let state = PayoutState::RewardingProviders; - let total_customer_charge = CustomerCharge { - transfer: (200 * CERE).saturating_mul(b.into()), // price for 200 mb per customer - storage: (100 * CERE).saturating_mul(b.into()), // price for 100 mb per customer - gets: (10 * CERE).saturating_mul(b.into()), // price for 10 gets per customer - puts: (5 * CERE).saturating_mul(b.into()), // price for 5 puts per customer - }; - let total_distributed_reward: u128 = 0; - let cluster_usage = NodeUsage { - transferred_bytes: 200000000u64.saturating_mul(b.into()), // 200 mb per provider - stored_bytes: 100000000i64.saturating_mul(b.into()), // 100 mb per provider - number_of_gets: 10u64.saturating_mul(b.into()), // 10 gets per provider - number_of_puts: 10u64.saturating_mul(b.into()), // 5 puts per provider - }; + let total_collected_charges = (315 * CERE).saturating_mul(b.into()); + let total_distributed_rewards: u128 = 0; + let total_settled_fees: u128 = 0; let charging_max_batch_index = 0; let charging_processed_batches = vec![0]; let rewarding_max_batch_index = 0; @@ -663,50 +493,21 @@ mod benchmarks { whitelist_account!(validator); let batch_index: BatchIndex = 0; - let mut payees_batch: Vec<(NodePubKey, NodeUsage)> = vec![]; + let mut payees_batch: Vec<(T::AccountId, u128)> = vec![]; for i in 0..b { let provider = create_account::("provider", i, i); - endow_account::(&provider, T::Currency::minimum_balance().saturated_into()); - - let usage = NodeUsage { - transferred_bytes: 200000000, // 200 mb - stored_bytes: 100000000, // 100 mb - number_of_gets: 10, // 10 gets - number_of_puts: 5, // 5 puts - }; - - let key = blake2_256(&i.encode()); - let node_key = NodePubKey::StoragePubKey(AccountId32::from(key)); - - T::NodeManager::create_node( - node_key.clone(), - provider, - NodeParams::StorageParams(StorageNodeParams { - mode: StorageNodeMode::Storage, - host: vec![1u8; 255], - domain: vec![2u8; 255], - ssl: true, - http_port: 35000u16, - grpc_port: 25000u16, - p2p_port: 15000u16, - }), - ) - .expect("Node to be created"); - - payees_batch.push((node_key, usage)); + let reward_amount = (10 * CERE).saturating_mul(b.into()); + payees_batch.push((provider, reward_amount)); } let activity_hashes = payees_batch .clone() .into_iter() - .map(|(node_key, usage)| { - let mut data = node_key.encode(); - data.extend_from_slice(&usage.stored_bytes.encode()); - data.extend_from_slice(&usage.transferred_bytes.encode()); - data.extend_from_slice(&usage.number_of_puts.encode()); - data.extend_from_slice(&usage.number_of_gets.encode()); + .map(|(provider_id, amount)| { + let mut data = provider_id.encode(); + data.extend_from_slice(&amount.encode()); H256(blake2_256(&data)) }) .collect::>(); @@ -730,24 +531,13 @@ mod benchmarks { let proof = mmr2.gen_proof(vec![pos]).unwrap().proof_items().to_vec(); - setup_validation_era::( + create_payout_receipt::( cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::PayoutInProgress, - ); - - create_billing_report::( - cluster_id, - era_id, - start_era, - end_era, + ehd_id, state, - total_customer_charge, - total_distributed_reward, - cluster_usage, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index, @@ -761,40 +551,27 @@ mod benchmarks { send_rewarding_providers_batch( RawOrigin::Signed(validator), cluster_id, - era_id, + payment_era, batch_index, payees_batch, MMRProof { proof }, ); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::RewardingProviders); - assert!(T::PayoutProcessor::all_provider_batches_processed(&cluster_id, era_id)); + assert!(T::PayoutProcessor::is_providers_rewarding_finished(&cluster_id, payment_era)); } #[benchmark] fn end_rewarding_providers() { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); let state = PayoutState::RewardingProviders; - let total_customer_charge = CustomerCharge { - transfer: 200 * CERE, // price for 200 mb - storage: 100 * CERE, // price for 100 mb - gets: 10 * CERE, // price for 10 gets - puts: 5 * CERE, // price for 5 puts - }; - let total_distributed_reward: u128 = total_customer_charge.transfer + - total_customer_charge.storage + - total_customer_charge.gets + - total_customer_charge.puts; - let cluster_usage = NodeUsage { - transferred_bytes: 200000000, // 200 mb - stored_bytes: 100000000, // 100 mb - number_of_gets: 10, // 10 gets - number_of_puts: 5, // 5 puts - }; + let total_collected_charges = 315 * CERE; + let total_distributed_rewards: u128 = total_collected_charges; + let total_settled_fees: u128 = 0; let charging_max_batch_index = 0; let charging_processed_batches = vec![0]; let rewarding_max_batch_index = 0; @@ -806,23 +583,13 @@ mod benchmarks { let validator = create_validator_account::(); whitelist_account!(validator); - setup_validation_era::( + create_payout_receipt::( cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::PayoutInProgress, - ); - create_billing_report::( - cluster_id, - era_id, - start_era, - end_era, + ehd_id, state, - total_customer_charge.clone(), - total_distributed_reward, - cluster_usage, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index, @@ -833,35 +600,22 @@ mod benchmarks { ); #[extrinsic_call] - end_rewarding_providers(RawOrigin::Signed(validator), cluster_id, era_id); + end_rewarding_providers(RawOrigin::Signed(validator), cluster_id, payment_era); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::ProvidersRewarded); } #[benchmark] - fn end_billing_report() { + fn end_payout() { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; - let start_era: i64 = 1_000_000_000; - let end_era: i64 = start_era + AVG_SECONDS_MONTH; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); let state = PayoutState::ProvidersRewarded; - let total_customer_charge = CustomerCharge { - transfer: 200 * CERE, // price for 200 mb - storage: 100 * CERE, // price for 100 mb - gets: 10 * CERE, // price for 10 gets - puts: 5 * CERE, // price for 5 puts - }; - let total_distributed_reward: u128 = total_customer_charge.transfer + - total_customer_charge.storage + - total_customer_charge.gets + - total_customer_charge.puts; - let cluster_usage = NodeUsage { - transferred_bytes: 200000000, // 200 mb - stored_bytes: 100000000, // 100 mb - number_of_gets: 10, // 10 gets - number_of_puts: 5, // 5 puts - }; + let total_collected_charges = 315 * CERE; + let total_distributed_rewards: u128 = total_collected_charges; + let total_settled_fees: u128 = 0; let charging_max_batch_index = 0; let charging_processed_batches = vec![0]; let rewarding_max_batch_index = 0; @@ -873,23 +627,13 @@ mod benchmarks { let validator = create_validator_account::(); whitelist_account!(validator); - setup_validation_era::( + create_payout_receipt::( cluster_id, - era_id, - vec![validator.clone()], - H256(blake2_256(&1.encode())), - H256(blake2_256(&2.encode())), - EraValidationStatus::PayoutInProgress, - ); - create_billing_report::( - cluster_id, - era_id, - start_era, - end_era, + ehd_id, state, - total_customer_charge.clone(), - total_distributed_reward, - cluster_usage, + total_collected_charges, + total_distributed_rewards, + total_settled_fees, charging_max_batch_index, charging_processed_batches, rewarding_max_batch_index, @@ -900,9 +644,9 @@ mod benchmarks { ); #[extrinsic_call] - end_billing_report(RawOrigin::Signed(validator), cluster_id, era_id); + end_payout(RawOrigin::Signed(validator), cluster_id, payment_era); - let status = T::PayoutProcessor::get_billing_report_status(&cluster_id, era_id); + let status = T::PayoutProcessor::get_payout_state(&cluster_id, payment_era); assert_eq!(status, PayoutState::Finalized); } @@ -924,13 +668,15 @@ mod benchmarks { } #[benchmark] - fn set_era_validations() { + fn force_skip_inspection() { let cluster_id = ClusterId::from([1; 20]); - let era_id: DdcEra = 1; + let payment_era = 1; + let collector_key = NodePubKey::StoragePubKey(AccountId32::from([0u8; 32])); + let ehd_id = EHDId(cluster_id, collector_key, payment_era); - #[extrinsic_call] - set_era_validations(RawOrigin::Root, cluster_id, era_id); + create_default_cluster::(cluster_id); - >::contains_key(cluster_id, era_id); + #[extrinsic_call] + force_skip_inspection(RawOrigin::Root, cluster_id, ehd_id.into()); } } diff --git a/pallets/ddc-verification/src/lib.rs b/pallets/ddc-verification/src/lib.rs index cd63a9d70..5a00bef70 100644 --- a/pallets/ddc-verification/src/lib.rs +++ b/pallets/ddc-verification/src/lib.rs @@ -17,13 +17,14 @@ use ddc_primitives::traits::{BucketManager, ClusterCreator, CustomerDepositor}; use ddc_primitives::{ ocw_mutex::OcwMutex, traits::{ - ClusterManager, ClusterValidator, CustomerVisitor, NodeManager, PayoutProcessor, - StorageUsageProvider, ValidatorVisitor, + ClusterManager, ClusterProtocol, ClusterValidator, CustomerVisitor, NodeManager, + PayoutProcessor, StorageUsageProvider, ValidatorVisitor, }, - BatchIndex, BillingFingerprintParams, BillingReportParams, BucketStorageUsage, BucketUsage, - ClusterId, ClusterStatus, DdcEra, EraValidation, EraValidationStatus, MMRProof, NodeParams, - NodePubKey, NodeStorageUsage, NodeUsage, PayableUsageHash, PayoutState, StorageNodeParams, - StorageNodePubKey, + BatchIndex, BucketStorageUsage, ClusterFeesParams, ClusterId, ClusterPricingParams, + ClusterStatus, CustomerCharge as CustomerCosts, DdcEra, EHDId, EhdEra, MMRProof, NodeParams, + NodePubKey, NodeStorageUsage, PHDId, PayableUsageHash, PaymentEra, PayoutFingerprintParams, + PayoutReceiptParams, PayoutState, ProviderReward as ProviderProfits, StorageNodeParams, + StorageNodePubKey, AVG_SECONDS_MONTH, }; use frame_support::{ pallet_prelude::*, @@ -55,7 +56,7 @@ pub use sp_io::{ use sp_runtime::{ offchain::{http, Duration, StorageKind}, traits::{Hash, IdentifyAccount}, - Percent, + ArithmeticError, Percent, Perquintill, }; use sp_staking::StakingInterface; use sp_std::{ @@ -63,7 +64,6 @@ use sp_std::{ fmt::Debug, prelude::*, }; - pub mod weights; use crate::weights::WeightInfo; @@ -79,11 +79,17 @@ pub mod migrations; mod aggregator_client; +pub mod aggregate_tree; +use aggregate_tree::{ + calculate_sample_size_fin, calculate_sample_size_inf, get_leaves_ids, D_099, P_001, +}; + pub mod proto { include!(concat!(env!("OUT_DIR"), "/activity.rs")); } mod signature; +use signature::Verify; pub(crate) type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -102,61 +108,55 @@ pub mod pallet { use super::*; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); const _SUCCESS_CODE: u16 = 200; const _BUF_SIZE: usize = 128; const RESPONSE_TIMEOUT: u64 = 20000; + pub const MAX_RETRIES_COUNT: u32 = 3; pub const BUCKETS_AGGREGATES_FETCH_BATCH_SIZE: usize = 100; pub const NODES_AGGREGATES_FETCH_BATCH_SIZE: usize = 10; pub const OCW_MUTEX_ID: &[u8] = b"inspection"; - /// Delta usage of a bucket includes only the delta usage for the processing era reported by - /// collectors. This usage can be verified of unverified by inspectors. - pub(crate) type BucketDeltaUsage = aggregator_client::json::BucketSubAggregate; - - /// Delta usage of a node includes only the delta usage for the processing era reported by - /// collectors. This usage can be verified of unverified by inspectors. - pub(crate) type NodeDeltaUsage = aggregator_client::json::NodeAggregate; + // todo(yahortsaryk): provide an endpoint for fetching G-collector from DDC + pub const G_COLLECTOR_KEY: NodePubKey = NodePubKey::StoragePubKey(AccountId32::new([ + 0x9e, 0xf9, 0x8a, 0xd9, 0xc3, 0x62, 0x6b, 0xa7, 0x25, 0xe7, 0x8d, 0x76, 0xcf, 0xcf, 0xc4, + 0xb4, 0xd0, 0x7e, 0x84, 0xf0, 0x38, 0x84, 0x65, 0xbc, 0x7e, 0xb9, 0x92, 0xe3, 0xe1, 0x17, + 0x23, 0x4a, + ])); - /// Payable usage of a bucket includes the current storage usage this bucket consumes and the - /// delta usage verified by inspectors. This is overall amount of bytes that the bucket owner - /// will be charged for. + /// This is overall amount that the bucket owner will be charged for his buckets within a + /// payment Era. #[derive(Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] - pub(crate) struct BucketPayableUsage(BucketId, BucketUsage); + pub(crate) struct CustomerCharge(AccountId32, u128); - /// Payable usage of a node includes the current storage usage this node provides and the delta - /// usage verified by inspectors. This is overall amount of bytes that the node owner will be - /// rewarded for. + /// This is overall amount of bytes that the node owner will be #[derive(Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] - pub(crate) struct NodePayableUsage(NodePubKey, NodeUsage); + pub(crate) struct ProviderReward(AccountId32, u128); - /// Payable usage of an Era includes all the batches of customers and providers that will be - /// processed during the payout process along with merkle root hashes and proofs. To calculate - /// the same billing fingerprint and let the payouts to start the required quorum of validators - /// need to agree on the same values for the Era usage and commit the same billing fingerprint. + /// Payable usage of a Payment Era (EHD) includes all the batches of customers and providers + /// that will be processed during the payout process along with merkle root hashes and proofs. + /// To calculate the same payout fingerprint and let the payouts to start the required quorum + /// of validators need to agree on the same values for the Era usage and commit the same payout + /// fingerprint. #[derive(Clone, Encode, Decode)] - pub(crate) struct PayableEraUsage { + pub(crate) struct PayableEHDUsage { cluster_id: ClusterId, - era: EraActivity, - payers_usage: Vec, + ehd_id: String, + payers_usage: Vec, payers_root: PayableUsageHash, payers_batch_roots: Vec, - payees_usage: Vec, + payees_usage: Vec, payees_root: PayableUsageHash, payees_batch_roots: Vec, - cluster_usage: NodeUsage, } - impl PayableEraUsage { + impl PayableEHDUsage { fn fingerprint(&self) -> Fingerprint { let mut data = self.cluster_id.encode(); - data.extend_from_slice(&self.era.id.encode()); - data.extend_from_slice(&self.era.start.encode()); - data.extend_from_slice(&self.era.end.encode()); + data.extend_from_slice(&self.ehd_id.encode()); data.extend_from_slice(&self.payers_root.encode()); data.extend_from_slice(&self.payees_root.encode()); - data.extend_from_slice(&self.cluster_usage.encode()); blake2_256(&data).into() } } @@ -223,6 +223,7 @@ pub mod pallet { >; type Currency: Currency; const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool; + type ClusterProtocol: ClusterProtocol>; #[cfg(feature = "runtime-benchmarks")] type CustomerDepositor: CustomerDepositor; #[cfg(feature = "runtime-benchmarks")] @@ -239,8 +240,8 @@ pub mod pallet { /// `frame_system::Pallet::deposit_event`. #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// A new billing report was created from `ClusterId` and `ERA`. - BillingReportCreated { + /// A new payout report was created from `ClusterId` and `ERA`. + PayoutReceiptCreated { cluster_id: ClusterId, era_id: DdcEra, }, @@ -287,14 +288,14 @@ pub mod pallet { payees_merkle_root_hash: DeltaUsageHash, validator: T::AccountId, }, - CommitBillingFingerprintTransactionError { + CommitPayoutFingerprintTransactionError { cluster_id: ClusterId, era_id: DdcEra, payers_root: PayableUsageHash, payees_root: PayableUsageHash, validator: T::AccountId, }, - BeginBillingReportTransactionError { + BeginPayoutTransactionError { cluster_id: ClusterId, era_id: DdcEra, validator: T::AccountId, @@ -331,12 +332,12 @@ pub mod pallet { era_id: DdcEra, validator: T::AccountId, }, - EndBillingReportTransactionError { + EndPayoutTransactionError { cluster_id: ClusterId, era_id: DdcEra, validator: T::AccountId, }, - BillingReportDoesNotExist { + PayoutReceiptDoesNotExist { cluster_id: ClusterId, era_id: DdcEra, validator: T::AccountId, @@ -424,6 +425,14 @@ pub mod pallet { EmptyConsistentGroup, FailedToFetchVerifiedDeltaUsage, FailedToFetchVerifiedPayableUsage, + FailedToFetchEHD, + FailedToFetchPHD, + FailedToFetchTraversedEHD, + FailedToFetchTraversedPHD, + FailedToFetchNodeChallenge, + FailedToSaveInspectionReceipt, + FailedToFetchInspectionReceipt, + FailedToFetchPaymentEra, } /// Consensus Errors @@ -472,13 +481,13 @@ pub mod pallet { payers_merkle_root_hash: DeltaUsageHash, payees_merkle_root_hash: DeltaUsageHash, }, - CommitBillingFingerprintTransactionError { + CommitPayoutFingerprintTransactionError { cluster_id: ClusterId, era_id: DdcEra, payers_root: PayableUsageHash, payees_root: PayableUsageHash, }, - BeginBillingReportTransactionError { + BeginPayoutTransactionError { cluster_id: ClusterId, era_id: DdcEra, }, @@ -508,11 +517,11 @@ pub mod pallet { cluster_id: ClusterId, era_id: DdcEra, }, - EndBillingReportTransactionError { + EndPayoutTransactionError { cluster_id: ClusterId, era_id: DdcEra, }, - BillingReportDoesNotExist { + PayoutReceiptDoesNotExist { cluster_id: ClusterId, era_id: DdcEra, }, @@ -548,6 +557,14 @@ pub mod pallet { EmptyConsistentGroup, FailedToFetchVerifiedDeltaUsage, FailedToFetchVerifiedPayableUsage, + FailedToFetchEHD, + FailedToFetchPHD, + FailedToFetchTraversedEHD, + FailedToFetchTraversedPHD, + FailedToFetchNodeChallenge, + FailedToSaveInspectionReceipt, + FailedToFetchInspectionReceipt, + FailedToFetchPaymentEra, } #[pallet::error] @@ -578,9 +595,6 @@ pub mod pallet { NotController, /// Not a validator stash. NotValidatorStash, - /// DDC Validator Key Not Registered - DDCValidatorKeyNotRegistered, - TransactionSubmissionError, NoAvailableSigner, /// Fail to generate proof FailedToGenerateProof, @@ -590,19 +604,10 @@ pub mod pallet { NoEraValidation, /// Given era is already validated and paid. EraAlreadyPaid, + VerificationKeyAlreadySet, + VerificationKeyAlreadyPaired, } - /// Era validations - #[pallet::storage] - pub type EraValidations = StorageDoubleMap< - _, - Blake2_128Concat, - ClusterId, - Blake2_128Concat, - DdcEra, - EraValidation, - >; - /// List of validators. #[pallet::storage] pub type ValidatorSet = StorageValue<_, Vec, ValueQuery>; @@ -611,201 +616,546 @@ pub mod pallet { #[pallet::storage] pub type ValidatorToStashKey = StorageMap<_, Identity, T::AccountId, T::AccountId>; - /// Era activity of a node. - #[derive( - Debug, - Serialize, - Deserialize, - Clone, - Copy, - Hash, - Ord, - PartialOrd, - PartialEq, - Eq, - TypeInfo, - Encode, - Decode, - )] - pub struct EraActivity { - /// Era id. - pub id: DdcEra, - pub start: i64, - pub end: i64, - } - - impl From for EraActivity { - fn from(era: aggregator_client::json::AggregationEraResponse) -> Self { - Self { id: era.id, start: era.start, end: era.end } - } - } - - #[derive(Clone)] - pub struct CustomerBatch { - pub(crate) batch_index: BatchIndex, - pub(crate) payers: Vec<(BucketId, BucketUsage)>, - pub(crate) batch_proof: MMRProof, - } + #[pallet::call] + impl Pallet { + /// Set validator key. + /// + /// The origin must be a validator. + /// + /// Parameters: + /// - `ddc_validator_pub`: validator Key + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::set_validator_key())] + pub fn set_validator_key( + origin: OriginFor, + ddc_validator_pub: T::AccountId, + ) -> DispatchResult { + let controller = ensure_signed(origin)?; - #[derive(Clone)] - pub struct ProviderBatch { - pub(crate) batch_index: BatchIndex, - pub(crate) payees: Vec<(NodePubKey, NodeUsage)>, - pub(crate) batch_proof: MMRProof, - } + let stash = T::ValidatorStaking::stash_by_ctrl(&controller) + .map_err(|_| Error::::NotController)?; - /// The `ConsolidatedAggregate` struct represents a merging result of multiple aggregates - /// that have reached consensus on the usage criteria. This result should be taken into - /// consideration when choosing the intensity of the challenge. - #[derive(Debug, Clone, PartialEq)] - pub(crate) struct ConsolidatedAggregate { - /// The representative aggregate after consolidation - pub(crate) aggregate: A, - /// Number of aggregates that were consistent - pub(crate) count: u16, - /// Aggregators that provided consistent aggregates - pub(crate) aggregators: Vec, - } + ensure!( + >::get().contains(&ddc_validator_pub), + Error::::NotValidatorStash + ); - impl ConsolidatedAggregate { - pub(crate) fn new(aggregate: A, count: u16, aggregators: Vec) -> Self { - ConsolidatedAggregate { aggregate, count, aggregators } + ValidatorToStashKey::::insert(&ddc_validator_pub, &stash); + Self::deposit_event(Event::::ValidatorKeySet { validator: ddc_validator_pub }); + Ok(()) } - } - - #[derive(Debug, Clone, PartialEq)] - pub(crate) struct ConsistencyGroups { - pub(crate) consensus: Vec>, - pub(crate) quorum: Vec>, - pub(crate) others: Vec>, - } - - #[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq)] - pub enum AggregateKey { - NodeAggregateKey(String), - BucketSubAggregateKey(BucketId, String), - } - pub(crate) trait Hashable { - /// Hash of the entity - fn hash(&self) -> H256; - } + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::commit_payout_fingerprint())] + #[allow(clippy::too_many_arguments)] + pub fn commit_payout_fingerprint( + origin: OriginFor, + cluster_id: ClusterId, + ehd_id: String, + payers_root: PayableUsageHash, + payees_root: PayableUsageHash, + ) -> DispatchResult { + let sender = ensure_signed(origin.clone())?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); - /// The 'Aggregate' trait defines a set of members common to activity aggregates, which reflect - /// the usage of a node or bucket within an Era.. - pub(crate) trait Aggregate: - Hashable + Clone + Ord + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> + Debug - { - /// Aggregation key of this aggregate, i.e. bucket composite key or node key - fn get_key(&self) -> AggregateKey; - /// Number of activity records this aggregated by this aggregate - fn get_number_of_leaves(&self) -> u64; - /// Aggregator provided this aggregate - fn get_aggregator(&self) -> AggregatorInfo; - } + T::PayoutProcessor::commit_payout_fingerprint( + sender, + cluster_id, + ehd_id, + payers_root, + payees_root, + ) + } - impl Hashable for aggregator_client::json::BucketSubAggregate { - fn hash(&self) -> DeltaUsageHash { - let mut data = self.bucket_id.encode(); - data.extend_from_slice(&self.node_id.encode()); - data.extend_from_slice(&self.stored_bytes.encode()); - data.extend_from_slice(&self.transferred_bytes.encode()); - data.extend_from_slice(&self.number_of_puts.encode()); - data.extend_from_slice(&self.number_of_gets.encode()); - T::Hasher::hash(&data) + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::begin_payout())] + pub fn begin_payout( + origin: OriginFor, + cluster_id: ClusterId, + era_id: PaymentEra, + fingerprint: Fingerprint, + ) -> DispatchResult { + let sender = ensure_signed(origin.clone())?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + T::PayoutProcessor::begin_payout(cluster_id, era_id, fingerprint)?; + Ok(()) } - } - impl Aggregate for aggregator_client::json::BucketSubAggregate { - fn get_key(&self) -> AggregateKey { - AggregateKey::BucketSubAggregateKey(self.bucket_id, self.node_id.clone()) + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::begin_charging_customers())] + pub fn begin_charging_customers( + origin: OriginFor, + cluster_id: ClusterId, + era_id: PaymentEra, + max_batch_index: BatchIndex, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + T::PayoutProcessor::begin_charging_customers(cluster_id, era_id, max_batch_index) } - fn get_number_of_leaves(&self) -> u64 { - self.number_of_gets.saturating_add(self.number_of_puts) + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::send_charging_customers_batch(payers.len() as u32))] + pub fn send_charging_customers_batch( + origin: OriginFor, + cluster_id: ClusterId, + era_id: PaymentEra, + batch_index: BatchIndex, + payers: Vec<(T::AccountId, u128)>, + batch_proof: MMRProof, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + T::PayoutProcessor::send_charging_customers_batch( + cluster_id, + era_id, + batch_index, + &payers, + batch_proof, + ) } - fn get_aggregator(&self) -> AggregatorInfo { - self.aggregator.clone() + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::end_charging_customers())] + pub fn end_charging_customers( + origin: OriginFor, + cluster_id: ClusterId, + era_id: PaymentEra, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + T::PayoutProcessor::end_charging_customers(cluster_id, era_id) } - } - impl Hashable for aggregator_client::json::NodeAggregate { - fn hash(&self) -> DeltaUsageHash { - let mut data = self.node_id.encode(); - data.extend_from_slice(&self.stored_bytes.encode()); - data.extend_from_slice(&self.transferred_bytes.encode()); - data.extend_from_slice(&self.number_of_puts.encode()); - data.extend_from_slice(&self.number_of_gets.encode()); - T::Hasher::hash(&data) + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::begin_rewarding_providers())] + pub fn begin_rewarding_providers( + origin: OriginFor, + cluster_id: ClusterId, + era_id: PaymentEra, + max_batch_index: BatchIndex, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + T::PayoutProcessor::begin_rewarding_providers(cluster_id, era_id, max_batch_index) } - } - impl Aggregate for aggregator_client::json::NodeAggregate { - fn get_key(&self) -> AggregateKey { - AggregateKey::NodeAggregateKey(self.node_id.clone()) + #[pallet::call_index(8)] + #[pallet::weight(::WeightInfo::send_rewarding_providers_batch(payees.len() as u32))] + pub fn send_rewarding_providers_batch( + origin: OriginFor, + cluster_id: ClusterId, + era_id: PaymentEra, + batch_index: BatchIndex, + payees: Vec<(T::AccountId, u128)>, + batch_proof: MMRProof, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + T::PayoutProcessor::send_rewarding_providers_batch( + cluster_id, + era_id, + batch_index, + &payees, + batch_proof, + ) } - fn get_aggregator(&self) -> AggregatorInfo { - self.aggregator.clone() + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::end_rewarding_providers())] + pub fn end_rewarding_providers( + origin: OriginFor, + cluster_id: ClusterId, + era_id: PaymentEra, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + T::PayoutProcessor::end_rewarding_providers(cluster_id, era_id) } - fn get_number_of_leaves(&self) -> u64 { - self.number_of_gets.saturating_add(self.number_of_puts) + #[pallet::call_index(10)] + #[pallet::weight(::WeightInfo::end_payout())] + pub fn end_payout( + origin: OriginFor, + cluster_id: ClusterId, + era_id: PaymentEra, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + T::PayoutProcessor::end_payout(cluster_id, era_id)?; + T::ClusterValidator::set_last_paid_era(&cluster_id, era_id) } - } - pub trait NodeAggregateLeaf: - Clone + Ord + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> - { - fn leaf_hash(&self) -> DeltaUsageHash; - } - pub trait BucketSubAggregateLeaf: - Clone + Ord + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> - { - fn leaf_hash(&self) -> DeltaUsageHash; - } + /// Emit consensus errors. + /// + /// The origin must be a validator. + /// + /// Parameters: + /// - errors`: List of consensus errors + /// + /// Emits `NotEnoughNodesForConsensus` OR `ActivityNotInConsensus` event depend of error + /// type, when successful. + #[pallet::call_index(11)] + #[pallet::weight(::WeightInfo::emit_consensus_errors(errors.len() as u32))] + pub fn emit_consensus_errors( + origin: OriginFor, + errors: Vec, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + ensure!(Self::is_ocw_validator(caller.clone()), Error::::Unauthorized); - impl NodeAggregateLeaf for aggregator_client::json::Leaf { - fn leaf_hash(&self) -> DeltaUsageHash { - let mut data = self.record.id.encode(); - data.extend_from_slice(&self.record.upstream.request.requestType.encode()); - data.extend_from_slice(&self.stored_bytes.encode()); - data.extend_from_slice(&self.transferred_bytes.encode()); - T::Hasher::hash(&data) - } - } + for error in errors { + match error { + OCWError::NodeUsageRetrievalError { cluster_id, era_id, node_pub_key } => { + Self::deposit_event(Event::NodeUsageRetrievalError { + cluster_id, + era_id, + node_pub_key, + validator: caller.clone(), + }); + }, + OCWError::BucketAggregatesRetrievalError { + cluster_id, + era_id, + node_pub_key, + } => { + Self::deposit_event(Event::BucketAggregatesRetrievalError { + cluster_id, + era_id, + node_pub_key, + validator: caller.clone(), + }); + }, + OCWError::EraRetrievalError { cluster_id, node_pub_key } => { + Self::deposit_event(Event::EraRetrievalError { + cluster_id, + node_pub_key, + validator: caller.clone(), + }); + }, + OCWError::PrepareEraTransactionError { + cluster_id, + era_id, + payers_merkle_root_hash, + payees_merkle_root_hash, + } => { + Self::deposit_event(Event::PrepareEraTransactionError { + cluster_id, + era_id, + payers_merkle_root_hash, + payees_merkle_root_hash, + validator: caller.clone(), + }); + }, + OCWError::CommitPayoutFingerprintTransactionError { + cluster_id, + era_id, + payers_root, + payees_root, + } => { + Self::deposit_event(Event::CommitPayoutFingerprintTransactionError { + cluster_id, + era_id, + payers_root, + payees_root, + validator: caller.clone(), + }); + }, + OCWError::BeginPayoutTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::BeginPayoutTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::BeginChargingCustomersTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::BeginChargingCustomersTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::SendChargingCustomersBatchTransactionError { + cluster_id, + era_id, + batch_index, + } => { + Self::deposit_event(Event::SendChargingCustomersBatchTransactionError { + cluster_id, + era_id, + batch_index, + validator: caller.clone(), + }); + }, + OCWError::SendRewardingProvidersBatchTransactionError { + cluster_id, + era_id, + batch_index, + } => { + Self::deposit_event(Event::SendRewardingProvidersBatchTransactionError { + cluster_id, + era_id, + batch_index, + validator: caller.clone(), + }); + }, + OCWError::EndChargingCustomersTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::EndChargingCustomersTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::BeginRewardingProvidersTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::BeginRewardingProvidersTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::EndRewardingProvidersTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::EndRewardingProvidersTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::EndPayoutTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::EndPayoutTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::PayoutReceiptDoesNotExist { cluster_id, era_id } => { + Self::deposit_event(Event::PayoutReceiptDoesNotExist { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::EmptyCustomerActivity { cluster_id, era_id } => { + Self::deposit_event(Event::EmptyCustomerActivity { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::BatchIndexConversionFailed { cluster_id, era_id } => { + Self::deposit_event(Event::BatchIndexConversionFailed { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::NoAvailableSigner => { + Self::deposit_event(Event::NoAvailableSigner { validator: caller.clone() }); + }, + OCWError::NotEnoughDACNodes { num_nodes } => { + Self::deposit_event(Event::NotEnoughDACNodes { + num_nodes, + validator: caller.clone(), + }); + }, + OCWError::FailedToCreateMerkleRoot { cluster_id, era_id } => { + Self::deposit_event(Event::FailedToCreateMerkleRoot { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::FailedToCreateMerkleProof { cluster_id, era_id } => { + Self::deposit_event(Event::FailedToCreateMerkleProof { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::FailedToCollectVerificationKey => { + Self::deposit_event(Event::FailedToCollectVerificationKey { + validator: caller.clone(), + }); + }, + OCWError::FailedToFetchVerificationKey => { + Self::deposit_event(Event::FailedToFetchVerificationKey { + validator: caller.clone(), + }); + }, + OCWError::FailedToFetchNodeProvider => { + Self::deposit_event(Event::FailedToFetchNodeProvider { + validator: caller.clone(), + }); + }, + OCWError::FailedToFetchNodeTotalUsage { cluster_id, node_pub_key } => { + Self::deposit_event(Event::FailedToFetchNodeTotalUsage { + cluster_id, + node_pub_key, + validator: caller.clone(), + }); + }, + OCWError::BucketAggregateRetrievalError { + cluster_id, + era_id, + bucket_id, + node_pub_key, + } => { + Self::deposit_event(Event::BucketAggregateRetrievalError { + cluster_id, + era_id, + bucket_id, + node_pub_key, + validator: caller.clone(), + }); + }, + OCWError::ChallengeResponseRetrievalError { + cluster_id, + era_id, + aggregate_key, + aggregator, + } => { + Self::deposit_event(Event::ChallengeResponseRetrievalError { + cluster_id, + era_id, + aggregate_key, + aggregator, + validator: caller.clone(), + }); + }, + OCWError::TraverseResponseRetrievalError { + cluster_id, + era_id, + aggregate_key, + aggregator, + } => { + Self::deposit_event(Event::TraverseResponseRetrievalError { + cluster_id, + era_id, + aggregate_key, + aggregator, + validator: caller.clone(), + }); + }, + OCWError::FailedToFetchClusterNodes => { + Self::deposit_event(Event::FailedToFetchClusterNodes { + validator: caller.clone(), + }); + }, + OCWError::FailedToFetchDacNodes => { + Self::deposit_event(Event::FailedToFetchDacNodes { + validator: caller.clone(), + }); + }, + OCWError::EmptyConsistentGroup => { + Self::deposit_event(Event::EmptyConsistentGroup); + }, + OCWError::FailedToFetchVerifiedDeltaUsage => { + Self::deposit_event(Event::FailedToFetchVerifiedDeltaUsage); + }, + OCWError::FailedToFetchVerifiedPayableUsage => { + Self::deposit_event(Event::FailedToFetchVerifiedPayableUsage); + }, + OCWError::FailedToFetchEHD => { + Self::deposit_event(Event::FailedToFetchEHD); + }, + OCWError::FailedToFetchPHD => { + Self::deposit_event(Event::FailedToFetchPHD); + }, + OCWError::FailedToFetchTraversedEHD => { + Self::deposit_event(Event::FailedToFetchTraversedEHD); + }, + OCWError::FailedToFetchTraversedPHD => { + Self::deposit_event(Event::FailedToFetchTraversedPHD); + }, + OCWError::FailedToFetchNodeChallenge => { + Self::deposit_event(Event::FailedToFetchNodeChallenge); + }, + OCWError::FailedToSaveInspectionReceipt => { + Self::deposit_event(Event::FailedToSaveInspectionReceipt); + }, + OCWError::FailedToFetchInspectionReceipt => { + Self::deposit_event(Event::FailedToFetchInspectionReceipt); + }, + OCWError::FailedToFetchPaymentEra => { + Self::deposit_event(Event::FailedToFetchPaymentEra); + }, + } + } - impl BucketSubAggregateLeaf for aggregator_client::json::Leaf { - fn leaf_hash(&self) -> DeltaUsageHash { - let mut data = self.record.upstream.request.bucketId.encode(); - data.extend_from_slice(&self.record.encode()); - data.extend_from_slice(&self.record.upstream.request.requestType.encode()); - data.extend_from_slice(&self.stored_bytes.encode()); - data.extend_from_slice(&self.transferred_bytes.encode()); - T::Hasher::hash(&data) + Ok(()) } - } - impl Hashable for BucketPayableUsage { - fn hash(&self) -> PayableUsageHash { - let mut data = self.0.encode(); // bucket_id - data.extend_from_slice(&self.1.stored_bytes.encode()); - data.extend_from_slice(&self.1.transferred_bytes.encode()); - data.extend_from_slice(&self.1.number_of_puts.encode()); - data.extend_from_slice(&self.1.number_of_gets.encode()); - T::Hasher::hash(&data) + /// Continue DAC inspection from an era after a given one. It updates `last_paid_era` of a + /// given cluster, creates an empty payout receipt with a finalized state, and sets an empty + /// validation result on validators (in case it does not exist yet). + #[pallet::call_index(12)] + #[pallet::weight(::WeightInfo::force_skip_inspection())] + pub fn force_skip_inspection( + origin: OriginFor, + cluster_id: ClusterId, + ehd_id: String, + ) -> DispatchResult { + let era_id = EHDId::try_from(ehd_id.clone()).map_err(|_| Error::::BadRequest)?.2; + + ensure_root(origin)?; + ensure!( + era_id > T::ClusterValidator::get_last_paid_era(&cluster_id)?, + Error::::EraAlreadyPaid + ); + + let fingerprint = + T::PayoutProcessor::create_payout_fingerprint(PayoutFingerprintParams { + cluster_id, + ehd_id: ehd_id.clone(), + payers_merkle_root: Default::default(), + payees_merkle_root: Default::default(), + validators: BTreeSet::new(), + }); + + let payout_receipt_params = PayoutReceiptParams { + cluster_id, + era: era_id, + state: PayoutState::Finalized, + fingerprint, + ..Default::default() + }; + + T::PayoutProcessor::create_payout_receipt( + T::AccountId::decode(&mut [0u8; 32].as_slice()).unwrap(), + payout_receipt_params, + ); + + T::ClusterValidator::set_last_paid_era(&cluster_id, era_id)?; + + Ok(()) } - } - impl Hashable for NodePayableUsage { - fn hash(&self) -> PayableUsageHash { - let mut data = self.0.encode(); // node_key - data.extend_from_slice(&self.1.stored_bytes.encode()); - data.extend_from_slice(&self.1.transferred_bytes.encode()); - data.extend_from_slice(&self.1.number_of_puts.encode()); - data.extend_from_slice(&self.1.number_of_gets.encode()); - T::Hasher::hash(&data) + /// For Devnet usage only + #[pallet::call_index(13)] + #[pallet::weight(::WeightInfo::force_set_validator_key())] + pub fn force_set_validator_key( + origin: OriginFor, + controller: T::AccountId, + ddc_validator_pub: T::AccountId, + ) -> DispatchResult { + ensure_root(origin)?; + + let stash = T::ValidatorStaking::stash_by_ctrl(&controller) + .map_err(|_| Error::::NotController)?; + + ensure!( + !>::get().contains(&ddc_validator_pub), + Error::::VerificationKeyAlreadySet + ); + ensure!( + !>::contains_key(&ddc_validator_pub), + Error::::VerificationKeyAlreadyPaired + ); + + >::append(ddc_validator_pub.clone()); + ValidatorToStashKey::::insert(&ddc_validator_pub, &stash); + + Self::deposit_event(Event::::ValidatorKeySet { validator: ddc_validator_pub }); + Ok(()) } } @@ -865,17 +1215,17 @@ pub mod pallet { for cluster_id in clusters_ids { let mut errors: Vec = Vec::new(); - let validation_result = - Self::start_validation_phase(&cluster_id, &verification_account, &signer); + let inspection_result = + Self::start_inspection_phase(&cluster_id, &verification_account, &signer); - if let Err(errs) = validation_result { + if let Err(errs) = inspection_result { errors.extend(errs); } - let payouts_result = + let payout_result = Self::start_payouts_phase(&cluster_id, &verification_account, &signer); - if let Err(errs) = payouts_result { + if let Err(errs) = payout_result { errors.extend(errs); } @@ -950,278 +1300,250 @@ pub mod pallet { } impl Pallet { - pub(crate) fn do_skip_era_validation( + #[allow(clippy::collapsible_else_if)] + pub(crate) fn start_inspection_phase( cluster_id: &ClusterId, - era_id: DdcEra, - ) -> DispatchResult { - let era_validations = >::get(cluster_id, era_id); - - if era_validations.is_none() { - let mut era_validation = EraValidation { - status: EraValidationStatus::PayoutSkipped, - ..Default::default() - }; - - let signed_validators = era_validation - .validators - .entry((DeltaUsageHash::default(), DeltaUsageHash::default())) - .or_insert_with(Vec::new); - - let validators = >::get(); - - signed_validators.extend(validators); - - >::insert(cluster_id, era_id, era_validation); - } - - Ok(()) - } - - #[allow(clippy::type_complexity)] - pub(crate) fn process_dac_era( - cluster_id: &ClusterId, - era_id_to_process: Option, - ) -> Result< - Option<( - EraActivity, - DeltaUsageHash, - DeltaUsageHash, - Vec, - Vec, - )>, - Vec, - > { - let batch_size = T::MAX_PAYOUT_BATCH_SIZE; - - let dac_nodes = Self::get_dac_nodes(cluster_id).map_err(|_| { - log::error!("❌ Error retrieving dac nodes to validate cluster {:?}", cluster_id); + verification_account: &Account, + _signer: &Signer, + ) -> Result<(), Vec> { + let collectors = Self::get_collectors_nodes(cluster_id).map_err(|_| { + log::error!("❌ Error retrieving collectors for cluster {:?}", cluster_id); vec![OCWError::FailedToFetchDacNodes] })?; + let g_collector = collectors + .iter() + .find(|(key, _)| *key == G_COLLECTOR_KEY) + .cloned() + .expect("G-Collector to be found"); - let era_activity = if let Some(era_activity) = era_id_to_process { - EraActivity { - id: era_activity.id, - start: era_activity.start, - end: era_activity.end, - } - } else { - match Self::get_era_for_validation(cluster_id, &dac_nodes) { - Ok(Some(era_activity)) => era_activity, - Ok(None) => return Ok(None), - Err(err) => return Err(vec![err]), + if let Some(ehd_era) = + Self::get_ehd_era_for_inspection(cluster_id, vec![g_collector.clone()].as_slice()) + .map_err(|e| vec![e])? + { + let ehd_root = Self::get_ehd_root( + cluster_id, + EHDId(*cluster_id, g_collector.0.clone(), ehd_era.id), + ) + .expect("EHD to be fetched"); + + let ehd_id = EHDId::try_from(ehd_root.ehd_id).expect("EHD to be parsed"); + + let mut phd_roots = vec![]; + for phd_id in &ehd_root.pdh_ids { + let phd_id = PHDId::try_from(phd_id.clone()) + .map_err(|_| vec![OCWError::FailedToFetchEHD])?; + let traversed_phd_tree = Self::fetch_traversed_partial_historical_document( + cluster_id, phd_id, 1, 1, + )?; + + if let Some(phd_root) = traversed_phd_tree.first() { + phd_roots.push(phd_root.clone()); + } } - }; - - log::info!( - "👁️‍🗨️ Start processing DAC for cluster_id: {:?} era_id; {:?}", - cluster_id, - era_activity.id - ); - // todo: move to cluster protocol parameters - let dac_redundancy_factor = T::DAC_REDUNDANCY_FACTOR; - let aggregators_quorum = T::AggregatorsQuorum::get(); + #[allow(clippy::type_complexity)] + let mut era_leaves_map: BTreeMap< + NodePubKey, + BTreeMap<(PHDId, (u64, u64)), BTreeMap>>, + > = BTreeMap::new(); + + let mut unverified_phd_parts = vec![]; + let mut verified_phd_parts = vec![]; + + for phd_root in phd_roots { + let phd_id = PHDId::try_from(phd_root.phd_id) + .map_err(|_| vec![OCWError::FailedToFetchPHD])?; + + let collector_key = phd_id.0.clone(); + let tcaa_start = ehd_era.era_start.expect("TCAA start in EHD era"); + let tcaa_end = ehd_era.era_end.expect("TCAA start in EHD era"); + + for node_aggregate in &phd_root.nodes_aggregates { + let node_key = NodePubKey::try_from(node_aggregate.node_key.clone()) + .expect("Node pub key to be parsed"); + + if era_leaves_map.contains_key(&node_key) { + // Currently, we inspect node aggregation from a single (first + // responded) collector. We may compare the aggregation for the same + // node between different collectors in the next iterations to ensure + // redundancy. + continue; + } - let nodes_aggregates_by_aggregator = - Self::fetch_nodes_aggregates_for_era(cluster_id, era_activity.id, &dac_nodes) - .map_err(|err| vec![err])?; + let mut node_leaves_count: u64 = 0; + let mut node_tcaa_count: u64 = 0; - let buckets_aggregates_by_aggregator = - Self::fetch_buckets_aggregates_for_era(cluster_id, era_activity.id, &dac_nodes) - .map_err(|err| vec![err])?; + let mut tcaa_leaves = BTreeMap::new(); + for tcaa_id in tcaa_start..=tcaa_end { + if let Ok(challenge_res) = Self::fetch_node_challenge_response( + cluster_id, + tcaa_id, + collector_key.clone(), + node_key.clone(), + vec![1], + ) { + let tcaa_root = + challenge_res.proofs.first().expect("TCAA root to exist."); + + let tcaa_leaves_count = + tcaa_root.usage.expect("TCAA root usage to exist.").puts + + tcaa_root.usage.expect("TCAA root usage to exist.").gets; + + let ids = get_leaves_ids(tcaa_leaves_count); + tcaa_leaves.insert(tcaa_id, ids); + + node_leaves_count += tcaa_leaves_count; + node_tcaa_count += 1; + } + } - let buckets_sub_aggregates_groups = Self::group_buckets_sub_aggregates_by_consistency( - cluster_id, - era_activity.id, - buckets_aggregates_by_aggregator, - dac_redundancy_factor, - aggregators_quorum, - ); + era_leaves_map.insert( + node_key, + BTreeMap::from([( + (phd_id.clone(), (node_leaves_count, node_tcaa_count)), + tcaa_leaves, + )]), + ); + } + } - let total_buckets_usage = Self::get_total_usage( - cluster_id, - era_activity.id, - buckets_sub_aggregates_groups, - true, - )?; + let n0 = calculate_sample_size_inf(D_099, P_001); - let customer_activity_hashes: Vec = - total_buckets_usage.clone().into_iter().map(|c| c.hash::()).collect(); + for (node_key, mut val) in era_leaves_map { + let ((phd_id, (node_leaves_count, node_tcaa_count)), tcaa_leaves) = + val.pop_first().expect("Collected activity records to exist."); + let collector_key = phd_id.0.clone(); - let customer_activity_hashes_string: Vec = - customer_activity_hashes.clone().into_iter().map(hex::encode).collect(); + let n = calculate_sample_size_fin(n0, node_leaves_count) + .expect("Sample size to be calculated."); + let n_per_tcaa = n / node_tcaa_count; + let mut remainder = n % node_tcaa_count; - log::info!( - "👁️‍🗨️ Customer Activity hashes for cluster_id: {:?} era_id: {:?} is: {:?}", - cluster_id, - era_activity.id, - customer_activity_hashes_string - ); - let customers_activity_batch_roots = Self::convert_to_batch_merkle_roots( - cluster_id, - era_activity.id, - Self::split_to_batches(&total_buckets_usage, batch_size.into()), - ) - .map_err(|err| vec![err])?; + let mut verified_tcaas: BTreeMap> = BTreeMap::new(); + let mut unverified_tcaas: BTreeMap> = BTreeMap::new(); - let customer_batch_roots_string: Vec = - customers_activity_batch_roots.clone().into_iter().map(hex::encode).collect(); + for (tcaa_id, ids) in tcaa_leaves { + let ids_count: u64 = ids.len().try_into().unwrap(); - for (pos, batch_root) in customer_batch_roots_string.iter().enumerate() { - log::info!( - "👁️‍🗨️‍ Customer Activity batches for cluster_id: {:?} era_id: {:?} is: batch {:?} with root {:?} for activities {:?}", - cluster_id, - era_activity.id, - pos + 1, - batch_root, - customer_activity_hashes_string - ); - } + let leaves_to_inspect = if n_per_tcaa < ids_count { + let sample_size = + if remainder > 0 && (n_per_tcaa + remainder) <= ids_count { + let size = n_per_tcaa + remainder; + remainder = 0; + size + } else { + n_per_tcaa + }; - let customers_activity_root = Self::create_merkle_root( - cluster_id, - era_activity.id, - &customers_activity_batch_roots, - ) - .map_err(|err| vec![err])?; + Self::select_random_leaves(sample_size, ids, node_key.clone().into()) + } else { + remainder += n_per_tcaa - ids_count; + ids + }; - log::info!( - "👁️‍🗨️‍ Customer Activity batches tree for cluster_id: {:?} era_id: {:?} is: batch with root {:?} for activities {:?}", - cluster_id, - era_activity.id, - hex::encode(customers_activity_root), - customer_batch_roots_string, - ); + log::info!( + "Node {:?} - TCAA {:?}. Selecting {:?} leaves out of {:?} for inspection. Selected leaves {:?}. Additional reminder is {:?}.", + node_key.clone(), + tcaa_id, + n_per_tcaa, + ids_count, + leaves_to_inspect, + remainder + ); - let nodes_aggregates_groups = Self::group_nodes_aggregates_by_consistency( - cluster_id, - era_activity.id, - nodes_aggregates_by_aggregator, - dac_redundancy_factor, - aggregators_quorum, - ); + if let Ok(challenge_res) = Self::fetch_node_challenge_response( + cluster_id, + tcaa_id, + collector_key.clone(), + node_key.clone(), + leaves_to_inspect.clone(), + ) { + if challenge_res.verify() { + if verified_tcaas.contains_key(&tcaa_id) { + let mut verified_ids = verified_tcaas + .get(&tcaa_id) + .expect("Verified TCAA to exist.") + .clone(); + verified_ids.extend(leaves_to_inspect); + verified_tcaas.insert(tcaa_id, verified_ids.clone()); + } else { + verified_tcaas.insert(tcaa_id, leaves_to_inspect); + }; + } else { + if unverified_tcaas.contains_key(&tcaa_id) { + let mut unverified_ids = unverified_tcaas + .get(&tcaa_id) + .expect("Unverified TCAA to exist.") + .clone(); + unverified_ids.extend(leaves_to_inspect); + unverified_tcaas.insert(tcaa_id, unverified_ids); + } else { + unverified_tcaas.insert(tcaa_id, leaves_to_inspect); + }; + } + } + } - let total_nodes_usage = - Self::get_total_usage(cluster_id, era_activity.id, nodes_aggregates_groups, true)?; + if !verified_tcaas.is_empty() { + verified_phd_parts.push(aggregator_client::json::InspectedTreePart { + collector: phd_id.0.clone().into(), + aggregate: AggregateKey::NodeAggregateKey(node_key.clone().into()), + nodes: Vec::new(), /* todo: re-calculate aggregations in branch nodes + * of PHD merkle tree */ + leafs: verified_tcaas, + }); + } - let node_activity_hashes: Vec = - total_nodes_usage.clone().into_iter().map(|c| c.hash::()).collect(); + if !unverified_tcaas.is_empty() { + unverified_phd_parts.push(aggregator_client::json::InspectedTreePart { + collector: phd_id.0.clone().into(), + aggregate: AggregateKey::NodeAggregateKey(node_key.clone().into()), + nodes: Vec::new(), /* todo: re-calculate aggregations in branch nodes + * of PHD merkle tree */ + leafs: unverified_tcaas, + }); + } + } - let node_activity_hashes_string: Vec = - node_activity_hashes.clone().into_iter().map(hex::encode).collect(); + let nodes_inspection = aggregator_client::json::InspectionResult { + unverified_branches: unverified_phd_parts, + verified_branches: verified_phd_parts, + }; - log::info!( - "👁️‍🗨️ Node Activity hashes for cluster_id: {:?} era_id: {:?} is: {:?}", - cluster_id, - era_activity.id, - node_activity_hashes_string - ); + let buckets_inspection = aggregator_client::json::InspectionResult { + unverified_branches: vec![], + verified_branches: vec![], + }; - let nodes_activity_batch_roots = Self::convert_to_batch_merkle_roots( - cluster_id, - era_activity.id, - Self::split_to_batches(&total_nodes_usage, batch_size.into()), - ) - .map_err(|err| vec![err])?; + let payload = + (Into::::into(ehd_id.clone()), nodes_inspection.clone()).encode(); + let signature = + >::sign( + &payload, + verification_account.public.clone(), + ) + .expect("Inspection receipt to be signed."); + + let inspector_pub_key: Vec = verification_account.public.encode(); + let inspector = format!("0x{}", hex::encode(&inspector_pub_key[1..])); // skip byte of SCALE encoding + let signature = format!("0x{}", hex::encode(signature.encode())); + + let receipt = aggregator_client::json::InspectionReceipt { + ehd_id: ehd_id.clone().into(), + inspector, + signature, + nodes_inspection, + buckets_inspection, + }; - let nodes_activity_batch_roots_string: Vec = - nodes_activity_batch_roots.clone().into_iter().map(hex::encode).collect(); + log::info!("##### INSPECTION RECEIPT ##### {:?}", receipt); - for (pos, batch_root) in nodes_activity_batch_roots_string.iter().enumerate() { - log::info!( - "👁️‍🗨️ Node Activity batches for cluster_id: {:?} era_id: {:?} are: batch {:?} with root {:?} for activities {:?}", - cluster_id, - era_activity.id, - pos + 1, - batch_root, - node_activity_hashes_string - ); + Self::send_inspection_receipt(cluster_id, &g_collector, receipt) + .map_err(|e| vec![e])?; + Self::store_last_inspected_ehd(cluster_id, ehd_id); } - let nodes_activity_root = - Self::create_merkle_root(cluster_id, era_activity.id, &nodes_activity_batch_roots) - .map_err(|err| vec![err])?; - - log::info!( - "👁️‍🗨️ Node Activity batches tree for cluster_id: {:?} era_id: {:?} are: batch with root {:?} for activities {:?}", - cluster_id, - era_activity.id, - hex::encode(nodes_activity_root), - nodes_activity_batch_roots_string, - ); - - Self::store_verified_delta_usage( - cluster_id, - era_activity.id, - &total_buckets_usage, - customers_activity_root, - &customers_activity_batch_roots, - &total_nodes_usage, - nodes_activity_root, - &nodes_activity_batch_roots, - ); - log::info!( - "👁️‍🗨️‍ End processing DAC for cluster_id: {:?} era_id: {:?}", - cluster_id, - era_activity.id - ); - Ok(Some(( - era_activity, - customers_activity_root, - nodes_activity_root, - customers_activity_batch_roots, - nodes_activity_batch_roots, - ))) - } - - pub(crate) fn start_validation_phase( - cluster_id: &ClusterId, - verification_account: &Account, - signer: &Signer, - ) -> Result<(), Vec> { - let validation_output = Self::process_dac_era(cluster_id, None)?; - - match validation_output { - Some(( - era_activity, - payers_merkle_root_hash, - payees_merkle_root_hash, - payers_batch_merkle_root_hashes, - payees_batch_merkle_root_hashes, - )) => { - let call = Call::set_prepare_era_for_payout { - cluster_id: *cluster_id, - era_activity, - payers_merkle_root_hash, - payees_merkle_root_hash, - payers_batch_merkle_root_hashes: payers_batch_merkle_root_hashes.clone(), - payees_batch_merkle_root_hashes: payees_batch_merkle_root_hashes.clone(), - }; - - let result = signer.send_single_signed_transaction(verification_account, call); - - match result { - Some(Ok(_)) => { - log::info!( - "👁️‍🗨️ DAC Validation merkle roots posted on-chain for cluster_id: {:?}, era: {:?}", - cluster_id, - era_activity.clone() - ); - Ok(()) - }, - _ => Err(vec![OCWError::PrepareEraTransactionError { - cluster_id: *cluster_id, - era_id: era_activity.id, - payers_merkle_root_hash, - payees_merkle_root_hash, - }]), - } - }, - None => { - log::info!("👁️‍🗨️ No eras for DAC processing for cluster_id: {:?}", cluster_id); - Ok(()) - }, - } + Ok(()) } pub(crate) fn start_payouts_phase( @@ -1231,11 +1553,11 @@ pub mod pallet { ) -> Result<(), Vec> { let mut errors: Vec = Vec::new(); - if let Err(errs) = Self::step_commit_billing_fingerprint(cluster_id, account, signer) { + if let Err(errs) = Self::step_commit_payout_fingerprint(cluster_id, account, signer) { errors.extend(errs); } - if let Err(errs) = Self::step_begin_billing_report(cluster_id, account, signer) { + if let Err(errs) = Self::step_begin_payout(cluster_id, account, signer) { errors.extend(errs); } @@ -1263,9 +1585,9 @@ pub mod pallet { errors.extend(errs); } - match Self::step_end_billing_report(cluster_id, account, signer) { - Ok(Some(era_id)) => { - Self::clear_verified_delta_usage(cluster_id, era_id); + match Self::step_end_payout(cluster_id, account, signer) { + Ok(Some(_ehd_id)) => { + // Self::clear_verified_delta_usage(cluster_id, era_id); }, Err(errs) => errors.extend(errs), _ => {}, @@ -1279,25 +1601,22 @@ pub mod pallet { } define_payout_step_function!( - step_commit_billing_fingerprint, - prepare_commit_billing_fingerprint, - |cluster_id: &ClusterId, (era, era_payable_usage): (EraActivity, PayableEraUsage)| { - Call::commit_billing_fingerprint { + step_commit_payout_fingerprint, + prepare_commit_payout_fingerprint, + |cluster_id: &ClusterId, (ehd_id, ehd_payable_usage): (EHDId, PayableEHDUsage)| { + Call::commit_payout_fingerprint { cluster_id: *cluster_id, - era_id: era.id, - start_era: era.start, - end_era: era.end, - payers_root: era_payable_usage.payers_root, - payees_root: era_payable_usage.payees_root, - cluster_usage: era_payable_usage.cluster_usage, + ehd_id: ehd_id.into(), + payers_root: ehd_payable_usage.payers_root, + payees_root: ehd_payable_usage.payees_root, } }, - |prepared_data: &(EraActivity, PayableEraUsage)| prepared_data.0.id, + |prepared_data: &(EHDId, PayableEHDUsage)| prepared_data.0 .2, "🔑", - |cluster_id: &ClusterId, (era, era_payable_usage): (EraActivity, PayableEraUsage)| { - OCWError::CommitBillingFingerprintTransactionError { + |cluster_id: &ClusterId, (ehd_id, era_payable_usage): (EHDId, PayableEHDUsage)| { + OCWError::CommitPayoutFingerprintTransactionError { cluster_id: *cluster_id, - era_id: era.id, + era_id: ehd_id.2, payers_root: era_payable_usage.payers_root, payees_root: era_payable_usage.payees_root, } @@ -1305,54 +1624,66 @@ pub mod pallet { ); define_payout_step_function!( - step_begin_billing_report, - prepare_begin_billing_report, - |cluster_id: &ClusterId, (era_id, fingerprint)| Call::begin_billing_report { - cluster_id: *cluster_id, - era_id, - fingerprint + step_begin_payout, + prepare_begin_payout, + |cluster_id: &ClusterId, (ehd_id, fingerprint): (EHDId, PayableUsageHash)| { + Call::begin_payout { cluster_id: *cluster_id, era_id: ehd_id.2, fingerprint } }, - |prepared_data: &(DdcEra, _)| prepared_data.0, + |prepared_data: &(EHDId, _)| prepared_data.0 .2, "🗓️ ", - |cluster_id: &ClusterId, (era_id, _)| OCWError::BeginBillingReportTransactionError { - cluster_id: *cluster_id, - era_id, + |cluster_id: &ClusterId, (ehd_id, _): (EHDId, _)| { + OCWError::BeginPayoutTransactionError { cluster_id: *cluster_id, era_id: ehd_id.2 } } ); define_payout_step_function!( step_begin_charging_customers, prepare_begin_charging_customers, - |cluster_id: &ClusterId, (era_id, max_batch_index)| Call::begin_charging_customers { - cluster_id: *cluster_id, - era_id, - max_batch_index, + |cluster_id: &ClusterId, (ehd_id, max_batch_index): (EHDId, u16)| { + Call::begin_charging_customers { + cluster_id: *cluster_id, + era_id: ehd_id.2, + max_batch_index, + } }, - |prepared_data: &(DdcEra, _)| prepared_data.0, + |prepared_data: &(EHDId, _)| prepared_data.0 .2, "📥", - |cluster_id: &ClusterId, (era_id, _)| { - OCWError::BeginChargingCustomersTransactionError { cluster_id: *cluster_id, era_id } + |cluster_id: &ClusterId, (ehd_id, _): (EHDId, _)| { + OCWError::BeginChargingCustomersTransactionError { + cluster_id: *cluster_id, + era_id: ehd_id.2, + } } ); define_payout_step_function!( step_send_charging_customers, prepare_send_charging_customers_batch, - |cluster_id: &ClusterId, (era_id, batch_payout): (DdcEra, CustomerBatch)| { + |cluster_id: &ClusterId, (ehd_id, batch_payout): (EHDId, CustomerBatch)| { Call::send_charging_customers_batch { cluster_id: *cluster_id, - era_id, + era_id: ehd_id.2, batch_index: batch_payout.batch_index, - payers: batch_payout.payers.clone(), + payers: batch_payout + .payers + .into_iter() + .map(|payer| { + ( + T::AccountId::decode(&mut &payer.0.encode()[..]) + .expect("CustomerId to be parsed"), + payer.1, + ) + }) + .collect(), batch_proof: batch_payout.batch_proof.clone(), } }, - |prepared_data: &(DdcEra, _)| prepared_data.0, + |prepared_data: &(EHDId, _)| prepared_data.0 .2, "🧾", - |cluster_id: &ClusterId, (era_id, batch_payout): (DdcEra, CustomerBatch)| { + |cluster_id: &ClusterId, (ehd_id, batch_payout): (EHDId, CustomerBatch)| { OCWError::SendChargingCustomersBatchTransactionError { cluster_id: *cluster_id, - era_id, + era_id: ehd_id.2, batch_index: batch_payout.batch_index, } } @@ -1361,30 +1692,36 @@ pub mod pallet { define_payout_step_function!( step_end_charging_customers, prepare_end_charging_customers, - |cluster_id: &ClusterId, era_id| Call::end_charging_customers { + |cluster_id: &ClusterId, ehd_id: EHDId| Call::end_charging_customers { cluster_id: *cluster_id, - era_id + era_id: ehd_id.2 }, - |prepared_data: &DdcEra| *prepared_data, + |prepared_data: &EHDId| prepared_data.2, "📪", - |cluster_id: &ClusterId, era_id| OCWError::EndChargingCustomersTransactionError { - cluster_id: *cluster_id, - era_id, + |cluster_id: &ClusterId, ehd_id: EHDId| { + OCWError::EndChargingCustomersTransactionError { + cluster_id: *cluster_id, + era_id: ehd_id.2, + } } ); define_payout_step_function!( step_begin_rewarding_providers, prepare_begin_rewarding_providers, - |cluster_id: &ClusterId, (era_id, max_batch_index): (DdcEra, u16)| { - Call::begin_rewarding_providers { cluster_id: *cluster_id, era_id, max_batch_index } + |cluster_id: &ClusterId, (ehd_id, max_batch_index): (EHDId, u16)| { + Call::begin_rewarding_providers { + cluster_id: *cluster_id, + era_id: ehd_id.2, + max_batch_index, + } }, - |prepared_data: &(DdcEra, _)| prepared_data.0, + |prepared_data: &(EHDId, _)| prepared_data.0 .2, "📤", - |cluster_id: &ClusterId, (era_id, _)| { + |cluster_id: &ClusterId, (ehd_id, _): (EHDId, _)| { OCWError::BeginRewardingProvidersTransactionError { cluster_id: *cluster_id, - era_id, + era_id: ehd_id.2, } } ); @@ -1392,21 +1729,31 @@ pub mod pallet { define_payout_step_function!( step_send_rewarding_providers, prepare_send_rewarding_providers_batch, - |cluster_id: &ClusterId, (era_id, batch_payout): (DdcEra, ProviderBatch)| { + |cluster_id: &ClusterId, (ehd_id, batch_payout): (EHDId, ProviderBatch)| { Call::send_rewarding_providers_batch { cluster_id: *cluster_id, - era_id, + era_id: ehd_id.2, batch_index: batch_payout.batch_index, - payees: batch_payout.payees.clone(), + payees: batch_payout + .payees + .into_iter() + .map(|payee| { + ( + T::AccountId::decode(&mut &payee.0.encode()[..]) + .expect("CustomerId to be parsed"), + payee.1, + ) + }) + .collect(), batch_proof: batch_payout.batch_proof.clone(), } }, - |prepared_data: &(DdcEra, _)| prepared_data.0, + |prepared_data: &(EHDId, _)| prepared_data.0 .2, "💸", - |cluster_id: &ClusterId, (era_id, batch_payout): (DdcEra, ProviderBatch)| { + |cluster_id: &ClusterId, (ehd_id, batch_payout): (EHDId, ProviderBatch)| { OCWError::SendRewardingProvidersBatchTransactionError { cluster_id: *cluster_id, - era_id, + era_id: ehd_id.2, batch_index: batch_payout.batch_index, } } @@ -1415,30 +1762,32 @@ pub mod pallet { define_payout_step_function!( step_end_rewarding_providers, prepare_end_rewarding_providers, - |cluster_id: &ClusterId, era_id| Call::end_rewarding_providers { + |cluster_id: &ClusterId, ehd_id: EHDId| Call::end_rewarding_providers { cluster_id: *cluster_id, - era_id + era_id: ehd_id.2, }, - |prepared_data: &DdcEra| *prepared_data, + |prepared_data: &EHDId| prepared_data.2, "📭", - |cluster_id: &ClusterId, era_id| OCWError::EndRewardingProvidersTransactionError { - cluster_id: *cluster_id, - era_id, + |cluster_id: &ClusterId, ehd_id: EHDId| { + OCWError::EndRewardingProvidersTransactionError { + cluster_id: *cluster_id, + era_id: ehd_id.2, + } } ); define_payout_step_function!( - step_end_billing_report, - prepare_end_billing_report, - |cluster_id: &ClusterId, era_id| Call::end_billing_report { + step_end_payout, + prepare_end_payout, + |cluster_id: &ClusterId, ehd_id: EHDId| Call::end_payout { cluster_id: *cluster_id, - era_id + era_id: ehd_id.2, }, - |prepared_data: &DdcEra| *prepared_data, + |prepared_data: &EHDId| prepared_data.2, "🧮", - |cluster_id: &ClusterId, era_id| OCWError::EndBillingReportTransactionError { + |cluster_id: &ClusterId, ehd_id: EHDId| OCWError::EndPayoutTransactionError { cluster_id: *cluster_id, - era_id, + era_id: ehd_id.2, } ); @@ -1459,1510 +1808,1966 @@ pub mod pallet { } } - pub(crate) fn get_total_usage( + pub(crate) fn select_random_leaves( + sample_size: u64, + leaves_ids: Vec, + nonce_key: String, + ) -> Vec { + let nonce = Self::store_and_fetch_nonce(nonce_key); + let mut small_rng = SmallRng::seed_from_u64(nonce); + + leaves_ids + .choose_multiple(&mut small_rng, sample_size.try_into().unwrap()) + .cloned() + .sorted() + .collect::>() + } + + pub(crate) fn build_and_store_ehd_payable_usage( cluster_id: &ClusterId, - era_id: DdcEra, - consistency_groups: ConsistencyGroups, - should_challenge: bool, - ) -> Result, Vec> { - let mut total_usage = vec![]; - let mut total_usage_keys = vec![]; + ehd_id: EHDId, + ) -> Result<(), Vec> { + let batch_size = T::MAX_PAYOUT_BATCH_SIZE; - // todo: implement 'challenge_consensus' fn and run a light challenge for unanimous - // consensus - let in_consensus_usage = consistency_groups - .consensus - .clone() - .into_iter() - .map(|ca| ca.aggregate.clone()) - .collect::>(); - total_usage.extend(in_consensus_usage.clone()); - total_usage_keys - .extend(in_consensus_usage.into_iter().map(|a| a.get_key()).collect::>()); + let collectors = Self::get_collectors_nodes(cluster_id).map_err(|_| { + log::error!("❌ Error retrieving collectors for cluster {:?}", cluster_id); + vec![OCWError::FailedToFetchDacNodes] + })?; + + let g_collector = collectors + .iter() + .find(|(key, _)| *key == G_COLLECTOR_KEY) + .cloned() + .expect("G-Collector to be found"); + + let ehd = + Self::fetch_traversed_era_historical_document(cluster_id, ehd_id.clone(), 1, 1) + .expect("EHD list be fetched") + .first() + .cloned() + .expect("EHD to be fetched"); + + let era = + Self::fetch_processed_ehd_era_from_collector(cluster_id, ehd_id.2, &g_collector) + .map_err(|e| vec![e])? + .expect("EHD era be found"); - // todo: implement 'challenge_quorum' fn and run a light challenge for the quorum, i.e. - // for majority - let in_quorum_usage = consistency_groups - .quorum - .clone() - .into_iter() - .map(|ca| ca.aggregate.clone()) - .collect::>(); - total_usage.extend(in_quorum_usage.clone()); - total_usage_keys - .extend(in_quorum_usage.into_iter().map(|a| a.get_key()).collect::>()); + let pricing = T::ClusterProtocol::get_pricing_params(cluster_id) + .expect("Pricing params to be fetched"); - let verified_usage = Self::challenge_others( - cluster_id, - era_id, - consistency_groups, - &mut total_usage_keys, - should_challenge, - )?; + let fees = + T::ClusterProtocol::get_fees_params(cluster_id).expect("Fees params to be fetched"); - if !verified_usage.is_empty() { - total_usage.extend(verified_usage.clone()); - } + let (payers, cluster_costs) = Self::calculate_ehd_customers_charges( + &ehd.customers, + &pricing, + era.time_start.expect("Processed era to contain start time"), + era.time_end.expect("Processed era to contain end time"), + ) + .expect("Payers batch to be calculated"); - Ok(total_usage) - } + let cluster_usage = ehd.get_cluster_usage(); - pub(crate) fn challenge_others( - _cluster_id: &ClusterId, - _era_id: DdcEra, - consistency_groups: ConsistencyGroups, - accepted_keys: &mut Vec, - should_challenge: bool, - ) -> Result, Vec> { - let redundancy_factor = T::DAC_REDUNDANCY_FACTOR; - let mut verified_usage: Vec = vec![]; + let payees = Self::calculate_ehd_providers_rewards( + &ehd.providers, + &fees, + &cluster_usage, + &cluster_costs, + ) + .expect("Payees batch to be calculated"); - for consolidated_aggregate in consistency_groups.others { - let aggregate_key = consolidated_aggregate.aggregate.get_key(); + let payers_batch_roots = Self::convert_to_batch_merkle_roots( + cluster_id, + Self::split_to_batches(&payers, batch_size.into()), + ehd_id.2, + ) + .map_err(|err| vec![err])?; - if accepted_keys.contains(&aggregate_key) { - log::warn!( - "⚠️ The aggregate {:?} is inconsistent between aggregators.", - aggregate_key - ); + let payees_batch_roots = Self::convert_to_batch_merkle_roots( + cluster_id, + Self::split_to_batches(&payees, batch_size.into()), + ehd_id.2, + ) + .map_err(|err| vec![err])?; - // This prevents the double spending in case of inconsistencies between - // aggregators for the same aggregation key - continue; - } + let payers_root = Self::create_merkle_root(cluster_id, &payers_batch_roots, ehd_id.2) + .map_err(|err| vec![err])?; - if consolidated_aggregate.count > redundancy_factor { - let excessive_aggregate = consolidated_aggregate.aggregate.clone(); + let payees_root = Self::create_merkle_root(cluster_id, &payees_batch_roots, ehd_id.2) + .map_err(|err| vec![err])?; - log::warn!( - "⚠️ Number of consistent aggregates with key {:?} exceeds the redundancy factor", - aggregate_key - ); + Self::store_ehd_payable_usage( + cluster_id, + ehd_id, + payers, + payers_root, + payers_batch_roots, + payees, + payees_root, + payees_batch_roots, + ); - log::info!( - "🔎‍ Challenging excessive aggregate with key {:?} and hash {:?}", - aggregate_key, - excessive_aggregate.hash::() - ); + Ok(()) + } - // todo: run a challenge dedicated to the excessive number of aggregates. - // we assume it won't happen at the moment, so we just take the aggregate to - // payouts stage - verified_usage.push(excessive_aggregate); - accepted_keys.push(aggregate_key); + fn fetch_ehd_payable_usage_or_retry( + cluster_id: &ClusterId, + ehd_id: EHDId, + ) -> Result> { + if let Some(ehd_paybale_usage) = + Self::fetch_ehd_payable_usage(cluster_id, ehd_id.clone()) + { + Ok(ehd_paybale_usage) + } else { + Self::build_and_store_ehd_payable_usage(cluster_id, ehd_id.clone())?; + if let Some(ehd_paybale_usage) = Self::fetch_ehd_payable_usage(cluster_id, ehd_id) { + Ok(ehd_paybale_usage) } else { - let defective_aggregate = consolidated_aggregate.aggregate.clone(); - - log::info!( - "🔎‍ Challenging defective aggregate with key {:?} and hash {:?}", - aggregate_key, - defective_aggregate.hash::() - ); + Err(vec![OCWError::FailedToFetchVerifiedPayableUsage]) + } + } + } - let mut is_passed = true; - // todo: run an intensive challenge for deviating aggregate - // let is_passed = Self::_challenge_aggregate(_cluster_id, _era_id, - // &defective_aggregate)?; - if should_challenge { - is_passed = Self::challenge_aggregate_proto( - _cluster_id, - _era_id, - &defective_aggregate, - )?; - } - if is_passed { - // we assume all aggregates are valid at the moment, so we just take the - // aggregate to payouts stage - verified_usage.push(defective_aggregate); - accepted_keys.push(aggregate_key); - } + fn calculate_ehd_customers_charges( + customers: &Vec, + pricing: &ClusterPricingParams, + time_start: i64, + time_end: i64, + ) -> Result<(Vec, CustomerCosts), ArithmeticError> { + let mut customers_charges = Vec::new(); + let mut cluster_costs = CustomerCosts::default(); + + for customer in customers { + if let Ok(customer_id) = customer.parse_customer_id() { + let customer_costs = Self::get_customer_costs( + pricing, + &customer.consumed_usage, + time_start, + time_end, + )?; + + // todo: cut off unverified activity of buckets if it is detected during + // inspection + + cluster_costs.storage = cluster_costs + .storage + .checked_add(customer_costs.storage) + .ok_or(ArithmeticError::Overflow)?; + + cluster_costs.transfer = cluster_costs + .transfer + .checked_add(customer_costs.transfer) + .ok_or(ArithmeticError::Overflow)?; + + cluster_costs.puts = cluster_costs + .puts + .checked_add(customer_costs.puts) + .ok_or(ArithmeticError::Overflow)?; + + cluster_costs.gets = cluster_costs + .gets + .checked_add(customer_costs.gets) + .ok_or(ArithmeticError::Overflow)?; + + let charge_amount = (|| -> Option { + customer_costs + .transfer + .checked_add(customer_costs.storage)? + .checked_add(customer_costs.puts)? + .checked_add(customer_costs.gets) + })() + .ok_or(ArithmeticError::Overflow)?; + + customers_charges.push(CustomerCharge(customer_id, charge_amount)); } } - Ok(verified_usage) + Ok((customers_charges, cluster_costs)) } - pub(crate) fn _challenge_aggregate( - cluster_id: &ClusterId, - era_id: DdcEra, - aggregate: &A, - ) -> Result> { - let number_of_identifiers = T::MAX_MERKLE_NODE_IDENTIFIER; + #[allow(clippy::field_reassign_with_default)] + fn get_customer_costs( + pricing: &ClusterPricingParams, + consumed_usage: &aggregator_client::json::EHDUsage, + time_start: i64, + time_end: i64, + ) -> Result { + #[allow(clippy::field_reassign_with_default)] + let mut customer_costs = CustomerCosts::default(); + + customer_costs.transfer = (|| -> Option { + (consumed_usage.transferred_bytes as u128) + .checked_mul(pricing.unit_per_mb_streamed)? + .checked_div(byte_unit::MEBIBYTE) + })() + .ok_or(ArithmeticError::Overflow)?; + + // Calculate the duration of the period in seconds + let duration_seconds = time_end - time_start; + let fraction_of_month = + Perquintill::from_rational(duration_seconds as u64, AVG_SECONDS_MONTH as u64); + + customer_costs.storage = fraction_of_month * + (|| -> Option { + (consumed_usage.stored_bytes as u128) + .checked_mul(pricing.unit_per_mb_stored)? + .checked_div(byte_unit::MEBIBYTE) + })() + .ok_or(ArithmeticError::Overflow)?; + + customer_costs.gets = (consumed_usage.number_of_gets as u128) + .checked_mul(pricing.unit_per_get_request) + .ok_or(ArithmeticError::Overflow)?; + + customer_costs.puts = (consumed_usage.number_of_puts as u128) + .checked_mul(pricing.unit_per_put_request) + .ok_or(ArithmeticError::Overflow)?; + + Ok(customer_costs) + } - log::info!( - "👁️‍🗨️ Challenge process starts when bucket sub aggregates are not in consensus!" + fn get_provider_profits( + provided_usage: &aggregator_client::json::EHDUsage, + cluster_usage: &aggregator_client::json::EHDUsage, + cluster_costs: &CustomerCosts, + ) -> Result { + let mut provider_profits = ProviderProfits::default(); + + let mut ratio = Perquintill::from_rational( + provided_usage.transferred_bytes as u128, + cluster_usage.transferred_bytes as u128, ); - let aggregate_key = aggregate.get_key(); - let merkle_node_ids = Self::find_random_merkle_node_ids( - number_of_identifiers.into(), - aggregate.get_number_of_leaves(), - aggregate_key.clone(), + // ratio multiplied by X will be > 0, < X no overflow + provider_profits.transfer = ratio * cluster_costs.transfer; + + ratio = Perquintill::from_rational( + provided_usage.stored_bytes as u128, + cluster_usage.stored_bytes as u128, ); + provider_profits.storage = ratio * cluster_costs.storage; - log::info!( - "👁️‍🗨️ Merkle Node Identifiers for aggregate key: {:?} identifiers: {:?}", - aggregate_key, - merkle_node_ids + ratio = Perquintill::from_rational( + provided_usage.number_of_puts, + cluster_usage.number_of_puts, ); + provider_profits.puts = ratio * cluster_costs.puts; - let aggregator = aggregate.get_aggregator(); + ratio = Perquintill::from_rational( + provided_usage.number_of_gets, + cluster_usage.number_of_gets, + ); + provider_profits.gets = ratio * cluster_costs.gets; - let challenge_response = Self::_fetch_challenge_responses( - cluster_id, - era_id, - aggregate_key.clone(), - merkle_node_ids, - aggregator.clone(), - ) - .map_err(|err| vec![err])?; + Ok(provider_profits) + } - log::info!( - "👁️‍🗨️ Fetched challenge response for aggregate key: {:?}, challenge_response: {:?}", - aggregate_key, - challenge_response - ); + fn calculate_ehd_providers_rewards( + providers: &Vec, + fees: &ClusterFeesParams, + cluster_usage: &aggregator_client::json::EHDUsage, + cluster_costs: &CustomerCosts, + ) -> Result, ArithmeticError> { + let mut providers_profits = Vec::new(); + + for provider in providers { + if let Ok(provider_id) = provider.parse_provider_id() { + let provider_profits = Self::get_provider_profits( + &provider.provided_usage, + cluster_usage, + cluster_costs, + )?; + + let reward_amount = (|| -> Option { + provider_profits + .transfer + .checked_add(provider_profits.storage)? + .checked_add(provider_profits.puts)? + .checked_add(provider_profits.gets) + })() + .ok_or(ArithmeticError::Overflow)?; + + let treasury_fee_amount = fees.treasury_share * reward_amount; + let validators_fee_amount = fees.validators_share * reward_amount; + let cluster_reserve_fee_amount = fees.cluster_reserve_share * reward_amount; + + let profit_amount = (|| -> Option { + reward_amount + .checked_sub(treasury_fee_amount)? + .checked_sub(validators_fee_amount)? + .checked_sub(cluster_reserve_fee_amount) + })() + .ok_or(ArithmeticError::Overflow)?; + + providers_profits.push(ProviderReward(provider_id, profit_amount)); + } + } - let calculated_merkle_root = Self::_get_hash_from_merkle_path( - challenge_response, - cluster_id, - era_id, - aggregate_key.clone(), - )?; + Ok(providers_profits) + } - log::info!( - "👁️‍🗨️ Calculated merkle root for aggregate key: {:?}, calculated_merkle_root: {:?}", - aggregate_key, - calculated_merkle_root - ); + pub(crate) fn prepare_commit_payout_fingerprint( + cluster_id: &ClusterId, + ) -> Result, Vec> { + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + let era_payable_usage = + Self::fetch_ehd_payable_usage_or_retry(cluster_id, ehd_id.clone())?; + Ok(Some((ehd_id, era_payable_usage))) + } else { + Ok(None) + } + } - let root_merkle_node = Self::_fetch_traverse_response( - era_id, - aggregate_key.clone(), - 1, - 1, - &aggregator.node_params, - ) - .map_err(|_| { - vec![OCWError::TraverseResponseRetrievalError { + #[allow(dead_code)] + pub(crate) fn prepare_begin_payout( + cluster_id: &ClusterId, + ) -> Result, Vec> { + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + let ehd_payable_usage = + Self::fetch_ehd_payable_usage_or_retry(cluster_id, ehd_id.clone())?; + Ok(Some((ehd_id, ehd_payable_usage.fingerprint()))) + } else { + Ok(None) + } + } + + pub(crate) fn prepare_begin_charging_customers( + cluster_id: &ClusterId, + ) -> Result, Vec> { + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + if T::PayoutProcessor::get_payout_state(cluster_id, ehd_id.2) == + PayoutState::Initialized + { + let era_payable_usage = + Self::fetch_ehd_payable_usage_or_retry(cluster_id, ehd_id.clone())?; + Self::fetch_ehd_charging_loop_input( + cluster_id, + ehd_id, + era_payable_usage.payers_batch_roots, + ) + } else { + Ok(None) + } + } else { + Ok(None) + } + } + + pub(crate) fn fetch_ehd_charging_loop_input( + cluster_id: &ClusterId, + ehd_id: EHDId, + payers_batch_roots: Vec, + ) -> Result, Vec> { + if let Some(max_batch_index) = payers_batch_roots.len().checked_sub(1) { + let max_batch_index: u16 = max_batch_index.try_into().map_err(|_| { + vec![OCWError::BatchIndexConversionFailed { + cluster_id: *cluster_id, + era_id: ehd_id.2, + }] + })?; + Ok(Some((ehd_id, max_batch_index))) + } else { + Err(vec![OCWError::EmptyCustomerActivity { cluster_id: *cluster_id, - era_id, - aggregate_key: aggregate_key.clone(), - aggregator: aggregator.node_pub_key, - }] - })?; + era_id: ehd_id.2, + }]) + } + } - let mut merkle_root_buf = [0u8; _BUF_SIZE]; - let bytes = - Base64::decode(root_merkle_node.hash.clone(), &mut merkle_root_buf).unwrap(); // todo! remove unwrap - let traversed_merkle_root = DeltaUsageHash::from(sp_core::H256::from_slice(bytes)); + pub(crate) fn prepare_send_charging_customers_batch( + cluster_id: &ClusterId, + ) -> Result, Vec> { + let batch_size = T::MAX_PAYOUT_BATCH_SIZE; + + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + if T::PayoutProcessor::get_payout_state(cluster_id, ehd_id.2) == + PayoutState::ChargingCustomers + { + let era_payable_usage = + Self::fetch_ehd_payable_usage_or_retry(cluster_id, ehd_id.clone())?; + Self::fetch_charging_customers_batch( + cluster_id, + batch_size.into(), + ehd_id, + era_payable_usage.payers_usage, + era_payable_usage.payers_batch_roots, + ) + } else { + Ok(None) + } + } else { + Ok(None) + } + } + + fn fetch_charging_customers_batch( + cluster_id: &ClusterId, + batch_size: usize, + ehd_id: EHDId, + payers_usage: Vec, + payers_batch_roots: Vec, + ) -> Result, Vec> { + let batch_index = T::PayoutProcessor::get_next_customers_batch(cluster_id, ehd_id.2) + .map_err(|_| { + vec![OCWError::PayoutReceiptDoesNotExist { + cluster_id: *cluster_id, + era_id: ehd_id.2, + }] + })?; + + if let Some(index) = batch_index { + let i: usize = index.into(); + // todo! store batched activity to avoid splitting it again each time + let payers_batches = Self::split_to_batches(&payers_usage, batch_size); + + let batch_root = payers_batch_roots[i]; + let store = MemStore::default(); + let mut mmr: MMR> = + MemMMR::<_, MergeMMRHash>::new(0, &store); + + let leaf_position_map: Vec<(DeltaUsageHash, u64)> = + payers_batch_roots.iter().map(|a| (*a, mmr.push(*a).unwrap())).collect(); + + let leaf_position: Vec<(u64, DeltaUsageHash)> = leaf_position_map + .iter() + .filter(|&(l, _)| l == &batch_root) + .map(|&(ref l, p)| (p, *l)) + .collect(); + let position: Vec = + leaf_position.clone().into_iter().map(|(p, _)| p).collect(); + + let proof = mmr + .gen_proof(position) + .map_err(|_| OCWError::FailedToCreateMerkleProof { + cluster_id: *cluster_id, + era_id: ehd_id.2, + }) + .map_err(|e| vec![e])? + .proof_items() + .to_vec(); - log::info!( - "👁️‍🗨️ Fetched merkle root for aggregate key: {:?} traversed_merkle_root: {:?}", - aggregate_key, - traversed_merkle_root - ); + let batch_proof = MMRProof { proof }; + Ok(Some(( + ehd_id.clone(), + CustomerBatch { + batch_index: index, + payers: payers_batches[i].clone(), + batch_proof, + }, + ))) + } else { + Ok(None) + } + } - let is_matched = if calculated_merkle_root == traversed_merkle_root { - log::info!( - "👁️‍🗨️👍 The aggregate with hash {:?} and key {:?} has passed the challenge.", - aggregate.hash::(), - aggregate_key, - ); + pub(crate) fn prepare_end_charging_customers( + cluster_id: &ClusterId, + ) -> Result, Vec> { + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + if T::PayoutProcessor::get_payout_state(cluster_id, ehd_id.2) == + PayoutState::ChargingCustomers && + T::PayoutProcessor::is_customers_charging_finished(cluster_id, ehd_id.2) + { + return Ok(Some(ehd_id)); + } + } + Ok(None) + } - true + pub(crate) fn prepare_begin_rewarding_providers( + cluster_id: &ClusterId, + ) -> Result, Vec> { + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + if T::PayoutProcessor::get_payout_state(cluster_id, ehd_id.2) == + PayoutState::CustomersChargedWithFees + { + let ehd_payable_usage = + Self::fetch_ehd_payable_usage_or_retry(cluster_id, ehd_id.clone())?; + Self::fetch_ehd_rewarding_loop_input( + cluster_id, + ehd_id, + ehd_payable_usage.payees_batch_roots, + ) + } else { + Ok(None) + } } else { - log::info!( - "👁️‍🗨️👎 The aggregate with hash {:?} and key {:?} has not passed the challenge.", - aggregate.hash::(), - aggregate_key, - ); + Ok(None) + } + } - false - }; + pub(crate) fn fetch_ehd_rewarding_loop_input( + cluster_id: &ClusterId, + ehd_id: EHDId, + payees_batch_roots: Vec, + ) -> Result, Vec> { + if let Some(max_batch_index) = payees_batch_roots.len().checked_sub(1) { + let max_batch_index: u16 = max_batch_index.try_into().map_err(|_| { + vec![OCWError::BatchIndexConversionFailed { + cluster_id: *cluster_id, + era_id: ehd_id.2, + }] + })?; - Ok(is_matched) + Ok(Some((ehd_id, max_batch_index))) + } else { + Err(vec![OCWError::EmptyCustomerActivity { + cluster_id: *cluster_id, + era_id: ehd_id.2, + }]) + } } - pub(crate) fn challenge_aggregate_proto( + pub(crate) fn prepare_send_rewarding_providers_batch( cluster_id: &ClusterId, - era_id: DdcEra, - aggregate: &A, - ) -> Result> { - let number_of_identifiers = T::MAX_MERKLE_NODE_IDENTIFIER; + ) -> Result, Vec> { + let batch_size = T::MAX_PAYOUT_BATCH_SIZE; - log::info!( - "👁️‍🗨️ Challenge process starts when bucket sub aggregates are not in consensus!" - ); + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + if T::PayoutProcessor::get_payout_state(cluster_id, ehd_id.2) == + PayoutState::RewardingProviders + { + let era_payable_usage = + Self::fetch_ehd_payable_usage_or_retry(cluster_id, ehd_id.clone())?; + Self::fetch_rewarding_providers_batch( + cluster_id, + batch_size.into(), + ehd_id, + era_payable_usage.payees_usage, + era_payable_usage.payees_batch_roots, + ) + } else { + Ok(None) + } + } else { + Ok(None) + } + } - let aggregate_key = aggregate.get_key(); - let merkle_node_ids = Self::find_random_merkle_node_ids( - number_of_identifiers.into(), - aggregate.get_number_of_leaves(), - aggregate_key.clone(), - ); + fn fetch_rewarding_providers_batch( + cluster_id: &ClusterId, + batch_size: usize, + ehd_id: EHDId, + payees_usage: Vec, + payees_batch_roots: Vec, + ) -> Result, Vec> { + let batch_index = T::PayoutProcessor::get_next_providers_batch(cluster_id, ehd_id.2) + .map_err(|_| { + vec![OCWError::PayoutReceiptDoesNotExist { + cluster_id: *cluster_id, + era_id: ehd_id.2, + }] + })?; - log::info!( - "👁️‍🗨️ Merkle Node Identifiers for aggregate key: {:?} identifiers: {:?}", - aggregate_key, - merkle_node_ids - ); + if let Some(index) = batch_index { + let i: usize = index.into(); + // todo! store batched activity to avoid splitting it again each time + let nodes_activity_batched = Self::split_to_batches(&payees_usage, batch_size); - let aggregator = aggregate.get_aggregator(); + let batch_root = payees_batch_roots[i]; + let store = MemStore::default(); + let mut mmr: MMR> = + MemMMR::<_, MergeMMRHash>::new(0, &store); - let challenge_response = Self::_fetch_challenge_responses_proto( - cluster_id, - era_id, - aggregate_key.clone(), - merkle_node_ids.iter().map(|id| *id as u32).collect(), - aggregator.clone(), - ) - .map_err(|err| vec![err])?; + let leaf_position_map: Vec<(DeltaUsageHash, u64)> = + payees_batch_roots.iter().map(|a| (*a, mmr.push(*a).unwrap())).collect(); - log::info!( - "👁️‍🗨️ Fetched challenge response for aggregate key: {:?}, challenge_response: {:?}", - aggregate_key, - challenge_response - ); + let leaf_position: Vec<(u64, DeltaUsageHash)> = leaf_position_map + .iter() + .filter(|&(l, _)| l == &batch_root) + .map(|&(ref l, p)| (p, *l)) + .collect(); + let position: Vec = + leaf_position.clone().into_iter().map(|(p, _)| p).collect(); - let are_signatures_valid = signature::Verify::verify(&challenge_response); + let proof = mmr + .gen_proof(position) + .map_err(|_| { + vec![OCWError::FailedToCreateMerkleProof { + cluster_id: *cluster_id, + era_id: ehd_id.2, + }] + })? + .proof_items() + .to_vec(); - if are_signatures_valid { - log::info!("👍 Valid challenge signatures for aggregate key: {:?}", aggregate_key,); + let batch_proof = MMRProof { proof }; + Ok(Some(( + ehd_id, + ProviderBatch { + batch_index: index, + payees: nodes_activity_batched[i].clone(), + batch_proof, + }, + ))) } else { - log::info!("👎 Invalid challenge signatures at aggregate key: {:?}", aggregate_key,); + Ok(None) } + } - Ok(are_signatures_valid) + pub(crate) fn prepare_end_rewarding_providers( + cluster_id: &ClusterId, + ) -> Result, Vec> { + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + if T::PayoutProcessor::get_payout_state(cluster_id, ehd_id.2) == + PayoutState::RewardingProviders && + T::PayoutProcessor::is_providers_rewarding_finished(cluster_id, ehd_id.2) + { + return Ok(Some(ehd_id)); + } + } + Ok(None) } - pub(crate) fn _get_hash_from_merkle_path( - challenge_response: aggregator_client::json::ChallengeAggregateResponse, + pub(crate) fn prepare_end_payout( cluster_id: &ClusterId, - era_id: DdcEra, - aggregate_key: AggregateKey, - ) -> Result> { - log::info!("Getting hash from merkle tree path for aggregate key: {:?}", aggregate_key); + ) -> Result, Vec> { + if let Some(ehd_id) = Self::get_ehd_id_for_payout(cluster_id) { + if T::PayoutProcessor::get_payout_state(cluster_id, ehd_id.2) == + PayoutState::ProvidersRewarded + { + return Ok(Some(ehd_id)); + } + } + Ok(None) + } - let mut resulting_hash = DeltaUsageHash::default(); + pub(crate) fn derive_ehd_paybale_usage_key( + cluster_id: &ClusterId, + ehd_id: EHDId, + ) -> Vec { + format!("offchain::paybale_usage::{:?}::{:?}", cluster_id, Into::::into(ehd_id)) + .into_bytes() + } - for proof in challenge_response.proofs { - let leaf_record_hashes: Vec = match aggregate_key { - AggregateKey::BucketSubAggregateKey(_, _) => proof - .leafs - .into_iter() - .map(|p| NodeAggregateLeaf::leaf_hash::(&p)) - .collect(), - AggregateKey::NodeAggregateKey(_) => proof - .leafs - .into_iter() - .map(|p| BucketSubAggregateLeaf::leaf_hash::(&p)) - .collect(), - }; + pub(crate) fn collect_verification_pub_key() -> Result, OCWError> { + let session_verification_keys = >::RuntimeAppPublic::all() + .into_iter() + .enumerate() + .filter_map(|(i, key)| { + let generic_public = >::GenericPublic::from(key); + let public_key: T::Public = generic_public.into(); + let account_id = public_key.clone().into_account(); - let leaf_record_hashes_string: Vec = - leaf_record_hashes.clone().into_iter().map(hex::encode).collect(); + if >::get().contains(&account_id) { + let account = Account::new(i, account_id, public_key); + Option::Some(account) + } else { + Option::None + } + }) + .collect::>(); - log::info!( - "👁️‍🗨️ Fetched leaf record hashes aggregate key: {:?} leaf_record_hashes: {:?}", - aggregate_key, - leaf_record_hashes_string + if session_verification_keys.len() != 1 { + log::error!( + "🚨 Unexpected number of session verification keys is found. Expected: 1, Actual: {:?}", + session_verification_keys.len() ); + return Err(OCWError::FailedToCollectVerificationKey); + } - let leaf_node_root = - Self::create_merkle_root(cluster_id, era_id, &leaf_record_hashes) - .map_err(|err| vec![err])?; + session_verification_keys + .into_iter() + .next() // first + .ok_or(OCWError::FailedToCollectVerificationKey) + } - log::info!( - "👁️‍🗨️ Fetched leaf record root aggregate key: {:?} leaf_record_root_hash: {:?}", - aggregate_key, - hex::encode(leaf_node_root) - ); + pub(crate) fn store_verification_account_id(account_id: T::AccountId) { + let validator: Vec = account_id.encode(); + let key = format!("offchain::validator::{:?}", DAC_VERIFICATION_KEY_TYPE).into_bytes(); + local_storage_set(StorageKind::PERSISTENT, &key, &validator); + } + + pub(crate) fn fetch_verification_account_id() -> Result { + let key = format!("offchain::validator::{:?}", DAC_VERIFICATION_KEY_TYPE).into_bytes(); + + match local_storage_get(StorageKind::PERSISTENT, &key) { + Some(data) => { + let account_id = T::AccountId::decode(&mut &data[..]) + .map_err(|_| OCWError::FailedToFetchVerificationKey)?; + Ok(account_id) + }, + None => Err(OCWError::FailedToFetchVerificationKey), + } + } - let paths = proof.path.iter().rev(); + #[allow(clippy::too_many_arguments)] + pub(crate) fn store_ehd_payable_usage( + cluster_id: &ClusterId, + ehd_id: EHDId, + payers_usage: Vec, + payers_root: PayableUsageHash, + payers_batch_roots: Vec, + payees_usage: Vec, + payees_root: PayableUsageHash, + payees_batch_roots: Vec, + ) { + let key = Self::derive_ehd_paybale_usage_key(cluster_id, ehd_id.clone()); - resulting_hash = leaf_node_root; - for path in paths { - let mut dec_buf = [0u8; _BUF_SIZE]; - let bytes = Base64::decode(path, &mut dec_buf).unwrap(); // todo! remove unwrap - let path_hash: DeltaUsageHash = DeltaUsageHash::from(H256::from_slice(bytes)); + let ehd_paybale_usage = PayableEHDUsage { + cluster_id: *cluster_id, + ehd_id: ehd_id.into(), + payers_usage, + payers_root, + payers_batch_roots, + payees_usage, + payees_root, + payees_batch_roots, + }; + let encoded_ehd_paybale_usage = ehd_paybale_usage.encode(); - let node_root = - Self::create_merkle_root(cluster_id, era_id, &[resulting_hash, path_hash]) - .map_err(|err| vec![err])?; + // Store the serialized data in local offchain storage + local_storage_set(StorageKind::PERSISTENT, &key, &encoded_ehd_paybale_usage); + } - log::info!("👁️‍🗨️ Fetched leaf node root aggregate_key: {:?} for path:{:?} leaf_node_hash: {:?}", - aggregate_key, path, hex::encode(node_root)); + #[allow(clippy::type_complexity)] + pub(crate) fn fetch_ehd_payable_usage( + cluster_id: &ClusterId, + ehd_id: EHDId, + ) -> Option { + log::info!( + "🪙🏠 Off-chain cache hit for Payable Usage in cluster_id: {:?} ehd_id: {:?}", + cluster_id, + ehd_id + ); + let key = Self::derive_ehd_paybale_usage_key(cluster_id, ehd_id); - resulting_hash = node_root; - } - } + let encoded_ehd_paybale_usage = match local_storage_get(StorageKind::PERSISTENT, &key) { + Some(encoded_data) => encoded_data, + None => return None, + }; - Ok(resulting_hash) + match Decode::decode(&mut &encoded_ehd_paybale_usage[..]) { + Ok(ehd_paybale_usage) => Some(ehd_paybale_usage), + Err(err) => { + log::error!("Decoding error: {:?}", err); + None + }, + } } - pub(crate) fn find_random_merkle_node_ids( - number_of_identifiers: usize, - number_of_leaves: u64, - aggregate_key: AggregateKey, - ) -> Vec { - let nonce_key = match aggregate_key { - AggregateKey::NodeAggregateKey(node_id) => node_id, - AggregateKey::BucketSubAggregateKey(.., node_id) => node_id, + pub(crate) fn store_and_fetch_nonce(node_id: String) -> u64 { + let key = format!("offchain::activities::nonce::{:?}", node_id).into_bytes(); + let encoded_nonce = + local_storage_get(StorageKind::PERSISTENT, &key).unwrap_or_else(|| 0.encode()); + + let nonce_data = match Decode::decode(&mut &encoded_nonce[..]) { + Ok(nonce) => nonce, + Err(err) => { + log::error!("Decoding error while fetching nonce: {:?}", err); + 0 + }, }; - let nonce = Self::_store_and_fetch_nonce(nonce_key); - let mut small_rng = SmallRng::seed_from_u64(nonce); + let new_nonce = nonce_data + 1; - let total_levels = number_of_leaves.ilog2() + 1; - let int_list: Vec = (0..total_levels as u64).collect(); + local_storage_set(StorageKind::PERSISTENT, &key, &new_nonce.encode()); + nonce_data + } - let ids: Vec = int_list - .choose_multiple(&mut small_rng, number_of_identifiers) - .cloned() - .collect::>(); + /// Converts a vector of hashable batches into their corresponding Merkle roots. + /// + /// This function takes a vector of hashable batches, where each batch is a vector of + /// hashable items. It computes the Merkle root for each batch by first hashing each + /// activity and then combining these hashes into a single Merkle root. + /// + /// # Input Parameters + /// - `batches: Vec>`: A vector of vectors, where each inner vector represents a + /// batch of hashable items.. + /// + /// # Output + /// - `Vec`: A vector of Merkle roots, one for each batch of items. + pub(crate) fn convert_to_batch_merkle_roots( + cluster_id: &ClusterId, + batches: Vec>, + era_id: EhdEra, + ) -> Result, OCWError> { + batches + .into_iter() + .map(|batch| { + let activity_hashes: Vec = + batch.into_iter().map(|a| a.hash::()).collect(); + Self::create_merkle_root(cluster_id, &activity_hashes, era_id).map_err(|_| { + OCWError::FailedToCreateMerkleRoot { cluster_id: *cluster_id, era_id } + }) + }) + .collect::, OCWError>>() + } - ids + /// Splits a slice of activities into batches of a specified size. + /// + /// This function sorts the given activities and splits them into batches of the specified + /// size. Each batch is returned as a separate vector. + /// + /// # Input Parameters + /// - `activities: &[A]`: A slice of activities to be split into batches. + /// - `batch_size: usize`: The size of each batch. + /// + /// # Output + /// - `Vec>`: A vector of vectors, where each inner vector is a batch of activities. + pub(crate) fn split_to_batches( + activities: &[A], + batch_size: usize, + ) -> Vec> { + if activities.is_empty() { + return vec![]; + } + // Sort the activities first + let mut sorted_activities = activities.to_vec(); + sorted_activities.sort(); // Sort using the derived Ord trait + + // Split the sorted activities into chunks and collect them into vectors + sorted_activities.chunks(batch_size).map(|chunk| chunk.to_vec()).collect() } - /// Computes the consensus for a set of partial activities across multiple buckets within a - /// given cluster and era. + /// Creates a Merkle root from a list of hashes. /// - /// This function collects activities from various buckets, groups them by their consensus - /// ID, and then determines if a consensus is reached for each group based on the minimum - /// number of nodes and a given threshold. If the consensus is reached, the activity is - /// included in the result. Otherwise, appropriate errors are returned. + /// This function takes a slice of `H256` and constructs a Merkle tree + /// using an in-memory store. It returns a tuple containing the Merkle root hash, + /// the size of the Merkle tree, and a vector mapping each input leaf to its position + /// in the Merkle tree. /// /// # Input Parameters - /// - `cluster_id: &ClusterId`: The ID of the cluster for which consensus is being computed. - /// - `era_id: DdcEra`: The era ID within the cluster. - /// - `buckets_aggregates_by_aggregator: &[(NodePubKey, Vec)]`: A list of tuples, where - /// each tuple contains a node's public key and a vector of activities reported for that - /// bucket. - /// - `redundancy_factor: u16`: The number of aggregators that should report total activity - /// for a node or a bucket - /// - `quorum: Percent`: The threshold percentage that determines if an activity is in - /// consensus. + /// + /// * `leaves` - A slice of `H256` representing the leaves of the Merkle tree. /// /// # Output - /// - `Result, Vec>`: - /// - `Ok(Vec)`: A vector of activities that have reached consensus. - /// - `Err(Vec)`: A vector of errors indicating why consensus was not reached - /// for some activities. - pub(crate) fn group_buckets_sub_aggregates_by_consistency( + /// + /// A `Result` containing: + /// * A tuple with the Merkle root `H256`, the size of the Merkle tree, and a vector mapping + /// each input leaf to its position in the Merkle tree. + /// * `OCWError::FailedToCreateMerkleRoot` if there is an error creating the Merkle root. + pub(crate) fn create_merkle_root( cluster_id: &ClusterId, - era_id: DdcEra, - buckets_aggregates_by_aggregator: Vec<( - AggregatorInfo, - Vec, - )>, - redundancy_factor: u16, - quorum: Percent, - ) -> ConsistencyGroups { - let mut buckets_sub_aggregates: Vec = - Vec::new(); + leaves: &[H256], + era_id: EhdEra, + ) -> Result { + if leaves.is_empty() { + return Ok(H256::default()); + } - log::info!( - "👁️‍🗨️‍ Starting fetching bucket sub-aggregates for cluster_id: {:?} for era_id: {:?}", - cluster_id, - era_id - ); - for (aggregator_info, buckets_aggregates_resp) in - buckets_aggregates_by_aggregator.clone() - { - for bucket_aggregate_resp in buckets_aggregates_resp { - for bucket_sub_aggregate_resp in bucket_aggregate_resp.sub_aggregates.clone() { - let bucket_sub_aggregate = aggregator_client::json::BucketSubAggregate { - bucket_id: bucket_aggregate_resp.bucket_id, - node_id: bucket_sub_aggregate_resp.NodeID, - stored_bytes: bucket_sub_aggregate_resp.stored_bytes, - transferred_bytes: bucket_sub_aggregate_resp.transferred_bytes, - number_of_puts: bucket_sub_aggregate_resp.number_of_puts, - number_of_gets: bucket_sub_aggregate_resp.number_of_gets, - aggregator: aggregator_info.clone(), - }; + let store = MemStore::default(); + let mut mmr: MMR> = + MemMMR::<_, MergeMMRHash>::new(0, &store); - buckets_sub_aggregates.push(bucket_sub_aggregate); - } + let mut leaves_with_position: Vec<(u64, H256)> = Vec::with_capacity(leaves.len()); - log::info!("👁️‍🗨️‍ Fetched Bucket sub-aggregates for cluster_id: {:?} for era_id: {:?} for bucket_id {:?}::: Bucket Sub-Aggregates are {:?}", cluster_id, era_id, bucket_aggregate_resp.bucket_id, bucket_aggregate_resp.sub_aggregates); + for &leaf in leaves { + match mmr.push(leaf) { + Ok(pos) => leaves_with_position.push((pos, leaf)), + Err(_) => + return Err(OCWError::FailedToCreateMerkleRoot { + cluster_id: *cluster_id, + era_id, + }), } } - let buckets_sub_aggregates_groups = - Self::group_by_consistency(buckets_sub_aggregates, redundancy_factor, quorum); + mmr.get_root() + .map_err(|_| OCWError::FailedToCreateMerkleRoot { cluster_id: *cluster_id, era_id }) + } - log::info!("👁️‍🗨️‍🌕 Bucket Sub-Aggregates, which are in consensus for cluster_id: {:?} for era_id: {:?}::: {:?}", cluster_id, era_id, buckets_sub_aggregates_groups.consensus); - log::info!("👁️‍🗨️‍🌗 Bucket Sub-Aggregates, which are in quorum for cluster_id: {:?} for era_id: {:?}::: {:?}", cluster_id, era_id, buckets_sub_aggregates_groups.quorum); - log::info!("👁️‍🗨️‍🌘 Bucket Sub-Aggregates, which are neither in consensus nor in quorum for cluster_id: {:?} for era_id: {:?}::: {:?}", cluster_id, era_id, buckets_sub_aggregates_groups.others); + pub(crate) fn get_ehd_root( + cluster_id: &ClusterId, + ehd_id: EHDId, + ) -> Option { + Self::fetch_traversed_era_historical_document(cluster_id, ehd_id, 1, 1) + .ok()? + .first() + .cloned() + } - buckets_sub_aggregates_groups + pub(crate) fn get_ehd_id_for_payout(cluster_id: &ClusterId) -> Option { + match T::ClusterValidator::get_last_paid_era(cluster_id) { + Ok(last_paid_era_for_cluster) => { + if let Some(inspected_ehds) = Self::fetch_last_inspected_ehds(cluster_id) { + for inspected_ehd in inspected_ehds.clone().into_iter().sorted() { + if inspected_ehd.2 > last_paid_era_for_cluster { + let receipts_by_inspector = Self::fetch_inspection_receipts( + cluster_id, + inspected_ehd.clone(), + ) + .map_err(|e| vec![e]) + .ok()?; + + let inspectors_quorum = T::ValidatorsQuorum::get(); + let threshold = inspectors_quorum * >::get().len(); + + if threshold <= receipts_by_inspector.len() { + return Some(inspected_ehd); + } + } + } + } + None + }, + Err(_) => None, + } } - pub(crate) fn build_and_store_payable_usage( - cluster_id: &ClusterId, - era: EraActivity, - ) -> Result<(), Vec> { - let batch_size = T::MAX_PAYOUT_BATCH_SIZE; + pub(crate) fn get_last_inspected_ehd(cluster_id: &ClusterId) -> Option { + if let Some(last_inspected_ehds) = Self::fetch_last_inspected_ehds(cluster_id) { + last_inspected_ehds.iter().max_by_key(|ehd| ehd.2).cloned() + } else { + None + } + } - let (buckets_delta_usage, nodes_delta_usage) = - Self::fetch_verified_delta_usage_or_retry(cluster_id, era.id, era.start, era.end)?; + pub(crate) fn derive_last_inspected_ehd_key(cluster_id: &ClusterId) -> Vec { + format!("offchain::inspected_ehds::v1::{:?}", cluster_id).into_bytes() + } - let payers_usage = - Self::calculate_buckets_payable_usage(cluster_id, buckets_delta_usage); + pub(crate) fn store_last_inspected_ehd(cluster_id: &ClusterId, ehd_id: EHDId) { + let key = Self::derive_last_inspected_ehd_key(cluster_id); - let payees_usage = Self::calculate_nodes_payable_usage(cluster_id, nodes_delta_usage); + if let Some(mut inspected_ehds) = Self::fetch_last_inspected_ehds(cluster_id) { + inspected_ehds.push(ehd_id); - let payers_batch_roots = Self::convert_to_batch_merkle_roots( - cluster_id, - era.id, - Self::split_to_batches(&payers_usage, batch_size.into()), - ) - .map_err(|err| vec![err])?; + let encoded_ehds_ids = + inspected_ehds.into_iter().sorted().collect::>().encode(); + local_storage_set(StorageKind::PERSISTENT, &key, &encoded_ehds_ids); + } else { + log::warn!( + "🗄️ Failed to retrieve last inspected ehds from offchain storage for cluster_id: {:?}", + cluster_id, + ); + } + } + + pub(crate) fn fetch_last_inspected_ehds(cluster_id: &ClusterId) -> Option> { + log::info!("🗄️ Trying to fetch last inspected ehds for cluster_id: {:?}", cluster_id,); + + let key = Self::derive_last_inspected_ehd_key(cluster_id); + + let encoded_last_inspected_ehd: Vec = + match local_storage_get(StorageKind::PERSISTENT, &key) { + Some(encoded_data) => encoded_data, + None => return Some(vec![]), + }; + + match Decode::decode(&mut &encoded_last_inspected_ehd[..]) { + Ok(last_inspected_ehd) => Some(last_inspected_ehd), + Err(err) => { + log::error!("🗄️ Error occured while decoding last inspected ehds in cluster_id: {:?} {:?}", cluster_id, err); + None + }, + } + } + + /// Fetch current era across all DAC nodes to validate. + /// + /// Parameters: + /// - `cluster_id`: cluster id of a cluster + /// - `g_collectors`: List of G-Collectors nodes + pub(crate) fn get_ehd_era_for_inspection( + cluster_id: &ClusterId, + g_collectors: &[(NodePubKey, StorageNodeParams)], + ) -> Result, OCWError> { + let _this_validator = Self::fetch_verification_account_id()?; - let payees_batch_roots = Self::convert_to_batch_merkle_roots( - cluster_id, - era.id, - Self::split_to_batches(&payees_usage, batch_size.into()), - ) - .map_err(|err| vec![err])?; + let last_validated_ehd_by_this_validator = Self::get_last_inspected_ehd(cluster_id); - let payers_root = Self::create_merkle_root(cluster_id, era.id, &payers_batch_roots) - .map_err(|err| vec![err])?; + let last_validated_era_by_this_validator: EhdEra = + if let Some(ehd) = last_validated_ehd_by_this_validator { + ehd.2 + } else { + Default::default() + }; - let payees_root = Self::create_merkle_root(cluster_id, era.id, &payees_batch_roots) - .map_err(|err| vec![err])?; + let last_paid_era_for_cluster = + T::ClusterValidator::get_last_paid_era(cluster_id).map_err(|_| { + OCWError::EraRetrievalError { cluster_id: *cluster_id, node_pub_key: None } + })?; - Self::store_payable_usage( + log::info!( + "👁️‍🗨️ The last era inspected by this specific validator for cluster_id: {:?} is {:?}. The last paid era for the cluster is {:?}", cluster_id, - era, - payers_usage, - payers_root, - payers_batch_roots, - payees_usage, - payees_root, - payees_batch_roots, + last_validated_era_by_this_validator, + last_paid_era_for_cluster ); - Ok(()) - } + // we want to fetch processed eras from all available validators + let available_processed_ehd_eras = + Self::fetch_processed_ehd_eras_from_collector(cluster_id, g_collectors)?; - fn fetch_payable_usage_or_retry( - cluster_id: &ClusterId, - era: EraActivity, - ) -> Result> { - if let Some(payble_usage) = Self::fetch_payable_usage(cluster_id, era.id) { - Ok(payble_usage) - } else { - Self::build_and_store_payable_usage(cluster_id, era)?; - if let Some(payble_usage) = Self::fetch_payable_usage(cluster_id, era.id) { - Ok(payble_usage) - } else { - Err(vec![OCWError::FailedToFetchVerifiedPayableUsage]) - } - } - } + // we want to let the current validator to validate available processed/completed eras + // that are greater than the last validated era in the cluster + let processed_ehd_eras_to_inspect: Vec = + available_processed_ehd_eras + .iter() + .flat_map(|eras| { + eras.iter() + .filter(|&ids| { + ids.id > last_validated_era_by_this_validator && + ids.id > last_paid_era_for_cluster + }) + .cloned() + }) + .sorted() + .collect::>(); - fn calculate_buckets_payable_usage( - cluster_id: &ClusterId, - buckets_delta_usage: Vec, - ) -> Vec { - let mut result = Vec::new(); + // We want to process only eras reported by quorum of validators + let mut processed_ehd_eras_with_quorum: Vec = vec![]; - let delta_usage_map: BTreeMap = buckets_delta_usage - .into_iter() - .map(|delta_usage| (delta_usage.bucket_id, delta_usage)) - .collect(); - - let mut merged_bucket_ids: BTreeSet = BTreeSet::new(); - - for current_usage in T::BucketsStorageUsageProvider::iter_storage_usage(cluster_id) { - if let Some(delta_usage) = delta_usage_map.get(¤t_usage.bucket_id) { - // Intersection: Charge for the sum of the current usage and delta usage. - let payable_usage = BucketPayableUsage( - current_usage.bucket_id, - BucketUsage { - transferred_bytes: delta_usage.transferred_bytes, - stored_bytes: current_usage - .stored_bytes - .saturating_add(delta_usage.stored_bytes), - number_of_puts: delta_usage.number_of_puts, - number_of_gets: delta_usage.number_of_gets, - }, - ); + // let quorum = T::AggregatorsQuorum::get(); + // let threshold = quorum * collectors_nodes.len(); - result.push(payable_usage); + // At the moment we have only one G-collector + let threshold = 1; + for (era_key, candidates) in + &processed_ehd_eras_to_inspect.into_iter().chunk_by(|elt| elt.clone()) + { + let count = candidates.count(); + if count >= threshold { + processed_ehd_eras_with_quorum.push(era_key); } else { - // No Intersection: Charge for the current usage only. There was no activity for - // this bucket in the operating era. - let payable_usage = BucketPayableUsage( - current_usage.bucket_id, - BucketUsage { - transferred_bytes: 0, - stored_bytes: current_usage.stored_bytes, - number_of_puts: 0, - number_of_gets: 0, - }, + log::warn!( + "⚠️ Era {:?} in cluster_id: {:?} has been reported with unmet quorum. Desired: {:?} Actual: {:?}", + era_key, + cluster_id, + threshold, + count ); - result.push(payable_usage); } - merged_bucket_ids.insert(current_usage.bucket_id); } - for delta_usage in delta_usage_map.values() { - if !merged_bucket_ids.contains(&delta_usage.bucket_id) { - // No Intersection: Charge for the delta usage only. Possibly, this is a new - // bucket that is charged after its first operating era. - let payable_usage = BucketPayableUsage( - delta_usage.bucket_id, - BucketUsage { - transferred_bytes: delta_usage.transferred_bytes, - stored_bytes: delta_usage.stored_bytes, - number_of_puts: delta_usage.number_of_puts, - number_of_gets: delta_usage.number_of_gets, - }, - ); - result.push(payable_usage); - } - } + let ehd_era_to_inspect = + processed_ehd_eras_with_quorum.iter().cloned().min_by_key(|n| n.id); + + log::info!( + "👁️‍🗨️ Era {:?} has been selected for inspection in cluster_id: {:?}", + ehd_era_to_inspect, + cluster_id, + ); - result + Ok(ehd_era_to_inspect) } - fn calculate_nodes_payable_usage( + /// Fetch DAC nodes of a cluster. + /// Parameters: + /// - `cluster_id`: Cluster id of a cluster. + fn get_collectors_nodes( cluster_id: &ClusterId, - nodes_delta_usage: Vec, - ) -> Vec { - let mut result = Vec::new(); + ) -> Result, Error> { + let mut dac_nodes = Vec::new(); - let delta_usage_map: BTreeMap = nodes_delta_usage - .into_iter() - .filter_map(|delta_usage| { - if let Ok(node_key) = Self::node_key_from_hex(delta_usage.node_id.clone()) { - Option::Some((node_key, delta_usage)) - } else { - Option::None - } - }) - .collect(); - - let mut merged_nodes_keys: BTreeSet = BTreeSet::new(); - - for current_usage in T::NodesStorageUsageProvider::iter_storage_usage(cluster_id) { - if let Some(delta_usage) = delta_usage_map.get(¤t_usage.node_key) { - // Intersection: Reward for the sum of the current usage and delta usage. - let payable_usage = NodePayableUsage( - current_usage.node_key.clone(), - NodeUsage { - transferred_bytes: delta_usage.transferred_bytes, - stored_bytes: current_usage - .stored_bytes - .saturating_add(delta_usage.stored_bytes), - number_of_puts: delta_usage.number_of_puts, - number_of_gets: delta_usage.number_of_gets, - }, - ); + let nodes = T::ClusterManager::get_nodes(cluster_id) + .map_err(|_| Error::::NodeRetrievalError)?; - result.push(payable_usage); - } else { - // No Intersection: Reward for the current usage only. There was no activity for - // this node in the operating era. - let payable_usage = NodePayableUsage( - current_usage.node_key.clone(), - NodeUsage { - transferred_bytes: 0, - stored_bytes: current_usage.stored_bytes, - number_of_puts: 0, - number_of_gets: 0, - }, - ); - result.push(payable_usage); - } - merged_nodes_keys.insert(current_usage.node_key); - } + // Iterate over each node + for node_pub_key in nodes { + // Get the node parameters + if let Ok(NodeParams::StorageParams(storage_params)) = + T::NodeManager::get_node_params(&node_pub_key) + { + // log::info!( + // "🏭 Obtained DAC Node for cluster_id: {:?} and with key: {:?}", + // cluster_id, + // node_pub_key + // ); - for (node_key, delta_usage) in delta_usage_map.into_iter() { - if !merged_nodes_keys.contains(&node_key) { - // No Intersection: Reward for the delta usage only. Possibly, this is a new - // node that is rewarded after its first operating era. - let payable_usage = NodePayableUsage( - node_key, - NodeUsage { - transferred_bytes: delta_usage.transferred_bytes, - stored_bytes: delta_usage.stored_bytes, - number_of_puts: delta_usage.number_of_puts, - number_of_gets: delta_usage.number_of_gets, - }, - ); - result.push(payable_usage); + // Add to the results if the mode matches + dac_nodes.push((node_pub_key, storage_params)); } } - result + Ok(dac_nodes) } - fn fetch_verified_delta_usage_or_retry( + /// Fetch processed payment era for across all nodes. + /// + /// Parameters: + /// - `cluster_id`: Cluster Id + /// - `g_collector_key`: G-collector node key to fetch the payment eras from + /// - `node_params`: DAC node parameters + fn fetch_processed_ehd_eras_from_collector( cluster_id: &ClusterId, - era_id: DdcEra, - start: i64, - end: i64, - ) -> Result<(Vec, Vec), Vec> { - if let Some((buckets_deltas, _, _, nodes_deltas, _, _)) = - Self::fetch_verified_delta_usage(cluster_id, era_id) - { - Ok((buckets_deltas, nodes_deltas)) - } else { - let era_activity = EraActivity { id: era_id, start, end }; - Self::process_dac_era(cluster_id, Some(era_activity))?; - if let Some((buckets_deltas, _, _, nodes_deltas, _, _)) = - Self::fetch_verified_delta_usage(cluster_id, era_id) - { - Ok((buckets_deltas, nodes_deltas)) + g_collectors: &[(NodePubKey, StorageNodeParams)], + ) -> Result>, OCWError> { + let mut processed_eras_by_nodes: Vec> = Vec::new(); + + for (collector_key, node_params) in g_collectors { + let processed_payment_eras = Self::fetch_processed_ehd_eras(node_params); + if processed_payment_eras.is_err() { + log::warn!( + "Aggregator from cluster {:?} is unavailable while fetching processed eras. Key: {:?} Host: {:?}", + cluster_id, + collector_key, + String::from_utf8(node_params.host.clone()) + ); + // Skip unavailable aggregators and continue with available ones + continue; } else { - Err(vec![OCWError::FailedToFetchVerifiedDeltaUsage]) + let eras = processed_payment_eras.expect("Era Response to be available"); + if !eras.is_empty() { + processed_eras_by_nodes.push(eras.into_iter().collect::>()); + } } } + + Ok(processed_eras_by_nodes) } - pub(crate) fn prepare_commit_billing_fingerprint( + fn fetch_processed_ehd_era_from_collector( cluster_id: &ClusterId, - ) -> Result, Vec> { - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::ReadyForPayout) - { - let era_payable_usage = Self::fetch_payable_usage_or_retry(cluster_id, era)?; - Ok(Some((era, era_payable_usage))) - } else { - Ok(None) - } + era: EhdEra, + g_collector: &(NodePubKey, StorageNodeParams), + ) -> Result, OCWError> { + let ehd_eras = Self::fetch_processed_ehd_eras_from_collector( + cluster_id, + vec![g_collector.clone()].as_slice(), + )?; + + let era = + ehd_eras.iter().flat_map(|eras| eras.iter()).find(|ehd| ehd.id == era).cloned(); + + Ok(era) } - #[allow(dead_code)] - pub(crate) fn prepare_begin_billing_report( - cluster_id: &ClusterId, - ) -> Result, Vec> { - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::ReadyForPayout) - { - let era_payable_usage = Self::fetch_payable_usage_or_retry(cluster_id, era)?; - Ok(Some((era.id, era_payable_usage.fingerprint()))) - } else { - Ok(None) - } + /// Verify whether leaf is part of tree + /// + /// Parameters: + /// - `root_hash`: merkle root hash + /// - `batch_hash`: hash of the batch + /// - `batch_index`: index of the batch + /// - `batch_proof`: MMR proofs + pub(crate) fn _proof_merkle_leaf( + root_hash: PayableUsageHash, + batch_hash: PayableUsageHash, + batch_index: BatchIndex, + max_batch_index: BatchIndex, + batch_proof: &MMRProof, + ) -> Result> { + let batch_position = leaf_index_to_pos(batch_index.into()); + let mmr_size = leaf_index_to_mmr_size(max_batch_index.into()); + let proof: MerkleProof = + MerkleProof::new(mmr_size, batch_proof.proof.clone()); + proof + .verify(root_hash, vec![(batch_position, batch_hash)]) + .map_err(|_| Error::::FailedToVerifyMerkleProof) } + } - pub(crate) fn prepare_begin_charging_customers( - cluster_id: &ClusterId, - ) -> Result, Vec> { - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) - { - if T::PayoutProcessor::get_billing_report_status(cluster_id, era.id) == - PayoutState::Initialized - { - let era_payable_usage = Self::fetch_payable_usage_or_retry(cluster_id, era)?; - Self::fetch_charging_loop_input( - cluster_id, - era.id, - era_payable_usage.payers_batch_roots, - ) - } else { - Ok(None) - } + impl ValidatorVisitor for Pallet { + fn is_ocw_validator(caller: T::AccountId) -> bool { + if ValidatorToStashKey::::contains_key(caller.clone()) { + >::get().contains(&caller) } else { - Ok(None) + false } } - pub(crate) fn fetch_charging_loop_input( - cluster_id: &ClusterId, - era_id: DdcEra, - payers_batch_roots: Vec, - ) -> Result, Vec> { - if let Some(max_batch_index) = payers_batch_roots.len().checked_sub(1) { - let max_batch_index: u16 = max_batch_index.try_into().map_err(|_| { - vec![OCWError::BatchIndexConversionFailed { cluster_id: *cluster_id, era_id }] - })?; - Ok(Some((era_id, max_batch_index))) - } else { - Err(vec![OCWError::EmptyCustomerActivity { cluster_id: *cluster_id, era_id }]) - } + fn is_quorum_reached(quorum: Percent, members_count: usize) -> bool { + let threshold = quorum * >::get().len(); + threshold <= members_count } + } - pub(crate) fn prepare_send_charging_customers_batch( - cluster_id: &ClusterId, - ) -> Result, Vec> { - let batch_size = T::MAX_PAYOUT_BATCH_SIZE; + /// Era activity of a node. + #[derive( + Debug, + Serialize, + Deserialize, + Clone, + Copy, + Hash, + Ord, + PartialOrd, + PartialEq, + Eq, + TypeInfo, + Encode, + Decode, + )] + pub struct EraActivity { + /// Era id. + pub id: DdcEra, + pub start: i64, + pub end: i64, + } - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) - { - if T::PayoutProcessor::get_billing_report_status(cluster_id, era.id) == - PayoutState::ChargingCustomers - { - let era_payable_usage = Self::fetch_payable_usage_or_retry(cluster_id, era)?; - Self::fetch_charging_customers_batch( - cluster_id, - batch_size.into(), - era.id, - era_payable_usage.payers_usage, - era_payable_usage.payers_batch_roots, - ) - } else { - Ok(None) - } - } else { - Ok(None) - } + impl From for EraActivity { + fn from(era: aggregator_client::json::AggregationEraResponse) -> Self { + Self { id: era.id, start: era.start, end: era.end } + } + } + + #[derive(Clone)] + pub struct CustomerBatch { + pub(crate) batch_index: BatchIndex, + pub(crate) payers: Vec, + pub(crate) batch_proof: MMRProof, + } + + #[derive(Clone)] + pub struct ProviderBatch { + pub(crate) batch_index: BatchIndex, + pub(crate) payees: Vec, + pub(crate) batch_proof: MMRProof, + } + + impl Hashable for CustomerCharge { + fn hash(&self) -> PayableUsageHash { + let mut data = self.0.encode(); // customer_id + data.extend_from_slice(&self.1.encode()); // amount + T::Hasher::hash(&data) + } + } + + impl Hashable for ProviderReward { + fn hash(&self) -> PayableUsageHash { + let mut data = self.0.encode(); // provider_id + data.extend_from_slice(&self.1.encode()); // amount + T::Hasher::hash(&data) + } + } + + /// The `ConsolidatedAggregate` struct represents a merging result of multiple aggregates + /// that have reached consensus on the usage criteria. This result should be taken into + /// consideration when choosing the intensity of the challenge. + #[allow(unused)] + #[derive(Debug, Clone, PartialEq)] + pub(crate) struct ConsolidatedAggregate { + /// The representative aggregate after consolidation + pub(crate) aggregate: A, + /// Number of aggregates that were consistent + pub(crate) count: u16, + /// Aggregators that provided consistent aggregates + pub(crate) aggregators: Vec, + } + + #[allow(unused)] + impl ConsolidatedAggregate { + pub(crate) fn new(aggregate: A, count: u16, aggregators: Vec) -> Self { + ConsolidatedAggregate { aggregate, count, aggregators } } + } + + #[allow(unused)] + #[derive(Debug, Clone, PartialEq)] + pub(crate) struct ConsistencyGroups { + pub(crate) consensus: Vec>, + pub(crate) quorum: Vec>, + pub(crate) others: Vec>, + } - fn fetch_charging_customers_batch( - cluster_id: &ClusterId, - batch_size: usize, - era_id: DdcEra, - payers_usage: Vec, - payers_batch_roots: Vec, - ) -> Result, Vec> { - let batch_index = - T::PayoutProcessor::get_next_customer_batch_for_payment(cluster_id, era_id) - .map_err(|_| { - vec![OCWError::BillingReportDoesNotExist { - cluster_id: *cluster_id, - era_id, - }] - })?; + #[allow(unused)] + #[derive(Debug, Serialize, Deserialize, Clone, Encode, Decode, Hash, TypeInfo, PartialEq)] + pub enum AggregateKey { + NodeAggregateKey(String), + BucketSubAggregateKey(BucketId, String), + } - if let Some(index) = batch_index { - let i: usize = index.into(); - // todo! store batched activity to avoid splitting it again each time - let payers_batches = Self::split_to_batches(&payers_usage, batch_size); + pub(crate) trait Hashable { + /// Hash of the entity + fn hash(&self) -> H256; + } - let batch_root = payers_batch_roots[i]; - let store = MemStore::default(); - let mut mmr: MMR> = - MemMMR::<_, MergeMMRHash>::new(0, &store); + /// The 'Aggregate' trait defines a set of members common to activity aggregates, which reflect + /// the usage of a node or bucket within an Era.. + #[allow(unused)] + pub(crate) trait Aggregate: + Hashable + Clone + Ord + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> + Debug + { + /// Aggregation key of this aggregate, i.e. bucket composite key or node key + fn get_key(&self) -> AggregateKey; + /// Number of activity records this aggregated by this aggregate + fn get_number_of_leaves(&self) -> u64; + /// Aggregator provided this aggregate + fn get_aggregator(&self) -> AggregatorInfo; + } - let leaf_position_map: Vec<(DeltaUsageHash, u64)> = - payers_batch_roots.iter().map(|a| (*a, mmr.push(*a).unwrap())).collect(); + impl Hashable for aggregator_client::json::BucketSubAggregate { + fn hash(&self) -> DeltaUsageHash { + let mut data = self.bucket_id.encode(); + data.extend_from_slice(&self.node_id.encode()); + data.extend_from_slice(&self.stored_bytes.encode()); + data.extend_from_slice(&self.transferred_bytes.encode()); + data.extend_from_slice(&self.number_of_puts.encode()); + data.extend_from_slice(&self.number_of_gets.encode()); + T::Hasher::hash(&data) + } + } - let leaf_position: Vec<(u64, DeltaUsageHash)> = leaf_position_map - .iter() - .filter(|&(l, _)| l == &batch_root) - .map(|&(ref l, p)| (p, *l)) - .collect(); - let position: Vec = - leaf_position.clone().into_iter().map(|(p, _)| p).collect(); + impl Aggregate for aggregator_client::json::BucketSubAggregate { + fn get_key(&self) -> AggregateKey { + AggregateKey::BucketSubAggregateKey(self.bucket_id, self.node_id.clone()) + } - let proof = mmr - .gen_proof(position) - .map_err(|_| OCWError::FailedToCreateMerkleProof { - cluster_id: *cluster_id, - era_id, - }) - .map_err(|e| vec![e])? - .proof_items() - .to_vec(); + fn get_number_of_leaves(&self) -> u64 { + self.number_of_gets.saturating_add(self.number_of_puts) + } - let batch_proof = MMRProof { proof }; - Ok(Some(( - era_id, - CustomerBatch { - batch_index: index, - payers: payers_batches[i] - .iter() - .map(|payable_usage| { - let bucket_id = payable_usage.0; - let customer_usage = BucketUsage { - transferred_bytes: payable_usage.1.transferred_bytes, - stored_bytes: payable_usage.1.stored_bytes, - number_of_puts: payable_usage.1.number_of_puts, - number_of_gets: payable_usage.1.number_of_gets, - }; - (bucket_id, customer_usage) - }) - .collect(), - batch_proof, - }, - ))) - } else { - Ok(None) - } + fn get_aggregator(&self) -> AggregatorInfo { + self.aggregator.clone() } + } - pub(crate) fn prepare_end_charging_customers( - cluster_id: &ClusterId, - ) -> Result, Vec> { - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) - { - if T::PayoutProcessor::get_billing_report_status(cluster_id, era.id) == - PayoutState::ChargingCustomers && - T::PayoutProcessor::all_customer_batches_processed(cluster_id, era.id) - { - return Ok(Some(era.id)); - } - } - Ok(None) + impl Hashable for aggregator_client::json::NodeAggregate { + fn hash(&self) -> DeltaUsageHash { + let mut data = self.node_id.encode(); + data.extend_from_slice(&self.stored_bytes.encode()); + data.extend_from_slice(&self.transferred_bytes.encode()); + data.extend_from_slice(&self.number_of_puts.encode()); + data.extend_from_slice(&self.number_of_gets.encode()); + T::Hasher::hash(&data) } + } - pub(crate) fn prepare_begin_rewarding_providers( - cluster_id: &ClusterId, - ) -> Result, Vec> { - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) - { - if T::PayoutProcessor::get_billing_report_status(cluster_id, era.id) == - PayoutState::CustomersChargedWithFees - { - let era_payable_usage = Self::fetch_payable_usage_or_retry(cluster_id, era)?; - Self::fetch_rewarding_loop_input( - cluster_id, - era.id, - era_payable_usage.payees_batch_roots, - ) - } else { - Ok(None) - } - } else { - Ok(None) - } + impl Aggregate for aggregator_client::json::NodeAggregate { + fn get_key(&self) -> AggregateKey { + AggregateKey::NodeAggregateKey(self.node_id.clone()) } - pub(crate) fn fetch_rewarding_loop_input( - cluster_id: &ClusterId, - era_id: DdcEra, - payees_batch_roots: Vec, - ) -> Result, Vec> { - if let Some(max_batch_index) = payees_batch_roots.len().checked_sub(1) { - let max_batch_index: u16 = max_batch_index.try_into().map_err(|_| { - vec![OCWError::BatchIndexConversionFailed { cluster_id: *cluster_id, era_id }] - })?; + fn get_aggregator(&self) -> AggregatorInfo { + self.aggregator.clone() + } - Ok(Some((era_id, max_batch_index))) - } else { - Err(vec![OCWError::EmptyCustomerActivity { cluster_id: *cluster_id, era_id }]) - } + fn get_number_of_leaves(&self) -> u64 { + self.number_of_gets.saturating_add(self.number_of_puts) } + } + pub trait NodeAggregateLeaf: + Clone + Ord + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> + { + fn leaf_hash(&self) -> DeltaUsageHash; + } - pub(crate) fn prepare_send_rewarding_providers_batch( - cluster_id: &ClusterId, - ) -> Result, Vec> { - let batch_size = T::MAX_PAYOUT_BATCH_SIZE; + pub trait BucketSubAggregateLeaf: + Clone + Ord + PartialEq + Eq + Serialize + for<'de> Deserialize<'de> + { + fn leaf_hash(&self) -> DeltaUsageHash; + } - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) - { - if T::PayoutProcessor::get_billing_report_status(cluster_id, era.id) == - PayoutState::RewardingProviders - { - let era_payable_usage = Self::fetch_payable_usage_or_retry(cluster_id, era)?; - Self::fetch_rewarding_providers_batch( - cluster_id, - batch_size.into(), - era.id, - era_payable_usage.payees_usage, - era_payable_usage.payees_batch_roots, - ) - } else { - Ok(None) - } - } else { - Ok(None) - } + impl NodeAggregateLeaf for aggregator_client::json::Leaf { + fn leaf_hash(&self) -> DeltaUsageHash { + let mut data = self.record.id.encode(); + data.extend_from_slice(&self.record.upstream.request.requestType.encode()); + data.extend_from_slice(&self.stored_bytes.encode()); + data.extend_from_slice(&self.transferred_bytes.encode()); + T::Hasher::hash(&data) } + } - fn fetch_rewarding_providers_batch( - cluster_id: &ClusterId, - batch_size: usize, - era_id: DdcEra, - payees_usage: Vec, - payees_batch_roots: Vec, - ) -> Result, Vec> { - let batch_index = - T::PayoutProcessor::get_next_provider_batch_for_payment(cluster_id, era_id) - .map_err(|_| { - vec![OCWError::BillingReportDoesNotExist { - cluster_id: *cluster_id, - era_id, - }] - })?; + impl BucketSubAggregateLeaf for aggregator_client::json::Leaf { + fn leaf_hash(&self) -> DeltaUsageHash { + let mut data = self.record.upstream.request.bucketId.encode(); + data.extend_from_slice(&self.record.encode()); + data.extend_from_slice(&self.record.upstream.request.requestType.encode()); + data.extend_from_slice(&self.stored_bytes.encode()); + data.extend_from_slice(&self.transferred_bytes.encode()); + T::Hasher::hash(&data) + } + } - if let Some(index) = batch_index { - let i: usize = index.into(); - // todo! store batched activity to avoid splitting it again each time - let nodes_activity_batched = Self::split_to_batches(&payees_usage, batch_size); + /* ######## DAC v5 DDC endpoints ######## */ + impl Pallet { + /// Fetch processed EHD eras. + /// + /// Parameters: + /// - `node_params`: DAC node parameters + #[allow(dead_code)] + pub(crate) fn fetch_processed_ehd_eras( + node_params: &StorageNodeParams, + ) -> Result, http::Error> { + let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; + let base_url = format!("http://{}:{}", host, node_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, + ); - let batch_root = payees_batch_roots[i]; - let store = MemStore::default(); - let mut mmr: MMR> = - MemMMR::<_, MergeMMRHash>::new(0, &store); + let response = client.payment_eras()?; - let leaf_position_map: Vec<(DeltaUsageHash, u64)> = - payees_batch_roots.iter().map(|a| (*a, mmr.push(*a).unwrap())).collect(); + Ok(response.into_iter().filter(|e| e.status == "PROCESSED").collect::>()) + } - let leaf_position: Vec<(u64, DeltaUsageHash)> = leaf_position_map - .iter() - .filter(|&(l, _)| l == &batch_root) - .map(|&(ref l, p)| (p, *l)) - .collect(); - let position: Vec = - leaf_position.clone().into_iter().map(|(p, _)| p).collect(); + /// Traverse EHD record. + /// + /// Parameters: + /// - `cluster_id`: cluster id of a cluster + /// - `ehd_id`: EHDId is a concatenated representation of: + /// 1) A 32-byte node public key in hex + /// 2) Starting TCAA id + /// 3) Ending TCAA id + /// - `tree_node_id` - merkle tree node identifier + /// - `tree_levels_count` - merkle tree levels to request + pub(crate) fn fetch_traversed_era_historical_document( + cluster_id: &ClusterId, + ehd_id: EHDId, + tree_node_id: u32, + tree_levels_count: u32, + ) -> Result, Vec> { + let collectors = Self::get_collectors_nodes(cluster_id).map_err(|_| { + log::error!("❌ Error retrieving collectors for cluster {:?}", cluster_id); + vec![OCWError::FailedToFetchDacNodes] + })?; - let proof = mmr - .gen_proof(position) - .map_err(|_| { - vec![OCWError::FailedToCreateMerkleProof { - cluster_id: *cluster_id, - era_id, - }] - })? - .proof_items() - .to_vec(); + for (collector_key, collector_params) in collectors { + if collector_key != ehd_id.1 { + continue; + }; - let batch_proof = MMRProof { proof }; - Ok(Some(( - era_id, - ProviderBatch { - batch_index: index, - payees: nodes_activity_batched[i] - .iter() - .map(|payable_usage| { - let node_key = payable_usage.0.clone(); - let provider_usage = NodeUsage { - transferred_bytes: payable_usage.1.transferred_bytes, - stored_bytes: payable_usage.1.stored_bytes, - number_of_puts: payable_usage.1.number_of_puts, - number_of_gets: payable_usage.1.number_of_gets, - }; - (node_key, provider_usage) - }) - .collect(), - batch_proof, - }, - ))) - } else { - Ok(None) + if let Ok(host) = str::from_utf8(&collector_params.host) { + let base_url = format!("http://{}:{}", host, collector_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + false, // no response signature verification for now + ); + + if let Ok(traversed_ehd) = client.traverse_era_historical_document( + ehd_id.clone(), + tree_node_id, + tree_levels_count, + ) { + // proceed with the first available EHD record for the prototype + return Ok(traversed_ehd); + } else { + log::warn!( + "⚠️ Collector from cluster {:?} is unavailable while fetching EHD record or responded with unexpected body. Key: {:?} Host: {:?}", + cluster_id, + collector_key, + String::from_utf8(collector_params.host) + ); + } + } } + + Err(vec![OCWError::FailedToFetchTraversedEHD]) } - pub(crate) fn prepare_end_rewarding_providers( + /// Traverse PHD record. + /// + /// Parameters: + /// - `cluster_id`: cluster id of a cluster + /// - `phd_id`: PHDId is a concatenated representation of: + /// 1) A 32-byte node public key in hex + /// 2) Starting TCAA id + /// 3) Ending TCAA id + /// - `tree_node_id` - merkle tree node identifier + /// - `tree_levels_count` - merkle tree levels to request + pub(crate) fn fetch_traversed_partial_historical_document( cluster_id: &ClusterId, - ) -> Result, Vec> { - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) - { - if T::PayoutProcessor::get_billing_report_status(cluster_id, era.id) == - PayoutState::RewardingProviders && - T::PayoutProcessor::all_provider_batches_processed(cluster_id, era.id) - { - return Ok(Some(era.id)); + phd_id: PHDId, + tree_node_id: u32, + tree_levels_count: u32, + ) -> Result, Vec> { + let collectors = Self::get_collectors_nodes(cluster_id).map_err(|_| { + log::error!("❌ Error retrieving collectors for cluster {:?}", cluster_id); + vec![OCWError::FailedToFetchDacNodes] + })?; + + for (collector_key, collector_params) in collectors { + if collector_key != phd_id.0 { + continue; + }; + + if let Ok(host) = str::from_utf8(&collector_params.host) { + let base_url = format!("http://{}:{}", host, collector_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + false, // no response signature verification for now + ); + + if let Ok(traversed_phd) = client.traverse_partial_historical_document( + phd_id.clone(), + tree_node_id, + tree_levels_count, + ) { + // proceed with the first available EHD record for the prototype + return Ok(traversed_phd); + } else { + log::warn!( + "⚠️ Collector from cluster {:?} is unavailable while fetching PHD record or responded with unexpected body. Key: {:?} Host: {:?}", + cluster_id, + collector_key, + String::from_utf8(collector_params.host) + ); + } } } - Ok(None) + + Err(vec![OCWError::FailedToFetchTraversedPHD]) } - pub(crate) fn prepare_end_billing_report( + fn fetch_inspection_receipts( cluster_id: &ClusterId, - ) -> Result, Vec> { - if let Some(era) = - Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) - { - if T::PayoutProcessor::get_billing_report_status(cluster_id, era.id) == - PayoutState::ProvidersRewarded - { - return Ok(Some(era.id)); + ehd_id: EHDId, + ) -> Result, OCWError> { + let collectors = Self::get_collectors_nodes(cluster_id) + .map_err(|_| OCWError::FailedToFetchDacNodes)?; + let g_collector = collectors + .iter() + .find(|(key, _)| *key == G_COLLECTOR_KEY) + .cloned() + .expect("G-Collector to be found"); + + if let Ok(host) = str::from_utf8(&g_collector.1.host) { + let base_url = format!("http://{}:{}", host, g_collector.1.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + false, // no response signature verification for now + ); + + if let Ok(res) = client.fetch_grouped_inspection_receipts(ehd_id) { + return Ok(res); } } - Ok(None) - } - - pub(crate) fn derive_delta_usage_key(cluster_id: &ClusterId, era_id: DdcEra) -> Vec { - format!("offchain::activities::{:?}::{:?}", cluster_id, era_id).into_bytes() - } - pub(crate) fn derive_paybale_usage_key(cluster_id: &ClusterId, era_id: DdcEra) -> Vec { - format!("offchain::paybale_usage::{:?}::{:?}", cluster_id, era_id).into_bytes() + Err(OCWError::FailedToFetchInspectionReceipt) } - pub(crate) fn collect_verification_pub_key() -> Result, OCWError> { - let session_verification_keys = >::RuntimeAppPublic::all() - .into_iter() - .enumerate() - .filter_map(|(i, key)| { - let generic_public = >::GenericPublic::from(key); - let public_key: T::Public = generic_public.into(); - let account_id = public_key.clone().into_account(); + /// Send Inspection Receipt. + /// + /// Parameters: + /// - `cluster_id`: cluster id of a cluster + /// - `g_collector`: grouping collector node to save the receipt + /// - `receipt`: inspection receipt + pub(crate) fn send_inspection_receipt( + cluster_id: &ClusterId, + g_collector: &(NodePubKey, StorageNodeParams), + receipt: aggregator_client::json::InspectionReceipt, + ) -> Result<(), OCWError> { + if let Ok(host) = str::from_utf8(&g_collector.1.host) { + let base_url = format!("http://{}:{}", host, g_collector.1.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + false, // no response signature verification for now + ); - if >::get().contains(&account_id) { - let account = Account::new(i, account_id, public_key); - Option::Some(account) + if client.send_inspection_receipt(receipt.clone()).is_ok() { + // proceed with the first available EHD record for the prototype + return Ok(()); } else { - Option::None + log::warn!( + "⚠️ Collector from cluster {:?} is unavailable while fetching EHD record or responded with unexpected body. Key: {:?} Host: {:?}", + cluster_id, + g_collector.0, + String::from_utf8(g_collector.1.host.clone()) + ); } - }) - .collect::>(); - - if session_verification_keys.len() != 1 { - log::error!( - "🚨 Unexpected number of session verification keys is found. Expected: 1, Actual: {:?}", - session_verification_keys.len() - ); - return Err(OCWError::FailedToCollectVerificationKey); } - session_verification_keys - .into_iter() - .next() // first - .ok_or(OCWError::FailedToCollectVerificationKey) + Err(OCWError::FailedToSaveInspectionReceipt) } - pub(crate) fn store_verification_account_id(account_id: T::AccountId) { - let validator: Vec = account_id.encode(); - let key = format!("offchain::validator::{:?}", DAC_VERIFICATION_KEY_TYPE).into_bytes(); - local_storage_set(StorageKind::PERSISTENT, &key, &validator); - } + pub(crate) fn fetch_node_challenge_response( + cluster_id: &ClusterId, + era_id: DdcEra, + collector_key: NodePubKey, + node_key: NodePubKey, + tree_node_ids: Vec, + ) -> Result> { + let collectors = Self::get_collectors_nodes(cluster_id).map_err(|_| { + log::error!("❌ Error retrieving collectors for cluster {:?}", cluster_id); + vec![OCWError::FailedToFetchDacNodes] + })?; - pub(crate) fn fetch_verification_account_id() -> Result { - let key = format!("offchain::validator::{:?}", DAC_VERIFICATION_KEY_TYPE).into_bytes(); + for (key, collector_params) in collectors { + if key != collector_key { + continue; + }; - match local_storage_get(StorageKind::PERSISTENT, &key) { - Some(data) => { - let account_id = T::AccountId::decode(&mut &data[..]) - .map_err(|_| OCWError::FailedToFetchVerificationKey)?; - Ok(account_id) - }, - None => Err(OCWError::FailedToFetchVerificationKey), - } - } + if let Ok(host) = str::from_utf8(&collector_params.host) { + let base_url = format!("http://{}:{}", host, collector_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + false, // no response signature verification for now + ); - #[allow(clippy::too_many_arguments)] - pub(crate) fn store_verified_delta_usage( - cluster_id: &ClusterId, - era_id: DdcEra, - buckets_deltas: &[A], - buckets_deltas_root: DeltaUsageHash, - buckets_deltas_batch_roots: &[DeltaUsageHash], - nodes_deltas: &[B], - nodes_deltas_root: DeltaUsageHash, - nodes_deltas_batch_roots: &[DeltaUsageHash], - ) { - let key = Self::derive_delta_usage_key(cluster_id, era_id); - let encoded_tuple = ( - buckets_deltas, - buckets_deltas_root, - buckets_deltas_batch_roots, - nodes_deltas, - nodes_deltas_root, - nodes_deltas_batch_roots, - ) - .encode(); + if let Ok(node_challenge_res) = client.challenge_node_aggregate( + era_id, + Into::::into(node_key.clone()).as_str(), + tree_node_ids.clone(), + ) { + return Ok(node_challenge_res); + } else { + log::warn!( + "Collector from cluster {:?} is unavailable while fetching EHD record or responded with unexpected body. Key: {:?} Host: {:?}", + cluster_id, + collector_key, + String::from_utf8(collector_params.host) + ); + } + } + } - // Store the serialized data in local offchain storage - local_storage_set(StorageKind::PERSISTENT, &key, &encoded_tuple); + Err(vec![OCWError::FailedToFetchNodeChallenge]) } + } + /* ######## DAC v4 functions ######## */ + impl Pallet { #[allow(clippy::type_complexity)] - pub(crate) fn fetch_verified_delta_usage( + pub(crate) fn _v4_process_era( cluster_id: &ClusterId, - era_id: DdcEra, - ) -> Option<( - Vec, - DeltaUsageHash, - Vec, - Vec, - DeltaUsageHash, - Vec, - )> { + era_activity: EraActivity, + ) -> Result< + Option<( + EraActivity, + DeltaUsageHash, + DeltaUsageHash, + Vec, + Vec, + )>, + Vec, + > { + let batch_size = T::MAX_PAYOUT_BATCH_SIZE; + + let dac_nodes = Self::get_collectors_nodes(cluster_id).map_err(|_| { + log::error!("❌ Error retrieving dac nodes to validate cluster {:?}", cluster_id); + vec![OCWError::FailedToFetchDacNodes] + })?; + log::info!( - "👁️‍🗨️🏠 Off-chain cache hit for Verified Delta in cluster_id: {:?} era_id: {:?}", + "👁️‍🗨️ Start processing DAC for cluster_id: {:?} era_id; {:?}", cluster_id, - era_id + era_activity.id ); - let key = Self::derive_delta_usage_key(cluster_id, era_id); - // Retrieve encoded tuple from local storage - let encoded_tuple = match local_storage_get(StorageKind::PERSISTENT, &key) { - Some(data) => data, - None => return None, - }; + // todo: move to cluster protocol parameters + let dac_redundancy_factor = T::DAC_REDUNDANCY_FACTOR; + let aggregators_quorum = T::AggregatorsQuorum::get(); - // Attempt to decode tuple from bytes - match Decode::decode(&mut &encoded_tuple[..]) { - Ok(( - buckets_deltas, - buckets_deltas_root, - buckets_deltas_batch_roots, - nodes_deltas, - nodes_deltas_root, - nodes_deltas_batch_roots, - )) => Some(( - buckets_deltas, - buckets_deltas_root, - buckets_deltas_batch_roots, - nodes_deltas, - nodes_deltas_root, - nodes_deltas_batch_roots, - )), - Err(err) => { - // Print error message with details of the decoding error - log::error!("Decoding error: {:?}", err); - None - }, - } - } + let nodes_aggregates_by_aggregator = + Self::_v4_fetch_nodes_aggregates_for_era(cluster_id, era_activity.id, &dac_nodes) + .map_err(|err| vec![err])?; - #[allow(clippy::too_many_arguments)] - pub(crate) fn store_payable_usage( - cluster_id: &ClusterId, - era: EraActivity, - payers_usage: Vec, - payers_root: PayableUsageHash, - payers_batch_roots: Vec, - payees_usage: Vec, - payees_root: PayableUsageHash, - payees_batch_roots: Vec, - ) { - let key = Self::derive_paybale_usage_key(cluster_id, era.id); + let buckets_aggregates_by_aggregator = + Self::_v4_fetch_buckets_aggregates_for_era(cluster_id, era_activity.id, &dac_nodes) + .map_err(|err| vec![err])?; - let mut cluster_usage = NodeUsage { - transferred_bytes: 0, - stored_bytes: 0, - number_of_puts: 0, - number_of_gets: 0, - }; + let buckets_sub_aggregates_groups = + Self::_v4_group_buckets_sub_aggregates_by_consistency( + cluster_id, + era_activity.id, + buckets_aggregates_by_aggregator, + dac_redundancy_factor, + aggregators_quorum, + ); + + let total_buckets_usage = Self::_v4_get_total_usage( + cluster_id, + era_activity.id, + buckets_sub_aggregates_groups, + true, + )?; + + let customer_activity_hashes: Vec = + total_buckets_usage.clone().into_iter().map(|c| c.hash::()).collect(); + + let customer_activity_hashes_string: Vec = + customer_activity_hashes.clone().into_iter().map(hex::encode).collect(); + + log::info!( + "👁️‍🗨️ Customer Activity hashes for cluster_id: {:?} era_id: {:?} is: {:?}", + cluster_id, + era_activity.id, + customer_activity_hashes_string + ); + let customers_activity_batch_roots = Self::convert_to_batch_merkle_roots( + cluster_id, + Self::split_to_batches(&total_buckets_usage, batch_size.into()), + era_activity.id, + ) + .map_err(|err| vec![err])?; - for usage in payees_usage.clone() { - cluster_usage.transferred_bytes += usage.1.transferred_bytes; - cluster_usage.stored_bytes += usage.1.stored_bytes; - cluster_usage.number_of_puts += usage.1.number_of_puts; - cluster_usage.number_of_gets += usage.1.number_of_gets; + let customer_batch_roots_string: Vec = + customers_activity_batch_roots.clone().into_iter().map(hex::encode).collect(); + + for (pos, batch_root) in customer_batch_roots_string.iter().enumerate() { + log::info!( + "👁️‍🗨️‍ Customer Activity batches for cluster_id: {:?} era_id: {:?} is: batch {:?} with root {:?} for activities {:?}", + cluster_id, + era_activity.id, + pos + 1, + batch_root, + customer_activity_hashes_string + ); } - let era_paybale_usage = PayableEraUsage { - cluster_id: *cluster_id, - era, - payers_usage, - payers_root, - payers_batch_roots, - payees_usage, - payees_root, - payees_batch_roots, - cluster_usage, - }; - let encoded_era_paybale_usage = era_paybale_usage.encode(); - // Store the serialized data in local offchain storage - local_storage_set(StorageKind::PERSISTENT, &key, &encoded_era_paybale_usage); - } + let customers_activity_root = Self::create_merkle_root( + cluster_id, + &customers_activity_batch_roots, + era_activity.id, + ) + .map_err(|err| vec![err])?; - #[allow(clippy::type_complexity)] - pub(crate) fn fetch_payable_usage( - cluster_id: &ClusterId, - era_id: DdcEra, - ) -> Option { log::info!( - "🪙🏠 Off-chain cache hit for Payable Usage in cluster_id: {:?} era_id: {:?}", + "👁️‍🗨️‍ Customer Activity batches tree for cluster_id: {:?} era_id: {:?} is: batch with root {:?} for activities {:?}", cluster_id, - era_id + era_activity.id, + hex::encode(customers_activity_root), + customer_batch_roots_string, ); - let key = Self::derive_paybale_usage_key(cluster_id, era_id); - let encoded_era_paybale_usage = match local_storage_get(StorageKind::PERSISTENT, &key) { - Some(encoded_data) => encoded_data, - None => return None, - }; + let nodes_aggregates_groups = Self::_v4_group_nodes_aggregates_by_consistency( + cluster_id, + era_activity.id, + nodes_aggregates_by_aggregator, + dac_redundancy_factor, + aggregators_quorum, + ); + + let total_nodes_usage = Self::_v4_get_total_usage( + cluster_id, + era_activity.id, + nodes_aggregates_groups, + true, + )?; + + let node_activity_hashes: Vec = + total_nodes_usage.clone().into_iter().map(|c| c.hash::()).collect(); - match Decode::decode(&mut &encoded_era_paybale_usage[..]) { - Ok(era_paybale_usage) => Some(era_paybale_usage), - Err(err) => { - log::error!("Decoding error: {:?}", err); - None - }, - } - } + let node_activity_hashes_string: Vec = + node_activity_hashes.clone().into_iter().map(hex::encode).collect(); - pub(crate) fn clear_verified_delta_usage(cluster_id: &ClusterId, era_id: DdcEra) { - let key = Self::derive_delta_usage_key(cluster_id, era_id); - log::debug!( - "Clearing validation activities for cluster {:?} at era {:?}, key {:?}", + log::info!( + "👁️‍🗨️ Node Activity hashes for cluster_id: {:?} era_id: {:?} is: {:?}", cluster_id, - era_id, - key, + era_activity.id, + node_activity_hashes_string ); - local_storage_clear(StorageKind::PERSISTENT, &key); - } + let nodes_activity_batch_roots = Self::convert_to_batch_merkle_roots( + cluster_id, + Self::split_to_batches(&total_nodes_usage, batch_size.into()), + era_activity.id, + ) + .map_err(|err| vec![err])?; - pub(crate) fn _store_and_fetch_nonce(node_id: String) -> u64 { - let key = format!("offchain::activities::nonce::{:?}", node_id).into_bytes(); - let encoded_nonce = - local_storage_get(StorageKind::PERSISTENT, &key).unwrap_or_else(|| 0.encode()); + let nodes_activity_batch_roots_string: Vec = + nodes_activity_batch_roots.clone().into_iter().map(hex::encode).collect(); - let nonce_data = match Decode::decode(&mut &encoded_nonce[..]) { - Ok(nonce) => nonce, - Err(err) => { - log::error!("Decoding error while fetching nonce: {:?}", err); - 0 - }, - }; + for (pos, batch_root) in nodes_activity_batch_roots_string.iter().enumerate() { + log::info!( + "👁️‍🗨️ Node Activity batches for cluster_id: {:?} era_id: {:?} are: batch {:?} with root {:?} for activities {:?}", + cluster_id, + era_activity.id, + pos + 1, + batch_root, + node_activity_hashes_string + ); + } - let new_nonce = nonce_data + 1; + let nodes_activity_root = + Self::create_merkle_root(cluster_id, &nodes_activity_batch_roots, era_activity.id) + .map_err(|err| vec![err])?; - local_storage_set(StorageKind::PERSISTENT, &key, &new_nonce.encode()); - nonce_data + log::info!( + "👁️‍🗨️ Node Activity batches tree for cluster_id: {:?} era_id: {:?} are: batch with root {:?} for activities {:?}", + cluster_id, + era_activity.id, + hex::encode(nodes_activity_root), + nodes_activity_batch_roots_string, + ); + + Self::_v4_store_verified_usage( + cluster_id, + era_activity.id, + &total_buckets_usage, + customers_activity_root, + &customers_activity_batch_roots, + &total_nodes_usage, + nodes_activity_root, + &nodes_activity_batch_roots, + ); + log::info!( + "👁️‍🗨️‍ End processing DAC for cluster_id: {:?} era_id: {:?}", + cluster_id, + era_activity.id + ); + Ok(Some(( + era_activity, + customers_activity_root, + nodes_activity_root, + customers_activity_batch_roots, + nodes_activity_batch_roots, + ))) } - /// Converts a vector of hashable batches into their corresponding Merkle roots. - /// - /// This function takes a vector of hashable batches, where each batch is a vector of - /// hashable items. It computes the Merkle root for each batch by first hashing each - /// activity and then combining these hashes into a single Merkle root. - /// - /// # Input Parameters - /// - `batches: Vec>`: A vector of vectors, where each inner vector represents a - /// batch of hashable items.. + /// Fetch node usage of an era. /// - /// # Output - /// - `Vec`: A vector of Merkle roots, one for each batch of items. - pub(crate) fn convert_to_batch_merkle_roots( + /// Parameters: + /// - `cluster_id`: cluster id of a cluster + /// - `era_id`: era id + /// - `node_params`: DAC node parameters + pub(crate) fn _v4_fetch_nodes_aggregates_for_era( cluster_id: &ClusterId, era_id: DdcEra, - batches: Vec>, - ) -> Result, OCWError> { - batches - .into_iter() - .map(|batch| { - let activity_hashes: Vec = - batch.into_iter().map(|a| a.hash::()).collect(); - Self::create_merkle_root(cluster_id, era_id, &activity_hashes).map_err(|_| { - OCWError::FailedToCreateMerkleRoot { cluster_id: *cluster_id, era_id } - }) - }) - .collect::, OCWError>>() - } + dac_nodes: &[(NodePubKey, StorageNodeParams)], + ) -> Result< + Vec<(AggregatorInfo, Vec)>, + OCWError, + > { + let mut nodes_aggregates = Vec::new(); - /// Splits a slice of activities into batches of a specified size. - /// - /// This function sorts the given activities and splits them into batches of the specified - /// size. Each batch is returned as a separate vector. - /// - /// # Input Parameters - /// - `activities: &[A]`: A slice of activities to be split into batches. - /// - `batch_size: usize`: The size of each batch. - /// - /// # Output - /// - `Vec>`: A vector of vectors, where each inner vector is a batch of activities. - pub(crate) fn split_to_batches( - activities: &[A], - batch_size: usize, - ) -> Vec> { - if activities.is_empty() { - return vec![]; + for (node_key, node_params) in dac_nodes { + let aggregates_res = + Self::_v4_fetch_node_aggregates(cluster_id, era_id, node_params); + if aggregates_res.is_err() { + log::warn!( + "Aggregator from cluster {:?} is unavailable while fetching nodes aggregates. Key: {:?} Host: {:?}", + cluster_id, + node_key, + String::from_utf8(node_params.host.clone()) + ); + // skip unavailable aggregators and continue with available ones + continue; + } + + let aggregates = aggregates_res.expect("Nodes Aggregates Response to be available"); + + nodes_aggregates.push(( + AggregatorInfo { + node_pub_key: node_key.clone(), + node_params: node_params.clone(), + }, + aggregates, + )); } - // Sort the activities first - let mut sorted_activities = activities.to_vec(); - sorted_activities.sort(); // Sort using the derived Ord trait - // Split the sorted activities into chunks and collect them into vectors - sorted_activities.chunks(batch_size).map(|chunk| chunk.to_vec()).collect() + Ok(nodes_aggregates) } - /// Creates a Merkle root from a list of hashes. - /// - /// This function takes a slice of `H256` and constructs a Merkle tree - /// using an in-memory store. It returns a tuple containing the Merkle root hash, - /// the size of the Merkle tree, and a vector mapping each input leaf to its position - /// in the Merkle tree. - /// - /// # Input Parameters - /// - /// * `leaves` - A slice of `H256` representing the leaves of the Merkle tree. - /// - /// # Output + /// Fetch customer usage for an era. /// - /// A `Result` containing: - /// * A tuple with the Merkle root `H256`, the size of the Merkle tree, and a vector mapping - /// each input leaf to its position in the Merkle tree. - /// * `OCWError::FailedToCreateMerkleRoot` if there is an error creating the Merkle root. - pub(crate) fn create_merkle_root( + /// Parameters: + /// - `cluster_id`: cluster id of a cluster + /// - `era_id`: era id + /// - `node_params`: DAC node parameters + pub(crate) fn _v4_fetch_buckets_aggregates_for_era( cluster_id: &ClusterId, era_id: DdcEra, - leaves: &[H256], - ) -> Result { - if leaves.is_empty() { - return Ok(H256::default()); - } - - let store = MemStore::default(); - let mut mmr: MMR> = - MemMMR::<_, MergeMMRHash>::new(0, &store); - - let mut leaves_with_position: Vec<(u64, H256)> = Vec::with_capacity(leaves.len()); + dac_nodes: &[(NodePubKey, StorageNodeParams)], + ) -> Result< + Vec<(AggregatorInfo, Vec)>, + OCWError, + > { + let mut bucket_aggregates: Vec<( + AggregatorInfo, + Vec, + )> = Vec::new(); - for &leaf in leaves { - match mmr.push(leaf) { - Ok(pos) => leaves_with_position.push((pos, leaf)), - Err(_) => - return Err(OCWError::FailedToCreateMerkleRoot { - cluster_id: *cluster_id, - era_id, - }), + for (node_key, node_params) in dac_nodes { + let aggregates_res = + Self::_v4_fetch_bucket_aggregates(cluster_id, era_id, node_params); + if aggregates_res.is_err() { + log::warn!( + "Aggregator from cluster {:?} is unavailable while fetching buckets aggregates. Key: {:?} Host: {:?}", + cluster_id, + node_key, + String::from_utf8(node_params.host.clone()) + ); + // skip unavailable aggregators and continue with available ones + continue; } - } - mmr.get_root() - .map_err(|_| OCWError::FailedToCreateMerkleRoot { cluster_id: *cluster_id, era_id }) - } + let aggregates = + aggregates_res.expect("Buckets Aggregates Response to be available"); - pub(crate) fn get_era_for_payout( - cluster_id: &ClusterId, - status: EraValidationStatus, - ) -> Option { - let mut smallest_era_id: Option = None; - let mut start_era: i64 = Default::default(); - let mut end_era: i64 = Default::default(); - - for (stored_cluster_id, era_id, validation) in EraValidations::::iter() { - if stored_cluster_id == *cluster_id && - validation.status == status && - (smallest_era_id.is_none() || era_id < smallest_era_id.unwrap()) - { - smallest_era_id = Some(era_id); - start_era = validation.start_era; - end_era = validation.end_era; - } + bucket_aggregates.push(( + AggregatorInfo { + node_pub_key: node_key.clone(), + node_params: node_params.clone(), + }, + aggregates, + )); } - smallest_era_id.map(|era_id| EraActivity { id: era_id, start: start_era, end: end_era }) + Ok(bucket_aggregates) } - /// Retrieves the last era in which the specified validator participated for a given - /// cluster. + /// Computes the consensus for a set of partial activities across multiple buckets within a + /// given cluster and era. /// - /// This function iterates through all eras in `EraValidations` for the given `cluster_id`, - /// filtering for eras where the specified `validator` is present in the validators list. - /// It returns the maximum era found where the validator participated. + /// This function collects activities from various buckets, groups them by their consensus + /// ID, and then determines if a consensus is reached for each group based on the minimum + /// number of nodes and a given threshold. If the consensus is reached, the activity is + /// included in the result. Otherwise, appropriate errors are returned. /// /// # Input Parameters - /// - `cluster_id: &ClusterId`: The ID of the cluster to check for the validator's - /// participation. - /// - `validator: T::AccountId`: The account ID of the validator whose participation is - /// being checked. - /// - /// # Output - /// - `Result, OCWError>`: - /// - `Ok(Some(DdcEra))`: The maximum era in which the validator participated. - /// - `Ok(None)`: The validator did not participate in any era for the given cluster. - /// - `Err(OCWError)`: An error occurred while retrieving the data. - // todo! add tests for start and end era - pub(crate) fn get_last_paid_era( - cluster_id: &ClusterId, - validator: T::AccountId, - ) -> Result, OCWError> { - let mut max_era: Option = None; - - // Iterate through all eras in EraValidations for the given cluster_id - >::iter_prefix(cluster_id) - .filter_map(|(era, validation)| { - // Filter for validators that contain the given validator - if validation - .validators - .values() - .any(|validators| validators.contains(&validator)) - { - Some(era) - } else { - None - } - }) - .for_each(|era| { - // Update max_era to the maximum era found - if let Some(current_max) = max_era { - if era > current_max { - max_era = Some(era); - } - } else { - max_era = Some(era); - } - }); - - Ok(max_era) - } - - /// Fetch current era across all DAC nodes to validate. + /// - `cluster_id: &ClusterId`: The ID of the cluster for which consensus is being computed. + /// - `era_id: DdcEra`: The era ID within the cluster. + /// - `buckets_aggregates_by_aggregator: &[(NodePubKey, Vec)]`: A list of tuples, where + /// each tuple contains a node's public key and a vector of activities reported for that + /// bucket. + /// - `redundancy_factor: u16`: The number of aggregators that should report total activity + /// for a node or a bucket + /// - `quorum: Percent`: The threshold percentage that determines if an activity is in + /// consensus. /// - /// Parameters: - /// - `cluster_id`: cluster id of a cluster - /// - `dac_nodes`: List of DAC nodes - pub(crate) fn get_era_for_validation( + /// # Output + /// - `Result, Vec>`: + /// - `Ok(Vec)`: A vector of activities that have reached consensus. + /// - `Err(Vec)`: A vector of errors indicating why consensus was not reached + /// for some activities. + pub(crate) fn _v4_group_buckets_sub_aggregates_by_consistency( cluster_id: &ClusterId, - dac_nodes: &[(NodePubKey, StorageNodeParams)], - ) -> Result, OCWError> { - let this_validator = Self::fetch_verification_account_id()?; - - let last_validated_era_by_this_validator = - Self::get_last_paid_era(cluster_id, this_validator)? - .unwrap_or_else(DdcEra::default); - - let last_paid_era_for_cluster = - T::ClusterValidator::get_last_paid_era(cluster_id).map_err(|_| { - OCWError::EraRetrievalError { cluster_id: *cluster_id, node_pub_key: None } - })?; + era_id: DdcEra, + buckets_aggregates_by_aggregator: Vec<( + AggregatorInfo, + Vec, + )>, + redundancy_factor: u16, + quorum: Percent, + ) -> ConsistencyGroups { + let mut buckets_sub_aggregates: Vec = + Vec::new(); log::info!( - "👁️‍🗨️ The last era validated by this specific validator for cluster_id: {:?} is {:?}. The last paid era for the cluster is {:?}", + "👁️‍🗨️‍ Starting fetching bucket sub-aggregates for cluster_id: {:?} for era_id: {:?}", cluster_id, - last_validated_era_by_this_validator, - last_paid_era_for_cluster + era_id ); + for (aggregator_info, buckets_aggregates_resp) in + buckets_aggregates_by_aggregator.clone() + { + for bucket_aggregate_resp in buckets_aggregates_resp { + for bucket_sub_aggregate_resp in bucket_aggregate_resp.sub_aggregates.clone() { + let bucket_sub_aggregate = aggregator_client::json::BucketSubAggregate { + bucket_id: bucket_aggregate_resp.bucket_id, + node_id: bucket_sub_aggregate_resp.NodeID, + stored_bytes: bucket_sub_aggregate_resp.stored_bytes, + transferred_bytes: bucket_sub_aggregate_resp.transferred_bytes, + number_of_puts: bucket_sub_aggregate_resp.number_of_puts, + number_of_gets: bucket_sub_aggregate_resp.number_of_gets, + aggregator: aggregator_info.clone(), + }; - // we want to fetch processed eras from all available validators - let available_processed_eras = - Self::fetch_processed_era_for_nodes(cluster_id, dac_nodes)?; - - // we want to let the current validator to validate available processed/completed eras - // that are greater than the last validated era in the cluster - let processed_eras_to_validate: Vec = available_processed_eras - .iter() - .flat_map(|eras| { - eras.iter() - .filter(|&ids| { - ids.id > last_validated_era_by_this_validator && - ids.id > last_paid_era_for_cluster - }) - .cloned() - }) - .sorted() - .collect::>(); - - // we want to process only eras reported by quorum of validators - let mut processed_eras_with_quorum: Vec = vec![]; + buckets_sub_aggregates.push(bucket_sub_aggregate); + } - let quorum = T::AggregatorsQuorum::get(); - let threshold = quorum * dac_nodes.len(); - for (era_key, candidates) in - &processed_eras_to_validate.into_iter().chunk_by(|elt| *elt) - { - let count = candidates.count(); - if count >= threshold { - processed_eras_with_quorum.push(era_key); - } else { - log::warn!( - "⚠️ Era {:?} in cluster_id: {:?} has been reported with unmet quorum. Desired: {:?} Actual: {:?}", - era_key, - cluster_id, - threshold, - count - ); + log::info!("👁️‍🗨️‍ Fetched Bucket sub-aggregates for cluster_id: {:?} for era_id: {:?} for bucket_id {:?}::: Bucket Sub-Aggregates are {:?}", cluster_id, era_id, bucket_aggregate_resp.bucket_id, bucket_aggregate_resp.sub_aggregates); } } - Ok(processed_eras_with_quorum.iter().cloned().min_by_key(|n| n.id)) + let buckets_sub_aggregates_groups = + Self::_v4_group_by_consistency(buckets_sub_aggregates, redundancy_factor, quorum); + + log::info!("👁️‍🗨️‍🌕 Bucket Sub-Aggregates, which are in consensus for cluster_id: {:?} for era_id: {:?}::: {:?}", cluster_id, era_id, buckets_sub_aggregates_groups.consensus); + log::info!("👁️‍🗨️‍🌗 Bucket Sub-Aggregates, which are in quorum for cluster_id: {:?} for era_id: {:?}::: {:?}", cluster_id, era_id, buckets_sub_aggregates_groups.quorum); + log::info!("👁️‍🗨️‍🌘 Bucket Sub-Aggregates, which are neither in consensus nor in quorum for cluster_id: {:?} for era_id: {:?}::: {:?}", cluster_id, era_id, buckets_sub_aggregates_groups.others); + + buckets_sub_aggregates_groups } /// Computes the consensus for a set of activities across multiple nodes within a given @@ -2989,7 +3794,7 @@ pub mod pallet { /// - `Ok(Vec)`: A vector of activities that have reached consensus. /// - `Err(Vec)`: A vector of errors indicating why consensus was not reached /// for some activities. - pub(crate) fn group_nodes_aggregates_by_consistency( + pub(crate) fn _v4_group_nodes_aggregates_by_consistency( cluster_id: &ClusterId, era_id: DdcEra, nodes_aggregates_by_aggregator: Vec<( @@ -3024,7 +3829,7 @@ pub mod pallet { } let nodes_aggregates_groups = - Self::group_by_consistency(nodes_aggregates, redundancy_factor, quorum); + Self::_v4_group_by_consistency(nodes_aggregates, redundancy_factor, quorum); log::info!("👁️‍🗨️‍🌕 Node Aggregates, which are in consensus for cluster_id: {:?} for era_id: {:?}::: {:?}", cluster_id, era_id, nodes_aggregates_groups.consensus); log::info!("👁️‍🗨️‍🌗 Node Aggregates, which are in quorum for cluster_id: {:?} for era_id: {:?}::: {:?}", cluster_id, era_id, nodes_aggregates_groups.quorum); @@ -3033,1179 +3838,758 @@ pub mod pallet { nodes_aggregates_groups } - pub(crate) fn group_by_consistency( - aggregates: Vec, - redundancy_factor: u16, - quorum: Percent, - ) -> ConsistencyGroups - where - A: Aggregate + Clone, - { - let mut consistent_aggregates: BTreeMap> = BTreeMap::new(); - - for aggregate in aggregates.iter() { - consistent_aggregates - .entry(aggregate.hash::()) - .or_default() - .push(aggregate.clone()); - } - - let mut consensus_group = Vec::new(); - let mut quorum_group = Vec::new(); - let mut others_group = Vec::new(); + pub(crate) fn _v4_get_total_usage( + cluster_id: &ClusterId, + era_id: DdcEra, + consistency_groups: ConsistencyGroups, + should_challenge: bool, + ) -> Result, Vec> { + let mut total_usage = vec![]; + let mut total_usage_keys = vec![]; - let max_aggregates_count = redundancy_factor; - let quorum_threshold = quorum * max_aggregates_count; + // todo: implement 'challenge_consensus' fn and run a light challenge for unanimous + // consensus + let in_consensus_usage = consistency_groups + .consensus + .clone() + .into_iter() + .map(|ca| ca.aggregate.clone()) + .collect::>(); + total_usage.extend(in_consensus_usage.clone()); + total_usage_keys + .extend(in_consensus_usage.into_iter().map(|a| a.get_key()).collect::>()); - for (_hash, group) in consistent_aggregates { - let aggregate = group.first().unwrap(); - let aggregates_count = u16::try_from(group.len()).unwrap_or(u16::MAX); - let aggregators: Vec = - group.clone().into_iter().map(|a| a.get_aggregator()).collect(); + // todo: implement 'challenge_quorum' fn and run a light challenge for the quorum, i.e. + // for majority + let in_quorum_usage = consistency_groups + .quorum + .clone() + .into_iter() + .map(|ca| ca.aggregate.clone()) + .collect::>(); + total_usage.extend(in_quorum_usage.clone()); + total_usage_keys + .extend(in_quorum_usage.into_iter().map(|a| a.get_key()).collect::>()); - let consolidated_aggregate = ConsolidatedAggregate::::new( - aggregate.clone(), - aggregates_count, - aggregators, - ); + let verified_usage = Self::_v4_challenge_others( + cluster_id, + era_id, + consistency_groups, + &mut total_usage_keys, + should_challenge, + )?; - if aggregates_count == max_aggregates_count { - consensus_group.push(consolidated_aggregate); - } else if aggregates_count >= quorum_threshold { - quorum_group.push(consolidated_aggregate); - } else { - others_group.push(consolidated_aggregate); - } + if !verified_usage.is_empty() { + total_usage.extend(verified_usage.clone()); } - ConsistencyGroups { - consensus: consensus_group, - quorum: quorum_group, - others: others_group, - } + Ok(total_usage) } - /// Fetch Challenge node aggregate or bucket sub-aggregate. - pub(crate) fn _fetch_challenge_responses( - cluster_id: &ClusterId, - era_id: DdcEra, - aggregate_key: AggregateKey, - merkle_node_identifiers: Vec, - aggregator: AggregatorInfo, - ) -> Result { - let response = Self::_fetch_challenge_response( - era_id, - aggregate_key.clone(), - merkle_node_identifiers.clone(), - &aggregator.node_params, - ) - .map_err(|_| OCWError::ChallengeResponseRetrievalError { - cluster_id: *cluster_id, - era_id, - aggregate_key, - aggregator: aggregator.node_pub_key, - })?; + pub(crate) fn _v4_challenge_others( + _cluster_id: &ClusterId, + _era_id: DdcEra, + consistency_groups: ConsistencyGroups, + accepted_keys: &mut Vec, + should_challenge: bool, + ) -> Result, Vec> { + let redundancy_factor = T::DAC_REDUNDANCY_FACTOR; + let mut verified_usage: Vec = vec![]; - Ok(response) - } + for consolidated_aggregate in consistency_groups.others { + let aggregate_key = consolidated_aggregate.aggregate.get_key(); - /// Challenge node aggregate or bucket sub-aggregate. - pub(crate) fn _fetch_challenge_responses_proto( - cluster_id: &ClusterId, - era_id: DdcEra, - aggregate_key: AggregateKey, - merkle_tree_node_id: Vec, - aggregator: AggregatorInfo, - ) -> Result { - let response = Self::_fetch_challenge_response_proto( - era_id, - aggregate_key.clone(), - merkle_tree_node_id.clone(), - &aggregator.node_params, - ) - .map_err(|_| OCWError::ChallengeResponseRetrievalError { - cluster_id: *cluster_id, - era_id, - aggregate_key, - aggregator: aggregator.node_pub_key, - })?; + if accepted_keys.contains(&aggregate_key) { + log::warn!( + "⚠️ The aggregate {:?} is inconsistent between aggregators.", + aggregate_key + ); - Ok(response) - } + // This prevents the double spending in case of inconsistencies between + // aggregators for the same aggregation key + continue; + } - /// Fetch challenge response. - /// - /// Parameters: - /// - `era_id`: era id - /// - `aggregate_key`: key of the aggregate to challenge - /// - `merkle_node_identifiers`: set of merkle node identifiers to challenge - /// - `node_params`: aggregator node parameters - pub(crate) fn _fetch_challenge_response( - era_id: DdcEra, - aggregate_key: AggregateKey, - merkle_node_identifiers: Vec, - node_params: &StorageNodeParams, - ) -> Result { - let scheme = "http"; - let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; + if consolidated_aggregate.count > redundancy_factor { + let excessive_aggregate = consolidated_aggregate.aggregate.clone(); - let ids = merkle_node_identifiers - .iter() - .map(|x| format!("{}", x.clone())) - .collect::>() - .join(","); + log::warn!( + "⚠️ Number of consistent aggregates with key {:?} exceeds the redundancy factor", + aggregate_key + ); - let url = match aggregate_key { - AggregateKey::NodeAggregateKey(node_id) => format!( - "{}://{}:{}/activity/nodes/{}/challenge?eraId={}&merkleTreeNodeId={}", - scheme, host, node_params.http_port, node_id, era_id, ids - ), - AggregateKey::BucketSubAggregateKey(bucket_id, node_id) => format!( - "{}://{}:{}/activity/buckets/{}/challenge?eraId={}&nodeId={}&merkleTreeNodeId={}", - scheme, host, node_params.http_port, bucket_id, era_id, node_id, ids - ), - }; + log::info!( + "🔎‍ Challenging excessive aggregate with key {:?} and hash {:?}", + aggregate_key, + excessive_aggregate.hash::() + ); + + // todo: run a challenge dedicated to the excessive number of aggregates. + // we assume it won't happen at the moment, so we just take the aggregate to + // payouts stage + verified_usage.push(excessive_aggregate); + accepted_keys.push(aggregate_key); + } else { + let defective_aggregate = consolidated_aggregate.aggregate.clone(); - let request = http::Request::get(&url); - let timeout = sp_io::offchain::timestamp() - .add(sp_runtime::offchain::Duration::from_millis(RESPONSE_TIMEOUT)); - let pending = request.deadline(timeout).send().map_err(|_| http::Error::IoError)?; + log::info!( + "🔎‍ Challenging defective aggregate with key {:?} and hash {:?}", + aggregate_key, + defective_aggregate.hash::() + ); - let response = - pending.try_wait(timeout).map_err(|_| http::Error::DeadlineReached)??; - if response.code != _SUCCESS_CODE { - return Err(http::Error::Unknown); + let mut is_passed = true; + // todo: run an intensive challenge for deviating aggregate + // let is_passed = Self::_v4_challenge_aggregate(_cluster_id, _era_id, + // &defective_aggregate)?; + if should_challenge { + is_passed = Self::_v4_challenge_aggregate_proto( + _cluster_id, + _era_id, + &defective_aggregate, + )?; + } + if is_passed { + // we assume all aggregates are valid at the moment, so we just take the + // aggregate to payouts stage + verified_usage.push(defective_aggregate); + accepted_keys.push(aggregate_key); + } + } } - let body = response.body().collect::>(); - serde_json::from_slice(&body).map_err(|_| http::Error::Unknown) + Ok(verified_usage) } - /// Fetch protobuf challenge response. - pub(crate) fn _fetch_challenge_response_proto( - era_id: DdcEra, - aggregate_key: AggregateKey, - merkle_tree_node_id: Vec, - node_params: &StorageNodeParams, - ) -> Result { - let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; - let base_url = format!("http://{}:{}", host, node_params.http_port); - let client = aggregator_client::AggregatorClient::new( - &base_url, - Duration::from_millis(RESPONSE_TIMEOUT), - 3, - T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, - ); + pub(crate) fn _v4_group_by_consistency( + aggregates: Vec, + redundancy_factor: u16, + quorum: Percent, + ) -> ConsistencyGroups + where + A: Aggregate + Clone, + { + let mut consistent_aggregates: BTreeMap> = BTreeMap::new(); - match aggregate_key { - AggregateKey::BucketSubAggregateKey(bucket_id, node_id) => client - .challenge_bucket_sub_aggregate( - era_id, - bucket_id, - &node_id, - merkle_tree_node_id, - ), - AggregateKey::NodeAggregateKey(node_id) => - client.challenge_node_aggregate(era_id, &node_id, merkle_tree_node_id), + for aggregate in aggregates.iter() { + consistent_aggregates + .entry(aggregate.hash::()) + .or_default() + .push(aggregate.clone()); } - } - /// Fetch traverse response. - /// - /// Parameters: - /// - `era_id`: era id - /// - `aggregate_key`: key of the aggregate to challenge - /// - `merkle_node_identifiers`: set of merkle node identifiers to challenge - /// - `levels`: a number of levels to raverse - /// - `node_params`: aggregator node parameters - pub(crate) fn _fetch_traverse_response( - era_id: DdcEra, - aggregate_key: AggregateKey, - merkle_tree_node_id: u32, - levels: u16, - node_params: &StorageNodeParams, - ) -> Result { - let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; - let base_url = format!("http://{}:{}", host, node_params.http_port); - let client = aggregator_client::AggregatorClient::new( - &base_url, - Duration::from_millis(RESPONSE_TIMEOUT), - 3, - T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, - ); + let mut consensus_group = Vec::new(); + let mut quorum_group = Vec::new(); + let mut others_group = Vec::new(); - let response = match aggregate_key { - AggregateKey::BucketSubAggregateKey(bucket_id, node_id) => client - .traverse_bucket_sub_aggregate( - era_id, - bucket_id, - &node_id, - merkle_tree_node_id, - levels, - ), - AggregateKey::NodeAggregateKey(node_id) => - client.traverse_node_aggregate(era_id, &node_id, merkle_tree_node_id, levels), - }?; + let max_aggregates_count = redundancy_factor; + let quorum_threshold = quorum * max_aggregates_count; - Ok(response) - } + for (_hash, group) in consistent_aggregates { + let aggregate = group.first().unwrap(); + let aggregates_count = u16::try_from(group.len()).unwrap_or(u16::MAX); + let aggregators: Vec = + group.clone().into_iter().map(|a| a.get_aggregator()).collect(); - /// Fetch processed era. - /// - /// Parameters: - /// - `node_params`: DAC node parameters - #[allow(dead_code)] - pub(crate) fn fetch_processed_eras( - node_params: &StorageNodeParams, - ) -> Result, http::Error> { - let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; - let base_url = format!("http://{}:{}", host, node_params.http_port); - let client = aggregator_client::AggregatorClient::new( - &base_url, - Duration::from_millis(RESPONSE_TIMEOUT), - 3, - T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, - ); + let consolidated_aggregate = ConsolidatedAggregate::::new( + aggregate.clone(), + aggregates_count, + aggregators, + ); - let response = client.eras()?; + if aggregates_count == max_aggregates_count { + consensus_group.push(consolidated_aggregate); + } else if aggregates_count >= quorum_threshold { + quorum_group.push(consolidated_aggregate); + } else { + others_group.push(consolidated_aggregate); + } + } - Ok(response.into_iter().filter(|e| e.status == "PROCESSED").collect::>()) + ConsistencyGroups { + consensus: consensus_group, + quorum: quorum_group, + others: others_group, + } } - /// Fetch customer usage. - /// - /// Parameters: - /// - `cluster_id`: cluster id of a cluster - /// - `era_id`: era id - /// - `node_params`: DAC node parameters - pub(crate) fn fetch_bucket_aggregates( - _cluster_id: &ClusterId, - era_id: DdcEra, - node_params: &StorageNodeParams, - ) -> Result, http::Error> { - let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; - let base_url = format!("http://{}:{}", host, node_params.http_port); - let client = aggregator_client::AggregatorClient::new( - &base_url, - Duration::from_millis(RESPONSE_TIMEOUT), - 3, - T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, - ); - let mut buckets_aggregates = Vec::new(); - let mut prev_token = None; + pub(crate) fn _v4_challenge_aggregate( + cluster_id: &ClusterId, + era_id: DdcEra, + aggregate: &A, + ) -> Result> { + let number_of_identifiers = T::MAX_MERKLE_NODE_IDENTIFIER; - loop { - let response = client.buckets_aggregates( - era_id, - Some(BUCKETS_AGGREGATES_FETCH_BATCH_SIZE as u32), - prev_token, - )?; + log::info!( + "👁️‍🗨️ Challenge process starts when bucket sub aggregates are not in consensus!" + ); - let response_len = response.len(); - prev_token = response.last().map(|a| a.bucket_id); + let aggregate_key = aggregate.get_key(); + let merkle_node_ids = Self::_v4_find_random_merkle_node_ids( + number_of_identifiers.into(), + aggregate.get_number_of_leaves(), + aggregate_key.clone(), + ); - buckets_aggregates.extend(response); + log::info!( + "👁️‍🗨️ Merkle Node Identifiers for aggregate key: {:?} identifiers: {:?}", + aggregate_key, + merkle_node_ids + ); - if response_len < BUCKETS_AGGREGATES_FETCH_BATCH_SIZE { - break; - } - } + let aggregator = aggregate.get_aggregator(); - Ok(buckets_aggregates) - } + let challenge_response = Self::_v4_fetch_challenge_responses( + cluster_id, + era_id, + aggregate_key.clone(), + merkle_node_ids, + aggregator.clone(), + ) + .map_err(|err| vec![err])?; - /// Fetch node usage. - /// - /// Parameters: - /// - `cluster_id`: cluster id of a cluster - /// - `era_id`: era id - /// - `node_params`: DAC node parameters - pub(crate) fn fetch_node_aggregates( - _cluster_id: &ClusterId, - era_id: DdcEra, - node_params: &StorageNodeParams, - ) -> Result, http::Error> { - let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; - let base_url = format!("http://{}:{}", host, node_params.http_port); - let client = aggregator_client::AggregatorClient::new( - &base_url, - Duration::from_millis(RESPONSE_TIMEOUT), - 3, - T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, + log::info!( + "👁️‍🗨️ Fetched challenge response for aggregate key: {:?}, challenge_response: {:?}", + aggregate_key, + challenge_response ); - let mut nodes_aggregates = Vec::new(); - let mut prev_token = None; - - loop { - let response = client.nodes_aggregates( - era_id, - Some(NODES_AGGREGATES_FETCH_BATCH_SIZE as u32), - prev_token, - )?; - - let response_len = response.len(); - prev_token = response.last().map(|a| a.node_id.clone()); + let calculated_merkle_root = Self::_v4_get_hash_from_merkle_path( + challenge_response, + cluster_id, + era_id, + aggregate_key.clone(), + )?; - nodes_aggregates.extend(response); + log::info!( + "👁️‍🗨️ Calculated merkle root for aggregate key: {:?}, calculated_merkle_root: {:?}", + aggregate_key, + calculated_merkle_root + ); - if response_len < NODES_AGGREGATES_FETCH_BATCH_SIZE { - break; - } - } + let root_merkle_node = Self::_v4_fetch_traverse_response( + era_id, + aggregate_key.clone(), + 1, + 1, + &aggregator.node_params, + ) + .map_err(|_| { + vec![OCWError::TraverseResponseRetrievalError { + cluster_id: *cluster_id, + era_id, + aggregate_key: aggregate_key.clone(), + aggregator: aggregator.node_pub_key, + }] + })?; - Ok(nodes_aggregates) - } + let mut merkle_root_buf = [0u8; _BUF_SIZE]; + let bytes = + Base64::decode(root_merkle_node.hash.clone(), &mut merkle_root_buf).unwrap(); // todo! remove unwrap + let traversed_merkle_root = DeltaUsageHash::from(sp_core::H256::from_slice(bytes)); - /// Fetch DAC nodes of a cluster. - /// Parameters: - /// - `cluster_id`: Cluster id of a cluster. - fn get_dac_nodes( - cluster_id: &ClusterId, - ) -> Result, Error> { - let mut dac_nodes = Vec::new(); + log::info!( + "👁️‍🗨️ Fetched merkle root for aggregate key: {:?} traversed_merkle_root: {:?}", + aggregate_key, + traversed_merkle_root + ); - let nodes = T::ClusterManager::get_nodes(cluster_id) - .map_err(|_| Error::::NodeRetrievalError)?; + let is_matched = if calculated_merkle_root == traversed_merkle_root { + log::info!( + "👁️‍🗨️👍 The aggregate with hash {:?} and key {:?} has passed the challenge.", + aggregate.hash::(), + aggregate_key, + ); - // Iterate over each node - for node_pub_key in nodes { - // Get the node parameters - if let Ok(NodeParams::StorageParams(storage_params)) = - T::NodeManager::get_node_params(&node_pub_key) - { - log::info!( - "🏭 Obtained DAC Node for cluster_id: {:?} and with key: {:?}", - cluster_id, - node_pub_key.get_hex() - ); + true + } else { + log::info!( + "👁️‍🗨️👎 The aggregate with hash {:?} and key {:?} has not passed the challenge.", + aggregate.hash::(), + aggregate_key, + ); - // Add to the results if the mode matches - dac_nodes.push((node_pub_key, storage_params)); - } - } + false + }; - Ok(dac_nodes) + Ok(is_matched) } - /// Fetch node usage of an era. - /// - /// Parameters: - /// - `cluster_id`: cluster id of a cluster - /// - `era_id`: era id - /// - `node_params`: DAC node parameters - pub(crate) fn fetch_nodes_aggregates_for_era( + pub(crate) fn _v4_challenge_aggregate_proto( cluster_id: &ClusterId, era_id: DdcEra, - dac_nodes: &[(NodePubKey, StorageNodeParams)], - ) -> Result< - Vec<(AggregatorInfo, Vec)>, - OCWError, - > { - let mut nodes_aggregates = Vec::new(); + aggregate: &A, + ) -> Result> { + let number_of_identifiers = T::MAX_MERKLE_NODE_IDENTIFIER; - for (node_key, node_params) in dac_nodes { - let aggregates_res = Self::fetch_node_aggregates(cluster_id, era_id, node_params); - if aggregates_res.is_err() { - log::warn!( - "Aggregator from cluster {:?} is unavailable while fetching nodes aggregates. Key: {:?} Host: {:?}", - cluster_id, - node_key.get_hex(), - String::from_utf8(node_params.host.clone()) - ); - // skip unavailable aggregators and continue with available ones - continue; - } + log::info!( + "👁️‍🗨️ Challenge process starts when bucket sub aggregates are not in consensus!" + ); - let aggregates = aggregates_res.expect("Nodes Aggregates Response to be available"); + let aggregate_key = aggregate.get_key(); + let merkle_node_ids = Self::_v4_find_random_merkle_node_ids( + number_of_identifiers.into(), + aggregate.get_number_of_leaves(), + aggregate_key.clone(), + ); - nodes_aggregates.push(( - AggregatorInfo { - node_pub_key: node_key.clone(), - node_params: node_params.clone(), - }, - aggregates, - )); - } + log::info!( + "👁️‍🗨️ Merkle Node Identifiers for aggregate key: {:?} identifiers: {:?}", + aggregate_key, + merkle_node_ids + ); - Ok(nodes_aggregates) - } + let aggregator = aggregate.get_aggregator(); - /// Fetch customer usage for an era. - /// - /// Parameters: - /// - `cluster_id`: cluster id of a cluster - /// - `era_id`: era id - /// - `node_params`: DAC node parameters - pub(crate) fn fetch_buckets_aggregates_for_era( - cluster_id: &ClusterId, - era_id: DdcEra, - dac_nodes: &[(NodePubKey, StorageNodeParams)], - ) -> Result< - Vec<(AggregatorInfo, Vec)>, - OCWError, - > { - let mut bucket_aggregates: Vec<( - AggregatorInfo, - Vec, - )> = Vec::new(); + let challenge_response = Self::_v4_fetch_challenge_responses_proto( + cluster_id, + era_id, + aggregate_key.clone(), + merkle_node_ids, + aggregator.clone(), + ) + .map_err(|err| vec![err])?; - for (node_key, node_params) in dac_nodes { - let aggregates_res = Self::fetch_bucket_aggregates(cluster_id, era_id, node_params); - if aggregates_res.is_err() { - log::warn!( - "Aggregator from cluster {:?} is unavailable while fetching buckets aggregates. Key: {:?} Host: {:?}", - cluster_id, - node_key.get_hex(), - String::from_utf8(node_params.host.clone()) - ); - // skip unavailable aggregators and continue with available ones - continue; - } + log::info!( + "👁️‍🗨️ Fetched challenge response for aggregate key: {:?}, challenge_response: {:?}", + aggregate_key, + challenge_response + ); - let aggregates = - aggregates_res.expect("Buckets Aggregates Response to be available"); + let are_signatures_valid = signature::Verify::verify(&challenge_response); - bucket_aggregates.push(( - AggregatorInfo { - node_pub_key: node_key.clone(), - node_params: node_params.clone(), - }, - aggregates, - )); + if are_signatures_valid { + log::info!("👍 Valid challenge signatures for aggregate key: {:?}", aggregate_key,); + } else { + log::info!("👎 Invalid challenge signatures at aggregate key: {:?}", aggregate_key,); } - Ok(bucket_aggregates) + Ok(are_signatures_valid) } - /// Fetch processed era for across all nodes. - /// - /// Parameters: - /// - `cluster_id`: Cluster id - /// - `node_params`: DAC node parameters - fn fetch_processed_era_for_nodes( + /// Fetch Challenge node aggregate or bucket sub-aggregate. + pub(crate) fn _v4_fetch_challenge_responses( cluster_id: &ClusterId, - dac_nodes: &[(NodePubKey, StorageNodeParams)], - ) -> Result>, OCWError> { - let mut processed_eras_by_nodes: Vec> = Vec::new(); - - for (node_key, node_params) in dac_nodes { - let processed_eras_by_node = Self::fetch_processed_eras(node_params); - if processed_eras_by_node.is_err() { - log::warn!( - "Aggregator from cluster {:?} is unavailable while fetching processed eras. Key: {:?} Host: {:?}", - cluster_id, - node_key.get_hex(), - String::from_utf8(node_params.host.clone()) - ); - // skip unavailable aggregators and continue with available ones - continue; - } else { - let eras = processed_eras_by_node.expect("Era Response to be available"); - if !eras.is_empty() { - processed_eras_by_nodes - .push(eras.into_iter().map(|e| e.into()).collect::>()); - } - } - } + era_id: DdcEra, + aggregate_key: AggregateKey, + merkle_node_identifiers: Vec, + aggregator: AggregatorInfo, + ) -> Result { + let response = Self::_v4_fetch_challenge_response( + era_id, + aggregate_key.clone(), + merkle_node_identifiers.clone(), + &aggregator.node_params, + ) + .map_err(|_| OCWError::ChallengeResponseRetrievalError { + cluster_id: *cluster_id, + era_id, + aggregate_key, + aggregator: aggregator.node_pub_key, + })?; - Ok(processed_eras_by_nodes) + Ok(response) } - pub fn node_key_from_hex(hex_str: String) -> Result { - let bytes_vec = if hex_str.len() == 66 { - // cut `0x` prefix - hex::decode(&hex_str[2..])? - } else { - hex::decode(hex_str)? - }; - - let bytes_arr: [u8; 32] = - bytes_vec.try_into().map_err(|_| hex::FromHexError::InvalidStringLength)?; - let pub_key = AccountId32::from(bytes_arr); - Ok(NodePubKey::StoragePubKey(pub_key)) - } + /// Challenge node aggregate or bucket sub-aggregate. + pub(crate) fn _v4_fetch_challenge_responses_proto( + cluster_id: &ClusterId, + era_id: DdcEra, + aggregate_key: AggregateKey, + merkle_tree_node_id: Vec, + aggregator: AggregatorInfo, + ) -> Result { + let response = Self::_v4_fetch_challenge_response_proto( + era_id, + aggregate_key.clone(), + merkle_tree_node_id.clone(), + &aggregator.node_params, + ) + .map_err(|_| OCWError::ChallengeResponseRetrievalError { + cluster_id: *cluster_id, + era_id, + aggregate_key, + aggregator: aggregator.node_pub_key, + })?; - /// Verify whether leaf is part of tree - /// - /// Parameters: - /// - `root_hash`: merkle root hash - /// - `batch_hash`: hash of the batch - /// - `batch_index`: index of the batch - /// - `batch_proof`: MMR proofs - pub(crate) fn _proof_merkle_leaf( - root_hash: PayableUsageHash, - batch_hash: PayableUsageHash, - batch_index: BatchIndex, - max_batch_index: BatchIndex, - batch_proof: &MMRProof, - ) -> Result> { - let batch_position = leaf_index_to_pos(batch_index.into()); - let mmr_size = leaf_index_to_mmr_size(max_batch_index.into()); - let proof: MerkleProof = - MerkleProof::new(mmr_size, batch_proof.proof.clone()); - proof - .verify(root_hash, vec![(batch_position, batch_hash)]) - .map_err(|_| Error::::FailedToVerifyMerkleProof) + Ok(response) } - } - #[pallet::call] - impl Pallet { - /// Create billing reports from a public origin. - /// - /// The origin must be Signed. + /// Fetch customer usage. /// /// Parameters: - /// - `cluster_id`: Cluster id of a cluster. - /// - `era`: Era id. - /// - `payers_merkle_root_hash`: Merkle root hash of payers - /// - `payees_merkle_root_hash`: Merkle root hash of payees - /// - /// Emits `BillingReportCreated` event when successful. - #[pallet::call_index(0)] - #[pallet::weight(::WeightInfo::set_prepare_era_for_payout(payers_batch_merkle_root_hashes.len() as u32 + payees_batch_merkle_root_hashes.len() as u32))] - pub fn set_prepare_era_for_payout( - origin: OriginFor, - cluster_id: ClusterId, - era_activity: EraActivity, - payers_merkle_root_hash: DeltaUsageHash, - payees_merkle_root_hash: DeltaUsageHash, - payers_batch_merkle_root_hashes: Vec, - payees_batch_merkle_root_hashes: Vec, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; + /// - `cluster_id`: cluster id of a cluster + /// - `era_id`: era id + /// - `node_params`: DAC node parameters + pub(crate) fn _v4_fetch_bucket_aggregates( + _cluster_id: &ClusterId, + era_id: DdcEra, + node_params: &StorageNodeParams, + ) -> Result, http::Error> { + let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; + let base_url = format!("http://{}:{}", host, node_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, + ); - ensure!(Self::is_ocw_validator(caller.clone()), Error::::Unauthorized); - let mut era_validation = { - let era_validations = >::get(cluster_id, era_activity.id); - - if era_validations.is_none() { - EraValidation { - payers_merkle_root_hash: DeltaUsageHash::default(), - payees_merkle_root_hash: DeltaUsageHash::default(), - start_era: Default::default(), - end_era: Default::default(), - validators: Default::default(), - status: EraValidationStatus::ValidatingData, - } - } else { - era_validations.unwrap() - } - }; + let mut buckets_aggregates = Vec::new(); + let mut prev_token = None; - // disallow signatures after era status change - ensure!( - era_validation.status == EraValidationStatus::ValidatingData, - Error::::NotExpectedState - ); + loop { + let response = client.buckets_aggregates( + era_id, + Some(BUCKETS_AGGREGATES_FETCH_BATCH_SIZE as u32), + prev_token, + )?; - // Ensure the validators entry exists for the specified (payers_merkle_root_hash, - // payees_merkle_root_hash) - let signed_validators = era_validation - .validators - .entry((payers_merkle_root_hash, payees_merkle_root_hash)) - .or_insert_with(Vec::new); - - ensure!(!signed_validators.contains(&caller.clone()), Error::::AlreadySignedEra); - signed_validators.push(caller.clone()); - - let validators_quorum = T::ValidatorsQuorum::get(); - let threshold = validators_quorum * >::get().len(); - - let mut should_deposit_ready_event = false; - if threshold <= signed_validators.len() { - // Update payers_merkle_root_hash and payees_merkle_root_hash as ones passed the - // threshold - era_validation.payers_merkle_root_hash = payers_merkle_root_hash; - era_validation.payees_merkle_root_hash = payees_merkle_root_hash; - era_validation.start_era = era_activity.start; // todo! start/end is set by the last validator and is not in consensus - era_validation.end_era = era_activity.end; - - if payers_merkle_root_hash == DeltaUsageHash::default() && - payees_merkle_root_hash == DeltaUsageHash::default() - { - // this condition is satisfied when there is no activity within era, i.e. when a - // validator posts empty roots - era_validation.status = EraValidationStatus::PayoutSkipped; - } else { - era_validation.status = EraValidationStatus::ReadyForPayout; - } + let response_len = response.len(); + prev_token = response.last().map(|a| a.bucket_id); - should_deposit_ready_event = true; - } + buckets_aggregates.extend(response); - // Update the EraValidations storage - >::insert(cluster_id, era_activity.id, era_validation); - Self::deposit_event(Event::::EraValidationRootsPosted { - cluster_id, - era_id: era_activity.id, - validator: caller, - payers_merkle_root_hash, - payees_merkle_root_hash, - payers_batch_merkle_root_hashes, - payees_batch_merkle_root_hashes, - }); - if should_deposit_ready_event { - Self::deposit_event(Event::::EraValidationReady { - cluster_id, - era_id: era_activity.id, - }); - } else { - Self::deposit_event(Event::::EraValidationNotReady { - cluster_id, - era_id: era_activity.id, - }); + if response_len < BUCKETS_AGGREGATES_FETCH_BATCH_SIZE { + break; + } } - Ok(()) + Ok(buckets_aggregates) } - /// Set validator key. - /// - /// The origin must be a validator. + /// Fetch node usage. /// /// Parameters: - /// - `ddc_validator_pub`: validator Key - #[pallet::call_index(1)] - #[pallet::weight(::WeightInfo::set_validator_key())] - pub fn set_validator_key( - origin: OriginFor, - ddc_validator_pub: T::AccountId, - ) -> DispatchResult { - let controller = ensure_signed(origin)?; - - let stash = T::ValidatorStaking::stash_by_ctrl(&controller) - .map_err(|_| Error::::NotController)?; - - ensure!( - >::get().contains(&ddc_validator_pub), - Error::::NotValidatorStash + /// - `cluster_id`: cluster id of a cluster + /// - `era_id`: era id + /// - `node_params`: DAC node parameters + pub(crate) fn _v4_fetch_node_aggregates( + _cluster_id: &ClusterId, + era_id: DdcEra, + node_params: &StorageNodeParams, + ) -> Result, http::Error> { + let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; + let base_url = format!("http://{}:{}", host, node_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, ); - ValidatorToStashKey::::insert(&ddc_validator_pub, &stash); - Self::deposit_event(Event::::ValidatorKeySet { validator: ddc_validator_pub }); - Ok(()) - } - - #[pallet::call_index(2)] - #[pallet::weight(::WeightInfo::commit_billing_fingerprint())] - #[allow(clippy::too_many_arguments)] - pub fn commit_billing_fingerprint( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - start_era: i64, - end_era: i64, - payers_root: PayableUsageHash, - payees_root: PayableUsageHash, - cluster_usage: NodeUsage, - ) -> DispatchResult { - let sender = ensure_signed(origin.clone())?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + let mut nodes_aggregates = Vec::new(); + let mut prev_token = None; - T::PayoutProcessor::commit_billing_fingerprint( - sender, - cluster_id, - era_id, - start_era, - end_era, - payers_root, - payees_root, - cluster_usage, - ) - } + loop { + let response = client.nodes_aggregates( + era_id, + Some(NODES_AGGREGATES_FETCH_BATCH_SIZE as u32), + prev_token, + )?; - #[pallet::call_index(3)] - #[pallet::weight(::WeightInfo::begin_billing_report())] - pub fn begin_billing_report( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - fingerprint: Fingerprint, - ) -> DispatchResult { - let sender = ensure_signed(origin.clone())?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); + let response_len = response.len(); + prev_token = response.last().map(|a| a.node_id.clone()); - T::PayoutProcessor::begin_billing_report(cluster_id, era_id, fingerprint)?; + nodes_aggregates.extend(response); - EraValidations::::try_mutate( - cluster_id, - era_id, - |maybe_era_validations| -> DispatchResult { - maybe_era_validations.as_mut().ok_or(Error::::NoEraValidation)?.status = - EraValidationStatus::PayoutInProgress; - Ok(()) - }, - )?; + if response_len < NODES_AGGREGATES_FETCH_BATCH_SIZE { + break; + } + } - Ok(()) + Ok(nodes_aggregates) } - #[pallet::call_index(4)] - #[pallet::weight(::WeightInfo::begin_charging_customers())] - pub fn begin_charging_customers( - origin: OriginFor, - cluster_id: ClusterId, + /// Fetch challenge response. + /// + /// Parameters: + /// - `era_id`: era id + /// - `aggregate_key`: key of the aggregate to challenge + /// - `merkle_node_identifiers`: set of merkle node identifiers to challenge + /// - `node_params`: aggregator node parameters + pub(crate) fn _v4_fetch_challenge_response( era_id: DdcEra, - max_batch_index: BatchIndex, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); - T::PayoutProcessor::begin_charging_customers(cluster_id, era_id, max_batch_index) - } + aggregate_key: AggregateKey, + merkle_node_identifiers: Vec, + node_params: &StorageNodeParams, + ) -> Result { + let scheme = "http"; + let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; - #[pallet::call_index(5)] - #[pallet::weight(::WeightInfo::send_charging_customers_batch(payers.len() as u32))] - pub fn send_charging_customers_batch( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - batch_index: BatchIndex, - payers: Vec<(BucketId, BucketUsage)>, - batch_proof: MMRProof, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); - T::PayoutProcessor::send_charging_customers_batch( - cluster_id, - era_id, - batch_index, - &payers, - batch_proof, - ) - } + let ids = merkle_node_identifiers + .iter() + .map(|x| format!("{}", x.clone())) + .collect::>() + .join(","); - #[pallet::call_index(6)] - #[pallet::weight(::WeightInfo::end_charging_customers())] - pub fn end_charging_customers( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); - T::PayoutProcessor::end_charging_customers(cluster_id, era_id) - } + let url = match aggregate_key { + AggregateKey::NodeAggregateKey(node_id) => format!( + "{}://{}:{}/activity/nodes/{}/challenge?eraId={}&merkleTreeNodeId={}", + scheme, host, node_params.http_port, node_id, era_id, ids + ), + AggregateKey::BucketSubAggregateKey(bucket_id, node_id) => format!( + "{}://{}:{}/activity/buckets/{}/challenge?eraId={}&nodeId={}&merkleTreeNodeId={}", + scheme, host, node_params.http_port, bucket_id, era_id, node_id, ids + ), + }; - #[pallet::call_index(7)] - #[pallet::weight(::WeightInfo::begin_rewarding_providers())] - pub fn begin_rewarding_providers( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - max_batch_index: BatchIndex, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); - T::PayoutProcessor::begin_rewarding_providers(cluster_id, era_id, max_batch_index) - } + let request = http::Request::get(&url); + let timeout = sp_io::offchain::timestamp() + .add(sp_runtime::offchain::Duration::from_millis(RESPONSE_TIMEOUT)); + let pending = request.deadline(timeout).send().map_err(|_| http::Error::IoError)?; - #[pallet::call_index(8)] - #[pallet::weight(::WeightInfo::send_rewarding_providers_batch(payees.len() as u32))] - pub fn send_rewarding_providers_batch( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - batch_index: BatchIndex, - payees: Vec<(NodePubKey, NodeUsage)>, - batch_proof: MMRProof, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); - T::PayoutProcessor::send_rewarding_providers_batch( - cluster_id, - era_id, - batch_index, - &payees, - batch_proof, - ) - } + let response = + pending.try_wait(timeout).map_err(|_| http::Error::DeadlineReached)??; + if response.code != _SUCCESS_CODE { + return Err(http::Error::Unknown); + } - #[pallet::call_index(9)] - #[pallet::weight(::WeightInfo::end_rewarding_providers())] - pub fn end_rewarding_providers( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); - T::PayoutProcessor::end_rewarding_providers(cluster_id, era_id) + let body = response.body().collect::>(); + serde_json::from_slice(&body).map_err(|_| http::Error::Unknown) } - #[pallet::call_index(10)] - #[pallet::weight(::WeightInfo::end_billing_report())] - pub fn end_billing_report( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - ) -> DispatchResult { - let sender = ensure_signed(origin)?; - ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); - T::PayoutProcessor::end_billing_report(cluster_id, era_id)?; + pub(crate) fn _v4_find_random_merkle_node_ids( + number_of_identifiers: usize, + number_of_leaves: u64, + aggregate_key: AggregateKey, + ) -> Vec { + let nonce_key = match aggregate_key { + AggregateKey::NodeAggregateKey(node_id) => node_id, + AggregateKey::BucketSubAggregateKey(.., node_id) => node_id, + }; - let mut era_validation = >::get(cluster_id, era_id).unwrap(); // should exist - era_validation.status = EraValidationStatus::PayoutSuccess; - >::insert(cluster_id, era_id, era_validation); + let nonce = Self::store_and_fetch_nonce(nonce_key); + let mut small_rng = SmallRng::seed_from_u64(nonce); - T::ClusterValidator::set_last_paid_era(&cluster_id, era_id) + let total_levels = number_of_leaves.ilog2() + 1; + let int_list: Vec = (0..total_levels as u64).collect(); + + let ids: Vec = int_list + .choose_multiple(&mut small_rng, number_of_identifiers) + .cloned() + .collect::>(); + + ids } - /// Emit consensus errors. - /// - /// The origin must be a validator. - /// - /// Parameters: - /// - errors`: List of consensus errors + /// Fetch processed era for across all nodes. /// - /// Emits `NotEnoughNodesForConsensus` OR `ActivityNotInConsensus` event depend of error - /// type, when successful. - #[pallet::call_index(11)] - #[pallet::weight(::WeightInfo::emit_consensus_errors(errors.len() as u32))] - pub fn emit_consensus_errors( - origin: OriginFor, - errors: Vec, - ) -> DispatchResult { - let caller = ensure_signed(origin)?; - ensure!(Self::is_ocw_validator(caller.clone()), Error::::Unauthorized); - - for error in errors { - match error { - OCWError::NodeUsageRetrievalError { cluster_id, era_id, node_pub_key } => { - Self::deposit_event(Event::NodeUsageRetrievalError { - cluster_id, - era_id, - node_pub_key, - validator: caller.clone(), - }); - }, - OCWError::BucketAggregatesRetrievalError { - cluster_id, - era_id, - node_pub_key, - } => { - Self::deposit_event(Event::BucketAggregatesRetrievalError { - cluster_id, - era_id, - node_pub_key, - validator: caller.clone(), - }); - }, - OCWError::EraRetrievalError { cluster_id, node_pub_key } => { - Self::deposit_event(Event::EraRetrievalError { - cluster_id, - node_pub_key, - validator: caller.clone(), - }); - }, - OCWError::PrepareEraTransactionError { - cluster_id, - era_id, - payers_merkle_root_hash, - payees_merkle_root_hash, - } => { - Self::deposit_event(Event::PrepareEraTransactionError { - cluster_id, - era_id, - payers_merkle_root_hash, - payees_merkle_root_hash, - validator: caller.clone(), - }); - }, - OCWError::CommitBillingFingerprintTransactionError { - cluster_id, - era_id, - payers_root, - payees_root, - } => { - Self::deposit_event(Event::CommitBillingFingerprintTransactionError { - cluster_id, - era_id, - payers_root, - payees_root, - validator: caller.clone(), - }); - }, - OCWError::BeginBillingReportTransactionError { cluster_id, era_id } => { - Self::deposit_event(Event::BeginBillingReportTransactionError { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::BeginChargingCustomersTransactionError { cluster_id, era_id } => { - Self::deposit_event(Event::BeginChargingCustomersTransactionError { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::SendChargingCustomersBatchTransactionError { - cluster_id, - era_id, - batch_index, - } => { - Self::deposit_event(Event::SendChargingCustomersBatchTransactionError { - cluster_id, - era_id, - batch_index, - validator: caller.clone(), - }); - }, - OCWError::SendRewardingProvidersBatchTransactionError { - cluster_id, - era_id, - batch_index, - } => { - Self::deposit_event(Event::SendRewardingProvidersBatchTransactionError { - cluster_id, - era_id, - batch_index, - validator: caller.clone(), - }); - }, - OCWError::EndChargingCustomersTransactionError { cluster_id, era_id } => { - Self::deposit_event(Event::EndChargingCustomersTransactionError { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::BeginRewardingProvidersTransactionError { cluster_id, era_id } => { - Self::deposit_event(Event::BeginRewardingProvidersTransactionError { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::EndRewardingProvidersTransactionError { cluster_id, era_id } => { - Self::deposit_event(Event::EndRewardingProvidersTransactionError { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::EndBillingReportTransactionError { cluster_id, era_id } => { - Self::deposit_event(Event::EndBillingReportTransactionError { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::BillingReportDoesNotExist { cluster_id, era_id } => { - Self::deposit_event(Event::BillingReportDoesNotExist { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::EmptyCustomerActivity { cluster_id, era_id } => { - Self::deposit_event(Event::EmptyCustomerActivity { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::BatchIndexConversionFailed { cluster_id, era_id } => { - Self::deposit_event(Event::BatchIndexConversionFailed { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::NoAvailableSigner => { - Self::deposit_event(Event::NoAvailableSigner { validator: caller.clone() }); - }, - OCWError::NotEnoughDACNodes { num_nodes } => { - Self::deposit_event(Event::NotEnoughDACNodes { - num_nodes, - validator: caller.clone(), - }); - }, - OCWError::FailedToCreateMerkleRoot { cluster_id, era_id } => { - Self::deposit_event(Event::FailedToCreateMerkleRoot { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::FailedToCreateMerkleProof { cluster_id, era_id } => { - Self::deposit_event(Event::FailedToCreateMerkleProof { - cluster_id, - era_id, - validator: caller.clone(), - }); - }, - OCWError::FailedToCollectVerificationKey => { - Self::deposit_event(Event::FailedToCollectVerificationKey { - validator: caller.clone(), - }); - }, - OCWError::FailedToFetchVerificationKey => { - Self::deposit_event(Event::FailedToFetchVerificationKey { - validator: caller.clone(), - }); - }, - OCWError::FailedToFetchNodeProvider => { - Self::deposit_event(Event::FailedToFetchNodeProvider { - validator: caller.clone(), - }); - }, - OCWError::FailedToFetchNodeTotalUsage { cluster_id, node_pub_key } => { - Self::deposit_event(Event::FailedToFetchNodeTotalUsage { - cluster_id, - node_pub_key, - validator: caller.clone(), - }); - }, - OCWError::BucketAggregateRetrievalError { - cluster_id, - era_id, - bucket_id, - node_pub_key, - } => { - Self::deposit_event(Event::BucketAggregateRetrievalError { - cluster_id, - era_id, - bucket_id, - node_pub_key, - validator: caller.clone(), - }); - }, - OCWError::ChallengeResponseRetrievalError { - cluster_id, - era_id, - aggregate_key, - aggregator, - } => { - Self::deposit_event(Event::ChallengeResponseRetrievalError { - cluster_id, - era_id, - aggregate_key, - aggregator, - validator: caller.clone(), - }); - }, - OCWError::TraverseResponseRetrievalError { + /// Parameters: + /// - `cluster_id`: Cluster id + /// - `node_params`: DAC node parameters + fn _v4_fetch_processed_era_for_nodes( + cluster_id: &ClusterId, + dac_nodes: &[(NodePubKey, StorageNodeParams)], + ) -> Result>, OCWError> { + let mut processed_eras_by_nodes: Vec> = Vec::new(); + + for (node_key, node_params) in dac_nodes { + let processed_eras_by_node = Self::_v4_fetch_processed_eras(node_params); + if processed_eras_by_node.is_err() { + log::warn!( + "Aggregator from cluster {:?} is unavailable while fetching processed eras. Key: {:?} Host: {:?}", cluster_id, - era_id, - aggregate_key, - aggregator, - } => { - Self::deposit_event(Event::TraverseResponseRetrievalError { - cluster_id, - era_id, - aggregate_key, - aggregator, - validator: caller.clone(), - }); - }, - OCWError::FailedToFetchClusterNodes => { - Self::deposit_event(Event::FailedToFetchClusterNodes { - validator: caller.clone(), - }); - }, - OCWError::FailedToFetchDacNodes => { - Self::deposit_event(Event::FailedToFetchDacNodes { - validator: caller.clone(), - }); - }, - OCWError::EmptyConsistentGroup => { - Self::deposit_event(Event::EmptyConsistentGroup); - }, - OCWError::FailedToFetchVerifiedDeltaUsage => { - Self::deposit_event(Event::FailedToFetchVerifiedDeltaUsage); - }, - OCWError::FailedToFetchVerifiedPayableUsage => { - Self::deposit_event(Event::FailedToFetchVerifiedPayableUsage); - }, + node_key, + String::from_utf8(node_params.host.clone()) + ); + // skip unavailable aggregators and continue with available ones + continue; + } else { + let eras = processed_eras_by_node.expect("Era Response to be available"); + if !eras.is_empty() { + processed_eras_by_nodes + .push(eras.into_iter().map(|e| e.into()).collect::>()); + } } } - Ok(()) + Ok(processed_eras_by_nodes) } - - /// Set PayoutSkipped state of a given era if it is not validated yet. Otherwise does - /// nothing. + /// Fetch processed era. /// - /// Emits `EraValidationReady`. - #[pallet::call_index(12)] - #[pallet::weight(::WeightInfo::set_era_validations())] - pub fn set_era_validations( - origin: OriginFor, - cluster_id: ClusterId, - era_id: DdcEra, - ) -> DispatchResult { - ensure_root(origin)?; + /// Parameters: + /// - `node_params`: DAC node parameters + #[allow(dead_code)] + pub(crate) fn _v4_fetch_processed_eras( + node_params: &StorageNodeParams, + ) -> Result, http::Error> { + let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; + let base_url = format!("http://{}:{}", host, node_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, + ); - Self::do_skip_era_validation(&cluster_id, era_id)?; - Self::deposit_event(Event::::EraValidationReady { cluster_id, era_id }); + let response = client.eras()?; - Ok(()) + Ok(response.into_iter().filter(|e| e.status == "PROCESSED").collect::>()) } - /// Continue DAC validation from an era after a given one. It updates `last_paid_era` of a - /// given cluster, creates an empty billing report with a finalized state, and sets an empty - /// validation result on validators (in case it does not exist yet). - #[pallet::call_index(13)] - #[pallet::weight(::WeightInfo::skip_dac_validation_to_era())] - pub fn skip_dac_validation_to_era( - origin: OriginFor, - cluster_id: ClusterId, + pub(crate) fn _v4_get_hash_from_merkle_path( + challenge_response: aggregator_client::json::ChallengeAggregateResponse, + cluster_id: &ClusterId, era_id: DdcEra, - ) -> DispatchResult { - ensure_root(origin)?; - ensure!( - era_id > T::ClusterValidator::get_last_paid_era(&cluster_id)?, - Error::::EraAlreadyPaid - ); + aggregate_key: AggregateKey, + ) -> Result> { + log::info!("Getting hash from merkle tree path for aggregate key: {:?}", aggregate_key); - Self::do_skip_era_validation(&cluster_id, era_id)?; + let mut resulting_hash = DeltaUsageHash::default(); - let fingerprint = - T::PayoutProcessor::create_billing_fingerprint(BillingFingerprintParams { - cluster_id, - era: era_id, - start_era: Default::default(), - end_era: Default::default(), - payers_merkle_root: Default::default(), - payees_merkle_root: Default::default(), - cluster_usage: Default::default(), - validators: BTreeSet::new(), - }); + for proof in challenge_response.proofs { + let leaf_record_hashes: Vec = match aggregate_key { + AggregateKey::BucketSubAggregateKey(_, _) => proof + .leafs + .into_iter() + .map(|p| NodeAggregateLeaf::leaf_hash::(&p)) + .collect(), + AggregateKey::NodeAggregateKey(_) => proof + .leafs + .into_iter() + .map(|p| BucketSubAggregateLeaf::leaf_hash::(&p)) + .collect(), + }; - let billing_report_params = BillingReportParams { - cluster_id, - era: era_id, - state: PayoutState::Finalized, - fingerprint, - ..Default::default() - }; + let leaf_record_hashes_string: Vec = + leaf_record_hashes.clone().into_iter().map(hex::encode).collect(); - T::PayoutProcessor::create_billing_report( - T::AccountId::decode(&mut [0u8; 32].as_slice()).unwrap(), - billing_report_params, - ); + log::info!( + "👁️‍🗨️ Fetched leaf record hashes aggregate key: {:?} leaf_record_hashes: {:?}", + aggregate_key, + leaf_record_hashes_string + ); - T::ClusterValidator::set_last_paid_era(&cluster_id, era_id)?; + let leaf_node_root = + Self::create_merkle_root(cluster_id, &leaf_record_hashes, era_id) + .map_err(|err| vec![err])?; - Ok(()) - } - } + log::info!( + "👁️‍🗨️ Fetched leaf record root aggregate key: {:?} leaf_record_root_hash: {:?}", + aggregate_key, + hex::encode(leaf_node_root) + ); - impl ValidatorVisitor for Pallet { - fn is_ocw_validator(caller: T::AccountId) -> bool { - if ValidatorToStashKey::::contains_key(caller.clone()) { - >::get().contains(&caller) - } else { - false + let paths = proof.path.iter().rev(); + + resulting_hash = leaf_node_root; + for path in paths { + let mut dec_buf = [0u8; _BUF_SIZE]; + let bytes = Base64::decode(path, &mut dec_buf).unwrap(); // todo! remove unwrap + let path_hash: DeltaUsageHash = DeltaUsageHash::from(H256::from_slice(bytes)); + + let node_root = + Self::create_merkle_root(cluster_id, &[resulting_hash, path_hash], era_id) + .map_err(|err| vec![err])?; + + log::info!("👁️‍🗨️ Fetched leaf node root aggregate_key: {:?} for path:{:?} leaf_node_hash: {:?}", + aggregate_key, path, hex::encode(node_root)); + + resulting_hash = node_root; + } } + + Ok(resulting_hash) } - fn is_quorum_reached(quorum: Percent, members_count: usize) -> bool { - let threshold = quorum * >::get().len(); - threshold <= members_count + pub(crate) fn _v4_derive_usage_key(cluster_id: &ClusterId, era_id: DdcEra) -> Vec { + format!("offchain::activities::{:?}::{:?}", cluster_id, era_id).into_bytes() + } + + #[allow(clippy::too_many_arguments)] + pub(crate) fn _v4_store_verified_usage( + cluster_id: &ClusterId, + era_id: DdcEra, + buckets_deltas: &[A], + buckets_deltas_root: DeltaUsageHash, + buckets_deltas_batch_roots: &[DeltaUsageHash], + nodes_deltas: &[B], + nodes_deltas_root: DeltaUsageHash, + nodes_deltas_batch_roots: &[DeltaUsageHash], + ) { + let key = Self::_v4_derive_usage_key(cluster_id, era_id); + let encoded_tuple = ( + buckets_deltas, + buckets_deltas_root, + buckets_deltas_batch_roots, + nodes_deltas, + nodes_deltas_root, + nodes_deltas_batch_roots, + ) + .encode(); + + // Store the serialized data in local offchain storage + local_storage_set(StorageKind::PERSISTENT, &key, &encoded_tuple); + } + + /// Fetch traverse response. + /// + /// Parameters: + /// - `era_id`: era id + /// - `aggregate_key`: key of the aggregate to challenge + /// - `merkle_node_identifiers`: set of merkle node identifiers to challenge + /// - `levels`: a number of levels to raverse + /// - `node_params`: aggregator node parameters + pub(crate) fn _v4_fetch_traverse_response( + era_id: DdcEra, + aggregate_key: AggregateKey, + merkle_tree_node_id: u32, + levels: u16, + node_params: &StorageNodeParams, + ) -> Result { + let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; + let base_url = format!("http://{}:{}", host, node_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, + ); + + let response = match aggregate_key { + AggregateKey::BucketSubAggregateKey(bucket_id, node_id) => client + .traverse_bucket_sub_aggregate( + era_id, + bucket_id, + &node_id, + merkle_tree_node_id, + levels, + ), + AggregateKey::NodeAggregateKey(node_id) => + client.traverse_node_aggregate(era_id, &node_id, merkle_tree_node_id, levels), + }?; + + Ok(response) + } + + /// Fetch protobuf challenge response. + pub(crate) fn _v4_fetch_challenge_response_proto( + era_id: DdcEra, + aggregate_key: AggregateKey, + merkle_tree_node_id: Vec, + node_params: &StorageNodeParams, + ) -> Result { + let host = str::from_utf8(&node_params.host).map_err(|_| http::Error::Unknown)?; + let base_url = format!("http://{}:{}", host, node_params.http_port); + let client = aggregator_client::AggregatorClient::new( + &base_url, + Duration::from_millis(RESPONSE_TIMEOUT), + MAX_RETRIES_COUNT, + T::VERIFY_AGGREGATOR_RESPONSE_SIGNATURE, + ); + + match aggregate_key { + AggregateKey::BucketSubAggregateKey(bucket_id, node_id) => client + .challenge_bucket_sub_aggregate( + era_id, + bucket_id, + &node_id, + merkle_tree_node_id, + ), + AggregateKey::NodeAggregateKey(node_id) => + client.challenge_node_aggregate(era_id, &node_id, merkle_tree_node_id), + } } } diff --git a/pallets/ddc-verification/src/migrations.rs b/pallets/ddc-verification/src/migrations.rs index bbe8ae1e3..da3e33835 100644 --- a/pallets/ddc-verification/src/migrations.rs +++ b/pallets/ddc-verification/src/migrations.rs @@ -65,3 +65,64 @@ pub mod v1 { } } } + +pub mod v2 { + use super::*; + + pub fn migrate_to_v2() -> Weight { + let on_chain_version = Pallet::::on_chain_storage_version(); + let current_version = Pallet::::in_code_storage_version(); + + log::info!( + target: LOG_TARGET, + "Running migration with current storage version {:?} / onchain {:?}", + current_version, + on_chain_version + ); + + if on_chain_version == 1 && current_version == 2 { + log::info!(target: LOG_TARGET, "Running migration to v2."); + + let res = migration::clear_storage_prefix( + >::name().as_bytes(), + b"EraValidations", + b"", + None, + None, + ); + + log::info!( + target: LOG_TARGET, + "Cleared '{}' entries from 'EraValidations' storage prefix.", + res.unique + ); + + if res.maybe_cursor.is_some() { + log::error!( + target: LOG_TARGET, + "Storage prefix 'EraValidations' is not completely cleared." + ); + } + + // Update storage version. + StorageVersion::new(2).put::>(); + log::info!( + target: LOG_TARGET, + "Storage migrated to version {:?}", + current_version + ); + + T::DbWeight::get().reads_writes(1, res.unique.into()) + } else { + log::info!(target: LOG_TARGET, " >>> Unused migration!"); + T::DbWeight::get().reads(1) + } + } + + pub struct MigrateToV2(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV2 { + fn on_runtime_upgrade() -> Weight { + migrate_to_v2::() + } + } +} diff --git a/pallets/ddc-verification/src/mock.rs b/pallets/ddc-verification/src/mock.rs index f9cbb22df..9c53e7db5 100644 --- a/pallets/ddc-verification/src/mock.rs +++ b/pallets/ddc-verification/src/mock.rs @@ -1,15 +1,15 @@ use ddc_primitives::{ crypto, sr25519, - traits::{ClusterManager, ClusterQuery, StorageUsageProvider}, - BillingFingerprintParams, BucketId, BucketStorageUsage, ClusterNodeKind, ClusterNodeState, - ClusterNodeStatus, ClusterNodesStats, ClusterStatus, Fingerprint, NodeStorageUsage, - PayoutError, PayoutState, StorageNodeMode, StorageNodePubKey, MAX_PAYOUT_BATCH_COUNT, - MAX_PAYOUT_BATCH_SIZE, + traits::{ClusterManager, ClusterProtocol, ClusterQuery, StorageUsageProvider}, + BucketId, BucketStorageUsage, ClusterBondingParams, ClusterNodeKind, ClusterNodeState, + ClusterNodeStatus, ClusterNodesStats, ClusterProtocolParams, ClusterStatus, Fingerprint, + NodeStorageUsage, NodeType, NodeUsage, PayoutError, PayoutFingerprintParams, PayoutState, + StorageNodeMode, StorageNodePubKey, MAX_PAYOUT_BATCH_COUNT, MAX_PAYOUT_BATCH_SIZE, }; #[cfg(feature = "runtime-benchmarks")] use ddc_primitives::{ traits::{BucketManager, ClusterCreator, CustomerDepositor}, - BillingReportParams, BucketParams, ClusterId, ClusterParams, ClusterProtocolParams, + BucketParams, BucketUsage, ClusterId, ClusterParams, PayoutReceiptParams, }; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, @@ -22,7 +22,7 @@ use frame_support::{ traits::{ConstU16, ConstU64}, PalletId, }; -use frame_system::mocking::MockBlock; +use frame_system::{mocking::MockBlock, pallet_prelude::BlockNumberFor}; use pallet_staking::BalanceOf; use scale_info::prelude::string::String; use sp_core::{ByteArray, H256}; @@ -248,6 +248,7 @@ impl crate::Config for Test { const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool = false; type BucketsStorageUsageProvider = MockBucketValidator; type NodesStorageUsageProvider = MockNodeValidator; + type ClusterProtocol = MockClusterProtocol; #[cfg(feature = "runtime-benchmarks")] type CustomerDepositor = MockCustomerDepositor; #[cfg(feature = "runtime-benchmarks")] @@ -302,6 +303,82 @@ impl CustomerVisitor for MockCustomerVisitor { } } +pub struct MockClusterProtocol; +impl ClusterProtocol for MockClusterProtocol { + fn get_bond_size(_cluster_id: &ClusterId, _node_type: NodeType) -> Result { + unimplemented!() + } + + fn get_pricing_params(_cluster_id: &ClusterId) -> Result { + unimplemented!() + } + + fn get_fees_params(_cluster_id: &ClusterId) -> Result { + unimplemented!() + } + + fn get_chill_delay( + _cluster_id: &ClusterId, + _node_type: NodeType, + ) -> Result, DispatchError> { + unimplemented!() + } + + fn get_unbonding_delay( + _cluster_id: &ClusterId, + _node_type: NodeType, + ) -> Result, DispatchError> { + unimplemented!() + } + + fn get_bonding_params( + _cluster_id: &ClusterId, + ) -> Result>, DispatchError> { + unimplemented!() + } + + fn get_reserve_account_id(_cluster_id: &ClusterId) -> Result { + unimplemented!() + } + + fn activate_cluster_protocol(_cluster_id: &ClusterId) -> DispatchResult { + unimplemented!() + } + + fn update_cluster_protocol( + _cluster_id: &ClusterId, + _cluster_protocol_params: ClusterProtocolParams>, + ) -> DispatchResult { + unimplemented!() + } + + fn bond_cluster(_cluster_id: &ClusterId) -> DispatchResult { + unimplemented!() + } + + fn start_unbond_cluster(_cluster_id: &ClusterId) -> DispatchResult { + unimplemented!() + } + + fn end_unbond_cluster(_cluster_id: &ClusterId) -> DispatchResult { + unimplemented!() + } +} + +impl ClusterQuery for MockClusterProtocol { + fn cluster_exists(_cluster_id: &ClusterId) -> bool { + unimplemented!() + } + fn get_cluster_status(_cluster_id: &ClusterId) -> Result { + unimplemented!() + } + fn get_manager_and_reserve_id( + _cluster_id: &ClusterId, + ) -> Result<(T::AccountId, T::AccountId), DispatchError> { + unimplemented!() + } +} + #[cfg(feature = "runtime-benchmarks")] pub struct MockCustomerDepositor; #[cfg(feature = "runtime-benchmarks")] @@ -468,20 +545,17 @@ impl ClusterValidator for MockClusterValidator { pub struct MockPayoutProcessor; impl PayoutProcessor for MockPayoutProcessor { - fn commit_billing_fingerprint( + fn commit_payout_fingerprint( _validator: T::AccountId, _cluster_id: ClusterId, - _era_id: DdcEra, - _start_era: i64, - _end_era: i64, + _ehd_id: String, _payers_merkle_root: PayableUsageHash, _payees_merkle_root: PayableUsageHash, - _cluster_usage: NodeUsage, ) -> DispatchResult { unimplemented!() } - fn begin_billing_report( + fn begin_payout( _cluster_id: ClusterId, _era_id: DdcEra, _fingerprint: Fingerprint, @@ -501,7 +575,7 @@ impl PayoutProcessor for MockPayoutProcessor { _cluster_id: ClusterId, _era_id: DdcEra, _batch_index: BatchIndex, - _payers: &[(BucketId, BucketUsage)], + _payers: &[(T::AccountId, u128)], _batch_proof: MMRProof, ) -> DispatchResult { unimplemented!() @@ -523,7 +597,7 @@ impl PayoutProcessor for MockPayoutProcessor { _cluster_id: ClusterId, _era_id: DdcEra, _batch_index: BatchIndex, - _payees: &[(NodePubKey, NodeUsage)], + _payees: &[(T::AccountId, u128)], _batch_proof: MMRProof, ) -> DispatchResult { unimplemented!() @@ -533,41 +607,41 @@ impl PayoutProcessor for MockPayoutProcessor { unimplemented!() } - fn end_billing_report(_cluster_id: ClusterId, _era_id: DdcEra) -> DispatchResult { + fn end_payout(_cluster_id: ClusterId, _era_id: DdcEra) -> DispatchResult { unimplemented!() } - fn get_next_customer_batch_for_payment( + fn get_next_customers_batch( _cluster_id: &ClusterId, _era_id: DdcEra, ) -> Result, PayoutError> { Ok(None) } - fn get_next_provider_batch_for_payment( + fn get_next_providers_batch( _cluster_id: &ClusterId, _era_id: DdcEra, ) -> Result, PayoutError> { Ok(None) } - fn all_customer_batches_processed(_cluster_id: &ClusterId, _era_id: DdcEra) -> bool { + fn is_customers_charging_finished(_cluster_id: &ClusterId, _era_id: DdcEra) -> bool { true } - fn all_provider_batches_processed(_cluster_id: &ClusterId, _era_id: DdcEra) -> bool { + fn is_providers_rewarding_finished(_cluster_id: &ClusterId, _era_id: DdcEra) -> bool { true } - fn get_billing_report_status(_cluster_id: &ClusterId, _era_id: DdcEra) -> PayoutState { + fn get_payout_state(_cluster_id: &ClusterId, _era_id: DdcEra) -> PayoutState { PayoutState::NotInitialized } - fn create_billing_report(_vault: T::AccountId, _params: BillingReportParams) { + fn create_payout_receipt(_vault: T::AccountId, _params: PayoutReceiptParams) { unimplemented!() } - fn create_billing_fingerprint(_params: BillingFingerprintParams) -> Fingerprint { + fn create_payout_fingerprint(_params: PayoutFingerprintParams) -> Fingerprint { unimplemented!() } } diff --git a/pallets/ddc-verification/src/tests.rs b/pallets/ddc-verification/src/tests.rs index a75187bd9..a85e038b4 100644 --- a/pallets/ddc-verification/src/tests.rs +++ b/pallets/ddc-verification/src/tests.rs @@ -2,7 +2,7 @@ use ddc_primitives::{ AggregatorInfo, ClusterId, DeltaUsageHash, MergeMMRHash, StorageNodeMode, StorageNodeParams, StorageNodePubKey, DAC_VERIFICATION_KEY_TYPE, }; -use frame_support::{assert_noop, assert_ok}; +use frame_support::assert_noop; use prost::Message; use sp_core::{ offchain::{ @@ -32,7 +32,7 @@ fn register_validators(validators: Vec) { } } -fn get_validators() -> Vec { +fn _get_validators() -> Vec { let validator1: AccountId32 = [1; 32].into(); let validator2: AccountId32 = [2; 32].into(); let validator3: AccountId32 = [3; 32].into(); @@ -163,7 +163,7 @@ fn fetch_node_aggregates_works() { domain: b"example2.com".to_vec(), }; - let result = Pallet::::fetch_node_aggregates(&cluster_id, era_id, &node_params); + let result = Pallet::::_v4_fetch_node_aggregates(&cluster_id, era_id, &node_params); assert!(result.is_ok()); let activities = result.unwrap(); assert_eq!(activities[0].number_of_gets, node_activity1.number_of_gets); @@ -257,7 +257,7 @@ fn fetch_bucket_aggregates_works() { domain: b"example2.com".to_vec(), }; - let result = Pallet::::fetch_bucket_aggregates(&cluster_id, era_id, &node_params); + let result = Pallet::::_v4_fetch_bucket_aggregates(&cluster_id, era_id, &node_params); assert!(result.is_ok()); let activities = result.unwrap(); assert_eq!( @@ -396,7 +396,7 @@ fn buckets_sub_aggregates_in_consensus_merged() { }], ); - let groups = DdcVerification::group_buckets_sub_aggregates_by_consistency( + let groups = DdcVerification::_v4_group_buckets_sub_aggregates_by_consistency( &cluster_id, era_id, vec![resp1, resp2, resp3], @@ -407,7 +407,7 @@ fn buckets_sub_aggregates_in_consensus_merged() { assert_eq!(groups.quorum.len(), 0); assert_eq!(groups.others.len(), 0); - let result = DdcVerification::get_total_usage(&cluster_id, era_id, groups, false); + let result = DdcVerification::_v4_get_total_usage(&cluster_id, era_id, groups, false); assert!(result.is_ok()); let usages = result.unwrap(); @@ -520,7 +520,7 @@ fn buckets_sub_aggregates_in_quorum_merged() { }], ); - let groups = DdcVerification::group_buckets_sub_aggregates_by_consistency( + let groups = DdcVerification::_v4_group_buckets_sub_aggregates_by_consistency( &cluster_id, era_id, vec![resp1, resp2, resp3], @@ -531,7 +531,7 @@ fn buckets_sub_aggregates_in_quorum_merged() { assert_eq!(groups.quorum.len(), 1); // 2 consistent aggregates merged into 1 in 'quorum' assert_eq!(groups.others.len(), 1); // 1 inconsistent aggregate goes to 'others' - let result = DdcVerification::get_total_usage(&cluster_id, era_id, groups, false); + let result = DdcVerification::_v4_get_total_usage(&cluster_id, era_id, groups, false); assert!(result.is_ok()); let usages = result.unwrap(); @@ -644,7 +644,7 @@ fn buckets_sub_aggregates_in_others_merged() { }], ); - let groups = DdcVerification::group_buckets_sub_aggregates_by_consistency( + let groups = DdcVerification::_v4_group_buckets_sub_aggregates_by_consistency( &cluster_id, era_id, vec![resp1, resp2, resp3], @@ -656,7 +656,7 @@ fn buckets_sub_aggregates_in_others_merged() { assert_eq!(groups.quorum.len(), 0); assert_eq!(groups.others.len(), 2); - let result = DdcVerification::get_total_usage(&cluster_id, era_id, groups, false); + let result = DdcVerification::_v4_get_total_usage(&cluster_id, era_id, groups, false); assert!(result.is_ok()); let usages = result.unwrap(); @@ -769,7 +769,7 @@ fn buckets_sub_aggregates_in_others_merged_2() { }], ); - let groups = DdcVerification::group_buckets_sub_aggregates_by_consistency( + let groups = DdcVerification::_v4_group_buckets_sub_aggregates_by_consistency( &cluster_id, era_id, vec![resp1, resp2, resp3], @@ -781,7 +781,7 @@ fn buckets_sub_aggregates_in_others_merged_2() { assert_eq!(groups.quorum.len(), 0); assert_eq!(groups.others.len(), 2); // 2 inconsistent aggregates - let result = DdcVerification::get_total_usage(&cluster_id, era_id, groups, false); + let result = DdcVerification::_v4_get_total_usage(&cluster_id, era_id, groups, false); assert!(result.is_ok()); let usages = result.unwrap(); @@ -879,7 +879,7 @@ fn nodes_aggregates_in_consensus_merged() { }], ); - let groups = DdcVerification::group_nodes_aggregates_by_consistency( + let groups = DdcVerification::_v4_group_nodes_aggregates_by_consistency( &cluster_id, era_id, vec![resp1, resp2, resp3], @@ -890,7 +890,7 @@ fn nodes_aggregates_in_consensus_merged() { assert_eq!(groups.quorum.len(), 0); assert_eq!(groups.others.len(), 0); - let result = DdcVerification::get_total_usage(&cluster_id, era_id, groups, false); + let result = DdcVerification::_v4_get_total_usage(&cluster_id, era_id, groups, false); assert!(result.is_ok()); let usages = result.unwrap(); @@ -982,7 +982,7 @@ fn nodes_aggregates_in_quorum_merged() { }], ); - let groups = DdcVerification::group_nodes_aggregates_by_consistency( + let groups = DdcVerification::_v4_group_nodes_aggregates_by_consistency( &cluster_id, era_id, vec![resp1, resp2, resp3], @@ -993,7 +993,7 @@ fn nodes_aggregates_in_quorum_merged() { assert_eq!(groups.quorum.len(), 1); // 2 consistent aggregates merged into 1 in 'quorum' assert_eq!(groups.others.len(), 1); // 1 inconsistent aggregate goes to 'others' - let result = DdcVerification::get_total_usage(&cluster_id, era_id, groups, false); + let result = DdcVerification::_v4_get_total_usage(&cluster_id, era_id, groups, false); assert!(result.is_ok()); let usages = result.unwrap(); @@ -1085,7 +1085,7 @@ fn nodes_aggregates_in_others_merged() { }], ); - let groups = DdcVerification::group_nodes_aggregates_by_consistency( + let groups = DdcVerification::_v4_group_nodes_aggregates_by_consistency( &cluster_id, era_id, vec![resp1, resp2, resp3], @@ -1097,7 +1097,7 @@ fn nodes_aggregates_in_others_merged() { assert_eq!(groups.quorum.len(), 0); assert_eq!(groups.others.len(), 2); - let result = DdcVerification::get_total_usage(&cluster_id, era_id, groups, false); + let result = DdcVerification::_v4_get_total_usage(&cluster_id, era_id, groups, false); assert!(result.is_ok()); let usages = result.unwrap(); @@ -1189,7 +1189,7 @@ fn nodes_aggregates_in_others_merged_2() { }], ); - let groups = DdcVerification::group_nodes_aggregates_by_consistency( + let groups = DdcVerification::_v4_group_nodes_aggregates_by_consistency( &cluster_id, era_id, vec![resp1, resp2, resp3], @@ -1201,7 +1201,7 @@ fn nodes_aggregates_in_others_merged_2() { assert_eq!(groups.quorum.len(), 0); assert_eq!(groups.others.len(), 3); // 3 inconsistent aggregates - let result = DdcVerification::get_total_usage(&cluster_id, era_id, groups, false); + let result = DdcVerification::_v4_get_total_usage(&cluster_id, era_id, groups, false); assert!(result.is_ok()); let usages = result.unwrap(); @@ -1270,8 +1270,11 @@ fn buckets_sub_aggregates_grouped_by_consistency() { }, ]; - let groups = - DdcVerification::group_by_consistency(buckets_sub_aggregates, redundancy_factor, quorum); + let groups = DdcVerification::_v4_group_by_consistency( + buckets_sub_aggregates, + redundancy_factor, + quorum, + ); assert_eq!(groups.consensus.len(), 1); assert_eq!(groups.quorum.len(), 0); @@ -1364,8 +1367,11 @@ fn buckets_sub_aggregates_grouped_by_consistency_2() { }, ]; - let groups = - DdcVerification::group_by_consistency(buckets_sub_aggregates, redundancy_factor, quorum); + let groups = DdcVerification::_v4_group_by_consistency( + buckets_sub_aggregates, + redundancy_factor, + quorum, + ); assert_eq!(groups.consensus.len(), 2); assert_eq!(groups.quorum.len(), 0); @@ -1438,7 +1444,8 @@ fn nodes_aggregates_grouped_by_consistency() { }, ]; - let groups = DdcVerification::group_by_consistency(nodes_aggregates, redundancy_factor, quorum); + let groups = + DdcVerification::_v4_group_by_consistency(nodes_aggregates, redundancy_factor, quorum); assert_eq!(groups.consensus.len(), 1); assert_eq!(groups.quorum.len(), 0); @@ -1525,7 +1532,8 @@ fn nodes_aggregates_grouped_by_consistency_2() { }, ]; - let groups = DdcVerification::group_by_consistency(nodes_aggregates, redundancy_factor, quorum); + let groups = + DdcVerification::_v4_group_by_consistency(nodes_aggregates, redundancy_factor, quorum); assert_eq!(groups.consensus.len(), 2); assert_eq!(groups.quorum.len(), 0); @@ -1557,7 +1565,7 @@ fn empty_bucket_sub_aggregates() { let quorum = Percent::from_percent(67); let empty = Vec::::new(); - let groups = DdcVerification::group_by_consistency(empty, redundancy_factor, quorum); + let groups = DdcVerification::_v4_group_by_consistency(empty, redundancy_factor, quorum); assert_eq!(groups.consensus.len(), 0); assert_eq!(groups.quorum.len(), 0); @@ -1718,11 +1726,11 @@ fn bucket_sub_aggregates_are_fetched_and_grouped() { ]; let bucket_aggregates_by_aggregator = - DdcVerification::fetch_buckets_aggregates_for_era(&cluster_id, era_id, &dac_nodes) + DdcVerification::_v4_fetch_buckets_aggregates_for_era(&cluster_id, era_id, &dac_nodes) .unwrap(); let groups = - DdcVerification::group_buckets_sub_aggregates_by_consistency(&cluster_id, era_id, bucket_aggregates_by_aggregator, redundancy_factor, aggregators_quorum); + DdcVerification::_v4_group_buckets_sub_aggregates_by_consistency(&cluster_id, era_id, bucket_aggregates_by_aggregator, redundancy_factor, aggregators_quorum); // Sub aggregates which are in consensus @@ -1982,11 +1990,11 @@ fn node_aggregates_are_fetched_and_grouped() { ]; let aggregates_by_aggregator = - DdcVerification::fetch_nodes_aggregates_for_era(&cluster_id, era_id, &dac_nodes) + DdcVerification::_v4_fetch_nodes_aggregates_for_era(&cluster_id, era_id, &dac_nodes) .unwrap(); let groups = - DdcVerification::group_nodes_aggregates_by_consistency(&cluster_id, era_id, aggregates_by_aggregator, redundancy_factor, aggregators_quorum); + DdcVerification::_v4_group_nodes_aggregates_by_consistency(&cluster_id, era_id, aggregates_by_aggregator, redundancy_factor, aggregators_quorum); // Node aggregates which are in consensus let node_aggregate_in_consensus = aggregator_client::json::NodeAggregate { node_id: "0x48594f1fd4f05135914c42b03e63b61f6a3e4c537ccee3dbac555ef6df371b7e" @@ -2080,21 +2088,21 @@ fn test_convert_to_batch_merkle_roots() { let result_roots = DdcVerification::convert_to_batch_merkle_roots( &cluster_id, - era_id_1, vec![activities_batch_1.clone(), activities_batch_2.clone()], + era_id_1, ) .unwrap(); - let expected_roots: Vec = vec![ + let expected_roots = vec![ DdcVerification::create_merkle_root( &cluster_id, - era_id_1, &activities_batch_1.iter().map(|a| a.hash::()).collect::>(), + era_id_1, ) .unwrap(), DdcVerification::create_merkle_root( &cluster_id, - era_id_1, &activities_batch_2.iter().map(|a| a.hash::()).collect::>(), + era_id_1, ) .unwrap(), ]; @@ -2105,14 +2113,10 @@ fn test_convert_to_batch_merkle_roots() { #[test] fn test_convert_to_batch_merkle_roots_empty() { let cluster_id = ClusterId::default(); - let era_id_1 = 1; - let result_roots = DdcVerification::convert_to_batch_merkle_roots( - &cluster_id, - era_id_1, - Vec::>::new(), - ) - .unwrap(); - let expected_roots: Vec = Vec::::new(); + let batches: Vec> = vec![]; + let result_roots = + DdcVerification::convert_to_batch_merkle_roots(&cluster_id, batches, 1).unwrap(); + let expected_roots = vec![]; assert_eq!(result_roots, expected_roots); } @@ -2216,7 +2220,7 @@ fn fetch_processed_era_works() { domain: b"example2.com".to_vec(), }; - let result = Pallet::::fetch_processed_eras(&node_params); + let result = Pallet::::_v4_fetch_processed_eras(&node_params); assert!(result.is_ok()); let activities = result.unwrap(); @@ -2230,6 +2234,7 @@ fn fetch_processed_era_works() { }); } +#[ignore = "DAC v5 is in progress"] #[test] fn get_era_for_validation_works() { let mut ext = TestExternalities::default(); @@ -2290,7 +2295,7 @@ fn get_era_for_validation_works() { drop(offchain_state); - let node_params1 = StorageNodeParams { + let _node_params1 = StorageNodeParams { ssl: false, host: host1.as_bytes().to_vec(), http_port: port, @@ -2300,7 +2305,7 @@ fn get_era_for_validation_works() { domain: b"example2.com".to_vec(), }; - let node_params2 = StorageNodeParams { + let _node_params2 = StorageNodeParams { ssl: false, host: host2.as_bytes().to_vec(), http_port: port, @@ -2310,7 +2315,7 @@ fn get_era_for_validation_works() { domain: b"example3.com".to_vec(), }; - let node_params3 = StorageNodeParams { + let _node_params3 = StorageNodeParams { ssl: false, host: host3.as_bytes().to_vec(), http_port: port, @@ -2320,7 +2325,7 @@ fn get_era_for_validation_works() { domain: b"example4.com".to_vec(), }; - let node_params4 = StorageNodeParams { + let _node_params4 = StorageNodeParams { ssl: false, host: host4.as_bytes().to_vec(), http_port: port, @@ -2329,138 +2334,9 @@ fn get_era_for_validation_works() { grpc_port: 4444, domain: b"example5.com".to_vec(), }; - - let dac_nodes: Vec<(NodePubKey, StorageNodeParams)> = vec![ - (NodePubKey::StoragePubKey(StorageNodePubKey::new([1; 32])), node_params1), - (NodePubKey::StoragePubKey(StorageNodePubKey::new([2; 32])), node_params2), - (NodePubKey::StoragePubKey(StorageNodePubKey::new([3; 32])), node_params3), - (NodePubKey::StoragePubKey(StorageNodePubKey::new([4; 32])), node_params4), - ]; - - let cluster_id = ClusterId::from([12; 20]); - let result = Pallet::::get_era_for_validation(&cluster_id, &dac_nodes); - let era_activity = EraActivity { id: 16, start: 1, end: 2 }; - assert_eq!(result.unwrap().unwrap(), era_activity); }); } -#[test] -fn test_get_last_validated_era() { - let cluster_id1 = ClusterId::from([12; 20]); - let cluster_id2 = ClusterId::from([13; 20]); - let era_1 = 1; - let era_2 = 2; - let payers_root: DeltaUsageHash = H256([1; 32]); - let payees_root: DeltaUsageHash = H256([2; 32]); - let validators = get_validators(); - - new_test_ext().execute_with(|| { - assert_ok!(Pallet::::get_last_paid_era(&cluster_id1, validators[0].clone()).map( - |era| { - assert_eq!(era, None); - } - )); - - let mut validators_map_1 = BTreeMap::new(); - validators_map_1.insert( - (payers_root, payees_root), - vec![validators[1].clone(), validators[2].clone(), validators[3].clone()], - ); - - let validation_1 = EraValidation { - validators: validators_map_1, - start_era: 1, - end_era: 2, - payers_merkle_root_hash: payers_root, - payees_merkle_root_hash: payees_root, - status: EraValidationStatus::ValidatingData, - }; - - >::insert(cluster_id1, era_1, validation_1); - - // still no - different accountid - assert_ok!(Pallet::::get_last_paid_era(&cluster_id1, validators[0].clone()).map( - |era| { - assert_eq!(era, None); - } - )); - - // still no - different cluster id - assert_ok!(Pallet::::get_last_paid_era(&cluster_id2, validators[1].clone()).map( - |era| { - assert_eq!(era, None); - } - )); - - let mut validators_map_2 = BTreeMap::new(); - validators_map_2 - .insert((payers_root, payees_root), vec![validators[2].clone(), validators[3].clone()]); - - let validation_2 = EraValidation { - validators: validators_map_2, - start_era: 1, - end_era: 2, - payers_merkle_root_hash: payers_root, - payees_merkle_root_hash: payees_root, - status: EraValidationStatus::ValidatingData, - }; - - >::insert(cluster_id1, era_2, validation_2); - - // Now the last validated era should be ERA_2 - assert_ok!(Pallet::::get_last_paid_era(&cluster_id1, validators[2].clone()).map( - |era| { - assert_eq!(era, Some(era_2)); - } - )); - - assert_ok!(Pallet::::get_last_paid_era(&cluster_id1, validators[1].clone()).map( - |era| { - assert_eq!(era, Some(era_1)); - } - )); - }); -} - -#[test] -fn test_get_era_for_payout() { - // Initialize test data - let cluster_id = ClusterId::default(); // Replace with actual initialization - let status = EraValidationStatus::ReadyForPayout; // Test with different statuses - - // Insert some era validations into storage - let era_id_1 = 1; - let era_id_2 = 2; - let era_validation_1 = EraValidation:: { - validators: Default::default(), - start_era: 0, - end_era: 0, - payers_merkle_root_hash: Default::default(), - payees_merkle_root_hash: Default::default(), - status: EraValidationStatus::ReadyForPayout, - }; - let era_validation_2 = EraValidation:: { - validators: Default::default(), - start_era: 0, - end_era: 0, - payers_merkle_root_hash: Default::default(), - payees_merkle_root_hash: Default::default(), - status: EraValidationStatus::PayoutInProgress, - }; - - new_test_ext().execute_with(|| { - EraValidations::::insert(cluster_id, era_id_1, &era_validation_1); - EraValidations::::insert(cluster_id, era_id_2, &era_validation_2); - - let mut result = Pallet::::get_era_for_payout(&cluster_id, status); - assert_eq!(result, Some(EraActivity { id: era_id_1, start: 0, end: 0 })); - - result = - Pallet::::get_era_for_payout(&cluster_id, EraValidationStatus::PayoutSuccess); - assert_eq!(result, None); - }); -} - #[test] fn create_merkle_root_works() { new_test_ext().execute_with(|| { @@ -2474,7 +2350,7 @@ fn create_merkle_root_works() { let leaves = vec![a, b, c, d, e]; - let root = DdcVerification::create_merkle_root(&cluster_id, era_id_1, &leaves).unwrap(); + let root = DdcVerification::create_merkle_root(&cluster_id, &leaves, era_id_1).unwrap(); assert_eq!( root, @@ -2492,7 +2368,7 @@ fn create_merkle_root_empty() { let cluster_id = ClusterId::default(); let era_id_1 = 1; let leaves = Vec::::new(); - let root = DdcVerification::create_merkle_root(&cluster_id, era_id_1, &leaves).unwrap(); + let root = DdcVerification::create_merkle_root(&cluster_id, &leaves, era_id_1).unwrap(); assert_eq!(root, DeltaUsageHash::default()); }); @@ -2554,6 +2430,7 @@ fn proof_merkle_leaf_works() { }); } +#[ignore = "DAC v5 is in progress"] #[test] fn test_single_ocw_pallet_integration() { let mut ext = new_test_ext(); @@ -2851,11 +2728,15 @@ fn fetch_reward_activities_works() { let e: DeltaUsageHash = H256([4; 32]); let leaves = [a, b, c, d, e]; - let era_id = 1; + let ehd_id = EHDId(cluster_id, G_COLLECTOR_KEY, 1); - let result = DdcVerification::fetch_charging_loop_input(&cluster_id, era_id, leaves.to_vec()); + let result = DdcVerification::fetch_ehd_charging_loop_input( + &cluster_id, + ehd_id.clone(), + leaves.to_vec(), + ); - assert_eq!(result.unwrap(), Some((era_id, (leaves.len() - 1) as u16))); + assert_eq!(result.unwrap(), Some((ehd_id, (leaves.len() - 1) as u16))); } #[test] @@ -2897,7 +2778,7 @@ fn test_find_random_merkle_node_ids() { let number_of_leaves = deffective_bucket_sub_aggregate.get_number_of_leaves(); - let ids = DdcVerification::find_random_merkle_node_ids( + let ids = DdcVerification::_v4_find_random_merkle_node_ids( 3, number_of_leaves, deffective_bucket_sub_aggregate.get_key(), @@ -2999,7 +2880,7 @@ fn challenge_bucket_sub_aggregate_works() { }; let result = - DdcVerification::_challenge_aggregate(&cluster_id, era_id, &deffective_bucket_sub_aggregate); + DdcVerification::_v4_challenge_aggregate(&cluster_id, era_id, &deffective_bucket_sub_aggregate); assert!(result.is_ok()); diff --git a/pallets/ddc-verification/src/weights.rs b/pallets/ddc-verification/src/weights.rs index ff8b0bf76..7c82bf0fa 100644 --- a/pallets/ddc-verification/src/weights.rs +++ b/pallets/ddc-verification/src/weights.rs @@ -1,7 +1,7 @@ //! Autogenerated weights for pallet_ddc_verification //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2024-12-04, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bench`, CPU: `AMD EPYC Processor` //! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -16,7 +16,7 @@ // --steps=50 // --repeat=20 // --template=./.maintain/frame-weight-template.hbs -// --output=pallets/ddc-verification/src/weights.rs +// --output=pallets/ddc-verification/src/weights3.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -27,48 +27,35 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_ddc_verification. pub trait WeightInfo { - fn set_prepare_era_for_payout(b: u32, ) -> Weight; fn set_validator_key() -> Weight; - fn commit_billing_fingerprint() -> Weight; - fn begin_billing_report() -> Weight; + fn commit_payout_fingerprint() -> Weight; + fn begin_payout() -> Weight; fn begin_charging_customers() -> Weight; fn send_charging_customers_batch(b: u32, ) -> Weight; fn end_charging_customers() -> Weight; fn begin_rewarding_providers() -> Weight; fn send_rewarding_providers_batch(b: u32, ) -> Weight; fn end_rewarding_providers() -> Weight; - fn end_billing_report() -> Weight; + fn end_payout() -> Weight; fn emit_consensus_errors(b: u32, ) -> Weight; - fn set_era_validations() -> Weight; - fn skip_dac_validation_to_era() -> Weight; + fn force_skip_inspection() -> Weight; + fn force_set_validator_key() -> Weight; } /// Weights for pallet_ddc_verification using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) - // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) - // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::EraValidations` (r:1 w:1) - // Proof: `DdcVerification::EraValidations` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `b` is `[1, 5]`. - fn set_prepare_era_for_payout(b: u32, ) -> Weight { - Weight::from_parts(33_337_691_u64, 0) - // Standard Error: 12_689 - .saturating_add(Weight::from_parts(380_174_u64, 0).saturating_mul(b as u64)) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } // Storage: `Staking::Ledger` (r:1 w:0) // Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + // Storage: `Staking::Bonded` (r:1 w:0) + // Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorToStashKey` (r:0 w:1) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_validator_key() -> Weight { - Weight::from_parts(29_155_000_u64, 0) - .saturating_add(T::DbWeight::get().reads(2_u64)) + Weight::from_parts(47_030_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) @@ -77,10 +64,10 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `DdcClusters::Clusters` (r:1 w:0) // Proof: `DdcClusters::Clusters` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::BillingFingerprints` (r:1 w:1) - // Proof: `DdcPayouts::BillingFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn commit_billing_fingerprint() -> Weight { - Weight::from_parts(40_165_000_u64, 0) + // Storage: `DdcPayouts::PayoutFingerprints` (r:1 w:1) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn commit_payout_fingerprint() -> Weight { + Weight::from_parts(50_536_000_u64, 0) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -88,25 +75,23 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::BillingFingerprints` (r:1 w:0) - // Proof: `DdcPayouts::BillingFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::EraValidations` (r:1 w:1) - // Proof: `DdcVerification::EraValidations` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn begin_billing_report() -> Weight { - Weight::from_parts(45_956_000_u64, 0) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutFingerprints` (r:1 w:0) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn begin_payout() -> Weight { + Weight::from_parts(44_564_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) fn begin_charging_customers() -> Weight { - Weight::from_parts(32_561_000_u64, 0) + Weight::from_parts(38_613_000_u64, 0) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -114,14 +99,10 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::BillingFingerprints` (r:1 w:0) - // Proof: `DdcPayouts::BillingFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcClusters::ClustersGovParams` (r:1 w:0) - // Proof: `DdcClusters::ClustersGovParams` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcCustomers::Buckets` (r:500 w:500) - // Proof: `DdcCustomers::Buckets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutFingerprints` (r:1 w:0) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcCustomers::Ledger` (r:500 w:500) // Proof: `DdcCustomers::Ledger` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `System::Account` (r:2 w:2) @@ -130,20 +111,20 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcPayouts::DebtorCustomers` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `b` is `[1, 500]`. fn send_charging_customers_batch(b: u32, ) -> Weight { - Weight::from_parts(149_151_000_u64, 0) - // Standard Error: 236_904 - .saturating_add(Weight::from_parts(80_520_850_u64, 0).saturating_mul(b as u64)) - .saturating_add(T::DbWeight::get().reads(10_u64)) - .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(b as u64))) - .saturating_add(T::DbWeight::get().writes(6_u64)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(b as u64))) + Weight::from_parts(138_341_000_u64, 0) + // Standard Error: 222_721 + .saturating_add(Weight::from_parts(64_602_746_u64, 0).saturating_mul(b as u64)) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(b as u64))) + .saturating_add(T::DbWeight::get().writes(5_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(b as u64))) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcClusters::ClustersGovParams` (r:1 w:0) // Proof: `DdcClusters::ClustersGovParams` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `System::Account` (r:3 w:3) @@ -159,7 +140,7 @@ impl WeightInfo for SubstrateWeight { // Storage: `Staking::Nominators` (r:1 w:0) // Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) fn end_charging_customers() -> Weight { - Weight::from_parts(250_869_000_u64, 0) + Weight::from_parts(272_346_000_u64, 0) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -167,10 +148,10 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) fn begin_rewarding_providers() -> Weight { - Weight::from_parts(33_362_000_u64, 0) + Weight::from_parts(39_213_000_u64, 0) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -178,32 +159,30 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::BillingFingerprints` (r:1 w:0) - // Proof: `DdcPayouts::BillingFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcNodes::StorageNodes` (r:500 w:500) - // Proof: `DdcNodes::StorageNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:501 w:501) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutFingerprints` (r:1 w:0) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:316 w:316) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `b` is `[1, 500]`. fn send_rewarding_providers_batch(b: u32, ) -> Weight { - Weight::from_parts(122_499_000_u64, 0) - // Standard Error: 54_711 - .saturating_add(Weight::from_parts(76_199_725_u64, 0).saturating_mul(b as u64)) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(b as u64))) - .saturating_add(T::DbWeight::get().writes(2_u64)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(b as u64))) + Weight::from_parts(1_956_633_025_u64, 0) + // Standard Error: 459_599 + .saturating_add(Weight::from_parts(41_100_944_u64, 0).saturating_mul(b as u64)) + .saturating_add(T::DbWeight::get().reads(48_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(b as u64))) + .saturating_add(T::DbWeight::get().writes(45_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(b as u64))) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) fn end_rewarding_providers() -> Weight { - Weight::from_parts(34_094_000_u64, 0) + Weight::from_parts(39_716_000_u64, 0) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -211,16 +190,14 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::EraValidations` (r:1 w:1) - // Proof: `DdcVerification::EraValidations` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcClusters::Clusters` (r:1 w:1) // Proof: `DdcClusters::Clusters` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn end_billing_report() -> Weight { - Weight::from_parts(62_017_000_u64, 0) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(3_u64)) + fn end_payout() -> Weight { + Weight::from_parts(57_348_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -228,53 +205,42 @@ impl WeightInfo for SubstrateWeight { // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `b` is `[1, 5]`. fn emit_consensus_errors(b: u32, ) -> Weight { - Weight::from_parts(17_280_993_u64, 0) - // Standard Error: 11_424 - .saturating_add(Weight::from_parts(3_172_369_u64, 0).saturating_mul(b as u64)) + Weight::from_parts(21_243_502_u64, 0) + // Standard Error: 16_284 + .saturating_add(Weight::from_parts(3_208_119_u64, 0).saturating_mul(b as u64)) .saturating_add(T::DbWeight::get().reads(2_u64)) } - // Storage: `DdcVerification::EraValidations` (r:1 w:1) - // Proof: `DdcVerification::EraValidations` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) - // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_era_validations() -> Weight { - Weight::from_parts(23_815_000_u64, 0) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + // Storage: `DdcClusters::Clusters` (r:1 w:1) + // Proof: `DdcClusters::Clusters` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutFingerprints` (r:0 w:1) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:0 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_skip_inspection() -> Weight { + Weight::from_parts(41_167_000_u64, 0) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - - fn skip_dac_validation_to_era() -> Weight { - Weight::from_parts(0_u64, 0) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) + fn force_set_validator_key() -> Weight { + Weight::from_parts(41_167_000_u64, 0) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) - // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) - // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::EraValidations` (r:1 w:1) - // Proof: `DdcVerification::EraValidations` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `b` is `[1, 5]`. - fn set_prepare_era_for_payout(b: u32, ) -> Weight { - Weight::from_parts(33_337_691_u64, 0) - // Standard Error: 12_689 - .saturating_add(Weight::from_parts(380_174_u64, 0).saturating_mul(b as u64)) - .saturating_add(RocksDbWeight::get().reads(3_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } // Storage: `Staking::Ledger` (r:1 w:0) // Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + // Storage: `Staking::Bonded` (r:1 w:0) + // Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorToStashKey` (r:0 w:1) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_validator_key() -> Weight { - Weight::from_parts(29_155_000_u64, 0) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + Weight::from_parts(47_030_000_u64, 0) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) @@ -283,10 +249,10 @@ impl WeightInfo for () { // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `DdcClusters::Clusters` (r:1 w:0) // Proof: `DdcClusters::Clusters` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::BillingFingerprints` (r:1 w:1) - // Proof: `DdcPayouts::BillingFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn commit_billing_fingerprint() -> Weight { - Weight::from_parts(40_165_000_u64, 0) + // Storage: `DdcPayouts::PayoutFingerprints` (r:1 w:1) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn commit_payout_fingerprint() -> Weight { + Weight::from_parts(50_536_000_u64, 0) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -294,25 +260,23 @@ impl WeightInfo for () { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::BillingFingerprints` (r:1 w:0) - // Proof: `DdcPayouts::BillingFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::EraValidations` (r:1 w:1) - // Proof: `DdcVerification::EraValidations` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn begin_billing_report() -> Weight { - Weight::from_parts(45_956_000_u64, 0) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutFingerprints` (r:1 w:0) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn begin_payout() -> Weight { + Weight::from_parts(44_564_000_u64, 0) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) fn begin_charging_customers() -> Weight { - Weight::from_parts(32_561_000_u64, 0) + Weight::from_parts(38_613_000_u64, 0) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -320,14 +284,10 @@ impl WeightInfo for () { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::BillingFingerprints` (r:1 w:0) - // Proof: `DdcPayouts::BillingFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcClusters::ClustersGovParams` (r:1 w:0) - // Proof: `DdcClusters::ClustersGovParams` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcCustomers::Buckets` (r:500 w:500) - // Proof: `DdcCustomers::Buckets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutFingerprints` (r:1 w:0) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcCustomers::Ledger` (r:500 w:500) // Proof: `DdcCustomers::Ledger` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `System::Account` (r:2 w:2) @@ -336,20 +296,20 @@ impl WeightInfo for () { // Proof: `DdcPayouts::DebtorCustomers` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `b` is `[1, 500]`. fn send_charging_customers_batch(b: u32, ) -> Weight { - Weight::from_parts(149_151_000_u64, 0) - // Standard Error: 236_904 - .saturating_add(Weight::from_parts(80_520_850_u64, 0).saturating_mul(b as u64)) - .saturating_add(RocksDbWeight::get().reads(10_u64)) - .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(b as u64))) - .saturating_add(RocksDbWeight::get().writes(6_u64)) - .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(b as u64))) + Weight::from_parts(138_341_000_u64, 0) + // Standard Error: 222_721 + .saturating_add(Weight::from_parts(64_602_746_u64, 0).saturating_mul(b as u64)) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(b as u64))) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(b as u64))) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcClusters::ClustersGovParams` (r:1 w:0) // Proof: `DdcClusters::ClustersGovParams` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `System::Account` (r:3 w:3) @@ -365,7 +325,7 @@ impl WeightInfo for () { // Storage: `Staking::Nominators` (r:1 w:0) // Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) fn end_charging_customers() -> Weight { - Weight::from_parts(250_869_000_u64, 0) + Weight::from_parts(272_346_000_u64, 0) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -373,10 +333,10 @@ impl WeightInfo for () { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) fn begin_rewarding_providers() -> Weight { - Weight::from_parts(33_362_000_u64, 0) + Weight::from_parts(39_213_000_u64, 0) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -384,32 +344,30 @@ impl WeightInfo for () { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::BillingFingerprints` (r:1 w:0) - // Proof: `DdcPayouts::BillingFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcNodes::StorageNodes` (r:500 w:500) - // Proof: `DdcNodes::StorageNodes` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `System::Account` (r:501 w:501) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutFingerprints` (r:1 w:0) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:316 w:316) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `b` is `[1, 500]`. fn send_rewarding_providers_batch(b: u32, ) -> Weight { - Weight::from_parts(122_499_000_u64, 0) - // Standard Error: 54_711 - .saturating_add(Weight::from_parts(76_199_725_u64, 0).saturating_mul(b as u64)) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(b as u64))) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(b as u64))) + Weight::from_parts(1_956_633_025_u64, 0) + // Standard Error: 459_599 + .saturating_add(Weight::from_parts(41_100_944_u64, 0).saturating_mul(b as u64)) + .saturating_add(RocksDbWeight::get().reads(48_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(b as u64))) + .saturating_add(RocksDbWeight::get().writes(45_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(b as u64))) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) fn end_rewarding_providers() -> Weight { - Weight::from_parts(34_094_000_u64, 0) + Weight::from_parts(39_716_000_u64, 0) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -417,16 +375,14 @@ impl WeightInfo for () { // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `DdcPayouts::ActiveBillingReports` (r:1 w:1) - // Proof: `DdcPayouts::ActiveBillingReports` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::EraValidations` (r:1 w:1) - // Proof: `DdcVerification::EraValidations` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:1 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) // Storage: `DdcClusters::Clusters` (r:1 w:1) // Proof: `DdcClusters::Clusters` (`max_values`: None, `max_size`: None, mode: `Measured`) - fn end_billing_report() -> Weight { - Weight::from_parts(62_017_000_u64, 0) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(3_u64)) + fn end_payout() -> Weight { + Weight::from_parts(57_348_000_u64, 0) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } // Storage: `DdcVerification::ValidatorToStashKey` (r:1 w:0) // Proof: `DdcVerification::ValidatorToStashKey` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -434,24 +390,25 @@ impl WeightInfo for () { // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `b` is `[1, 5]`. fn emit_consensus_errors(b: u32, ) -> Weight { - Weight::from_parts(17_280_993_u64, 0) - // Standard Error: 11_424 - .saturating_add(Weight::from_parts(3_172_369_u64, 0).saturating_mul(b as u64)) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - } - // Storage: `DdcVerification::EraValidations` (r:1 w:1) - // Proof: `DdcVerification::EraValidations` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `DdcVerification::ValidatorSet` (r:1 w:0) - // Proof: `DdcVerification::ValidatorSet` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - fn set_era_validations() -> Weight { - Weight::from_parts(23_815_000_u64, 0) + Weight::from_parts(21_243_502_u64, 0) + // Standard Error: 16_284 + .saturating_add(Weight::from_parts(3_208_119_u64, 0).saturating_mul(b as u64)) .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) } - - fn skip_dac_validation_to_era() -> Weight { - Weight::from_parts(0_u64, 0) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Storage: `DdcClusters::Clusters` (r:1 w:1) + // Proof: `DdcClusters::Clusters` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutFingerprints` (r:0 w:1) + // Proof: `DdcPayouts::PayoutFingerprints` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `DdcPayouts::PayoutReceipts` (r:0 w:1) + // Proof: `DdcPayouts::PayoutReceipts` (`max_values`: None, `max_size`: None, mode: `Measured`) + fn force_skip_inspection() -> Weight { + Weight::from_parts(41_167_000_u64, 0) + .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } -} + fn force_set_validator_key() -> Weight { + Weight::from_parts(41_167_000_u64, 0) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } +} \ No newline at end of file diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 12982b62a..18f87adc7 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -3,18 +3,17 @@ use blake2::{Blake2s256, Digest}; use codec::{Decode, Encode}; use frame_support::parameter_types; -use frame_system::Config; use polkadot_ckb_merkle_mountain_range::Merge; use scale_info::{ - prelude::{collections::BTreeMap, string::String, vec::Vec}, + prelude::{format, string::String, vec::Vec}, TypeInfo, }; use serde::{Deserialize, Serialize}; use sp_core::{crypto::KeyTypeId, hash::H160, H256}; use sp_runtime::{AccountId32, Perquintill, RuntimeDebug}; use sp_std::collections::btree_set::BTreeSet; - pub mod traits; +use sp_std::str::FromStr; pub mod ocw_mutex; @@ -29,6 +28,8 @@ pub const MILLICENTS: u128 = 100_000; pub const CENTS: u128 = 1_000 * MILLICENTS; // assume this is worth about a cent. pub const DOLLARS: u128 = 100 * CENTS; pub type ClusterId = H160; +pub type PaymentEra = u32; +pub type EhdEra = u32; pub type DdcEra = u32; pub type BucketId = u64; pub type ClusterNodesCount = u16; @@ -131,6 +132,8 @@ pub struct AggregatorInfo { pub node_params: StorageNodeParams, } +// The `StoragePubKey` is the only variant of DDC node key. This enum should be replaced with +// trait-bounded type. #[derive( Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, PartialEq, Eq, Encode, Decode, TypeInfo, )] @@ -138,11 +141,129 @@ pub enum NodePubKey { StoragePubKey(StorageNodePubKey), } -impl NodePubKey { - pub fn get_hex(&self) -> String { - match self { - NodePubKey::StoragePubKey(pub_key_ref) => hex::encode(pub_key_ref), +impl From for String { + fn from(node_key: NodePubKey) -> Self { + match node_key { + NodePubKey::StoragePubKey(pub_key) => format!("0x{}", hex::encode(pub_key)), + } + } +} + +impl TryFrom for NodePubKey { + type Error = (); + fn try_from(value: String) -> Result { + if !value.starts_with("0x") || value.len() != 66 { + return Err(()); + } + + let hex_str = &value[2..]; // skip '0x' + let hex_bytes = match hex::decode(hex_str) { + Ok(bytes) => bytes, + Err(_) => return Err(()), + }; + if hex_bytes.len() != 32 { + return Err(()); + } + let mut pub_key = [0u8; 32]; + pub_key.copy_from_slice(&hex_bytes[..32]); + + Ok(NodePubKey::StoragePubKey(AccountId32::from(pub_key))) + } +} + +#[derive(Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode, Debug)] +pub struct EHDId(pub ClusterId, pub NodePubKey, pub EhdEra); + +impl From for String { + fn from(ehd_id: EHDId) -> Self { + let cluster_str = format!("0x{}", hex::encode(ehd_id.0.encode())); + let node_key_str: String = ehd_id.1.clone().into(); + format!("{}-{}-{}", cluster_str, node_key_str, ehd_id.2) + } +} + +impl TryFrom for EHDId { + type Error = (); + fn try_from(value: String) -> Result { + let parts: Vec<&str> = value.split('-').collect(); + if parts.len() != 3 { + return Err(()); + } + let cluster_str = parts[0]; + let g_collector_str = String::from(parts[1]); + let payment_era_str = parts[2]; + + if !cluster_str.starts_with("0x") || cluster_str.len() != 42 { + return Err(()); + } + + let cluster_hex_str = &cluster_str[2..]; // skip '0x' + let cluster_hex_bytes = match hex::decode(cluster_hex_str) { + Ok(bytes) => bytes, + Err(_) => return Err(()), + }; + + if cluster_hex_bytes.len() != 20 { + return Err(()); } + + let mut cluster_id = [0u8; 20]; + cluster_id.copy_from_slice(&cluster_hex_bytes[..20]); + + let g_collector: NodePubKey = g_collector_str.try_into()?; + + let payment_era = match DdcEra::from_str(payment_era_str) { + Ok(era) => era, + Err(_) => return Err(()), + }; + + Ok(EHDId(H160(cluster_id), g_collector, payment_era)) + } +} + +impl TryFrom<&str> for EHDId { + type Error = (); + fn try_from(value: &str) -> Result { + EHDId::try_from(String::from(value)) + } +} + +#[derive(Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode, Debug)] +pub struct PHDId(pub NodePubKey, pub EhdEra); + +impl From for String { + fn from(ehd_id: PHDId) -> Self { + let node_key_str: String = ehd_id.0.clone().into(); + format!("{}-{}", node_key_str, ehd_id.1) + } +} + +impl TryFrom for PHDId { + type Error = (); + fn try_from(value: String) -> Result { + let parts: Vec<&str> = value.split('-').collect(); + if parts.len() != 2 { + return Err(()); + } + + let collector_str = String::from(parts[0]); + let payment_era_str = parts[1]; + + let collector: NodePubKey = collector_str.try_into()?; + + let payment_era = match DdcEra::from_str(payment_era_str) { + Ok(era) => era, + Err(_) => return Err(()), + }; + + Ok(PHDId(collector, payment_era)) + } +} + +impl TryFrom<&str> for PHDId { + type Error = (); + fn try_from(value: &str) -> Result { + PHDId::try_from(String::from(value)) } } @@ -352,7 +473,7 @@ pub enum NodeRepositoryError { #[derive(Debug, PartialEq)] pub enum PayoutError { - BillingReportDoesNotExist, + PayoutReceiptDoesNotExist, } #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Default)] @@ -420,48 +541,15 @@ pub mod crypto { } } -#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] -pub enum EraValidationStatus { - ValidatingData, - ReadyForPayout, - PayoutInProgress, - PayoutFailed, - PayoutSuccess, - PayoutSkipped, -} - -#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] -#[scale_info(skip_type_params(T))] -pub struct EraValidation { - pub validators: BTreeMap<(DeltaUsageHash, DeltaUsageHash), Vec>, - pub start_era: i64, - pub end_era: i64, - pub payers_merkle_root_hash: DeltaUsageHash, - pub payees_merkle_root_hash: DeltaUsageHash, - pub status: EraValidationStatus, -} - -impl Default for EraValidation { - fn default() -> Self { - EraValidation { - validators: Default::default(), - start_era: Default::default(), - end_era: Default::default(), - payers_merkle_root_hash: Default::default(), - payees_merkle_root_hash: Default::default(), - status: EraValidationStatus::PayoutSkipped, - } - } -} - #[derive(Default)] -pub struct BillingReportParams { +pub struct PayoutReceiptParams { pub cluster_id: ClusterId, - pub era: DdcEra, + pub era: EhdEra, pub state: PayoutState, pub fingerprint: Fingerprint, - pub total_customer_charge: CustomerCharge, - pub total_distributed_reward: u128, + pub total_collected_charges: u128, + pub total_distributed_rewards: u128, + pub total_settled_fees: u128, pub charging_max_batch_index: BatchIndex, pub charging_processed_batches: Vec, pub rewarding_max_batch_index: BatchIndex, @@ -469,14 +557,11 @@ pub struct BillingReportParams { } #[derive(Default)] -pub struct BillingFingerprintParams { +pub struct PayoutFingerprintParams { pub cluster_id: ClusterId, - pub era: DdcEra, - pub start_era: i64, - pub end_era: i64, + pub ehd_id: String, pub payers_merkle_root: PayableUsageHash, pub payees_merkle_root: PayableUsageHash, - pub cluster_usage: NodeUsage, pub validators: BTreeSet, } diff --git a/primitives/src/traits/customer.rs b/primitives/src/traits/customer.rs index 3c0d65441..399c245d4 100644 --- a/primitives/src/traits/customer.rs +++ b/primitives/src/traits/customer.rs @@ -3,9 +3,9 @@ use sp_runtime::DispatchError; use crate::BucketId; pub trait CustomerCharger { - fn charge_bucket_owner( + fn charge_customer( content_owner: T::AccountId, - billing_vault: T::AccountId, + payout_vault: T::AccountId, amount: u128, ) -> Result; } diff --git a/primitives/src/traits/payout.rs b/primitives/src/traits/payout.rs index a84c116ea..af241e306 100644 --- a/primitives/src/traits/payout.rs +++ b/primitives/src/traits/payout.rs @@ -1,84 +1,81 @@ +use scale_info::prelude::string::String; use sp_runtime::DispatchResult; use sp_std::boxed::Box; use crate::{ - BatchIndex, BillingFingerprintParams, BillingReportParams, BucketId, BucketUsage, ClusterId, - DdcEra, Fingerprint, MMRProof, NodePubKey, NodeUsage, PayableUsageHash, PayoutError, - PayoutState, + BatchIndex, ClusterId, Fingerprint, MMRProof, PayableUsageHash, PaymentEra, PayoutError, + PayoutFingerprintParams, PayoutReceiptParams, PayoutState, }; pub trait PayoutProcessor { #[allow(clippy::too_many_arguments)] - fn commit_billing_fingerprint( + fn commit_payout_fingerprint( validator: T::AccountId, cluster_id: ClusterId, - era_id: DdcEra, - start_era: i64, - end_era: i64, + ehd_id: String, payers_merkle_root: PayableUsageHash, payees_merkle_root: PayableUsageHash, - cluster_usage: NodeUsage, ) -> DispatchResult; - fn begin_billing_report( + fn begin_payout( cluster_id: ClusterId, - era_id: DdcEra, + era_id: PaymentEra, fingerprint: Fingerprint, ) -> DispatchResult; fn begin_charging_customers( cluster_id: ClusterId, - era_id: DdcEra, + era_id: PaymentEra, max_batch_index: BatchIndex, ) -> DispatchResult; fn send_charging_customers_batch( cluster_id: ClusterId, - era_id: DdcEra, + era_id: PaymentEra, batch_index: BatchIndex, - payers: &[(BucketId, BucketUsage)], + payers: &[(T::AccountId, u128)], batch_proof: MMRProof, ) -> DispatchResult; - fn end_charging_customers(cluster_id: ClusterId, era: DdcEra) -> DispatchResult; + fn end_charging_customers(cluster_id: ClusterId, era: PaymentEra) -> DispatchResult; fn begin_rewarding_providers( cluster_id: ClusterId, - era_id: DdcEra, + era_id: PaymentEra, max_batch_index: BatchIndex, ) -> DispatchResult; fn send_rewarding_providers_batch( cluster_id: ClusterId, - era_id: DdcEra, + era_id: PaymentEra, batch_index: BatchIndex, - payees: &[(NodePubKey, NodeUsage)], + payees: &[(T::AccountId, u128)], batch_proof: MMRProof, ) -> DispatchResult; - fn end_rewarding_providers(cluster_id: ClusterId, era_id: DdcEra) -> DispatchResult; + fn end_rewarding_providers(cluster_id: ClusterId, era_id: PaymentEra) -> DispatchResult; - fn end_billing_report(cluster_id: ClusterId, era_id: DdcEra) -> DispatchResult; + fn end_payout(cluster_id: ClusterId, era_id: PaymentEra) -> DispatchResult; - fn get_billing_report_status(cluster_id: &ClusterId, era_id: DdcEra) -> PayoutState; + fn get_payout_state(cluster_id: &ClusterId, era_id: PaymentEra) -> PayoutState; - fn all_customer_batches_processed(cluster_id: &ClusterId, era_id: DdcEra) -> bool; + fn is_customers_charging_finished(cluster_id: &ClusterId, era_id: PaymentEra) -> bool; - fn all_provider_batches_processed(cluster_id: &ClusterId, era_id: DdcEra) -> bool; + fn is_providers_rewarding_finished(cluster_id: &ClusterId, era_id: PaymentEra) -> bool; - fn get_next_customer_batch_for_payment( + fn get_next_customers_batch( cluster_id: &ClusterId, - era_id: DdcEra, + era_id: PaymentEra, ) -> Result, PayoutError>; - fn get_next_provider_batch_for_payment( + fn get_next_providers_batch( cluster_id: &ClusterId, - era_id: DdcEra, + era_id: PaymentEra, ) -> Result, PayoutError>; - fn create_billing_report(vault: T::AccountId, params: BillingReportParams); + fn create_payout_receipt(vault: T::AccountId, params: PayoutReceiptParams); - fn create_billing_fingerprint(_params: BillingFingerprintParams) -> Fingerprint; + fn create_payout_fingerprint(params: PayoutFingerprintParams) -> Fingerprint; } pub trait StorageUsageProvider { diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index 0af6d2e52..68cde628a 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -155,7 +155,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 71000, + spec_version: 72000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 24, @@ -1333,9 +1333,10 @@ impl pallet_ddc_verification::Config for Runtime { type CustomerVisitor = pallet_ddc_customers::Pallet; const MAX_MERKLE_NODE_IDENTIFIER: u16 = 3; type Currency = Balances; - const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool = true; + const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool = false; type BucketsStorageUsageProvider = DdcCustomers; type NodesStorageUsageProvider = DdcNodes; + type ClusterProtocol = DdcClusters; #[cfg(feature = "runtime-benchmarks")] type CustomerDepositor = DdcCustomers; #[cfg(feature = "runtime-benchmarks")] diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 9de8562e5..48872dfa8 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -148,7 +148,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 71000, + spec_version: 72000, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 24, @@ -1356,6 +1356,7 @@ impl pallet_ddc_verification::Config for Runtime { const VERIFY_AGGREGATOR_RESPONSE_SIGNATURE: bool = true; type BucketsStorageUsageProvider = DdcCustomers; type NodesStorageUsageProvider = DdcNodes; + type ClusterProtocol = DdcClusters; #[cfg(feature = "runtime-benchmarks")] type CustomerDepositor = DdcCustomers; #[cfg(feature = "runtime-benchmarks")] @@ -1574,10 +1575,13 @@ pub type CheckedExtrinsic = generic::CheckedExtrinsic, + pallet_ddc_verification::migrations::v2::MigrateToV2, +); pub mod migrations { use super::*; @@ -1600,6 +1604,8 @@ pub mod migrations { pallet_ddc_verification::migrations::v1::MigrateToV1, pallet_ddc_payouts::migrations::v1::MigrateToV1, pallet_ddc_payouts::migrations::v2::MigrateToV2, + pallet_ddc_payouts::migrations::v3::MigrateToV3, + pallet_ddc_verification::migrations::v2::MigrateToV2, ); }