Skip to content
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

beautify TuxedoGenesisConfig #128

Merged
merged 5 commits into from
Nov 12, 2023
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
10 changes: 6 additions & 4 deletions node/src/chain_spec.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use node_template_runtime::TuxedoGenesisConfig;
use node_template_runtime::genesis::*;
use sc_service::ChainType;

// The URL for the telemetry server.
// const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/";

/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
pub type ChainSpec = sc_service::GenericChainSpec<TuxedoGenesisConfig>;
pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>;

// /// Generate a crypto pair from seed.
// pub fn get_from_seed<TPublic: Public>(seed: &str) -> <TPublic::Pair as Pair>::Public {
Expand Down Expand Up @@ -36,7 +36,8 @@ pub fn development_config() -> Result<ChainSpec, String> {
// ID
"dev",
ChainType::Development,
TuxedoGenesisConfig::default,
// TuxedoGenesisConfig
development_genesis_config,
// Bootnodes
vec![],
// Telemetry
Expand All @@ -58,7 +59,8 @@ pub fn local_testnet_config() -> Result<ChainSpec, String> {
// ID
"local_testnet",
ChainType::Local,
TuxedoGenesisConfig::default,
// TuxedoGenesisConfig
development_genesis_config,
// Bootnodes
vec![],
// Telemetry
Expand Down
103 changes: 77 additions & 26 deletions tuxedo-core/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

use crate::{
ensure,
types::{OutputRef, Transaction},
EXTRINSIC_KEY,
types::{Output, OutputRef, Transaction},
ConstraintChecker, Verifier, EXTRINSIC_KEY,
};
use parity_scale_codec::{Decode, Encode};
use sc_chain_spec::BuildGenesisBlock;
use sc_client_api::backend::{Backend, BlockImportOperation};
use sc_executor::RuntimeVersionOf;
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_core::{storage::Storage, traits::CodeExecutor};
use sp_runtime::{
traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT, Zero},
Expand Down Expand Up @@ -97,31 +97,82 @@ impl<'a, Block: BlockT, B: Backend<Block>, E: RuntimeVersionOf + CodeExecutor>
}
}

/// Assimilate the storage into the genesis block.
/// This is done by inserting the genesis extrinsics into the genesis block, along with their outputs.
/// Make sure to pass the transactions in order: the inherents should be first, then the extrinsics.
pub fn assimilate_storage<V: Encode + TypeInfo, C: Encode + TypeInfo>(
storage: &mut Storage,
#[derive(Serialize, Deserialize)]
/// The `TuxedoGenesisConfig` struct is used to configure the genesis state of the runtime.
/// It expects the wasm binary and a list of transactions to be included in the genesis block, and stored along with their outputs.
/// They must not contain any inputs or peeks. These transactions will not be validated by the corresponding ConstraintChecker or Verifier.
/// Make sure to pass the inherents before the extrinsics.
pub struct TuxedoGenesisConfig<V, C> {
wasm_binary: Vec<u8>,
genesis_transactions: Vec<Transaction<V, C>>,
) -> Result<(), String> {
storage
.top
.insert(EXTRINSIC_KEY.to_vec(), genesis_transactions.encode());

for tx in genesis_transactions {
ensure!(
tx.inputs.is_empty() && tx.peeks.is_empty(),
"Genesis transactions must not have any inputs or peeks."
);
let tx_hash = BlakeTwo256::hash_of(&tx.encode());
for (index, utxo) in tx.outputs.iter().enumerate() {
let output_ref = OutputRef {
tx_hash,
index: index as u32,
};
storage.top.insert(output_ref.encode(), utxo.encode());
}

impl<V, C> TuxedoGenesisConfig<V, C> {
/// Create a new `TuxedoGenesisConfig` from a WASM binary and a list of transactions.
/// Make sure to pass the transactions in order: the inherents should be first, then the extrinsics.
pub fn new(wasm_binary: Vec<u8>, genesis_transactions: Vec<Transaction<V, C>>) -> Self {
Self {
wasm_binary,
genesis_transactions,
}
}

Ok(())
pub fn get_transaction(&self, i: usize) -> Option<&Transaction<V, C>> {
self.genesis_transactions.get(i)
}
}

impl<V, C> BuildStorage for TuxedoGenesisConfig<V, C>
where
V: Verifier,
C: ConstraintChecker<V>,
Transaction<V, C>: Encode,
Output<V>: Encode,
{
/// Assimilate the storage into the genesis block.
/// This is done by inserting the genesis extrinsics into the genesis block, along with their outputs.
fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> {
// The wasm binary is stored under a special key.
storage.top.insert(
sp_storage::well_known_keys::CODE.into(),
self.wasm_binary.clone(),
);

// The transactions are stored under a special key.
storage
.top
.insert(EXTRINSIC_KEY.to_vec(), self.genesis_transactions.encode());

let mut finished_with_opening_inherents = false;

for tx in self.genesis_transactions.iter() {
// Enforce that inherents are in the right place
let current_tx_is_inherent = tx.checker.is_inherent();
if current_tx_is_inherent && finished_with_opening_inherents {
return Err(
"Tried to execute opening inherent after switching to non-inherents.".into(),
);
}
if !current_tx_is_inherent && !finished_with_opening_inherents {
// This is the first non-inherent, so we update our flag and continue.
finished_with_opening_inherents = true;
}
// Enforce that transactions do not have any inputs or peeks.
ensure!(
tx.inputs.is_empty() && tx.peeks.is_empty(),
"Genesis transactions must not have any inputs or peeks."
);
// Insert the outputs into the storage.
let tx_hash = BlakeTwo256::hash_of(&tx.encode());
for (index, utxo) in tx.outputs.iter().enumerate() {
let output_ref = OutputRef {
tx_hash,
index: index as u32,
};
storage.top.insert(output_ref.encode(), utxo.encode());
}
}

Ok(())
}
}
195 changes: 195 additions & 0 deletions tuxedo-template-runtime/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
//! Helper module to build a genesis configuration for the template runtime.

use super::{
kitties::{KittyData, Parent},
money::Coin,
OuterConstraintChecker, OuterConstraintCheckerInherentHooks, OuterVerifier, WASM_BINARY,
};
use hex_literal::hex;
use tuxedo_core::{
inherents::InherentInternal,
verifier::{SigCheck, ThresholdMultiSignature, UpForGrabs},
};

/// Helper type for the ChainSpec.
pub type RuntimeGenesisConfig =
tuxedo_core::genesis::TuxedoGenesisConfig<OuterVerifier, OuterConstraintChecker>;

const SHAWN_PUB_KEY_BYTES: [u8; 32] =
hex!("d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67");
const ANDREW_PUB_KEY_BYTES: [u8; 32] =
hex!("baa81e58b1b4d053c2e86d93045765036f9d265c7dfe8b9693bbc2c0f048d93a");

pub fn development_genesis_config() -> RuntimeGenesisConfig {
let signatories = vec![SHAWN_PUB_KEY_BYTES.into(), ANDREW_PUB_KEY_BYTES.into()];

// The inherents are computed using the appropriate method, and placed before the extrinsics.
let mut genesis_transactions = OuterConstraintCheckerInherentHooks::genesis_transactions();

genesis_transactions.extend([
// Money Transactions
Coin::<0>::mint(100, SigCheck::new(SHAWN_PUB_KEY_BYTES)),
Coin::<0>::mint(100, ThresholdMultiSignature::new(1, signatories)),
// Kitty Transactions
KittyData::mint(Parent::mom(), b"mother", UpForGrabs),
KittyData::mint(Parent::dad(), b"father", UpForGrabs),
// TODO: Initial Transactions for Existence
]);

RuntimeGenesisConfig::new(
WASM_BINARY
.expect("Runtime WASM binary must exist.")
.to_vec(),
genesis_transactions,
)
}

#[cfg(test)]
mod tests {
use super::*;

use crate::OuterVerifier;
use parity_scale_codec::{Decode, Encode};
use sp_api::HashT;
use sp_core::testing::SR25519;
use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt};
use sp_runtime::{traits::BlakeTwo256, BuildStorage};
use std::sync::Arc;
use tuxedo_core::{
dynamic_typing::{DynamicallyTypedData, UtxoData},
inherents::InherentInternal,
types::{Output, OutputRef},
};

// other random account generated with subkey
const SHAWN_PHRASE: &str =
"news slush supreme milk chapter athlete soap sausage put clutch what kitten";
const ANDREW_PHRASE: &str =
"monkey happy total rib lumber scrap guide photo country online rose diet";

fn default_runtime_genesis_config() -> RuntimeGenesisConfig {
let keystore = MemoryKeystore::new();

let shawn_pub_key_bytes = keystore
.sr25519_generate_new(SR25519, Some(SHAWN_PHRASE))
.unwrap()
.0;

let andrew_pub_key_bytes = keystore
.sr25519_generate_new(SR25519, Some(ANDREW_PHRASE))
.unwrap()
.0;

let signatories = vec![shawn_pub_key_bytes.into(), andrew_pub_key_bytes.into()];

let mut genesis_transactions = OuterConstraintCheckerInherentHooks::genesis_transactions();
genesis_transactions.extend([
// Money Transactions
Coin::<0>::mint(100, SigCheck::new(shawn_pub_key_bytes)),
Coin::<0>::mint(100, ThresholdMultiSignature::new(1, signatories)),
]);

RuntimeGenesisConfig::new(
WASM_BINARY
.expect("Runtime WASM binary must exist.")
.to_vec(),
genesis_transactions,
)
}

fn new_test_ext() -> sp_io::TestExternalities {
let keystore = MemoryKeystore::new();
let storage = default_runtime_genesis_config()
.build_storage()
.expect("System builds valid default genesis config");

let mut ext = sp_io::TestExternalities::from(storage);
ext.register_extension(KeystoreExt(Arc::new(keystore)));
ext
}

#[test]
fn genesis_utxo_money() {
new_test_ext().execute_with(|| {
let keystore = MemoryKeystore::new();
let shawn_pub_key = keystore
.sr25519_generate_new(SR25519, Some(SHAWN_PHRASE))
.unwrap();

// Grab genesis value from storage and assert it is correct
let genesis_utxo = Output {
verifier: OuterVerifier::SigCheck(SigCheck {
owner_pubkey: shawn_pub_key.into(),
}),
payload: DynamicallyTypedData {
data: 100u128.encode(),
type_id: <money::Coin<0> as UtxoData>::TYPE_ID,
},
};

let inherents_len = OuterConstraintCheckerInherentHooks::genesis_transactions().len();

let tx = default_runtime_genesis_config()
.get_transaction(inherents_len)
.unwrap()
.clone();

assert_eq!(tx.outputs.get(0), Some(&genesis_utxo));

let tx_hash = BlakeTwo256::hash_of(&tx.encode());
let output_ref = OutputRef {
tx_hash,
index: 0_u32,
};

let encoded_utxo =
sp_io::storage::get(&output_ref.encode()).expect("Retrieve Genesis UTXO");
let utxo = Output::decode(&mut &encoded_utxo[..]).expect("Can Decode UTXO correctly");
assert_eq!(utxo, genesis_utxo);
})
}

#[test]
fn genesis_utxo_money_multi_sig() {
new_test_ext().execute_with(|| {
let keystore = MemoryKeystore::new();
let shawn_pub_key = keystore
.sr25519_generate_new(SR25519, Some(SHAWN_PHRASE))
.unwrap();
let andrew_pub_key = keystore
.sr25519_generate_new(SR25519, Some(ANDREW_PHRASE))
.unwrap();

let genesis_multi_sig_utxo = Output {
verifier: OuterVerifier::ThresholdMultiSignature(ThresholdMultiSignature {
threshold: 1,
signatories: vec![shawn_pub_key.into(), andrew_pub_key.into()],
}),
payload: DynamicallyTypedData {
data: 100u128.encode(),
type_id: <money::Coin<0> as UtxoData>::TYPE_ID,
},
};

let inherents_len = OuterConstraintCheckerInherentHooks::genesis_transactions().len();

let tx = default_runtime_genesis_config()
.get_transaction(1 + inherents_len)
.unwrap()
.clone();

assert_eq!(tx.outputs.get(0), Some(&genesis_multi_sig_utxo));

let tx_hash = BlakeTwo256::hash_of(&tx.encode());
let output_ref = OutputRef {
tx_hash,
index: 0_u32,
};

let encoded_utxo =
sp_io::storage::get(&output_ref.encode()).expect("Retrieve Genesis MultiSig UTXO");
let utxo = Output::decode(&mut &encoded_utxo[..]).expect("Can Decode UTXO correctly");
assert_eq!(utxo, genesis_multi_sig_utxo);
})
}
}
Loading
Loading