feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
[package]
|
||||
name = "pezpallet-transaction-storage"
|
||||
version = "27.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Storage chain pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
array-bytes = { workspace = true, default-features = true }
|
||||
codec = { workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pezpallet-balances = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { optional = true, workspace = true, default-features = true }
|
||||
pezsp-inherents = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-transaction-storage-proof = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezsp-transaction-storage-proof = { default-features = true, workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-balances/std",
|
||||
"scale-info/std",
|
||||
"serde",
|
||||
"pezsp-inherents/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-inherents/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-transaction-storage-proof/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,86 @@
|
||||
# Transaction Storage Pallet
|
||||
|
||||
Indexes transactions and manages storage proofs.
|
||||
|
||||
Allows storing arbitrary data on the chain. Data is automatically removed after `StoragePeriod` blocks, unless the
|
||||
storage is renewed. Validators must submit proof of storing a random chunk of data for block `N - StoragePeriod` when
|
||||
producing block `N`.
|
||||
|
||||
# Running a chain
|
||||
|
||||
The following describes how to set up a new storage chain.
|
||||
|
||||
Start with generating a chain spec.
|
||||
|
||||
```bash
|
||||
cargo run --release -- build-spec --chain=local > sc_init.json
|
||||
```
|
||||
|
||||
Edit the json chain spec file to customise the chain. The storage chain genesis params are configured in the
|
||||
`transactionStorage` section. Note that `storagePeriod` is specified in blocks and changing it also requires code
|
||||
changes at the moment.
|
||||
|
||||
Build a raw spec from the init spec.
|
||||
|
||||
```bash
|
||||
cargo run --release build-spec --chain=sc_init.json --raw > sc.json
|
||||
```
|
||||
|
||||
Run a few validator nodes.
|
||||
|
||||
```bash
|
||||
cargo run --release -- --chain=sc.json -d /tmp/alice --storage-chain --keep-blocks=100800 --ipfs-server --validator --alice
|
||||
cargo run --release -- --chain=sc.json -d /tmp/bob --storage-chain --keep-blocks=100800 --ipfs-server --validator --bob
|
||||
```
|
||||
|
||||
`--storage-chain` enables transaction indexing. `--keep-blocks=100800` enables block pruning. The value here should be
|
||||
greater or equal than the storage period. `--ipfs-server` enables serving stored content over IPFS.
|
||||
|
||||
Once the network is started, any other joining nodes need to sync with `--sync=fast`. Regular sync will fail because
|
||||
block pruning removes old blocks. The chain does not keep full block history.
|
||||
|
||||
```bash
|
||||
cargo run --release -- --chain=sc.json -d /tmp/charlie --storage-chain --keep-blocks=100800 --ipfs-server --validator --charlie --sync=fast
|
||||
```
|
||||
|
||||
# Making transactions
|
||||
|
||||
To store data use the `transactionStorage.store` extrinsic. And IPFS CID can be generated from the Blake2-256 hash of
|
||||
the data.
|
||||
|
||||
```js
|
||||
const util_crypto = require('@pezkuwi/util-crypto');
|
||||
const keyring_api = require('@pezkuwi/keyring');
|
||||
const pezkuwi_api = require('@pezkuwi/api');
|
||||
const fs = require('fs');
|
||||
const multihash = require('multihashes');
|
||||
const CID = require('cids')
|
||||
|
||||
const wsProvider = new pezkuwi_api.WsProvider();
|
||||
const api = await pezkuwi_api.ApiPromise.create({ provider: wsProvider });
|
||||
|
||||
const keyring = new keyring_api.Keyring({ type: "sr25519" });
|
||||
const alice = keyring.addFromUri("//Alice");
|
||||
|
||||
const file = fs.readFileSync('cute_kitten.jpeg');
|
||||
const hash = util_crypto.blake2AsU8a(file)
|
||||
const encoded_hash = multihash.encode(hash, 'blake2b-256');
|
||||
|
||||
const cid = new CID(1, 'blake2b-256', encoded_hash)
|
||||
console.log(cid.toString());
|
||||
|
||||
const txHash = await api.tx.transactionStorage.store('0x' + file.toString('hex')).signAndSend(alice);
|
||||
```
|
||||
Data can be queried over IPFS
|
||||
|
||||
```bash
|
||||
ipfs swarm connect <bizinikiwi peer address>
|
||||
ipfs block get /ipfs/<CID> > kitten.jpeg
|
||||
```
|
||||
|
||||
To renew data and prevent it from being disposed after the storage period, use `transactionStorage.renew(block, index)`
|
||||
where `block` is the block number of the previous store or renew transaction, and index is the index of that transaction
|
||||
in the block.
|
||||
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,184 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for transaction-storage Pallet
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use crate::*;
|
||||
use alloc::{vec, vec::Vec};
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::traits::{Get, OnFinalize, OnInitialize};
|
||||
use pezframe_system::{pezpallet_prelude::BlockNumberFor, EventRecord, Pallet as System, RawOrigin};
|
||||
use pezsp_runtime::traits::{Bounded, CheckedDiv, One, Zero};
|
||||
use pezsp_transaction_storage_proof::TransactionStorageProof;
|
||||
|
||||
// Proof generated from max size storage:
|
||||
// ```
|
||||
// let mut transactions = Vec::new();
|
||||
// let tx_size = DEFAULT_MAX_TRANSACTION_SIZE;
|
||||
// for _ in 0..DEFAULT_MAX_BLOCK_TRANSACTIONS {
|
||||
// transactions.push(vec![0; tx_size]);
|
||||
// }
|
||||
// let hash = vec![0; 32];
|
||||
// build_proof(hash.as_slice(), transactions).unwrap().encode()
|
||||
// ```
|
||||
// while hardforcing target chunk key in `build_proof` to [22, 21, 1, 0].
|
||||
const PROOF: &str = "\
|
||||
0104000000000000000000000000000000000000000000000000000000000000000000000000\
|
||||
0000000000000000000000000000000000000000000000000000000000000000000000000000\
|
||||
0000000000000000000000000000000000000000000000000000000000000000000000000000\
|
||||
0000000000000000000000000000000000000000000000000000000000000000000000000000\
|
||||
0000000000000000000000000000000000000000000000000000000000000000000000000000\
|
||||
0000000000000000000000000000000000000000000000000000000000000000000000000000\
|
||||
00000000000000000000000000000000000000000000000000000000000014cd0780ffff8030\
|
||||
2eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba0080302eb0a6d2\
|
||||
f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15\
|
||||
f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1\
|
||||
004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e304\
|
||||
8cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697\
|
||||
eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a\
|
||||
30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302e\
|
||||
b0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b\
|
||||
834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e7\
|
||||
29d1c1004657e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c10046\
|
||||
57e3048cf206d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf2\
|
||||
06d697eeb153f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb1\
|
||||
53f61a30ba80302eb0a6d2f63b834d15f1e729d1c1004657e3048cf206d697eeb153f61a30ba\
|
||||
bd058077778010fd81bc1359802f0b871aeb95e4410a8ec92b93af10ea767a2027cf4734e8de\
|
||||
808da338e6b722f7bf2051901bd5bccee5e71d5cf6b1faff338ad7120b0256c28380221ce17f\
|
||||
19117affa96e077905fe48a99723a065969c638593b7d9ab57b538438010fd81bc1359802f0b\
|
||||
871aeb95e4410a8ec92b93af10ea767a2027cf4734e8de808da338e6b722f7bf2051901bd5bc\
|
||||
cee5e71d5cf6b1faff338ad7120b0256c283008010fd81bc1359802f0b871aeb95e4410a8ec9\
|
||||
2b93af10ea767a2027cf4734e8de808da338e6b722f7bf2051901bd5bccee5e71d5cf6b1faff\
|
||||
338ad7120b0256c28380221ce17f19117affa96e077905fe48a99723a065969c638593b7d9ab\
|
||||
57b538438010fd81bc1359802f0b871aeb95e4410a8ec92b93af10ea767a2027cf4734e8de80\
|
||||
8da338e6b722f7bf2051901bd5bccee5e71d5cf6b1faff338ad7120b0256c28380221ce17f19\
|
||||
117affa96e077905fe48a99723a065969c638593b7d9ab57b53843cd0780ffff804509f59593\
|
||||
fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939c00804509f59593fd47b1a9\
|
||||
7189127ba65a5649cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba6\
|
||||
5a5649cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0\
|
||||
346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f983\
|
||||
6e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf89\
|
||||
1a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939c8045\
|
||||
09f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939c804509f59593fd\
|
||||
47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189\
|
||||
127ba65a5649cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a56\
|
||||
49cfb0346637f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb03466\
|
||||
37f9836e155eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e15\
|
||||
5eaf891a939c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a93\
|
||||
9c804509f59593fd47b1a97189127ba65a5649cfb0346637f9836e155eaf891a939ccd0780ff\
|
||||
ff8078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e\
|
||||
776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea\
|
||||
05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f\
|
||||
015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d\
|
||||
06feafa3610fc44a5b2ef543cb81008078916e776c64ccea05e958559f015c082d9d06feafa3\
|
||||
610fc44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b\
|
||||
2ef543cb818078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb81\
|
||||
8078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e77\
|
||||
6c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05\
|
||||
e958559f015c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f01\
|
||||
5c082d9d06feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d06\
|
||||
feafa3610fc44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d06feafa3610f\
|
||||
c44a5b2ef543cb818078916e776c64ccea05e958559f015c082d9d06feafa3610fc44a5b2ef5\
|
||||
43cb811044010000\
|
||||
";
|
||||
fn proof() -> Vec<u8> {
|
||||
array_bytes::hex2bytes_unchecked(PROOF)
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
let events = System::<T>::events();
|
||||
let system_event: <T as pezframe_system::Config>::RuntimeEvent = generic_event.into();
|
||||
let EventRecord { event, .. } = &events[events.len() - 1];
|
||||
assert_eq!(event, &system_event);
|
||||
}
|
||||
|
||||
pub fn run_to_block<T: Config>(n: pezframe_system::pezpallet_prelude::BlockNumberFor<T>) {
|
||||
while pezframe_system::Pallet::<T>::block_number() < n {
|
||||
crate::Pallet::<T>::on_finalize(pezframe_system::Pallet::<T>::block_number());
|
||||
pezframe_system::Pallet::<T>::on_finalize(pezframe_system::Pallet::<T>::block_number());
|
||||
pezframe_system::Pallet::<T>::set_block_number(
|
||||
pezframe_system::Pallet::<T>::block_number() + One::one(),
|
||||
);
|
||||
pezframe_system::Pallet::<T>::on_initialize(pezframe_system::Pallet::<T>::block_number());
|
||||
crate::Pallet::<T>::on_initialize(pezframe_system::Pallet::<T>::block_number());
|
||||
}
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn store(l: Linear<1, { T::MaxTransactionSize::get() }>) {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let initial_balance = BalanceOf::<T>::max_value().checked_div(&2u32.into()).unwrap();
|
||||
T::Currency::set_balance(&caller, initial_balance);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), vec![0u8; l as usize]);
|
||||
|
||||
assert!(!BlockTransactions::<T>::get().is_empty());
|
||||
assert_last_event::<T>(Event::Stored { index: 0 }.into());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn renew() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let initial_balance = BalanceOf::<T>::max_value().checked_div(&2u32.into()).unwrap();
|
||||
T::Currency::set_balance(&caller, initial_balance);
|
||||
Pallet::<T>::store(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
vec![0u8; T::MaxTransactionSize::get() as usize],
|
||||
)?;
|
||||
run_to_block::<T>(1u32.into());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), BlockNumberFor::<T>::zero(), 0);
|
||||
|
||||
assert_last_event::<T>(Event::Renewed { index: 0 }.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn check_proof_max() -> Result<(), BenchmarkError> {
|
||||
run_to_block::<T>(1u32.into());
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let initial_balance = BalanceOf::<T>::max_value().checked_div(&2u32.into()).unwrap();
|
||||
T::Currency::set_balance(&caller, initial_balance);
|
||||
for _ in 0..T::MaxBlockTransactions::get() {
|
||||
Pallet::<T>::store(
|
||||
RawOrigin::Signed(caller.clone()).into(),
|
||||
vec![0u8; T::MaxTransactionSize::get() as usize],
|
||||
)?;
|
||||
}
|
||||
run_to_block::<T>(StoragePeriod::<T>::get() + BlockNumberFor::<T>::one());
|
||||
let encoded_proof = proof();
|
||||
let proof = TransactionStorageProof::decode(&mut &*encoded_proof).unwrap();
|
||||
|
||||
#[extrinsic_call]
|
||||
check_proof(RawOrigin::None, proof);
|
||||
|
||||
assert_last_event::<T>(Event::ProofChecked.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, mock::new_test_ext(), mock::Test);
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Transaction storage pallet. Indexes transactions and manages storage proofs.
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod benchmarking;
|
||||
pub mod weights;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use core::result;
|
||||
use pezframe_support::{
|
||||
dispatch::GetDispatchInfo,
|
||||
traits::{
|
||||
fungible::{hold::Balanced, Inspect, Mutate, MutateHold},
|
||||
tokens::fungible::Credit,
|
||||
OnUnbalanced,
|
||||
},
|
||||
};
|
||||
use pezsp_runtime::traits::{BlakeTwo256, Dispatchable, Hash, One, Saturating, Zero};
|
||||
use pezsp_transaction_storage_proof::{
|
||||
encode_index, num_chunks, random_chunk, ChunkIndex, InherentError, TransactionStorageProof,
|
||||
CHUNK_SIZE, INHERENT_IDENTIFIER,
|
||||
};
|
||||
|
||||
/// A type alias for the balance type from this pallet's point of view.
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Inspect<<T as pezframe_system::Config>::AccountId>>::Balance;
|
||||
pub type CreditOf<T> = Credit<<T as pezframe_system::Config>::AccountId, <T as Config>::Currency>;
|
||||
|
||||
// Re-export pallet items so that they can be accessed from the crate namespace.
|
||||
pub use pallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
/// Maximum bytes that can be stored in one transaction.
|
||||
// Setting higher limit also requires raising the allocator limit.
|
||||
pub const DEFAULT_MAX_TRANSACTION_SIZE: u32 = 8 * 1024 * 1024;
|
||||
pub const DEFAULT_MAX_BLOCK_TRANSACTIONS: u32 = 512;
|
||||
|
||||
/// State data for a stored transaction.
|
||||
#[derive(
|
||||
Encode,
|
||||
Decode,
|
||||
Clone,
|
||||
pezsp_runtime::RuntimeDebug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
scale_info::TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct TransactionInfo {
|
||||
/// Chunk trie root.
|
||||
chunk_root: <BlakeTwo256 as Hash>::Output,
|
||||
/// Plain hash of indexed data.
|
||||
content_hash: <BlakeTwo256 as Hash>::Output,
|
||||
/// Size of indexed data in bytes.
|
||||
size: u32,
|
||||
/// Total number of chunks added in the block with this transaction. This
|
||||
/// is used to find transaction info by block chunk index using binary search.
|
||||
///
|
||||
/// Cumulative value of all previous transactions in the block; the last transaction holds the
|
||||
/// total chunks value.
|
||||
block_chunks: ChunkIndex,
|
||||
}
|
||||
|
||||
impl TransactionInfo {
|
||||
/// Get the number of total chunks.
|
||||
///
|
||||
/// See the `block_chunks` field of [`TransactionInfo`] for details.
|
||||
pub fn total_chunks(txs: &[TransactionInfo]) -> ChunkIndex {
|
||||
txs.last().map_or(0, |t| t.block_chunks)
|
||||
}
|
||||
}
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
/// A reason for this pallet placing a hold on funds.
|
||||
#[pallet::composite_enum]
|
||||
pub enum HoldReason {
|
||||
/// The funds are held as deposit for the used storage.
|
||||
StorageFeeHold,
|
||||
}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: pezframe_system::Config {
|
||||
/// The overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
/// A dispatchable call.
|
||||
type RuntimeCall: Parameter
|
||||
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
|
||||
+ GetDispatchInfo
|
||||
+ From<pezframe_system::Call<Self>>;
|
||||
/// The fungible type for this pallet.
|
||||
type Currency: Mutate<Self::AccountId>
|
||||
+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
|
||||
+ Balanced<Self::AccountId>;
|
||||
/// The overarching runtime hold reason.
|
||||
type RuntimeHoldReason: From<HoldReason>;
|
||||
/// Handler for the unbalanced decrease when fees are burned.
|
||||
type FeeDestination: OnUnbalanced<CreditOf<Self>>;
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
/// Maximum number of indexed transactions in the block.
|
||||
type MaxBlockTransactions: Get<u32>;
|
||||
/// Maximum data set in a single transaction in bytes.
|
||||
type MaxTransactionSize: Get<u32>;
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Invalid configuration.
|
||||
NotConfigured,
|
||||
/// Renewed extrinsic is not found.
|
||||
RenewedNotFound,
|
||||
/// Attempting to store an empty transaction
|
||||
EmptyTransaction,
|
||||
/// Proof was not expected in this block.
|
||||
UnexpectedProof,
|
||||
/// Proof failed verification.
|
||||
InvalidProof,
|
||||
/// Missing storage proof.
|
||||
MissingProof,
|
||||
/// Unable to verify proof because state data is missing.
|
||||
MissingStateData,
|
||||
/// Double proof check in the block.
|
||||
DoubleCheck,
|
||||
/// Storage proof was not checked in the block.
|
||||
ProofNotChecked,
|
||||
/// Transaction is too large.
|
||||
TransactionTooLarge,
|
||||
/// Too many transactions in the block.
|
||||
TooManyTransactions,
|
||||
/// Attempted to call `store` outside of block execution.
|
||||
BadContext,
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
|
||||
// TODO: https://github.com/pezkuwichain/pezkuwi-sdk/issues/160 - Replace this with benchmarked weights.
|
||||
let mut weight = Weight::zero();
|
||||
let db_weight = T::DbWeight::get();
|
||||
|
||||
// Drop obsolete roots. The proof for `obsolete` will be checked later
|
||||
// in this block, so we drop `obsolete` - 1.
|
||||
weight.saturating_accrue(db_weight.reads(1));
|
||||
let period = StoragePeriod::<T>::get();
|
||||
let obsolete = n.saturating_sub(period.saturating_add(One::one()));
|
||||
if obsolete > Zero::zero() {
|
||||
weight.saturating_accrue(db_weight.writes(1));
|
||||
Transactions::<T>::remove(obsolete);
|
||||
}
|
||||
|
||||
// For `on_finalize`
|
||||
weight.saturating_accrue(db_weight.reads_writes(3, 1));
|
||||
weight
|
||||
}
|
||||
|
||||
fn on_finalize(n: BlockNumberFor<T>) {
|
||||
assert!(
|
||||
ProofChecked::<T>::take() || {
|
||||
// Proof is not required for early or empty blocks.
|
||||
let number = pezframe_system::Pallet::<T>::block_number();
|
||||
let period = StoragePeriod::<T>::get();
|
||||
let target_number = number.saturating_sub(period);
|
||||
|
||||
target_number.is_zero() || {
|
||||
// An empty block means no transactions were stored, relying on the fact
|
||||
// below that we store transactions only if they contain chunks.
|
||||
!Transactions::<T>::contains_key(target_number)
|
||||
}
|
||||
},
|
||||
"Storage proof must be checked once in the block"
|
||||
);
|
||||
// Insert new transactions, iff they have chunks.
|
||||
let transactions = BlockTransactions::<T>::take();
|
||||
let total_chunks = TransactionInfo::total_chunks(&transactions);
|
||||
if total_chunks != 0 {
|
||||
Transactions::<T>::insert(n, transactions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Index and store data off chain. Minimum data size is 1 bytes, maximum is
|
||||
/// `MaxTransactionSize`. Data will be removed after `STORAGE_PERIOD` blocks, unless `renew`
|
||||
/// is called.
|
||||
/// ## Complexity
|
||||
/// - O(n*log(n)) of data size, as all data is pushed to an in-memory trie.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::WeightInfo::store(data.len() as u32))]
|
||||
pub fn store(origin: OriginFor<T>, data: Vec<u8>) -> DispatchResult {
|
||||
ensure!(data.len() > 0, Error::<T>::EmptyTransaction);
|
||||
ensure!(
|
||||
data.len() <= T::MaxTransactionSize::get() as usize,
|
||||
Error::<T>::TransactionTooLarge
|
||||
);
|
||||
let sender = ensure_signed(origin)?;
|
||||
Self::apply_fee(sender, data.len() as u32)?;
|
||||
|
||||
// Chunk data and compute storage root
|
||||
let chunk_count = num_chunks(data.len() as u32);
|
||||
let chunks = data.chunks(CHUNK_SIZE).map(|c| c.to_vec()).collect();
|
||||
let root = pezsp_io::trie::blake2_256_ordered_root(chunks, pezsp_runtime::StateVersion::V1);
|
||||
|
||||
let content_hash = pezsp_io::hashing::blake2_256(&data);
|
||||
let extrinsic_index =
|
||||
pezframe_system::Pallet::<T>::extrinsic_index().ok_or(Error::<T>::BadContext)?;
|
||||
pezsp_io::transaction_index::index(extrinsic_index, data.len() as u32, content_hash);
|
||||
|
||||
let mut index = 0;
|
||||
BlockTransactions::<T>::mutate(|transactions| {
|
||||
if transactions.len() + 1 > T::MaxBlockTransactions::get() as usize {
|
||||
return Err(Error::<T>::TooManyTransactions);
|
||||
}
|
||||
let total_chunks = TransactionInfo::total_chunks(&transactions) + chunk_count;
|
||||
index = transactions.len() as u32;
|
||||
transactions
|
||||
.try_push(TransactionInfo {
|
||||
chunk_root: root,
|
||||
size: data.len() as u32,
|
||||
content_hash: content_hash.into(),
|
||||
block_chunks: total_chunks,
|
||||
})
|
||||
.map_err(|_| Error::<T>::TooManyTransactions)?;
|
||||
Ok(())
|
||||
})?;
|
||||
Self::deposit_event(Event::Stored { index });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renew previously stored data. Parameters are the block number that contains
|
||||
/// previous `store` or `renew` call and transaction index within that block.
|
||||
/// Transaction index is emitted in the `Stored` or `Renewed` event.
|
||||
/// Applies same fees as `store`.
|
||||
/// ## Complexity
|
||||
/// - O(1).
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(T::WeightInfo::renew())]
|
||||
pub fn renew(
|
||||
origin: OriginFor<T>,
|
||||
block: BlockNumberFor<T>,
|
||||
index: u32,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let sender = ensure_signed(origin)?;
|
||||
let transactions = Transactions::<T>::get(block).ok_or(Error::<T>::RenewedNotFound)?;
|
||||
let info = transactions.get(index as usize).ok_or(Error::<T>::RenewedNotFound)?;
|
||||
let extrinsic_index =
|
||||
pezframe_system::Pallet::<T>::extrinsic_index().ok_or(Error::<T>::BadContext)?;
|
||||
|
||||
Self::apply_fee(sender, info.size)?;
|
||||
|
||||
pezsp_io::transaction_index::renew(extrinsic_index, info.content_hash.into());
|
||||
|
||||
let mut index = 0;
|
||||
BlockTransactions::<T>::mutate(|transactions| {
|
||||
if transactions.len() + 1 > T::MaxBlockTransactions::get() as usize {
|
||||
return Err(Error::<T>::TooManyTransactions);
|
||||
}
|
||||
let chunks = num_chunks(info.size);
|
||||
let total_chunks = TransactionInfo::total_chunks(&transactions) + chunks;
|
||||
index = transactions.len() as u32;
|
||||
transactions
|
||||
.try_push(TransactionInfo {
|
||||
chunk_root: info.chunk_root,
|
||||
size: info.size,
|
||||
content_hash: info.content_hash,
|
||||
block_chunks: total_chunks,
|
||||
})
|
||||
.map_err(|_| Error::<T>::TooManyTransactions)
|
||||
})?;
|
||||
Self::deposit_event(Event::Renewed { index });
|
||||
Ok(().into())
|
||||
}
|
||||
|
||||
/// Check storage proof for block number `block_number() - StoragePeriod`.
|
||||
/// If such a block does not exist, the proof is expected to be `None`.
|
||||
///
|
||||
/// ## Complexity
|
||||
/// - Linear w.r.t the number of indexed transactions in the proved block for random
|
||||
/// probing.
|
||||
/// There's a DB read for each transaction.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight((T::WeightInfo::check_proof_max(), DispatchClass::Mandatory))]
|
||||
pub fn check_proof(
|
||||
origin: OriginFor<T>,
|
||||
proof: TransactionStorageProof,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_none(origin)?;
|
||||
ensure!(!ProofChecked::<T>::get(), Error::<T>::DoubleCheck);
|
||||
|
||||
// Get the target block metadata.
|
||||
let number = pezframe_system::Pallet::<T>::block_number();
|
||||
let period = StoragePeriod::<T>::get();
|
||||
let target_number = number.saturating_sub(period);
|
||||
ensure!(!target_number.is_zero(), Error::<T>::UnexpectedProof);
|
||||
let transactions =
|
||||
Transactions::<T>::get(target_number).ok_or(Error::<T>::MissingStateData)?;
|
||||
|
||||
// Verify the proof with a "random" chunk (randomness is based on the parent hash).
|
||||
let parent_hash = pezframe_system::Pallet::<T>::parent_hash();
|
||||
Self::verify_chunk_proof(proof, parent_hash.as_ref(), transactions.to_vec())?;
|
||||
ProofChecked::<T>::put(true);
|
||||
Self::deposit_event(Event::ProofChecked);
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// Stored data under specified index.
|
||||
Stored { index: u32 },
|
||||
/// Renewed data under specified index.
|
||||
Renewed { index: u32 },
|
||||
/// Storage proof was successfully checked.
|
||||
ProofChecked,
|
||||
}
|
||||
|
||||
/// Collection of transaction metadata by block number.
|
||||
#[pallet::storage]
|
||||
pub type Transactions<T: Config> = StorageMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
BlockNumberFor<T>,
|
||||
BoundedVec<TransactionInfo, T::MaxBlockTransactions>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
#[pallet::storage]
|
||||
/// Storage fee per byte.
|
||||
pub type ByteFee<T: Config> = StorageValue<_, BalanceOf<T>>;
|
||||
|
||||
#[pallet::storage]
|
||||
/// Storage fee per transaction.
|
||||
pub type EntryFee<T: Config> = StorageValue<_, BalanceOf<T>>;
|
||||
|
||||
/// Storage period for data in blocks. Should match `pezsp_storage_proof::DEFAULT_STORAGE_PERIOD`
|
||||
/// for block authoring.
|
||||
#[pallet::storage]
|
||||
pub type StoragePeriod<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
|
||||
|
||||
// Intermediates
|
||||
#[pallet::storage]
|
||||
pub type BlockTransactions<T: Config> =
|
||||
StorageValue<_, BoundedVec<TransactionInfo, T::MaxBlockTransactions>, ValueQuery>;
|
||||
|
||||
/// Was the proof checked in this block?
|
||||
#[pallet::storage]
|
||||
pub type ProofChecked<T: Config> = StorageValue<_, bool, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub byte_fee: BalanceOf<T>,
|
||||
pub entry_fee: BalanceOf<T>,
|
||||
pub storage_period: BlockNumberFor<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
byte_fee: 10u32.into(),
|
||||
entry_fee: 1000u32.into(),
|
||||
storage_period: pezsp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
ByteFee::<T>::put(&self.byte_fee);
|
||||
EntryFee::<T>::put(&self.entry_fee);
|
||||
StoragePeriod::<T>::put(&self.storage_period);
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::inherent]
|
||||
impl<T: Config> ProvideInherent for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
type Error = InherentError;
|
||||
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
|
||||
|
||||
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
|
||||
let proof = data
|
||||
.get_data::<TransactionStorageProof>(&Self::INHERENT_IDENTIFIER)
|
||||
.unwrap_or(None);
|
||||
proof.map(|proof| Call::check_proof { proof })
|
||||
}
|
||||
|
||||
fn check_inherent(
|
||||
_call: &Self::Call,
|
||||
_data: &InherentData,
|
||||
) -> result::Result<(), Self::Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_inherent(call: &Self::Call) -> bool {
|
||||
matches!(call, Call::check_proof { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Get transaction storage information from outside of this pallet.
|
||||
pub fn transaction_roots(
|
||||
block: BlockNumberFor<T>,
|
||||
) -> Option<BoundedVec<TransactionInfo, T::MaxBlockTransactions>> {
|
||||
Transactions::<T>::get(block)
|
||||
}
|
||||
/// Get ByteFee storage information from outside of this pallet.
|
||||
pub fn byte_fee() -> Option<BalanceOf<T>> {
|
||||
ByteFee::<T>::get()
|
||||
}
|
||||
/// Get EntryFee storage information from outside of this pallet.
|
||||
pub fn entry_fee() -> Option<BalanceOf<T>> {
|
||||
EntryFee::<T>::get()
|
||||
}
|
||||
|
||||
fn apply_fee(sender: T::AccountId, size: u32) -> DispatchResult {
|
||||
let byte_fee = ByteFee::<T>::get().ok_or(Error::<T>::NotConfigured)?;
|
||||
let entry_fee = EntryFee::<T>::get().ok_or(Error::<T>::NotConfigured)?;
|
||||
let fee = byte_fee.saturating_mul(size.into()).saturating_add(entry_fee);
|
||||
T::Currency::hold(&HoldReason::StorageFeeHold.into(), &sender, fee)?;
|
||||
let (credit, _remainder) =
|
||||
T::Currency::slash(&HoldReason::StorageFeeHold.into(), &sender, fee);
|
||||
debug_assert!(_remainder.is_zero());
|
||||
T::FeeDestination::on_unbalanced(credit);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies that the provided proof corresponds to a randomly selected chunk from a list of
|
||||
/// transactions.
|
||||
pub(crate) fn verify_chunk_proof(
|
||||
proof: TransactionStorageProof,
|
||||
random_hash: &[u8],
|
||||
infos: Vec<TransactionInfo>,
|
||||
) -> Result<(), Error<T>> {
|
||||
// Get the random chunk index - from all transactions in the block = [0..total_chunks).
|
||||
let total_chunks: ChunkIndex = TransactionInfo::total_chunks(&infos);
|
||||
ensure!(total_chunks != 0, Error::<T>::UnexpectedProof);
|
||||
let selected_block_chunk_index = random_chunk(random_hash, total_chunks as _);
|
||||
|
||||
// Let's find the corresponding transaction and its "local" chunk index for "global"
|
||||
// `selected_block_chunk_index`.
|
||||
let (tx_info, tx_chunk_index) = {
|
||||
// Binary search for the transaction that owns this `selected_block_chunk_index`
|
||||
// chunk.
|
||||
let tx_index = infos
|
||||
.binary_search_by_key(&selected_block_chunk_index, |info| {
|
||||
// Each `info.block_chunks` is cumulative count,
|
||||
// so last chunk index = count - 1.
|
||||
info.block_chunks.saturating_sub(1)
|
||||
})
|
||||
.unwrap_or_else(|tx_index| tx_index);
|
||||
|
||||
// Get the transaction and its local chunk index.
|
||||
let tx_info = infos.get(tx_index).ok_or(Error::<T>::MissingStateData)?;
|
||||
// We shouldn't reach this point; we rely on the fact that `fn store` does not allow
|
||||
// empty transactions. Without this check, it would fail anyway below with
|
||||
// `InvalidProof`.
|
||||
ensure!(!tx_info.block_chunks.is_zero(), Error::<T>::EmptyTransaction);
|
||||
|
||||
// Convert a global chunk index into a transaction-local one.
|
||||
let tx_chunks = num_chunks(tx_info.size);
|
||||
let prev_chunks = tx_info.block_chunks - tx_chunks;
|
||||
let tx_chunk_index = selected_block_chunk_index - prev_chunks;
|
||||
|
||||
(tx_info, tx_chunk_index)
|
||||
};
|
||||
|
||||
// Verify the tx chunk proof.
|
||||
ensure!(
|
||||
pezsp_io::trie::blake2_256_verify_proof(
|
||||
tx_info.chunk_root,
|
||||
&proof.proof,
|
||||
&encode_index(tx_chunk_index),
|
||||
&proof.chunk,
|
||||
pezsp_runtime::StateVersion::V1,
|
||||
),
|
||||
Error::<T>::InvalidProof
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test environment for transaction-storage pallet.
|
||||
|
||||
use crate::{
|
||||
self as pezpallet_transaction_storage, TransactionStorageProof, DEFAULT_MAX_BLOCK_TRANSACTIONS,
|
||||
DEFAULT_MAX_TRANSACTION_SIZE,
|
||||
};
|
||||
use pezframe_support::{derive_impl, traits::ConstU32};
|
||||
use pezsp_runtime::{traits::IdentityLookup, BuildStorage};
|
||||
|
||||
pub type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
// Configure a mock runtime to test the pallet.
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
TransactionStorage: pezpallet_transaction_storage,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
type AccountId = u64;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_transaction_storage::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Currency = Balances;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type FeeDestination = ();
|
||||
type WeightInfo = ();
|
||||
type MaxBlockTransactions = ConstU32<{ DEFAULT_MAX_BLOCK_TRANSACTIONS }>;
|
||||
type MaxTransactionSize = ConstU32<{ DEFAULT_MAX_TRANSACTION_SIZE }>;
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = RuntimeGenesisConfig {
|
||||
system: Default::default(),
|
||||
balances: pezpallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 1000000000), (2, 100), (3, 100), (4, 100)],
|
||||
..Default::default()
|
||||
},
|
||||
transaction_storage: pezpallet_transaction_storage::GenesisConfig::<Test> {
|
||||
storage_period: 10,
|
||||
byte_fee: 2,
|
||||
entry_fee: 200,
|
||||
},
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
pub fn run_to_block(n: u64, f: impl Fn() -> Option<TransactionStorageProof> + 'static) {
|
||||
System::run_to_block_with::<AllPalletsWithSystem>(
|
||||
n,
|
||||
pezframe_system::RunToBlockHooks::default().before_finalize(|_| {
|
||||
if let Some(proof) = f() {
|
||||
TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap();
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests for transaction-storage pallet.
|
||||
|
||||
use super::{Pallet as TransactionStorage, *};
|
||||
use crate::mock::*;
|
||||
use pezframe_support::{assert_noop, assert_ok};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_runtime::{DispatchError, TokenError::FundsUnavailable};
|
||||
use pezsp_transaction_storage_proof::{registration::build_proof, CHUNK_SIZE};
|
||||
|
||||
const MAX_DATA_SIZE: u32 = DEFAULT_MAX_TRANSACTION_SIZE;
|
||||
|
||||
#[test]
|
||||
fn discards_data() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1, || None);
|
||||
let caller = 1;
|
||||
assert_ok!(TransactionStorage::<Test>::store(
|
||||
RawOrigin::Signed(caller).into(),
|
||||
vec![0u8; 2000 as usize]
|
||||
));
|
||||
assert_ok!(TransactionStorage::<Test>::store(
|
||||
RawOrigin::Signed(caller).into(),
|
||||
vec![0u8; 2000 as usize]
|
||||
));
|
||||
let proof_provider = || {
|
||||
let block_num = pezframe_system::Pallet::<Test>::block_number();
|
||||
if block_num == 11 {
|
||||
let parent_hash = pezframe_system::Pallet::<Test>::parent_hash();
|
||||
build_proof(parent_hash.as_ref(), vec![vec![0u8; 2000], vec![0u8; 2000]]).unwrap()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
run_to_block(11, proof_provider);
|
||||
assert!(Transactions::<Test>::get(1).is_some());
|
||||
let transactions = Transactions::<Test>::get(1).unwrap();
|
||||
assert_eq!(transactions.len(), 2);
|
||||
assert_eq!(TransactionInfo::total_chunks(&transactions), 16);
|
||||
run_to_block(12, proof_provider);
|
||||
assert!(Transactions::<Test>::get(1).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn burns_fee() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1, || None);
|
||||
let caller = 1;
|
||||
assert_noop!(
|
||||
TransactionStorage::<Test>::store(
|
||||
RawOrigin::Signed(5).into(),
|
||||
vec![0u8; 2000 as usize]
|
||||
),
|
||||
DispatchError::Token(FundsUnavailable),
|
||||
);
|
||||
assert_ok!(TransactionStorage::<Test>::store(
|
||||
RawOrigin::Signed(caller).into(),
|
||||
vec![0u8; 2000 as usize]
|
||||
));
|
||||
assert_eq!(Balances::free_balance(1), 1_000_000_000 - 2000 * 2 - 200);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn checks_proof() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1, || None);
|
||||
let caller = 1;
|
||||
assert_ok!(TransactionStorage::<Test>::store(
|
||||
RawOrigin::Signed(caller).into(),
|
||||
vec![0u8; MAX_DATA_SIZE as usize]
|
||||
));
|
||||
run_to_block(10, || None);
|
||||
let parent_hash = pezframe_system::Pallet::<Test>::parent_hash();
|
||||
let proof = build_proof(parent_hash.as_ref(), vec![vec![0u8; MAX_DATA_SIZE as usize]])
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_noop!(
|
||||
TransactionStorage::<Test>::check_proof(RuntimeOrigin::none(), proof,),
|
||||
Error::<Test>::UnexpectedProof,
|
||||
);
|
||||
run_to_block(11, || None);
|
||||
let parent_hash = pezframe_system::Pallet::<Test>::parent_hash();
|
||||
|
||||
let invalid_proof =
|
||||
build_proof(parent_hash.as_ref(), vec![vec![0u8; 1000]]).unwrap().unwrap();
|
||||
assert_noop!(
|
||||
TransactionStorage::<Test>::check_proof(RuntimeOrigin::none(), invalid_proof,),
|
||||
Error::<Test>::InvalidProof,
|
||||
);
|
||||
|
||||
let proof = build_proof(parent_hash.as_ref(), vec![vec![0u8; MAX_DATA_SIZE as usize]])
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_ok!(TransactionStorage::<Test>::check_proof(RuntimeOrigin::none(), proof));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_chunk_proof_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Prepare a bunch of transactions with variable chunk sizes.
|
||||
let transactions = vec![
|
||||
vec![0u8; CHUNK_SIZE - 1],
|
||||
vec![1u8; CHUNK_SIZE],
|
||||
vec![2u8; CHUNK_SIZE + 1],
|
||||
vec![3u8; 2 * CHUNK_SIZE - 1],
|
||||
vec![3u8; 2 * CHUNK_SIZE],
|
||||
vec![3u8; 2 * CHUNK_SIZE + 1],
|
||||
vec![4u8; 7 * CHUNK_SIZE - 1],
|
||||
vec![4u8; 7 * CHUNK_SIZE],
|
||||
vec![4u8; 7 * CHUNK_SIZE + 1],
|
||||
];
|
||||
let expected_total_chunks =
|
||||
transactions.iter().map(|t| t.len().div_ceil(CHUNK_SIZE) as u32).sum::<u32>();
|
||||
|
||||
// Store a couple of transactions in one block.
|
||||
run_to_block(1, || None);
|
||||
let caller = 1;
|
||||
for transaction in transactions.clone() {
|
||||
assert_ok!(TransactionStorage::<Test>::store(
|
||||
RawOrigin::Signed(caller).into(),
|
||||
transaction
|
||||
));
|
||||
}
|
||||
run_to_block(2, || None);
|
||||
|
||||
// Read all the block transactions metadata.
|
||||
let tx_infos = Transactions::<Test>::get(1).unwrap();
|
||||
let total_chunks = TransactionInfo::total_chunks(&tx_infos);
|
||||
assert_eq!(expected_total_chunks, total_chunks);
|
||||
assert_eq!(9, tx_infos.len());
|
||||
|
||||
// Verify proofs for all possible chunk indexes.
|
||||
for chunk_index in 0..total_chunks {
|
||||
// chunk index randomness
|
||||
let mut random_hash = [0u8; 32];
|
||||
random_hash[..8].copy_from_slice(&(chunk_index as u64).to_be_bytes());
|
||||
let selected_chunk_index = random_chunk(random_hash.as_ref(), total_chunks);
|
||||
assert_eq!(selected_chunk_index, chunk_index);
|
||||
|
||||
// build/check chunk proof roundtrip
|
||||
let proof = build_proof(random_hash.as_ref(), transactions.clone())
|
||||
.expect("valid proof")
|
||||
.unwrap();
|
||||
assert_ok!(TransactionStorage::<Test>::verify_chunk_proof(
|
||||
proof,
|
||||
random_hash.as_ref(),
|
||||
tx_infos.to_vec()
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renews_data() {
|
||||
new_test_ext().execute_with(|| {
|
||||
run_to_block(1, || None);
|
||||
let caller = 1;
|
||||
assert_noop!(
|
||||
TransactionStorage::<Test>::store(RawOrigin::Signed(caller).into(), vec![]),
|
||||
Error::<Test>::EmptyTransaction
|
||||
);
|
||||
assert_ok!(TransactionStorage::<Test>::store(
|
||||
RawOrigin::Signed(caller).into(),
|
||||
vec![0u8; 2000]
|
||||
));
|
||||
let info = BlockTransactions::<Test>::get().last().unwrap().clone();
|
||||
run_to_block(6, || None);
|
||||
assert_ok!(TransactionStorage::<Test>::renew(
|
||||
RawOrigin::Signed(caller).into(),
|
||||
1, // block
|
||||
0, // transaction
|
||||
));
|
||||
assert_eq!(Balances::free_balance(1), 1_000_000_000 - 4000 * 2 - 200 * 2);
|
||||
let proof_provider = || {
|
||||
let block_num = pezframe_system::Pallet::<Test>::block_number();
|
||||
if block_num == 11 || block_num == 16 {
|
||||
let parent_hash = pezframe_system::Pallet::<Test>::parent_hash();
|
||||
build_proof(parent_hash.as_ref(), vec![vec![0u8; 2000]]).unwrap()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
run_to_block(16, proof_provider);
|
||||
assert!(Transactions::<Test>::get(1).is_none());
|
||||
assert_eq!(Transactions::<Test>::get(6).unwrap().get(0), Some(info).as_ref());
|
||||
run_to_block(17, proof_provider);
|
||||
assert!(Transactions::<Test>::get(6).is_none());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_transaction_storage`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2024-11-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// ./target/production/bizinikiwi-node
|
||||
// benchmark
|
||||
// pallet
|
||||
// --chain=dev
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --pallet=pezpallet_transaction_storage
|
||||
// --no-storage-info
|
||||
// --no-median-slopes
|
||||
// --no-min-squares
|
||||
// --extrinsic=*
|
||||
// --wasm-execution=compiled
|
||||
// --heap-pages=4096
|
||||
// --output=./bizinikiwi/pezframe/transaction-storage/src/weights.rs
|
||||
// --header=./bizinikiwi/HEADER-APACHE2
|
||||
// --template=./bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_transaction_storage`.
|
||||
pub trait WeightInfo {
|
||||
fn store(l: u32, ) -> Weight;
|
||||
fn renew() -> Weight;
|
||||
fn check_proof_max() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_transaction_storage` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `TransactionStorage::ByteFee` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::ByteFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::EntryFee` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::EntryFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Balances::Holds` (r:1 w:1)
|
||||
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::BlockTransactions` (r:1 w:1)
|
||||
/// Proof: `TransactionStorage::BlockTransactions` (`max_values`: Some(1), `max_size`: Some(36866), added: 37361, mode: `MaxEncodedLen`)
|
||||
/// The range of component `l` is `[1, 8388608]`.
|
||||
fn store(l: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `242`
|
||||
// Estimated: `38351`
|
||||
// Minimum execution time: 65_899_000 picoseconds.
|
||||
Weight::from_parts(66_814_000, 38351)
|
||||
// Standard Error: 7
|
||||
.saturating_add(Weight::from_parts(7_678, 0).saturating_mul(l.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `TransactionStorage::Transactions` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::Transactions` (`max_values`: None, `max_size`: Some(36886), added: 39361, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::ByteFee` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::ByteFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::EntryFee` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::EntryFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Balances::Holds` (r:1 w:1)
|
||||
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::BlockTransactions` (r:1 w:1)
|
||||
/// Proof: `TransactionStorage::BlockTransactions` (`max_values`: Some(1), `max_size`: Some(36866), added: 37361, mode: `MaxEncodedLen`)
|
||||
fn renew() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `430`
|
||||
// Estimated: `40351`
|
||||
// Minimum execution time: 87_876_000 picoseconds.
|
||||
Weight::from_parts(91_976_000, 40351)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `TransactionStorage::ProofChecked` (r:1 w:1)
|
||||
/// Proof: `TransactionStorage::ProofChecked` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::StoragePeriod` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::StoragePeriod` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::ChunkCount` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::ChunkCount` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::ParentHash` (r:1 w:0)
|
||||
/// Proof: `System::ParentHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::Transactions` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::Transactions` (`max_values`: None, `max_size`: Some(36886), added: 39361, mode: `MaxEncodedLen`)
|
||||
fn check_proof_max() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `37211`
|
||||
// Estimated: `40351`
|
||||
// Minimum execution time: 78_423_000 picoseconds.
|
||||
Weight::from_parts(82_423_000, 40351)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `TransactionStorage::ByteFee` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::ByteFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::EntryFee` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::EntryFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Balances::Holds` (r:1 w:1)
|
||||
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::BlockTransactions` (r:1 w:1)
|
||||
/// Proof: `TransactionStorage::BlockTransactions` (`max_values`: Some(1), `max_size`: Some(36866), added: 37361, mode: `MaxEncodedLen`)
|
||||
/// The range of component `l` is `[1, 8388608]`.
|
||||
fn store(l: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `242`
|
||||
// Estimated: `38351`
|
||||
// Minimum execution time: 65_899_000 picoseconds.
|
||||
Weight::from_parts(66_814_000, 38351)
|
||||
// Standard Error: 7
|
||||
.saturating_add(Weight::from_parts(7_678, 0).saturating_mul(l.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `TransactionStorage::Transactions` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::Transactions` (`max_values`: None, `max_size`: Some(36886), added: 39361, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::ByteFee` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::ByteFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::EntryFee` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::EntryFee` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Balances::Holds` (r:1 w:1)
|
||||
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::BlockTransactions` (r:1 w:1)
|
||||
/// Proof: `TransactionStorage::BlockTransactions` (`max_values`: Some(1), `max_size`: Some(36866), added: 37361, mode: `MaxEncodedLen`)
|
||||
fn renew() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `430`
|
||||
// Estimated: `40351`
|
||||
// Minimum execution time: 87_876_000 picoseconds.
|
||||
Weight::from_parts(91_976_000, 40351)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `TransactionStorage::ProofChecked` (r:1 w:1)
|
||||
/// Proof: `TransactionStorage::ProofChecked` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::StoragePeriod` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::StoragePeriod` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::ChunkCount` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::ChunkCount` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::ParentHash` (r:1 w:0)
|
||||
/// Proof: `System::ParentHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TransactionStorage::Transactions` (r:1 w:0)
|
||||
/// Proof: `TransactionStorage::Transactions` (`max_values`: None, `max_size`: Some(36886), added: 39361, mode: `MaxEncodedLen`)
|
||||
fn check_proof_max() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `37211`
|
||||
// Estimated: `40351`
|
||||
// Minimum execution time: 78_423_000 picoseconds.
|
||||
Weight::from_parts(82_423_000, 40351)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user