pallet_block_producer_metadata/
lib.rs

1//! Pallet storing metadata for Partner Chain block producers.
2//!
3//! ## Purpose of this pallet
4//!
5//! This pallet enables Partner Chain block producers to provide information about themselves that can
6//! then be displayed by chain explorers and other tools to potential delegators looking for pools to
7//! delegate to.
8//!
9//! ## Usage - PC Builders
10//!
11//! PC Builders wishing to include this pallet in their runtime should first define a `BlockProducerMetadata`
12//! type for their runtime to use, eg.
13//!
14//! ```rust
15//! use sidechain_domain::byte_string::BoundedString;
16//! use sp_core::{Encode, ConstU32, Decode, MaxEncodedLen};
17//!
18//! type MaxNameLength = ConstU32<64>;
19//! type MaxDescriptionLength = ConstU32<512>;
20//! type MaxUrlLength = ConstU32<256>;
21//!
22//! #[derive(Encode, Decode, MaxEncodedLen)]
23//! pub struct BlockProducerMetadata {
24//!     pub name: BoundedString<MaxNameLength>,
25//!     pub description: BoundedString<MaxDescriptionLength>,
26//!     pub logo_url: BoundedString<MaxUrlLength>
27//! }
28//! ```
29//!
30//! This type can be arbitrary to allow PC Builders to include any data that would be relevant to their chain.
31//! However, care should be taken to keep its size to minimum to avoid inflating on-chain storage size by eg.
32//! linking to off-chain storage for bulkier data:
33//! ```
34//! # use sidechain_domain::byte_string::*;
35//! # use sp_core::ConstU32;
36//! # type MaxUrlLength = ConstU32<256>;
37//! pub struct BlockProducerMetadataType {
38//!     pub url: BoundedString<MaxUrlLength>,
39//!     pub hash: SizedByteString<32>,
40//! }
41//! ```
42//!
43//! Once the metadata type is defined, the pallet can be added to the runtime and should be configured:
44//! ```rust,ignore
45//! impl pallet_block_producer_metadata::Config for Runtime {
46//!     type WeightInfo = pallet_block_producer_metadata::weights::SubstrateWeight<Runtime>;
47//!
48//!     type BlockProducerMetadata = BlockProducerMetadata;
49//!
50//!     fn genesis_utxo() -> sidechain_domain::UtxoId {
51//!         Sidechain::genesis_utxo()
52//!     }
53//! }
54//! ```
55//!
56//! Here, besides providing the metadata type and using weights already provided with the pallet, we are also
57//! wiring the `genesis_utxo` function to fetch the chain's genesis UTXO from the `pallet_sidechain` pallet.
58//!
59//! At this point, the pallet is ready to be used.
60//!
61//! ### Signing command
62//!
63//! To ensure that only the block producer is able to update their own metadata, a signature is required by the
64//! pallet's extrinsic. To make it easy for PC Builders to provide their users with a signing utility, the
65//! `cli_commands` crate includes a command for signing the appropriate message. Consult the crate's own
66//! documentation for more details.
67//!
68//! ### Benchmarking
69//!
70//! See documentation of [benchmarking] module.
71//!
72//! ### RPC
73//!
74//! See documentation of `pallet_block_producer_metadata_rpc` crate.
75//!
76//! ## Usage - PC Users
77//!
78//! This pallet exposes a single extrinsic `upsert_metadata` for current or prospective block producers to add or
79//! update their metadata. The extrinsic requires a valid signature, which the user should prepare using the
80//! `sign-block-producer-metadata` command provided by the chain's node. This command returns the signature
81//! and the metadata encoded as hex bytes.
82//!
83//! After the signature has been obtained, the user should submit the `upsert_metadata` extrinsic (eg. using PolkadotJS)
84//! providing:
85//! - *metadata value*: when using PolkadotJS UI, care must be taken to submit the same values that were passed to the CLI
86//! - *signature* returned by the CLI
87//! - *cross-chain public key* corresponding to the private key used for signing with the CLI
88
89#![cfg_attr(not(feature = "std"), no_std)]
90#![deny(missing_docs)]
91
92extern crate alloc;
93
94pub use pallet::*;
95
96pub mod benchmarking;
97pub mod weights;
98
99#[cfg(test)]
100mod mock;
101
102#[cfg(test)]
103mod tests;
104
105use parity_scale_codec::Encode;
106use sidechain_domain::{CrossChainKeyHash, CrossChainPublicKey};
107use sp_block_producer_metadata::MetadataSignedMessage;
108
109#[frame_support::pallet]
110pub mod pallet {
111	use super::*;
112	use crate::weights::WeightInfo;
113	use frame_support::pallet_prelude::*;
114	use frame_system::pallet_prelude::OriginFor;
115	use sidechain_domain::{CrossChainSignature, UtxoId};
116
117	/// Current version of the pallet
118	pub const PALLET_VERSION: u32 = 1;
119
120	#[pallet::pallet]
121	pub struct Pallet<T>(_);
122
123	#[pallet::config]
124	pub trait Config: frame_system::Config {
125		/// Weight information for this pallet's extrinsics
126		type WeightInfo: crate::weights::WeightInfo;
127
128		/// Block producer metadata type
129		type BlockProducerMetadata: Member + Parameter + MaxEncodedLen;
130
131		/// Should return the chain's genesis UTXO
132		fn genesis_utxo() -> UtxoId;
133
134		/// Helper providing mock values for use in benchmarks
135		#[cfg(feature = "runtime-benchmarks")]
136		type BenchmarkHelper: benchmarking::BenchmarkHelper<Self::BlockProducerMetadata>;
137	}
138
139	/// Storage mapping from block producers to their metadata
140	#[pallet::storage]
141	pub type BlockProducerMetadataStorage<T: Config> = StorageMap<
142		Hasher = Blake2_128Concat,
143		Key = CrossChainKeyHash,
144		Value = T::BlockProducerMetadata,
145		QueryKind = OptionQuery,
146	>;
147
148	/// Error type returned by this pallet's extrinsic
149	#[pallet::error]
150	pub enum Error<T> {
151		/// Signals that the signature submitted to `upsert_metadata` does not match the metadata and public key
152		InvalidMainchainSignature,
153	}
154
155	#[pallet::call]
156	impl<T: Config> Pallet<T> {
157		/// Inserts or updates metadata for the block producer identified by `cross_chain_pub_key`.
158		///
159		/// Arguments:
160		/// - `metadata`: new metadata value
161		/// - `signature`: a signature of [MetadataSignedMessage] created from this inherent's arguments
162		///   and the current Partner Chain's genesis UTXO, created using the private key corresponding
163		///   to `cross_chain_pub_key`
164		/// - `cross_chain_pub_key`: public key identifying the block producer
165		#[pallet::call_index(0)]
166		#[pallet::weight(T::WeightInfo::upsert_metadata())]
167		pub fn upsert_metadata(
168			_origin: OriginFor<T>,
169			metadata: T::BlockProducerMetadata,
170			signature: CrossChainSignature,
171			cross_chain_pub_key: CrossChainPublicKey,
172		) -> DispatchResult {
173			let genesis_utxo = T::genesis_utxo();
174
175			let cross_chain_key_hash = cross_chain_pub_key.hash();
176
177			let metadata_message = MetadataSignedMessage {
178				cross_chain_pub_key: cross_chain_pub_key.clone(),
179				metadata: metadata.clone(),
180				genesis_utxo,
181			};
182
183			let is_valid_signature =
184				signature.verify(&cross_chain_pub_key, &metadata_message.encode()).is_ok();
185
186			ensure!(is_valid_signature, Error::<T>::InvalidMainchainSignature);
187
188			BlockProducerMetadataStorage::<T>::insert(cross_chain_key_hash, metadata);
189			Ok(())
190		}
191	}
192
193	impl<T: Config> Pallet<T> {
194		/// Returns the current pallet version.
195		pub fn get_version() -> u32 {
196			PALLET_VERSION
197		}
198
199		/// Retrieves the metadata for a given SPO public key if it exists.
200		pub fn get_metadata_for(
201			cross_chain_pub_key: &CrossChainPublicKey,
202		) -> Option<T::BlockProducerMetadata> {
203			BlockProducerMetadataStorage::<T>::get(cross_chain_pub_key.hash())
204		}
205	}
206}