Skip to content

Commit

Permalink
Genesis Transactions (#127)
Browse files Browse the repository at this point in the history
* draft of `InhrerentInternal::genesis_utxos`

* make it build

* transactions list in `GenesisConfig` imported in block 0 by `TuxedoGenesisBlockBuilder`

* added `genesis_builder.rs`

* make clippy happy

* fixed genesis tests

* beautify GenesisConfig

* moved `genesis_builder.rs` from `node` to `tuxedo-core`

* make it build

* move `assimilate_storage` in `tuxedo-core`

* forgot a file

* let's make toml-sort happy

* update wallet to apply extrinsics for genesis blocks

* clear extrinsics list from storage in `build_genesis_block`

* improved comments, check genesis transactions inputs and peeks length

* removed superfluous Box, ran `cargo clippy --fix` 

* removed "first block hack" references and consequences, fmt

---------

Signed-off-by: muraca <mmuraca247@gmail.com>
  • Loading branch information
muraca authored Nov 7, 2023
1 parent ac4bf26 commit a55975d
Show file tree
Hide file tree
Showing 23 changed files with 432 additions and 256 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk", t

# Substrate primitives and client
sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.2.0" }
sc-chain-spec = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.2.0" }
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.2.0" }
sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.2.0" }
sc-consensus = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-v1.2.0" }
Expand Down
8 changes: 4 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::GenesisConfig;
use node_template_runtime::TuxedoGenesisConfig;
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<GenesisConfig>;
pub type ChainSpec = sc_service::GenericChainSpec<TuxedoGenesisConfig>;

// /// 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,7 @@ pub fn development_config() -> Result<ChainSpec, String> {
// ID
"dev",
ChainType::Development,
GenesisConfig::default,
TuxedoGenesisConfig::default,
// Bootnodes
vec![],
// Telemetry
Expand All @@ -58,7 +58,7 @@ pub fn local_testnet_config() -> Result<ChainSpec, String> {
// ID
"local_testnet",
ChainType::Local,
GenesisConfig::default,
TuxedoGenesisConfig::default,
// Bootnodes
vec![],
// Telemetry
Expand Down
13 changes: 12 additions & 1 deletion node/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use sc_telemetry::{Telemetry, TelemetryWorker};
use sc_transaction_pool_api::OffchainTransactionPoolFactory;
use sp_consensus_aura::sr25519::AuthorityPair as AuraPair;
use std::{sync::Arc, time::Duration};
use tuxedo_core::genesis::TuxedoGenesisBlockBuilder;

// Our native executor instance.
pub struct ExecutorDispatch;
Expand Down Expand Up @@ -72,11 +73,21 @@ pub fn new_partial(

let executor = sc_service::new_native_or_wasm_executor(config);

let backend = sc_service::new_db_backend(config.db_config())?;
let genesis_block_builder = TuxedoGenesisBlockBuilder::new(
config.chain_spec.as_storage_builder(),
!config.no_genesis(),
backend.clone(),
executor.clone(),
)?;

let (client, backend, keystore_container, task_manager) =
sc_service::new_full_parts::<Block, RuntimeApi, _>(
sc_service::new_full_parts_with_genesis_builder::<Block, RuntimeApi, _, _>(
config,
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
executor,
backend,
genesis_block_builder,
)?;
let client = Arc::new(client);

Expand Down
10 changes: 10 additions & 0 deletions tuxedo-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ sp-runtime = { default_features = false, workspace = true }
sp-std = { default_features = false, workspace = true }
sp-storage = { default_features = false, workspace = true }

# Genesis Builder dependencies
sc-chain-spec = { optional = true, workspace = true }
sc-client-api = { optional = true, workspace = true }
sc-executor = { optional = true, workspace = true }
sp-blockchain = { optional = true, workspace = true }

[dev-dependencies]
array-bytes = { workspace = true }

Expand All @@ -44,4 +50,8 @@ std = [
"sp-runtime/std",
"parity-util-mem",
"sp-storage/std",
"sc-client-api",
"sc-chain-spec",
"sc-executor",
"sp-blockchain",
]
21 changes: 20 additions & 1 deletion tuxedo-core/aggregator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ pub fn tuxedo_constraint_checker(attrs: TokenStream, body: TokenStream) -> Token
let inner_types3 = inner_types.clone();
let inner_types4 = inner_types.clone();
let inner_types6 = inner_types.clone();
let inner_types7 = inner_types.clone();
let variants2 = variants.clone();
let variants3 = variants.clone();
let variants4 = variants.clone();
Expand Down Expand Up @@ -240,6 +241,24 @@ pub fn tuxedo_constraint_checker(attrs: TokenStream, body: TokenStream) -> Token
)*
}

#[cfg(feature = "std")]
fn genesis_transactions() -> Vec<tuxedo_core::types::Transaction<#verifier, #outer_type>> {
let mut all_transactions: Vec<tuxedo_core::types::Transaction<#verifier, #outer_type>> = Vec::new();

#(
let transactions =
<<#inner_types6 as tuxedo_core::ConstraintChecker<#verifier>>::InherentHooks as tuxedo_core::inherents::InherentInternal<#verifier, #inner_types6>>::genesis_transactions();
all_transactions.extend(
transactions
.into_iter()
.map(|tx| tx.transform::<#outer_type>())
.collect::<Vec<_>>()
);
)*

all_transactions
}

}

impl tuxedo_core::ConstraintChecker<#verifier> for #outer_type {
Expand All @@ -263,7 +282,7 @@ pub fn tuxedo_constraint_checker(attrs: TokenStream, body: TokenStream) -> Token
fn is_inherent(&self) -> bool {
match self {
#(
Self::#variants6(inner) => <#inner_types6 as tuxedo_core::ConstraintChecker<#verifier>>::is_inherent(inner),
Self::#variants6(inner) => <#inner_types7 as tuxedo_core::ConstraintChecker<#verifier>>::is_inherent(inner),
)*
}

Expand Down
4 changes: 2 additions & 2 deletions tuxedo-core/src/executive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,8 +1050,8 @@ mod tests {
assert_eq!(returned_header, expected_header);

// Make sure the transient storage has been removed
assert!(!sp_io::storage::exists(&HEADER_KEY));
assert!(!sp_io::storage::exists(&EXTRINSIC_KEY));
assert!(!sp_io::storage::exists(HEADER_KEY));
assert!(!sp_io::storage::exists(EXTRINSIC_KEY));
});
}

Expand Down
127 changes: 127 additions & 0 deletions tuxedo-core/src/genesis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
//! Custom GenesisBlockBuilder for Tuxedo, to allow extrinsics to be added to the genesis block.
use crate::{
ensure,
types::{OutputRef, Transaction},
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 sp_core::{storage::Storage, traits::CodeExecutor};
use sp_runtime::{
traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT, Zero},
BuildStorage,
};
use std::sync::Arc;

pub struct TuxedoGenesisBlockBuilder<
'a,
Block: BlockT,
B: Backend<Block>,
E: RuntimeVersionOf + CodeExecutor,
> {
build_genesis_storage: &'a dyn BuildStorage,
commit_genesis_state: bool,
backend: Arc<B>,
executor: E,
_phantom: std::marker::PhantomData<Block>,
}

impl<'a, Block: BlockT, B: Backend<Block>, E: RuntimeVersionOf + CodeExecutor>
TuxedoGenesisBlockBuilder<'a, Block, B, E>
{
pub fn new(
build_genesis_storage: &'a dyn BuildStorage,
commit_genesis_state: bool,
backend: Arc<B>,
executor: E,
) -> sp_blockchain::Result<Self> {
Ok(Self {
build_genesis_storage,
commit_genesis_state,
backend,
executor,
_phantom: Default::default(),
})
}
}

impl<'a, Block: BlockT, B: Backend<Block>, E: RuntimeVersionOf + CodeExecutor>
BuildGenesisBlock<Block> for TuxedoGenesisBlockBuilder<'a, Block, B, E>
{
type BlockImportOperation = <B as Backend<Block>>::BlockImportOperation;

/// Build the genesis block, including the extrinsics found in storage at EXTRINSIC_KEY.
/// The extrinsics are not checked for validity, nor executed, so the values in storage must be placed manually.
/// This can be done by using the `assimilate_storage` function.
fn build_genesis_block(self) -> sp_blockchain::Result<(Block, Self::BlockImportOperation)> {
// We build it here to gain mutable access to the storage.
let mut genesis_storage = self
.build_genesis_storage
.build_storage()
.map_err(sp_blockchain::Error::Storage)?;

let state_version =
sc_chain_spec::resolve_state_version_from_wasm(&genesis_storage, &self.executor)?;

let extrinsics = match genesis_storage.top.remove(crate::EXTRINSIC_KEY) {
Some(v) => <Vec<<Block as BlockT>::Extrinsic>>::decode(&mut &v[..]).unwrap_or_default(),
None => Vec::new(),
};

let extrinsics_root =
<<<Block as BlockT>::Header as HeaderT>::Hashing as HashT>::ordered_trie_root(
extrinsics.iter().map(Encode::encode).collect(),
state_version,
);

let mut op = self.backend.begin_operation()?;
let state_root =
op.set_genesis_state(genesis_storage, self.commit_genesis_state, state_version)?;

let block = Block::new(
HeaderT::new(
Zero::zero(),
extrinsics_root,
state_root,
Default::default(),
Default::default(),
),
extrinsics,
);

Ok((block, op))
}
}

/// 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,
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());
}
}

Ok(())
}
32 changes: 23 additions & 9 deletions tuxedo-core/src/inherents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,7 @@ pub trait TuxedoInherent<V, C: ConstraintChecker<V>>: Sized {
/// The inherent data is supplied by the authoring node.
fn create_inherent(
authoring_inherent_data: &InherentData,
// The option represents the so-called "first block hack".
// We need a way to initialize the chain with a first inherent on block one
// where there is no previous inherent. Once we introduce genesis extrinsics, this can be removed.
previous_inherent: Option<(Transaction<V, C>, H256)>,
previous_inherent: (Transaction<V, C>, H256),
) -> Transaction<V, C>;

/// Perform off-chain pre-execution checks on the inherent.
Expand All @@ -117,6 +114,12 @@ pub trait TuxedoInherent<V, C: ConstraintChecker<V>>: Sized {
inherent: Transaction<V, C>,
results: &mut CheckInherentsResult,
);

/// Return the genesis transactions that are required for this inherent.
#[cfg(feature = "std")]
fn genesis_transactions() -> Vec<Transaction<V, C>> {
Vec::new()
}
}

/// Almost identical to TuxedoInherent, but allows returning multiple extrinsics
Expand Down Expand Up @@ -144,6 +147,10 @@ pub trait InherentInternal<V, C: ConstraintChecker<V>>: Sized {
inherents: Vec<Transaction<V, C>>,
results: &mut CheckInherentsResult,
);

/// Return the genesis transactions that are required for the inherents.
#[cfg(feature = "std")]
fn genesis_transactions() -> Vec<Transaction<V, C>>;
}

/// An adapter to transform structured Tuxedo inherents into the more general and powerful
Expand All @@ -166,7 +173,7 @@ impl<V: Verifier, C: ConstraintChecker<V>, T: TuxedoInherent<V, C> + 'static> In

vec![<T as TuxedoInherent<V, C>>::create_inherent(
authoring_inherent_data,
previous_inherent,
previous_inherent.expect("Previous inherent exists."),
)]
}

Expand All @@ -191,12 +198,14 @@ impl<V: Verifier, C: ConstraintChecker<V>, T: TuxedoInherent<V, C> + 'static> In
.expect("Should be able to put an error.");
return;
}
let inherent = inherents
.get(0)
.expect("We already checked the bounds.")
.clone();
let inherent = inherents.get(0).expect("Previous inherent exists.").clone();
<T as TuxedoInherent<V, C>>::check_inherent(importing_inherent_data, inherent, results)
}

#[cfg(feature = "std")]
fn genesis_transactions() -> Vec<Transaction<V, C>> {
<T as TuxedoInherent<V, C>>::genesis_transactions()
}
}

impl<V, C: ConstraintChecker<V>> InherentInternal<V, C> for () {
Expand All @@ -219,4 +228,9 @@ impl<V, C: ConstraintChecker<V>> InherentInternal<V, C> for () {
"inherent extrinsic was passed to check inherents stub implementation."
)
}

#[cfg(feature = "std")]
fn genesis_transactions() -> Vec<Transaction<V, C>> {
Vec::new()
}
}
3 changes: 3 additions & 0 deletions tuxedo-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub mod types;
pub mod utxo_set;
pub mod verifier;

#[cfg(feature = "std")]
pub mod genesis;

pub use aggregator::{aggregate, tuxedo_constraint_checker, tuxedo_verifier};
pub use constraint_checker::{ConstraintChecker, SimpleConstraintChecker};
pub use executive::Executive;
Expand Down
9 changes: 9 additions & 0 deletions tuxedo-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ impl<V: Default> From<DynamicallyTypedData> for Output<V> {
}
}

impl<V, V1: Into<V>, P: Into<DynamicallyTypedData>> From<(P, V1)> for Output<V> {
fn from(values: (P, V1)) -> Self {
Self {
payload: values.0.into(),
verifier: values.1.into(),
}
}
}

#[cfg(test)]
pub mod tests {

Expand Down
Loading

0 comments on commit a55975d

Please sign in to comment.