Skip to content

feat: add constant fee to setting block producer metadata #868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This changelog is based on [Keep A Changelog](https://keepachangelog.com/en/1.1.

## Changed

* `pallet-block-producer-metadata` is updated with a configurable fee for inserting the metadata, to make attacks on unbounded storage economically infeasible

## Added

## Fixed
Expand Down
11 changes: 10 additions & 1 deletion demo/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ impl pallet_balances::Config for Runtime {
type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>;
type FreezeIdentifier = ();
type MaxFreezes = ();
type RuntimeHoldReason = ();
type RuntimeHoldReason = RuntimeHoldReason;
type RuntimeFreezeReason = RuntimeFreezeReason;
type DoneSlashHandler = ();
}
Expand Down Expand Up @@ -591,6 +591,11 @@ impl pallet_block_producer_fees::Config for Runtime {
type BenchmarkHelper = PalletBlockProducerFeesBenchmarkHelper;
}

parameter_types! {
/// Amount of tokens to hold when upserting block producer metadata.
pub const MetadataHoldAmount: Balance = 1_000_000;
}

impl pallet_block_producer_metadata::Config for Runtime {
type WeightInfo = pallet_block_producer_metadata::weights::SubstrateWeight<Runtime>;

Expand All @@ -600,6 +605,10 @@ impl pallet_block_producer_metadata::Config for Runtime {
Sidechain::genesis_utxo()
}

type Currency = Balances;
type HoldAmount = MetadataHoldAmount;
type RuntimeHoldReason = RuntimeHoldReason;

#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = PalletBlockProducerMetadataBenchmarkHelper;
}
Expand Down
6 changes: 4 additions & 2 deletions toolkit/block-producer-metadata/pallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ hex-literal = { workspace = true, optional = true }
sp-core = { workspace = true, optional = true }
sp-block-producer-metadata = { workspace = true }
sp-runtime = { workspace = true, optional = true }
sp-io = { workspace = true, optional = true}
sp-io = { workspace = true, optional = true }

[dev-dependencies]
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
hex-literal = { workspace = true }
k256 = { workspace = true }
pallet-balances = { workspace = true }

[features]
default = ["std"]
Expand All @@ -45,6 +46,7 @@ std = [
"sp-std/std",
"sp-core?/std",
"sp-block-producer-metadata/std",
"pallet-balances/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
Expand All @@ -53,5 +55,5 @@ runtime-benchmarks = [
"sp-runtime/runtime-benchmarks",
"hex-literal",
"sp-core",
"sp-io"
"sp-io",
]
9 changes: 7 additions & 2 deletions toolkit/block-producer-metadata/pallet/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,23 @@ pub trait BenchmarkHelper<BlockProducerMetadata> {
fn cross_chain_signature() -> CrossChainSignature;
}

#[benchmarks]
#[benchmarks(where <T as Config>::Currency: frame_support::traits::tokens::fungible::Mutate<<T as frame_system::Config>::AccountId>)]
mod benchmarks {
use super::*;
use frame_support::traits::{Get, tokens::fungible::Mutate};

#[benchmark]
fn upsert_metadata() {
let metadata = T::BenchmarkHelper::metadata();
let cross_chain_pub_key = T::BenchmarkHelper::cross_chain_pub_key();
let cross_chain_signature = T::BenchmarkHelper::cross_chain_signature();

// Create an account and fund it with sufficient balance
let caller: T::AccountId = account("caller", 0, 0);
let _ = T::Currency::mint_into(&caller, T::HoldAmount::get() * 2u32.into());

#[extrinsic_call]
_(RawOrigin::None, metadata, cross_chain_signature, cross_chain_pub_key);
_(RawOrigin::Signed(caller), metadata, cross_chain_signature, cross_chain_pub_key);
}

impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
Expand Down
53 changes: 49 additions & 4 deletions toolkit/block-producer-metadata/pallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
//! type WeightInfo = pallet_block_producer_metadata::weights::SubstrateWeight<Runtime>;
//!
//! type BlockProducerMetadata = BlockProducerMetadata;
//! type Currency = Balances;
//! type HoldAmount = MetadataHoldAmount;
//! type RuntimeHoldReason = RuntimeHoldReason;
//!
//! fn genesis_utxo() -> sidechain_domain::UtxoId {
//! Sidechain::genesis_utxo()
Expand All @@ -55,6 +58,8 @@
//!
//! Here, besides providing the metadata type and using weights already provided with the pallet, we are also
//! wiring the `genesis_utxo` function to fetch the chain's genesis UTXO from the `pallet_sidechain` pallet.
//! Currency, HoldAmount, and RuntimeHoldReason types are required to configure the deposit mechanism for occupying storage.
//! Removing Metadata is not supported, so this deposit is currently ethernal.
//!
//! At this point, the pallet is ready to be used.
//!
Expand All @@ -80,6 +85,9 @@
//! `sign-block-producer-metadata` command provided by the chain's node. This command returns the signature
//! and the metadata encoded as hex bytes.
//!
//! When metadata is inserted for the first time, a deposit is held from the caller's account. Updates to existing
//! metadata do not require additional deposits.
//!
//! After the signature has been obtained, the user should submit the `upsert_metadata` extrinsic (eg. using PolkadotJS)
//! providing:
//! - *metadata value*: when using PolkadotJS UI, care must be taken to submit the same values that were passed to the CLI
Expand All @@ -102,20 +110,27 @@ mod mock;
#[cfg(test)]
mod tests;

use frame_support::traits::tokens::fungible::Inspect;
use parity_scale_codec::Encode;
use sidechain_domain::{CrossChainKeyHash, CrossChainPublicKey};
use sp_block_producer_metadata::MetadataSignedMessage;

type BalanceOf<T> =
<<T as pallet::Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use crate::weights::WeightInfo;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::OriginFor;
use frame_support::{
pallet_prelude::*,
traits::{Get, tokens::fungible::MutateHold},
};
use frame_system::{ensure_signed, pallet_prelude::OriginFor};
use sidechain_domain::{CrossChainSignature, UtxoId};

/// Current version of the pallet
pub const PALLET_VERSION: u32 = 1;
pub const PALLET_VERSION: u32 = 2;

#[pallet::pallet]
pub struct Pallet<T>(_);
Expand All @@ -131,6 +146,16 @@ pub mod pallet {
/// Should return the chain's genesis UTXO
fn genesis_utxo() -> UtxoId;

/// The currency used for holding tokens
type Currency: MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;

/// The amount of tokens to hold when upserting metadata
#[pallet::constant]
type HoldAmount: Get<BalanceOf<Self>>;

/// The runtime's hold reason type
type RuntimeHoldReason: From<HoldReason>;

/// Helper providing mock values for use in benchmarks
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper: benchmarking::BenchmarkHelper<Self::BlockProducerMetadata>;
Expand All @@ -145,16 +170,26 @@ pub mod pallet {
QueryKind = OptionQuery,
>;

/// Hold reasons for this pallet
#[pallet::composite_enum]
pub enum HoldReason {
/// Tokens held as deposit for block producer metadata
MetadataDeposit,
}

/// Error type returned by this pallet's extrinsic
#[pallet::error]
pub enum Error<T> {
/// Signals that the signature submitted to `upsert_metadata` does not match the metadata and public key
InvalidMainchainSignature,
/// Insufficient balance to hold tokens as fee for upserting block producer metadata
InsufficientBalance,
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Inserts or updates metadata for the block producer identified by `cross_chain_pub_key`.
/// Holds a constant amount from the caller's account as a deposit for including metadata on the chain.
///
/// Arguments:
/// - `metadata`: new metadata value
Expand All @@ -165,11 +200,12 @@ pub mod pallet {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::upsert_metadata())]
pub fn upsert_metadata(
_origin: OriginFor<T>,
origin: OriginFor<T>,
metadata: T::BlockProducerMetadata,
signature: CrossChainSignature,
cross_chain_pub_key: CrossChainPublicKey,
) -> DispatchResult {
let origin_account = ensure_signed(origin)?;
let genesis_utxo = T::genesis_utxo();

let cross_chain_key_hash = cross_chain_pub_key.hash();
Expand All @@ -185,6 +221,15 @@ pub mod pallet {

ensure!(is_valid_signature, Error::<T>::InvalidMainchainSignature);

if BlockProducerMetadataStorage::<T>::get(cross_chain_key_hash).is_none() {
T::Currency::hold(
&HoldReason::MetadataDeposit.into(),
&origin_account,
T::HoldAmount::get(),
)
.map_err(|_| Error::<T>::InsufficientBalance)?;
}

BlockProducerMetadataStorage::<T>::insert(cross_chain_key_hash, metadata);
Ok(())
}
Expand Down
43 changes: 39 additions & 4 deletions toolkit/block-producer-metadata/pallet/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use frame_support::traits::ConstU32;
use frame_support::traits::{ConstU32, ConstU128};
use frame_support::{
construct_runtime,
construct_runtime, parameter_types,
traits::{ConstU16, ConstU64},
};
use hex_literal::hex;
Expand All @@ -16,10 +16,12 @@ use sp_runtime::{

pub type Block = frame_system::mocking::MockBlock<Test>;
pub type AccountId = AccountId32;
pub type Balance = u128;

construct_runtime! {
pub enum Test {
System: frame_system,
Balances: pallet_balances,
BlockProducerMetadata: crate::pallet
}
}
Expand All @@ -39,7 +41,7 @@ impl frame_system::Config for Test {
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type AccountData = pallet_balances::AccountData<Balance>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
Expand All @@ -57,6 +59,27 @@ impl frame_system::Config for Test {
type PostTransactions = ();
}

impl pallet_balances::Config for Test {
type MaxLocks = ConstU32<50>;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type Balance = Balance;
type RuntimeEvent = RuntimeEvent;
type DustRemoval = ();
type ExistentialDeposit = ConstU128<1>;
type AccountStore = System;
type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
type FreezeIdentifier = ();
type MaxFreezes = ();
type RuntimeHoldReason = RuntimeHoldReason;
type RuntimeFreezeReason = ();
type DoneSlashHandler = ();
}

parameter_types! {
pub const MetadataHoldAmount: Balance = 1000;
}

#[derive(
Clone, Debug, MaxEncodedLen, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, TypeInfo,
)]
Expand Down Expand Up @@ -90,16 +113,28 @@ impl crate::benchmarking::BenchmarkHelper<BlockProducerUrlMetadata>
}
}

pub(crate) const FUNDED_ACCOUNT: AccountId32 = AccountId32::new([1; 32]);

impl crate::pallet::Config for Test {
type WeightInfo = ();
type BlockProducerMetadata = BlockProducerUrlMetadata;
fn genesis_utxo() -> UtxoId {
UtxoId::new(hex!("59104061ffa0d66f9ba0135d6fc6a884a395b10f8ae9cb276fc2c3bfdfedc260"), 1)
}
type Currency = Balances;
type HoldAmount = MetadataHoldAmount;
type RuntimeHoldReason = RuntimeHoldReason;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = PalletBlockProducerMetadataBenchmarkHelper;
}

pub fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::<Test>::default().build_storage().unwrap().into()
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Test> {
balances: vec![(FUNDED_ACCOUNT, 100_000)],
dev_accounts: None,
}
.assimilate_storage(&mut t)
.unwrap();
t.into()
}
Loading
Loading