Genesis Builder
[!Note] Resolve all TODO in
runtime/src/utxo.rs
andruntime/src/chain_spec.rs
to complete this step.
Reading Materials
We've almost complete the UTXO pallet. However we have no balance to test it, that's quite ridiculous 🫤.
In this tutorial, I'll show you how to add gensis data for pallet, have initial UTXO balances, and something to easier to test.
Moreover, you can add faucet
extrinsic to pallet, that's work too. I let this is challenge for you. Feel free to complete it. The solution is on main branch.
Implement
Firstly, we've to declare genesis data type.
[!Important] Because code is built on
no-std
feature. If we keep using theTransactionOutput
struct, we'll get error... "the trait
Serialize
is not implemented forTransactionOutput
" ...... "the trait
Deserialize<'_>
is not implemented forTransactionOutput
" ...Hence, we need to simplify data type to make it work in both
std
andno-std
feature.
#![allow(unused)] fn main() { ... /// Genesis Utxo Type pub type GenesisUtxoType = (Value, H256); ... #[pallet::genesis_config] pub struct GenesisConfig<T: Config> { pub _ph_data: Option<PhantomData<T>>, pub genesis_utxos: Vec<GenesisUtxoType>, } }
Implement Default
trait for GenesisConfig
:
#![allow(unused)] fn main() { impl<T: Config> Default for GenesisConfig<T> { fn default() -> Self { Self { _ph_data: Default::default(), genesis_utxos: Vec::<GenesisUtxoType>::new(), } } } }
Implement logic to build genesis data
#![allow(unused)] fn main() { #[pallet::genesis_build] impl<T: Config> BuildGenesisConfig for GenesisConfig<T> { fn build(&self) { for utxo in self.genesis_utxos.iter() { let utxo = TransactionOutput { value: utxo.0, pubkey: utxo.1 }; let hash = BlakeTwo256::hash_of(&utxo); Pallet::<T>::store_utxo(&utxo, hash); } } } }
Additionally, I've put some codes to make it easier for testing as mentioned above
#![allow(unused)] fn main() { ... /// Keep track of latest UTXO hash of account /// Mapping from `sr25519::Pubkey` to `BlakeTwo256::hash_of(transaction, index)` /// Just for testing 🫤 /// Because 1 account may have multiple UTXOs #[pallet::storage] #[pallet::getter(fn utxo_of)] pub type UtxoOf<T: Config> = StorageMap<Hasher = Identity, Key = Public, Value = H256, QueryKind = OptionQuery>; ... fn store_utxo(utxo: &TransactionOutput, hash: H256) { ... // Convert H256 back to sr25519::Public let pubkey = Public::from_h256(utxo.pubkey); UtxoOf::<T>::insert(pubkey, hash); } ... }
Move on the node/src/chain_spec.rs
, add some genesis data for pallet UTXO:
...
use academy_pow_runtime::{
AccountId,
SS58Prefix,
Signature,
TOKEN_DECIMALS,
TOKEN_SYMBOL,
WASM_BINARY,
+ utxo::{GenesisUtxoType, Value},
};
...
pub fn development_config() -> Result<ChainSpec, String> {
Ok(ChainSpec::builder(
WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?,
ForkingExtensions {
manual_mode: 1, // change this to `0` if you want to try `auto_fork_validation`
add_sha3_keccak: 10,
remove_md5: 20,
split_sha3_keccak: 30,
maxi_position: String::from("follow-mining"),
},
)
.with_name("Development")
.with_id("dev")
.with_chain_type(ChainType::Development)
.with_genesis_config_patch(genesis(
// Pre-funded accounts
vec![
get_account_id_from_seed::<sr25519::Public>("Alice"),
get_account_id_from_seed::<sr25519::Public>("Bob"),
get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
],
// Initial Difficulty
4_000_000,
+ vec![
+ get_account_id_from_seed::<sr25519::Public>("Alice"),
+ get_account_id_from_seed::<sr25519::Public>("Bob"),
+ get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
+ get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
+ ],
))
.with_properties(system_properties())
.build())
}
...
pub fn testnet_config() -> Result<ChainSpec, String> {
Ok(ChainSpec::builder(
WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?,
ForkingExtensions {
manual_mode: 1,
add_sha3_keccak: 0,
remove_md5: 0,
split_sha3_keccak: 0,
maxi_position: String::new(),
},
)
.with_name("Testnet")
.with_id("testnet")
.with_chain_type(ChainType::Local)
.with_genesis_config_patch(genesis(
vec![
get_account_id_from_seed::<sr25519::Public>("Alice"),
get_account_id_from_seed::<sr25519::Public>("Bob"),
get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
],
4_000_000,
+ vec![
+ get_account_id_from_seed::<sr25519::Public>("Alice"),
+ get_account_id_from_seed::<sr25519::Public>("Bob"),
+ get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
+ get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
+ ],
))
.with_properties(system_properties())
.build())
}
...
fn genesis(
endowed_accounts: Vec<AccountId>,
initial_difficulty: u32,
+ utxo_genesis_accounts: Vec<AccountId>,
) -> serde_json::Value {
serde_json::json!({
"balances": {
// Configure endowed accounts with initial balance of 1 << 50.
"balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 50)).collect::<Vec<_>>(),
},
"keccakDifficultyAdjustment": {
"initialDifficulty": u32_to_u8_32(initial_difficulty),
},
"md5DifficultyAdjustment": {
"initialDifficulty": u32_to_u8_32(initial_difficulty),
},
"sha3DifficultyAdjustment": {
"initialDifficulty": u32_to_u8_32(initial_difficulty),
},
+ "utxo": {
+ "genesisUtxos": utxo_genesis_accounts
+ .iter().cloned()
+ .map(|k| {
+ let hash = H256::from_slice(&k.as_slice());
+ let value: Value = (1u64 << 50).into();
+ let genesis_utxo: GenesisUtxoType = (value, hash);
+ genesis_utxo
+ }).collect::<Vec<GenesisUtxoType>>(),
+ },
})
}
...
We've add some balances for Alice, Bob, also hard-derive account of Alice and Bob. Let's testing.
Build the code
cargo build --release
# start temporary local node in development environment
./target/release/academy-pow --dev --tmp
[!Note] Because Substrate default account is in SS58 format, so we've to map that address to sr25519 format. Powerful tool to convert
SS58 account
tosr25519::Pubkey
: https://polkadot.subscan.io/tools/format_transform
- Alice: from
5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
to0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
- Bob: from
5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty
to0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48
Direct to https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/chainstate.
Select pallet UTXO > utxoOf
:
- Input:
0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
, press+
button. - Output:
utxo.utxoOf: Option<H256>
0xc670c5f69081da78af400552edcafa3f0f31e84db1b50dd70776e0f87477b3dc`
Select pallet UTXO > utxoStore
:
- Input:
0xc670c5f69081da78af400552edcafa3f0f31e84db1b50dd70776e0f87477b3dc
, press+
button. - Output:
utxo.utxoStore: Option<AcademyPowRuntimeUtxoTransactionOutput>
{
value: 1,125,899,906,842,624
pubkey: 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d
}
Great work completing a new step! 💪 You're almost at the finish line. Now, let's dive into last step. Run this command to continue:
#![allow(unused)] fn main() { use std::str::FromStr; use academy_pow_runtime::{ AccountId, SS58Prefix, Signature, TOKEN_DECIMALS, TOKEN_SYMBOL, WASM_BINARY, // TODO [6-genesis-builder] utxo::{GenesisUtxoType, Value}, }; use multi_pow::{ForkHeights, ForkingConfig, MaxiPosition}; use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; use serde::{Deserialize, Serialize}; use sp_core::{sr25519, ByteArray, Pair, Public, H256}; use sp_runtime::traits::{IdentifyAccount, Verify}; /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec<ForkingExtensions>; /// PoW and Forking related chain spec extensions to configure the client side forking behavior. /// /// The forks here are all related to adding and removing hash algorithms from the PoW. /// The chain begins supporting only md5. Later is adds sha3 and keccak. Later it removes md5. /// And finally there is a contentious fork where people become maxis. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] #[serde(deny_unknown_fields)] pub struct ForkingExtensions { /// Manual mode is intended for when we you are running a live workshop. /// No forking happens automatically. Rather, you have to hard-code the forks. /// /// If manual mode is enabled, the rest of the parameters are ignored. /// This should really be an enum, but I have to work around the broken extension system. /// /// Aww damn it! I can't even use bool in this broken system? Okay then I guess 0 means /// automatic mode and anything else means manual mode. pub manual_mode: u32, /// The block height to perform the soft fork that adds sha3 and keccak support. pub add_sha3_keccak: u32, /// The block height to perform the hard fork that removes md5 support. pub remove_md5: u32, /// The block height to perform the contentious fork where some become sha3- or keccak-maxis. pub split_sha3_keccak: u32, // Damn extension thing is so fragile, I can't even use an enum here. // Let alone that time I tried to use the forked value feature. /// The political position that this node will take at the contentious fork. pub maxi_position: String, } impl From<&ForkingExtensions> for ForkingConfig { fn from(e: &ForkingExtensions) -> Self { if e.manual_mode > 0 { return Self::Manual; } let fork_heights = ForkHeights { add_sha3_keccak: e.add_sha3_keccak, remove_md5: e.remove_md5, split_sha3_keccak: e.split_sha3_keccak, }; let maxi_position = MaxiPosition::from_str(&e.maxi_position).expect("Should have a valid maxi position..."); Self::Automatic(fork_heights, maxi_position) } } impl ForkingExtensions { /// Try to get the extension from the given `ChainSpec`. pub fn try_get(chain_spec: &dyn sc_service::ChainSpec) -> Option<&Self> { sc_chain_spec::get_extension(chain_spec.extensions()) } } /// Generate a crypto pair from seed. pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public { TPublic::Pair::from_string(&format!("//{}", seed), None) .expect("static values are valid; qed") .public() } type AccountPublic = <Signature as Verify>::Signer; /// Generate an account ID from seed. pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId where AccountPublic: From<<TPublic::Pair as Pair>::Public>, { AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account() } pub fn development_config() -> Result<ChainSpec, String> { Ok(ChainSpec::builder( WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?, ForkingExtensions { manual_mode: 1, // change this to `0` if you want to try `auto_fork_validation` add_sha3_keccak: 10, remove_md5: 20, split_sha3_keccak: 30, maxi_position: String::from("follow-mining"), }, ) .with_name("Development") .with_id("dev") .with_chain_type(ChainType::Development) .with_genesis_config_patch(genesis( // Pre-funded accounts vec![ get_account_id_from_seed::<sr25519::Public>("Alice"), get_account_id_from_seed::<sr25519::Public>("Bob"), get_account_id_from_seed::<sr25519::Public>("Alice//stash"), get_account_id_from_seed::<sr25519::Public>("Bob//stash"), ], // Initial Difficulty 4_000_000, // TODO [6-genesis-builder] // vec![ // get_account_id_from_seed::<sr25519::Public>("Alice"), // get_account_id_from_seed::<sr25519::Public>("Bob"), // get_account_id_from_seed::<sr25519::Public>("Alice//stash"), // get_account_id_from_seed::<sr25519::Public>("Bob//stash"), // ], )) .with_properties(system_properties()) .build()) } pub fn testnet_config() -> Result<ChainSpec, String> { Ok(ChainSpec::builder( WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?, ForkingExtensions { manual_mode: 1, add_sha3_keccak: 0, remove_md5: 0, split_sha3_keccak: 0, maxi_position: String::new(), }, ) .with_name("Testnet") .with_id("testnet") .with_chain_type(ChainType::Local) .with_genesis_config_patch(genesis( vec![ get_account_id_from_seed::<sr25519::Public>("Alice"), get_account_id_from_seed::<sr25519::Public>("Bob"), get_account_id_from_seed::<sr25519::Public>("Alice//stash"), get_account_id_from_seed::<sr25519::Public>("Bob//stash"), ], 4_000_000, // TODO [6-genesis-builder] // vec![ // get_account_id_from_seed::<sr25519::Public>("Alice"), // get_account_id_from_seed::<sr25519::Public>("Bob"), // get_account_id_from_seed::<sr25519::Public>("Alice//stash"), // get_account_id_from_seed::<sr25519::Public>("Bob//stash"), // ], )) .with_properties(system_properties()) .build()) } fn genesis( endowed_accounts: Vec<AccountId>, initial_difficulty: u32, // TODO [6-genesis-builder] // utxo_genesis_accounts: Vec<AccountId>, ) -> serde_json::Value { serde_json::json!({ "balances": { // Configure endowed accounts with initial balance of 1 << 50. "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 50)).collect::<Vec<_>>(), }, "keccakDifficultyAdjustment": { "initialDifficulty": u32_to_u8_32(initial_difficulty), }, "md5DifficultyAdjustment": { "initialDifficulty": u32_to_u8_32(initial_difficulty), }, "sha3DifficultyAdjustment": { "initialDifficulty": u32_to_u8_32(initial_difficulty), }, // TODO [6-genesis-builder] // "utxo": { // "genesisUtxos": utxo_genesis_accounts // .iter().cloned() // .map(|k| { // let hash = H256::from_slice(&k.as_slice()); // let value: Value = (1u64 << 50).into(); // let genesis_utxo: GenesisUtxoType = (value, hash); // genesis_utxo // }).collect::<Vec<GenesisUtxoType>>(), // }, }) } /// Convert u32 (default value) to [u8;32] (U256) /// in little-endian format pub fn u32_to_u8_32(num: u32) -> [u8; 32] { let mut result = [0u8; 32]; let bytes = num.to_le_bytes(); result[..4].copy_from_slice(&bytes); result } fn system_properties() -> sc_chain_spec::Properties { let mut properties = sc_chain_spec::Properties::new(); properties.insert("ss58Format".into(), SS58Prefix::get().into()); properties.insert("tokenSymbol".into(), TOKEN_SYMBOL.into()); properties.insert("tokenDecimals".into(), TOKEN_DECIMALS.into()); properties } }
#![allow(unused)] fn main() { // We make sure this pallet uses `no_std` for compiling to Wasm. #![cfg_attr(not(feature = "std"), no_std)] use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_core::{ sr25519::{Public, Signature}, ByteArray, H256, H512, }; use sp_runtime::traits::{BlakeTwo256, Hash, SaturatedConversion}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; use super::{block_author::BlockAuthor, issuance::Issuance}; pub use pallet::*; /// [2-data-structure] pub type Value = u128; /// [2-data-structure] /// Single transaction to be dispatched #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug, TypeInfo)] pub struct Transaction { /// UTXOs to be used as inputs for current transaction pub inputs: Vec<TransactionInput>, /// UTXOs to be created as a result of current transaction dispatch pub outputs: Vec<TransactionOutput>, } /// [2-data-structure] /// Single transaction input that refers to one UTXO #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug, TypeInfo)] pub struct TransactionInput { /// Reference to an UTXO to be spent pub outpoint: H256, /// Proof that transaction owner is authorized to spend referred UTXO & /// that the entire transaction is untampered pub sigscript: H512, } /// [2-data-structure] /// Single transaction output to create upon transaction dispatch #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug, TypeInfo)] pub struct TransactionOutput { /// Value associated with this output pub value: Value, /// Public key associated with this output. In order to spend this output /// owner must provide a proof by hashing the whole `Transaction` and /// signing it with a corresponding private key. pub pubkey: H256, } /// TODO [6-genesis-builder] /// Because code is built on `no-std` feature. /// And we got error: /// ``` /// ... /// the trait `Serialize` is not implemented for `TransactionOutput` /// the trait `Deserialize<'_>` is not implemented for `TransactionOutput` /// ... /// ``` /// /// Hence, we need to simplify data type to make it work in both `std` and `no-std` feature. /// Genesis Utxo Type // pub type GenesisUtxoType = (Value, H256); #[frame_support::pallet(dev_mode)] pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use super::*; /// [2-data-structure] #[pallet::config] pub trait Config: frame_system::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. /// Read more: https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_runtime_types/index.html type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; /// A source to determine the block author type BlockAuthor: BlockAuthor; /// A source to determine the issuance portion of the block reward type Issuance: Issuance<BlockNumberFor<Self>, Value>; } #[pallet::pallet] pub struct Pallet<T>(_); /// [2-data-structure] /// Total reward value to be redistributed among authorities. /// It is accumulated from transactions during block execution /// and then dispersed to validators on block finalization. #[pallet::storage] #[pallet::getter(fn total_reward)] pub type TotalReward<T: Config> = StorageValue<_, Value, ValueQuery>; /// [2-data-structure] /// All valid unspent transaction outputs are stored in this map. /// Initial set of UTXO is populated from the list stored in genesis. /// We use the identity hasher here because the cryptographic hashing is /// done explicitly. /// Mapping from `BlakeTwo256::hash_of(transaction, index)` to `TransactionOutput` #[pallet::storage] #[pallet::getter(fn utxo_store)] pub type UtxoStore<T: Config> = StorageMap< Hasher = Identity, Key = H256, Value = TransactionOutput, QueryKind = OptionQuery, >; /// TODO [6-genesis-builder] /// Keep track of latest UTXO hash of account /// Mapping from `sr25519::Pubkey` to `BlakeTwo256::hash_of(transaction, index)` /// Just for testing 🫤 /// Because 1 account may have multiple UTXOs // #[pallet::storage] // #[pallet::getter(fn utxo_of)] // pub type UtxoOf<T: Config> = // StorageMap<Hasher = Identity, Key = Public, Value = H256, QueryKind = OptionQuery>; /// TODO [6-genesis-builder] // #[pallet::genesis_config] // pub struct GenesisConfig<T: Config> { // pub _ph_data: Option<PhantomData<T>>, // pub genesis_utxos: Vec<GenesisUtxoType>, // } /// TODO [6-genesis-builder] // impl<T: Config> Default for GenesisConfig<T> { // fn default() -> Self { // Self { // _ph_data: Default::default(), // genesis_utxos: Vec::<GenesisUtxoType>::new(), // } // } // } /// TODO [6-genesis-builder] // #[pallet::genesis_build] // impl<T: Config> BuildGenesisConfig for GenesisConfig<T> { // fn build(&self) { // for utxo in self.genesis_utxos.iter() { // let utxo = TransactionOutput { value: utxo.0, pubkey: utxo.1 }; // let hash = BlakeTwo256::hash_of(&utxo); // Pallet::<T>::store_utxo(&utxo, hash); // } // } // } /// [2-data-structure] /// Pallets use events to inform users when important changes are made. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { /// Dispatch transaction successful TransactionSuccess(Transaction), /// UTXO out processed TransactionOutputProcessed(H256), /// Reward distributed to `BlockAuthor` RewardDistributed(Value, H256), /// Faucet to `To` Faucet(Value, H256), /// No one get reward RewardWasted, } /// [2-data-structure] /// Errors inform users that something went wrong. #[pallet::error] pub enum Error<T> { /// Missing `Transaction` Input MissingInput, /// Reward overflow RewardOverflow, /// Maximum transaction depth MaximumTransactionDepth, /// Empty input EmptyInput, /// Empty output EmptyOutput, /// Each input must only be used once DuplicatedInput, /// Each output must be defined only once DuplicatedOutput, /// Input value is overflow InputOverflow, /// Output value is overflow OutputOverflow, /// Output spent must lte than Input spent OutputOverInput, /// Zero amount spent ZeroAmount, /// Invalid signature InvalidSignature, } /// [4-dispersed-reward] #[pallet::hooks] impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { fn on_finalize(_n: BlockNumberFor<T>) { match T::BlockAuthor::block_author() { // Block author did not provide key to claim reward None => Self::deposit_event(Event::RewardWasted), // Block author did provide key, so issue thir reward Some(author) => Self::disperse_reward(&author), } } } /// [2-data-structure] /// Define extrinsics / dispatchable function #[pallet::call] impl<T: Config> Pallet<T> { pub fn spend(_origin: OriginFor<T>, transaction: Transaction) -> DispatchResult { // [3-spend-utxo] // validate transaction let transaction_validity = Self::validate_transaction(&transaction)?; ensure!( transaction_validity.requires.is_empty(), Error::<T>::MissingInput ); // implement logic Self::do_spend(&transaction, transaction_validity.priority as Value)?; // emit event Self::deposit_event(Event::<T>::TransactionSuccess(transaction)); Ok(()) } } /// [2-data-structure] /// Define intrinsics impl<T: Config> Pallet<T> { /// Implement spend logic, update storage to reflect changes made by transaction /// Where each UTXO key is a hash of the entire transaction and its order in the `TransactionOutputs` vector fn do_spend(transaction: &Transaction, reward: Value) -> DispatchResult { // [3-spend-utxo] // Calculate new reward total. The rest of `total_input - total_output` will be used for block reward. let new_total = TotalReward::<T>::get() .checked_add(reward) .ok_or(Error::<T>::RewardOverflow)?; TotalReward::<T>::put(new_total); // Removing spent UTXOs for input in &transaction.inputs { UtxoStore::<T>::remove(input.outpoint); } let mut index: u64 = 0; for output in &transaction.outputs { let hash = BlakeTwo256::hash_of(&(&transaction.encode(), index)); // validated before, this is safe index = index .checked_add(1) .ok_or(Error::<T>::MaximumTransactionDepth) .unwrap(); Self::store_utxo(output, hash); Self::deposit_event(Event::TransactionOutputProcessed(hash)); } Ok(()) } /// Redistribute combined reward value to block Author fn disperse_reward(author: &Public) { // [4-dispersed-reward] // take the rest of reward // plus issuance reward of current block number let reward = TotalReward::<T>::take() + T::Issuance::issuance(frame_system::Pallet::<T>::block_number()); let utxo = TransactionOutput { value: reward, pubkey: H256::from_slice(author.as_slice()), }; let hash = BlakeTwo256::hash_of(&( &utxo, frame_system::Pallet::<T>::block_number().saturated_into::<u64>(), )); Self::store_utxo(&utxo, hash); Self::deposit_event(Event::RewardDistributed(reward, hash)); } /// Mutate storage, insert / update new UTXOs fn store_utxo(utxo: &TransactionOutput, hash: H256) { // [3-spend-utxo] UtxoStore::<T>::insert(hash, utxo); // TODO [6-genesis-builder] // Convert H256 back to sr25519::Public // let pubkey = Public::from_h256(utxo.pubkey); // UtxoOf::<T>::insert(pubkey, hash); } /// Strips a transaction of its Signature fields by replacing value with ZERO-initialized fixed hash. fn get_simple_transaction(transaction: &Transaction) -> Vec<u8> { // [3-spend-utxo] let mut trx = transaction.clone(); for input in trx.inputs.iter_mut() { input.sigscript = H512::zero(); } trx.encode() } /// Check transaction for validity, errors, & race conditions /// Called by both transaction pool and runtime execution pub fn validate_transaction( transaction: &Transaction, ) -> Result<ValidTransaction, &'static str> { // [3-spend-utxo] // Check inputs and outputs are not empty ensure!(!transaction.inputs.is_empty(), Error::<T>::EmptyInput); ensure!(!transaction.outputs.is_empty(), Error::<T>::EmptyOutput); // Check each input is used exactly once { let input_set: BTreeMap<_, ()> = transaction.inputs.iter().map(|input| (input, ())).collect(); ensure!( input_set.len() == transaction.inputs.len(), Error::<T>::DuplicatedInput ); } { let output_set: BTreeMap<_, ()> = transaction .outputs .iter() .map(|output| (output, ())) .collect(); ensure!( output_set.len() == transaction.outputs.len(), Error::<T>::DuplicatedOutput ); } let mut total_input: Value = 0; let mut total_output: Value = 0; let mut output_index: u64 = 0; let simple_transaction = Self::get_simple_transaction(transaction); // Variables sent to transaction pool let mut missing_utxos = Vec::new(); let mut new_utxos = Vec::new(); let mut reward = 0; for input in transaction.inputs.iter() { // Check all inputs match to existing, unspent and unlocked outputs if let Some(input_utxo) = UtxoStore::<T>::get(&input.outpoint) { // Check provided signatures are valid let is_valid_sig = sp_io::crypto::sr25519_verify( &Signature::from_raw(*input.sigscript.as_fixed_bytes()), &simple_transaction, &Public::from_h256(input_utxo.pubkey), ); ensure!(is_valid_sig, Error::<T>::InvalidSignature); // Check sum of input values does not overflow total_input = total_input .checked_add(input_utxo.value) .ok_or(Error::<T>::InputOverflow)?; } else { missing_utxos.push(input.outpoint.clone().as_fixed_bytes().to_vec()); } } // Check each output is defined exactly once and has nonzero value for output in transaction.outputs.iter() { ensure!(output.value > 0, Error::<T>::ZeroAmount); let hash = BlakeTwo256::hash_of(&(&transaction.encode(), output_index)); output_index = output_index .checked_add(1) .ok_or(Error::<T>::MaximumTransactionDepth)?; // Check new outputs do not collide with existing ones ensure!( !UtxoStore::<T>::contains_key(hash), Error::<T>::DuplicatedOutput ); // Check sum of output values does not overflow total_output = total_output .checked_add(output.value) .ok_or(Error::<T>::OutputOverflow)?; new_utxos.push(hash.as_fixed_bytes().to_vec()); } // If no race condition, check the math if missing_utxos.is_empty() { // Check total output value must not exceed total input value ensure!(total_input >= total_output, Error::<T>::OutputOverInput); reward = total_input .checked_sub(total_output) .ok_or(Error::<T>::RewardOverflow)?; } // Returns transaction details Ok(ValidTransaction { requires: missing_utxos, provides: new_utxos, priority: reward as u64, longevity: TransactionLongevity::max_value(), propagate: true, }) } } } }
#![allow(unused)] fn main() { use std::str::FromStr; use academy_pow_runtime::{ AccountId, SS58Prefix, Signature, TOKEN_DECIMALS, TOKEN_SYMBOL, WASM_BINARY, // [6-genesis-builder] utxo::{GenesisUtxoType, Value}, }; use multi_pow::{ForkHeights, ForkingConfig, MaxiPosition}; use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; use serde::{Deserialize, Serialize}; use sp_core::{sr25519, ByteArray, Pair, Public, H256}; use sp_runtime::traits::{IdentifyAccount, Verify}; /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec<ForkingExtensions>; /// PoW and Forking related chain spec extensions to configure the client side forking behavior. /// /// The forks here are all related to adding and removing hash algorithms from the PoW. /// The chain begins supporting only md5. Later is adds sha3 and keccak. Later it removes md5. /// And finally there is a contentious fork where people become maxis. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] #[serde(deny_unknown_fields)] pub struct ForkingExtensions { /// Manual mode is intended for when we you are running a live workshop. /// No forking happens automatically. Rather, you have to hard-code the forks. /// /// If manual mode is enabled, the rest of the parameters are ignored. /// This should really be an enum, but I have to work around the broken extension system. /// /// Aww damn it! I can't even use bool in this broken system? Okay then I guess 0 means /// automatic mode and anything else means manual mode. pub manual_mode: u32, /// The block height to perform the soft fork that adds sha3 and keccak support. pub add_sha3_keccak: u32, /// The block height to perform the hard fork that removes md5 support. pub remove_md5: u32, /// The block height to perform the contentious fork where some become sha3- or keccak-maxis. pub split_sha3_keccak: u32, // Damn extension thing is so fragile, I can't even use an enum here. // Let alone that time I tried to use the forked value feature. /// The political position that this node will take at the contentious fork. pub maxi_position: String, } impl From<&ForkingExtensions> for ForkingConfig { fn from(e: &ForkingExtensions) -> Self { if e.manual_mode > 0 { return Self::Manual; } let fork_heights = ForkHeights { add_sha3_keccak: e.add_sha3_keccak, remove_md5: e.remove_md5, split_sha3_keccak: e.split_sha3_keccak, }; let maxi_position = MaxiPosition::from_str(&e.maxi_position).expect("Should have a valid maxi position..."); Self::Automatic(fork_heights, maxi_position) } } impl ForkingExtensions { /// Try to get the extension from the given `ChainSpec`. pub fn try_get(chain_spec: &dyn sc_service::ChainSpec) -> Option<&Self> { sc_chain_spec::get_extension(chain_spec.extensions()) } } /// Generate a crypto pair from seed. pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public { TPublic::Pair::from_string(&format!("//{}", seed), None) .expect("static values are valid; qed") .public() } type AccountPublic = <Signature as Verify>::Signer; /// Generate an account ID from seed. pub fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId where AccountPublic: From<<TPublic::Pair as Pair>::Public>, { AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account() } pub fn development_config() -> Result<ChainSpec, String> { Ok(ChainSpec::builder( WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?, ForkingExtensions { manual_mode: 1, // change this to `0` if you want to try `auto_fork_validation` add_sha3_keccak: 10, remove_md5: 20, split_sha3_keccak: 30, maxi_position: String::from("follow-mining"), }, ) .with_name("Development") .with_id("dev") .with_chain_type(ChainType::Development) .with_genesis_config_patch(genesis( // Pre-funded accounts vec![ get_account_id_from_seed::<sr25519::Public>("Alice"), get_account_id_from_seed::<sr25519::Public>("Bob"), get_account_id_from_seed::<sr25519::Public>("Alice//stash"), get_account_id_from_seed::<sr25519::Public>("Bob//stash"), ], // Initial Difficulty 4_000_000, // [6-genesis-builder] vec![ get_account_id_from_seed::<sr25519::Public>("Alice"), get_account_id_from_seed::<sr25519::Public>("Bob"), get_account_id_from_seed::<sr25519::Public>("Alice//stash"), get_account_id_from_seed::<sr25519::Public>("Bob//stash"), ], )) .with_properties(system_properties()) .build()) } pub fn testnet_config() -> Result<ChainSpec, String> { Ok(ChainSpec::builder( WASM_BINARY.ok_or_else(|| "Development wasm not available".to_string())?, ForkingExtensions { manual_mode: 1, add_sha3_keccak: 0, remove_md5: 0, split_sha3_keccak: 0, maxi_position: String::new(), }, ) .with_name("Testnet") .with_id("testnet") .with_chain_type(ChainType::Local) .with_genesis_config_patch(genesis( vec![ get_account_id_from_seed::<sr25519::Public>("Alice"), get_account_id_from_seed::<sr25519::Public>("Bob"), get_account_id_from_seed::<sr25519::Public>("Alice//stash"), get_account_id_from_seed::<sr25519::Public>("Bob//stash"), ], 4_000_000, // [6-genesis-builder] vec![ get_account_id_from_seed::<sr25519::Public>("Alice"), get_account_id_from_seed::<sr25519::Public>("Bob"), get_account_id_from_seed::<sr25519::Public>("Alice//stash"), get_account_id_from_seed::<sr25519::Public>("Bob//stash"), ], )) .with_properties(system_properties()) .build()) } fn genesis( endowed_accounts: Vec<AccountId>, initial_difficulty: u32, // [6-genesis-builder] utxo_genesis_accounts: Vec<AccountId>, ) -> serde_json::Value { serde_json::json!({ "balances": { // Configure endowed accounts with initial balance of 1 << 50. "balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 50)).collect::<Vec<_>>(), }, "keccakDifficultyAdjustment": { "initialDifficulty": u32_to_u8_32(initial_difficulty), }, "md5DifficultyAdjustment": { "initialDifficulty": u32_to_u8_32(initial_difficulty), }, "sha3DifficultyAdjustment": { "initialDifficulty": u32_to_u8_32(initial_difficulty), }, // [6-genesis-builder] "utxo": { "genesisUtxos": utxo_genesis_accounts .iter().cloned() .map(|k| { let hash = H256::from_slice(&k.as_slice()); let value: Value = (1u64 << 50).into(); let genesis_utxo: GenesisUtxoType = (value, hash); genesis_utxo }).collect::<Vec<GenesisUtxoType>>(), }, }) } /// Convert u32 (default value) to [u8;32] (U256) /// in little-endian format pub fn u32_to_u8_32(num: u32) -> [u8; 32] { let mut result = [0u8; 32]; let bytes = num.to_le_bytes(); result[..4].copy_from_slice(&bytes); result } fn system_properties() -> sc_chain_spec::Properties { let mut properties = sc_chain_spec::Properties::new(); properties.insert("ss58Format".into(), SS58Prefix::get().into()); properties.insert("tokenSymbol".into(), TOKEN_SYMBOL.into()); properties.insert("tokenDecimals".into(), TOKEN_DECIMALS.into()); properties } }
#![allow(unused)] fn main() { // We make sure this pallet uses `no_std` for compiling to Wasm. #![cfg_attr(not(feature = "std"), no_std)] use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_core::{ sr25519::{Public, Signature}, ByteArray, H256, H512, }; use sp_runtime::traits::{BlakeTwo256, Hash, SaturatedConversion}; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; use super::{block_author::BlockAuthor, issuance::Issuance}; pub use pallet::*; /// [2-data-structure] pub type Value = u128; /// [2-data-structure] /// Single transaction to be dispatched #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug, TypeInfo)] pub struct Transaction { /// UTXOs to be used as inputs for current transaction pub inputs: Vec<TransactionInput>, /// UTXOs to be created as a result of current transaction dispatch pub outputs: Vec<TransactionOutput>, } /// [2-data-structure] /// Single transaction input that refers to one UTXO #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug, TypeInfo)] pub struct TransactionInput { /// Reference to an UTXO to be spent pub outpoint: H256, /// Proof that transaction owner is authorized to spend referred UTXO & /// that the entire transaction is untampered pub sigscript: H512, } /// [2-data-structure] /// Single transaction output to create upon transaction dispatch #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(PartialEq, Eq, PartialOrd, Ord, Default, Clone, Encode, Decode, Hash, Debug, TypeInfo)] pub struct TransactionOutput { /// Value associated with this output pub value: Value, /// Public key associated with this output. In order to spend this output /// owner must provide a proof by hashing the whole `Transaction` and /// signing it with a corresponding private key. pub pubkey: H256, } /// [6-genesis-builder] /// Because code is built on `no-std` feature. /// And we got error: /// ``` /// ... /// the trait `Serialize` is not implemented for `TransactionOutput` /// the trait `Deserialize<'_>` is not implemented for `TransactionOutput` /// ... /// ``` /// /// Hence, we need to simplify data type to make it work in both `std` and `no-std` feature. /// Genesis Utxo Type pub type GenesisUtxoType = (Value, H256); #[frame_support::pallet(dev_mode)] pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use super::*; /// [2-data-structure] #[pallet::config] pub trait Config: frame_system::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. /// Read more: https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_runtime_types/index.html type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; /// A source to determine the block author type BlockAuthor: BlockAuthor; /// A source to determine the issuance portion of the block reward type Issuance: Issuance<BlockNumberFor<Self>, Value>; } #[pallet::pallet] pub struct Pallet<T>(_); /// [2-data-structure] /// Total reward value to be redistributed among authorities. /// It is accumulated from transactions during block execution /// and then dispersed to validators on block finalization. #[pallet::storage] #[pallet::getter(fn total_reward)] pub type TotalReward<T: Config> = StorageValue<_, Value, ValueQuery>; /// [2-data-structure] /// All valid unspent transaction outputs are stored in this map. /// Initial set of UTXO is populated from the list stored in genesis. /// We use the identity hasher here because the cryptographic hashing is /// done explicitly. /// Mapping from `BlakeTwo256::hash_of(transaction, index)` to `TransactionOutput` #[pallet::storage] #[pallet::getter(fn utxo_store)] pub type UtxoStore<T: Config> = StorageMap< Hasher = Identity, Key = H256, Value = TransactionOutput, QueryKind = OptionQuery, >; /// [6-genesis-builder] /// Keep track of latest UTXO hash of account /// Mapping from `sr25519::Pubkey` to `BlakeTwo256::hash_of(transaction, index)` /// Just for testing 🫤 /// Because 1 account may have multiple UTXOs #[pallet::storage] #[pallet::getter(fn utxo_of)] pub type UtxoOf<T: Config> = StorageMap<Hasher = Identity, Key = Public, Value = H256, QueryKind = OptionQuery>; /// [6-genesis-builder] #[pallet::genesis_config] pub struct GenesisConfig<T: Config> { pub _ph_data: Option<PhantomData<T>>, pub genesis_utxos: Vec<GenesisUtxoType>, } /// [6-genesis-builder] impl<T: Config> Default for GenesisConfig<T> { fn default() -> Self { Self { _ph_data: Default::default(), genesis_utxos: Vec::<GenesisUtxoType>::new(), } } } /// [6-genesis-builder] #[pallet::genesis_build] impl<T: Config> BuildGenesisConfig for GenesisConfig<T> { fn build(&self) { for utxo in self.genesis_utxos.iter() { let utxo = TransactionOutput { value: utxo.0, pubkey: utxo.1 }; let hash = BlakeTwo256::hash_of(&utxo); Pallet::<T>::store_utxo(&utxo, hash); } } } /// [2-data-structure] /// Pallets use events to inform users when important changes are made. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { /// Dispatch transaction successful TransactionSuccess(Transaction), /// UTXO out processed TransactionOutputProcessed(H256), /// Reward distributed to `BlockAuthor` RewardDistributed(Value, H256), /// Faucet to `To` Faucet(Value, H256), /// No one get reward RewardWasted, } /// [2-data-structure] /// Errors inform users that something went wrong. #[pallet::error] pub enum Error<T> { /// Missing `Transaction` Input MissingInput, /// Reward overflow RewardOverflow, /// Maximum transaction depth MaximumTransactionDepth, /// Empty input EmptyInput, /// Empty output EmptyOutput, /// Each input must only be used once DuplicatedInput, /// Each output must be defined only once DuplicatedOutput, /// Input value is overflow InputOverflow, /// Output value is overflow OutputOverflow, /// Output spent must lte than Input spent OutputOverInput, /// Zero amount spent ZeroAmount, /// Invalid signature InvalidSignature, } /// [4-dispersed-reward] #[pallet::hooks] impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { fn on_finalize(_n: BlockNumberFor<T>) { match T::BlockAuthor::block_author() { // Block author did not provide key to claim reward None => Self::deposit_event(Event::RewardWasted), // Block author did provide key, so issue thir reward Some(author) => Self::disperse_reward(&author), } } } /// [2-data-structure] /// Define extrinsics / dispatchable function #[pallet::call] impl<T: Config> Pallet<T> { pub fn spend(_origin: OriginFor<T>, transaction: Transaction) -> DispatchResult { // [3-spend-utxo] // validate transaction let transaction_validity = Self::validate_transaction(&transaction)?; ensure!( transaction_validity.requires.is_empty(), Error::<T>::MissingInput ); // implement logic Self::do_spend(&transaction, transaction_validity.priority as Value)?; // emit event Self::deposit_event(Event::<T>::TransactionSuccess(transaction)); Ok(()) } } /// [2-data-structure] /// Define intrinsics impl<T: Config> Pallet<T> { /// Implement spend logic, update storage to reflect changes made by transaction /// Where each UTXO key is a hash of the entire transaction and its order in the `TransactionOutputs` vector fn do_spend(transaction: &Transaction, reward: Value) -> DispatchResult { // [3-spend-utxo] // Calculate new reward total. The rest of `total_input - total_output` will be used for block reward. let new_total = TotalReward::<T>::get() .checked_add(reward) .ok_or(Error::<T>::RewardOverflow)?; TotalReward::<T>::put(new_total); // Removing spent UTXOs for input in &transaction.inputs { UtxoStore::<T>::remove(input.outpoint); } let mut index: u64 = 0; for output in &transaction.outputs { let hash = BlakeTwo256::hash_of(&(&transaction.encode(), index)); // validated before, this is safe index = index .checked_add(1) .ok_or(Error::<T>::MaximumTransactionDepth) .unwrap(); Self::store_utxo(output, hash); Self::deposit_event(Event::TransactionOutputProcessed(hash)); } Ok(()) } /// Redistribute combined reward value to block Author fn disperse_reward(author: &Public) { // [4-dispersed-reward] // take the rest of reward // plus issuance reward of current block number let reward = TotalReward::<T>::take() + T::Issuance::issuance(frame_system::Pallet::<T>::block_number()); let utxo = TransactionOutput { value: reward, pubkey: H256::from_slice(author.as_slice()), }; let hash = BlakeTwo256::hash_of(&( &utxo, frame_system::Pallet::<T>::block_number().saturated_into::<u64>(), )); Self::store_utxo(&utxo, hash); Self::deposit_event(Event::RewardDistributed(reward, hash)); } /// Mutate storage, insert / update new UTXOs fn store_utxo(utxo: &TransactionOutput, hash: H256) { // [3-spend-utxo] UtxoStore::<T>::insert(hash, utxo); // [6-genesis-builder] // Convert H256 back to sr25519::Public let pubkey = Public::from_h256(utxo.pubkey); UtxoOf::<T>::insert(pubkey, hash); } /// Strips a transaction of its Signature fields by replacing value with ZERO-initialized fixed hash. fn get_simple_transaction(transaction: &Transaction) -> Vec<u8> { // [3-spend-utxo] let mut trx = transaction.clone(); for input in trx.inputs.iter_mut() { input.sigscript = H512::zero(); } trx.encode() } /// Check transaction for validity, errors, & race conditions /// Called by both transaction pool and runtime execution pub fn validate_transaction( transaction: &Transaction, ) -> Result<ValidTransaction, &'static str> { // [3-spend-utxo] // Check inputs and outputs are not empty ensure!(!transaction.inputs.is_empty(), Error::<T>::EmptyInput); ensure!(!transaction.outputs.is_empty(), Error::<T>::EmptyOutput); // Check each input is used exactly once { let input_set: BTreeMap<_, ()> = transaction.inputs.iter().map(|input| (input, ())).collect(); ensure!( input_set.len() == transaction.inputs.len(), Error::<T>::DuplicatedInput ); } { let output_set: BTreeMap<_, ()> = transaction .outputs .iter() .map(|output| (output, ())) .collect(); ensure!( output_set.len() == transaction.outputs.len(), Error::<T>::DuplicatedOutput ); } let mut total_input: Value = 0; let mut total_output: Value = 0; let mut output_index: u64 = 0; let simple_transaction = Self::get_simple_transaction(transaction); // Variables sent to transaction pool let mut missing_utxos = Vec::new(); let mut new_utxos = Vec::new(); let mut reward = 0; for input in transaction.inputs.iter() { // Check all inputs match to existing, unspent and unlocked outputs if let Some(input_utxo) = UtxoStore::<T>::get(&input.outpoint) { // Check provided signatures are valid let is_valid_sig = sp_io::crypto::sr25519_verify( &Signature::from_raw(*input.sigscript.as_fixed_bytes()), &simple_transaction, &Public::from_h256(input_utxo.pubkey), ); ensure!(is_valid_sig, Error::<T>::InvalidSignature); // Check sum of input values does not overflow total_input = total_input .checked_add(input_utxo.value) .ok_or(Error::<T>::InputOverflow)?; } else { missing_utxos.push(input.outpoint.clone().as_fixed_bytes().to_vec()); } } // Check each output is defined exactly once and has nonzero value for output in transaction.outputs.iter() { ensure!(output.value > 0, Error::<T>::ZeroAmount); let hash = BlakeTwo256::hash_of(&(&transaction.encode(), output_index)); output_index = output_index .checked_add(1) .ok_or(Error::<T>::MaximumTransactionDepth)?; // Check new outputs do not collide with existing ones ensure!( !UtxoStore::<T>::contains_key(hash), Error::<T>::DuplicatedOutput ); // Check sum of output values does not overflow total_output = total_output .checked_add(output.value) .ok_or(Error::<T>::OutputOverflow)?; new_utxos.push(hash.as_fixed_bytes().to_vec()); } // If no race condition, check the math if missing_utxos.is_empty() { // Check total output value must not exceed total input value ensure!(total_input >= total_output, Error::<T>::OutputOverInput); reward = total_input .checked_sub(total_output) .ok_or(Error::<T>::RewardOverflow)?; } // Returns transaction details Ok(ValidTransaction { requires: missing_utxos, provides: new_utxos, priority: reward as u64, longevity: TransactionLongevity::max_value(), propagate: true, }) } } } }
diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs
index 091d0e8..90e5e84 100644
--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -2,6 +2,8 @@ use std::str::FromStr;
use academy_pow_runtime::{
AccountId, SS58Prefix, Signature, TOKEN_DECIMALS, TOKEN_SYMBOL, WASM_BINARY,
+ // TODO [6-genesis-builder]
+ utxo::{GenesisUtxoType, Value},
};
use multi_pow::{ForkHeights, ForkingConfig, MaxiPosition};
use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup};
@@ -109,6 +111,13 @@ pub fn development_config() -> Result<ChainSpec, String> {
],
// Initial Difficulty
4_000_000,
+ // TODO [6-genesis-builder]
+ // vec![
+ // get_account_id_from_seed::<sr25519::Public>("Alice"),
+ // get_account_id_from_seed::<sr25519::Public>("Bob"),
+ // get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
+ // get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
+ // ],
))
.with_properties(system_properties())
.build())
@@ -136,12 +145,24 @@ pub fn testnet_config() -> Result<ChainSpec, String> {
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
],
4_000_000,
+ // TODO [6-genesis-builder]
+ // vec![
+ // get_account_id_from_seed::<sr25519::Public>("Alice"),
+ // get_account_id_from_seed::<sr25519::Public>("Bob"),
+ // get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
+ // get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
+ // ],
))
.with_properties(system_properties())
.build())
}
-fn genesis(endowed_accounts: Vec<AccountId>, initial_difficulty: u32) -> serde_json::Value {
+fn genesis(
+ endowed_accounts: Vec<AccountId>,
+ initial_difficulty: u32,
+ // TODO [6-genesis-builder]
+ // utxo_genesis_accounts: Vec<AccountId>,
+) -> serde_json::Value {
serde_json::json!({
"balances": {
// Configure endowed accounts with initial balance of 1 << 50.
@@ -156,6 +177,18 @@ fn genesis(endowed_accounts: Vec<AccountId>, initial_difficulty: u32) -> serde_j
"sha3DifficultyAdjustment": {
"initialDifficulty": u32_to_u8_32(initial_difficulty),
},
+ // TODO [6-genesis-builder]
+ // "utxo": {
+ // "genesisUtxos": utxo_genesis_accounts
+ // .iter().cloned()
+ // .map(|k| {
+ // let hash = H256::from_slice(&k.as_slice());
+ // let value: Value = (1u64 << 50).into();
+ // let genesis_utxo: GenesisUtxoType = (value, hash);
+
+ // genesis_utxo
+ // }).collect::<Vec<GenesisUtxoType>>(),
+ // },
})
}
diff --git a/runtime/src/utxo.rs b/runtime/src/utxo.rs
index ed8ec6a..0632046 100644
--- a/runtime/src/utxo.rs
+++ b/runtime/src/utxo.rs
@@ -57,6 +57,21 @@ pub struct TransactionOutput {
pub pubkey: H256,
}
+
+/// TODO [6-genesis-builder]
+/// Because code is built on `no-std` feature.
+/// And we got error:
+/// ```
+/// ...
+/// the trait `Serialize` is not implemented for `TransactionOutput`
+/// the trait `Deserialize<'_>` is not implemented for `TransactionOutput`
+/// ...
+/// ```
+///
+/// Hence, we need to simplify data type to make it work in both `std` and `no-std` feature.
+/// Genesis Utxo Type
+// pub type GenesisUtxoType = (Value, H256);
+
#[frame_support::pallet(dev_mode)]
pub mod pallet {
use frame_support::pallet_prelude::*;
@@ -105,6 +120,44 @@ pub mod pallet {
QueryKind = OptionQuery,
>;
+ /// TODO [6-genesis-builder]
+ /// Keep track of latest UTXO hash of account
+ /// Mapping from `sr25519::Pubkey` to `BlakeTwo256::hash_of(transaction, index)`
+ /// Just for testing 🫤
+ /// Because 1 account may have multiple UTXOs
+ // #[pallet::storage]
+ // #[pallet::getter(fn utxo_of)]
+ // pub type UtxoOf<T: Config> =
+ // StorageMap<Hasher = Identity, Key = Public, Value = H256, QueryKind = OptionQuery>;
+
+ /// TODO [6-genesis-builder]
+ // #[pallet::genesis_config]
+ // pub struct GenesisConfig<T: Config> {
+ // pub _ph_data: Option<PhantomData<T>>,
+ // pub genesis_utxos: Vec<GenesisUtxoType>,
+ // }
+
+ /// TODO [6-genesis-builder]
+ // impl<T: Config> Default for GenesisConfig<T> {
+ // fn default() -> Self {
+ // Self {
+ // _ph_data: Default::default(),
+ // genesis_utxos: Vec::<GenesisUtxoType>::new(),
+ // }
+ // }
+ // }
+
+ /// TODO [6-genesis-builder]
+ // #[pallet::genesis_build]
+ // impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
+ // fn build(&self) {
+ // for utxo in self.genesis_utxos.iter() {
+ // let utxo = TransactionOutput { value: utxo.0, pubkey: utxo.1 };
+ // let hash = BlakeTwo256::hash_of(&utxo);
+ // Pallet::<T>::store_utxo(&utxo, hash);
+ // }
+ // }
+ // }
/// [2-data-structure]
/// Pallets use events to inform users when important changes are made.
@@ -250,7 +303,10 @@ pub mod pallet {
// [3-spend-utxo]
UtxoStore::<T>::insert(hash, utxo);
- // // further update 😉
+ // TODO [6-genesis-builder]
+ // Convert H256 back to sr25519::Public
+ // let pubkey = Public::from_h256(utxo.pubkey);
+ // UtxoOf::<T>::insert(pubkey, hash);
}
/// Strips a transaction of its Signature fields by replacing value with ZERO-initialized fixed hash.
diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs
index 90e5e84..71fc6c7 100644
--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -2,7 +2,7 @@ use std::str::FromStr;
use academy_pow_runtime::{
AccountId, SS58Prefix, Signature, TOKEN_DECIMALS, TOKEN_SYMBOL, WASM_BINARY,
- // TODO [6-genesis-builder]
+ // [6-genesis-builder]
utxo::{GenesisUtxoType, Value},
};
use multi_pow::{ForkHeights, ForkingConfig, MaxiPosition};
@@ -111,13 +111,13 @@ pub fn development_config() -> Result<ChainSpec, String> {
],
// Initial Difficulty
4_000_000,
- // TODO [6-genesis-builder]
- // vec![
- // get_account_id_from_seed::<sr25519::Public>("Alice"),
- // get_account_id_from_seed::<sr25519::Public>("Bob"),
- // get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
- // get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
- // ],
+ // [6-genesis-builder]
+ vec![
+ get_account_id_from_seed::<sr25519::Public>("Alice"),
+ get_account_id_from_seed::<sr25519::Public>("Bob"),
+ get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
+ get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
+ ],
))
.with_properties(system_properties())
.build())
@@ -145,13 +145,13 @@ pub fn testnet_config() -> Result<ChainSpec, String> {
get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
],
4_000_000,
- // TODO [6-genesis-builder]
- // vec![
- // get_account_id_from_seed::<sr25519::Public>("Alice"),
- // get_account_id_from_seed::<sr25519::Public>("Bob"),
- // get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
- // get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
- // ],
+ // [6-genesis-builder]
+ vec![
+ get_account_id_from_seed::<sr25519::Public>("Alice"),
+ get_account_id_from_seed::<sr25519::Public>("Bob"),
+ get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
+ get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
+ ],
))
.with_properties(system_properties())
.build())
@@ -160,8 +160,8 @@ pub fn testnet_config() -> Result<ChainSpec, String> {
fn genesis(
endowed_accounts: Vec<AccountId>,
initial_difficulty: u32,
- // TODO [6-genesis-builder]
- // utxo_genesis_accounts: Vec<AccountId>,
+ // [6-genesis-builder]
+ utxo_genesis_accounts: Vec<AccountId>,
) -> serde_json::Value {
serde_json::json!({
"balances": {
@@ -177,18 +177,18 @@ fn genesis(
"sha3DifficultyAdjustment": {
"initialDifficulty": u32_to_u8_32(initial_difficulty),
},
- // TODO [6-genesis-builder]
- // "utxo": {
- // "genesisUtxos": utxo_genesis_accounts
- // .iter().cloned()
- // .map(|k| {
- // let hash = H256::from_slice(&k.as_slice());
- // let value: Value = (1u64 << 50).into();
- // let genesis_utxo: GenesisUtxoType = (value, hash);
-
- // genesis_utxo
- // }).collect::<Vec<GenesisUtxoType>>(),
- // },
+ // [6-genesis-builder]
+ "utxo": {
+ "genesisUtxos": utxo_genesis_accounts
+ .iter().cloned()
+ .map(|k| {
+ let hash = H256::from_slice(&k.as_slice());
+ let value: Value = (1u64 << 50).into();
+ let genesis_utxo: GenesisUtxoType = (value, hash);
+
+ genesis_utxo
+ }).collect::<Vec<GenesisUtxoType>>(),
+ },
})
}
diff --git a/runtime/src/utxo.rs b/runtime/src/utxo.rs
index 0632046..5f0bda0 100644
--- a/runtime/src/utxo.rs
+++ b/runtime/src/utxo.rs
@@ -58,7 +58,7 @@ pub struct TransactionOutput {
}
-/// TODO [6-genesis-builder]
+/// [6-genesis-builder]
/// Because code is built on `no-std` feature.
/// And we got error:
/// ```
@@ -70,7 +70,7 @@ pub struct TransactionOutput {
///
/// Hence, we need to simplify data type to make it work in both `std` and `no-std` feature.
/// Genesis Utxo Type
-// pub type GenesisUtxoType = (Value, H256);
+pub type GenesisUtxoType = (Value, H256);
#[frame_support::pallet(dev_mode)]
pub mod pallet {
@@ -120,44 +120,44 @@ pub mod pallet {
QueryKind = OptionQuery,
>;
- /// TODO [6-genesis-builder]
+ /// [6-genesis-builder]
/// Keep track of latest UTXO hash of account
/// Mapping from `sr25519::Pubkey` to `BlakeTwo256::hash_of(transaction, index)`
/// Just for testing 🫤
/// Because 1 account may have multiple UTXOs
- // #[pallet::storage]
- // #[pallet::getter(fn utxo_of)]
- // pub type UtxoOf<T: Config> =
- // StorageMap<Hasher = Identity, Key = Public, Value = H256, QueryKind = OptionQuery>;
-
- /// TODO [6-genesis-builder]
- // #[pallet::genesis_config]
- // pub struct GenesisConfig<T: Config> {
- // pub _ph_data: Option<PhantomData<T>>,
- // pub genesis_utxos: Vec<GenesisUtxoType>,
- // }
-
- /// TODO [6-genesis-builder]
- // impl<T: Config> Default for GenesisConfig<T> {
- // fn default() -> Self {
- // Self {
- // _ph_data: Default::default(),
- // genesis_utxos: Vec::<GenesisUtxoType>::new(),
- // }
- // }
- // }
-
- /// TODO [6-genesis-builder]
- // #[pallet::genesis_build]
- // impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
- // fn build(&self) {
- // for utxo in self.genesis_utxos.iter() {
- // let utxo = TransactionOutput { value: utxo.0, pubkey: utxo.1 };
- // let hash = BlakeTwo256::hash_of(&utxo);
- // Pallet::<T>::store_utxo(&utxo, hash);
- // }
- // }
- // }
+ #[pallet::storage]
+ #[pallet::getter(fn utxo_of)]
+ pub type UtxoOf<T: Config> =
+ StorageMap<Hasher = Identity, Key = Public, Value = H256, QueryKind = OptionQuery>;
+
+ /// [6-genesis-builder]
+ #[pallet::genesis_config]
+ pub struct GenesisConfig<T: Config> {
+ pub _ph_data: Option<PhantomData<T>>,
+ pub genesis_utxos: Vec<GenesisUtxoType>,
+ }
+
+ /// [6-genesis-builder]
+ impl<T: Config> Default for GenesisConfig<T> {
+ fn default() -> Self {
+ Self {
+ _ph_data: Default::default(),
+ genesis_utxos: Vec::<GenesisUtxoType>::new(),
+ }
+ }
+ }
+
+ /// [6-genesis-builder]
+ #[pallet::genesis_build]
+ impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
+ fn build(&self) {
+ for utxo in self.genesis_utxos.iter() {
+ let utxo = TransactionOutput { value: utxo.0, pubkey: utxo.1 };
+ let hash = BlakeTwo256::hash_of(&utxo);
+ Pallet::<T>::store_utxo(&utxo, hash);
+ }
+ }
+ }
/// [2-data-structure]
/// Pallets use events to inform users when important changes are made.
@@ -303,10 +303,10 @@ pub mod pallet {
// [3-spend-utxo]
UtxoStore::<T>::insert(hash, utxo);
- // TODO [6-genesis-builder]
+ // [6-genesis-builder]
// Convert H256 back to sr25519::Public
- // let pubkey = Public::from_h256(utxo.pubkey);
- // UtxoOf::<T>::insert(pubkey, hash);
+ let pubkey = Public::from_h256(utxo.pubkey);
+ UtxoOf::<T>::insert(pubkey, hash);
}
/// Strips a transaction of its Signature fields by replacing value with ZERO-initialized fixed hash.