diff --git a/Cargo.lock b/Cargo.lock index 10f32275f..cb0016809 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8281,9 +8281,13 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", + "sc-chain-spec", + "sc-client-api", + "sc-executor", "scale-info", "serde", "sp-api", + "sp-blockchain", "sp-core", "sp-debug-derive", "sp-inherents", diff --git a/Cargo.toml b/Cargo.toml index 905b5bba9..06ca721ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 276d45489..b2bdcd115 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -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; +pub type ChainSpec = sc_service::GenericChainSpec; // /// Generate a crypto pair from seed. // pub fn get_from_seed(seed: &str) -> ::Public { @@ -36,7 +36,7 @@ pub fn development_config() -> Result { // ID "dev", ChainType::Development, - GenesisConfig::default, + TuxedoGenesisConfig::default, // Bootnodes vec![], // Telemetry @@ -58,7 +58,7 @@ pub fn local_testnet_config() -> Result { // ID "local_testnet", ChainType::Local, - GenesisConfig::default, + TuxedoGenesisConfig::default, // Bootnodes vec![], // Telemetry diff --git a/node/src/service.rs b/node/src/service.rs index 2940f0aad..b0c0e88dc 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -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; @@ -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::( + sc_service::new_full_parts_with_genesis_builder::( config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, + backend, + genesis_block_builder, )?; let client = Arc::new(client); diff --git a/tuxedo-core/Cargo.toml b/tuxedo-core/Cargo.toml index a90c3af79..d0019f76b 100644 --- a/tuxedo-core/Cargo.toml +++ b/tuxedo-core/Cargo.toml @@ -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 } @@ -44,4 +50,8 @@ std = [ "sp-runtime/std", "parity-util-mem", "sp-storage/std", + "sc-client-api", + "sc-chain-spec", + "sc-executor", + "sp-blockchain", ] diff --git a/tuxedo-core/aggregator/src/lib.rs b/tuxedo-core/aggregator/src/lib.rs index 90a685cc1..f12f78f8b 100644 --- a/tuxedo-core/aggregator/src/lib.rs +++ b/tuxedo-core/aggregator/src/lib.rs @@ -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(); @@ -240,6 +241,24 @@ pub fn tuxedo_constraint_checker(attrs: TokenStream, body: TokenStream) -> Token )* } + #[cfg(feature = "std")] + fn genesis_transactions() -> Vec> { + let mut all_transactions: Vec> = 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::>() + ); + )* + + all_transactions + } + } impl tuxedo_core::ConstraintChecker<#verifier> for #outer_type { @@ -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), )* } diff --git a/tuxedo-core/src/executive.rs b/tuxedo-core/src/executive.rs index f2897f60d..a5181c753 100644 --- a/tuxedo-core/src/executive.rs +++ b/tuxedo-core/src/executive.rs @@ -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)); }); } diff --git a/tuxedo-core/src/genesis.rs b/tuxedo-core/src/genesis.rs new file mode 100644 index 000000000..e4117397f --- /dev/null +++ b/tuxedo-core/src/genesis.rs @@ -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, + E: RuntimeVersionOf + CodeExecutor, +> { + build_genesis_storage: &'a dyn BuildStorage, + commit_genesis_state: bool, + backend: Arc, + executor: E, + _phantom: std::marker::PhantomData, +} + +impl<'a, Block: BlockT, B: Backend, E: RuntimeVersionOf + CodeExecutor> + TuxedoGenesisBlockBuilder<'a, Block, B, E> +{ + pub fn new( + build_genesis_storage: &'a dyn BuildStorage, + commit_genesis_state: bool, + backend: Arc, + executor: E, + ) -> sp_blockchain::Result { + Ok(Self { + build_genesis_storage, + commit_genesis_state, + backend, + executor, + _phantom: Default::default(), + }) + } +} + +impl<'a, Block: BlockT, B: Backend, E: RuntimeVersionOf + CodeExecutor> + BuildGenesisBlock for TuxedoGenesisBlockBuilder<'a, Block, B, E> +{ + type BlockImportOperation = >::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) => ::Extrinsic>>::decode(&mut &v[..]).unwrap_or_default(), + None => Vec::new(), + }; + + let extrinsics_root = + <<::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( + storage: &mut Storage, + genesis_transactions: Vec>, +) -> 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(()) +} diff --git a/tuxedo-core/src/inherents.rs b/tuxedo-core/src/inherents.rs index f67fe50e8..4c2896f42 100644 --- a/tuxedo-core/src/inherents.rs +++ b/tuxedo-core/src/inherents.rs @@ -102,10 +102,7 @@ pub trait TuxedoInherent>: 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, H256)>, + previous_inherent: (Transaction, H256), ) -> Transaction; /// Perform off-chain pre-execution checks on the inherent. @@ -117,6 +114,12 @@ pub trait TuxedoInherent>: Sized { inherent: Transaction, results: &mut CheckInherentsResult, ); + + /// Return the genesis transactions that are required for this inherent. + #[cfg(feature = "std")] + fn genesis_transactions() -> Vec> { + Vec::new() + } } /// Almost identical to TuxedoInherent, but allows returning multiple extrinsics @@ -144,6 +147,10 @@ pub trait InherentInternal>: Sized { inherents: Vec>, results: &mut CheckInherentsResult, ); + + /// Return the genesis transactions that are required for the inherents. + #[cfg(feature = "std")] + fn genesis_transactions() -> Vec>; } /// An adapter to transform structured Tuxedo inherents into the more general and powerful @@ -166,7 +173,7 @@ impl, T: TuxedoInherent + 'static> In vec![>::create_inherent( authoring_inherent_data, - previous_inherent, + previous_inherent.expect("Previous inherent exists."), )] } @@ -191,12 +198,14 @@ impl, T: TuxedoInherent + '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(); >::check_inherent(importing_inherent_data, inherent, results) } + + #[cfg(feature = "std")] + fn genesis_transactions() -> Vec> { + >::genesis_transactions() + } } impl> InherentInternal for () { @@ -219,4 +228,9 @@ impl> InherentInternal for () { "inherent extrinsic was passed to check inherents stub implementation." ) } + + #[cfg(feature = "std")] + fn genesis_transactions() -> Vec> { + Vec::new() + } } diff --git a/tuxedo-core/src/lib.rs b/tuxedo-core/src/lib.rs index 6b702797b..c17402e2b 100644 --- a/tuxedo-core/src/lib.rs +++ b/tuxedo-core/src/lib.rs @@ -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; diff --git a/tuxedo-core/src/types.rs b/tuxedo-core/src/types.rs index 7050061f5..6dc781be9 100644 --- a/tuxedo-core/src/types.rs +++ b/tuxedo-core/src/types.rs @@ -185,6 +185,15 @@ impl From for Output { } } +impl, P: Into> From<(P, V1)> for Output { + fn from(values: (P, V1)) -> Self { + Self { + payload: values.0.into(), + verifier: values.1.into(), + } + } +} + #[cfg(test)] pub mod tests { diff --git a/tuxedo-core/src/verifier.rs b/tuxedo-core/src/verifier.rs index b9e7e0dba..62d16cf53 100644 --- a/tuxedo-core/src/verifier.rs +++ b/tuxedo-core/src/verifier.rs @@ -29,6 +29,14 @@ pub struct SigCheck { pub owner_pubkey: H256, } +impl SigCheck { + pub fn new>(value: T) -> Self { + SigCheck { + owner_pubkey: value.into(), + } + } +} + impl Verifier for SigCheck { fn verify(&self, simplified_tx: &[u8], redeemer: &[u8]) -> bool { let sig = match Signature::try_from(redeemer) { @@ -67,6 +75,13 @@ pub struct ThresholdMultiSignature { } impl ThresholdMultiSignature { + pub fn new(threshold: u8, signatories: Vec) -> Self { + ThresholdMultiSignature { + threshold, + signatories, + } + } + pub fn has_duplicate_signatories(&self) -> bool { let set: BTreeSet<_> = self.signatories.iter().collect(); set.len() < self.signatories.len() @@ -353,12 +368,12 @@ mod test { #[test] fn test_verifier_passes() { let result = TestVerifier { verifies: true }.verify(&[], &[]); - assert_eq!(result, true); + assert!(result); } #[test] fn test_verifier_fails() { let result = TestVerifier { verifies: false }.verify(&[], &[]); - assert_eq!(result, false); + assert!(!result); } } diff --git a/tuxedo-template-runtime/src/lib.rs b/tuxedo-template-runtime/src/lib.rs index fa78d8d80..044e4700b 100644 --- a/tuxedo-template-runtime/src/lib.rs +++ b/tuxedo-template-runtime/src/lib.rs @@ -9,6 +9,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +use kitties::FreeKittyConstraintChecker; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -35,7 +36,6 @@ use sp_version::RuntimeVersion; use serde::{Deserialize, Serialize}; use tuxedo_core::{ - dynamic_typing::{DynamicallyTypedData, UtxoData}, tuxedo_constraint_checker, tuxedo_verifier, types::Transaction as TuxedoTransaction, verifier::{SigCheck, ThresholdMultiSignature, UpForGrabs}, @@ -47,9 +47,6 @@ pub use money; pub use poe; pub use runtime_upgrade; -#[cfg(feature = "std")] -use tuxedo_core::types::OutputRef; - /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know /// the specifics of the runtime. They can then be made to be agnostic over specific formats /// of data like extrinsics, allowing for them to continue syncing the network through upgrades @@ -105,69 +102,85 @@ pub fn native_version() -> NativeVersion { } #[derive(Serialize, Deserialize)] -pub struct GenesisConfig { - pub genesis_utxos: Vec, -} +/// The `TuxedoGenesisConfig` struct is used to configure the genesis state of the runtime. +/// The only parameter is 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. +pub struct TuxedoGenesisConfig(pub Vec); -impl Default for GenesisConfig { +impl Default for TuxedoGenesisConfig { fn default() -> Self { use hex_literal::hex; + use kitties::{KittyDNA, KittyData, Parent}; + use money::{Coin, MoneyConstraintChecker}; + use sp_api::HashT; const SHAWN_PUB_KEY_BYTES: [u8; 32] = hex!("d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"); const ANDREW_PUB_KEY_BYTES: [u8; 32] = hex!("baa81e58b1b4d053c2e86d93045765036f9d265c7dfe8b9693bbc2c0f048d93a"); - - // Initial Config just for a Money UTXO - GenesisConfig { - genesis_utxos: vec![ - Output { - verifier: OuterVerifier::SigCheck(SigCheck { - owner_pubkey: SHAWN_PUB_KEY_BYTES.into(), - }), - payload: DynamicallyTypedData { - data: 100u128.encode(), - type_id: as UtxoData>::TYPE_ID, - }, - }, - Output { - verifier: OuterVerifier::ThresholdMultiSignature(ThresholdMultiSignature { - threshold: 1, - signatories: vec![SHAWN_PUB_KEY_BYTES.into(), ANDREW_PUB_KEY_BYTES.into()], - }), - payload: DynamicallyTypedData { - data: 100u128.encode(), - type_id: as UtxoData>::TYPE_ID, - }, - }, - ], - } - - // TODO: Initial UTXO for Kitties - - // TODO: Initial UTXO for Existence + let signatories = vec![SHAWN_PUB_KEY_BYTES.into(), ANDREW_PUB_KEY_BYTES.into()]; + + let genesis_transactions = vec![ + // Money Transaction + Transaction { + inputs: vec![], + peeks: vec![], + outputs: vec![ + (Coin::<0>(100), SigCheck::new(SHAWN_PUB_KEY_BYTES)).into(), + (Coin::<0>(100), ThresholdMultiSignature::new(1, signatories)).into(), + ], + checker: MoneyConstraintChecker::Mint.into(), + }, + // Kitty Transaction + Transaction { + inputs: vec![], + peeks: vec![], + outputs: vec![ + ( + KittyData { + parent: Parent::Mom(Default::default()), + dna: KittyDNA(BlakeTwo256::hash_of(b"mother")), + ..Default::default() + }, + UpForGrabs, + ) + .into(), + ( + KittyData { + parent: Parent::Dad(Default::default()), + dna: KittyDNA(BlakeTwo256::hash_of(b"father")), + ..Default::default() + }, + UpForGrabs, + ) + .into(), + ], + checker: FreeKittyConstraintChecker.into(), + }, + // TODO: Initial Transactions for Existence + ]; + + TuxedoGenesisConfig(genesis_transactions) } } #[cfg(feature = "std")] -impl BuildStorage for GenesisConfig { +impl BuildStorage for TuxedoGenesisConfig { fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { - // we have nothing to put into storage in genesis, except this: + use tuxedo_core::inherents::InherentInternal; + + // The wasm binary is stored under a special key. storage.top.insert( sp_storage::well_known_keys::CODE.into(), WASM_BINARY.unwrap().to_vec(), ); - for (index, utxo) in self.genesis_utxos.iter().enumerate() { - let output_ref = OutputRef { - // Genesis UTXOs don't come from any real transaction, so just use the zero hash - tx_hash:
::Hash::zero(), - index: index as u32, - }; - storage.top.insert(output_ref.encode(), utxo.encode()); - } + // The inherents transactions are computed using the appropriate method, + // and placed in the block before the normal transactions. + let mut genesis_transactions = OuterConstraintCheckerInherentHooks::genesis_transactions(); + genesis_transactions.extend(self.0.clone()); - Ok(()) + tuxedo_core::genesis::assimilate_storage(storage, genesis_transactions) } } @@ -425,11 +438,14 @@ impl_runtime_apis! { mod tests { use super::*; use parity_scale_codec::Encode; + use sp_api::HashT; use sp_core::testing::SR25519; - use sp_keystore::testing::MemoryKeystore; - use sp_keystore::{Keystore, KeystoreExt}; - + use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt}; use std::sync::Arc; + use tuxedo_core::{ + dynamic_typing::{DynamicallyTypedData, UtxoData}, + types::OutputRef, + }; // other random account generated with subkey const SHAWN_PHRASE: &str = @@ -440,7 +456,7 @@ mod tests { fn new_test_ext() -> sp_io::TestExternalities { let keystore = MemoryKeystore::new(); - let t = GenesisConfig::default() + let t = TuxedoGenesisConfig::default() .build_storage() .expect("System builds valid default genesis config"); @@ -468,10 +484,14 @@ mod tests { }, }; + let tx = TuxedoGenesisConfig::default().0.get(0).unwrap().clone(); + + assert_eq!(tx.outputs.get(0), Some(&genesis_utxo)); + + let tx_hash = BlakeTwo256::hash_of(&tx.encode()); let output_ref = OutputRef { - // Genesis UTXOs don't come from any real transaction, so just uze the zero hash - tx_hash:
::Hash::zero(), - index: 0 as u32, + tx_hash, + index: 0_u32, }; let encoded_utxo = @@ -503,10 +523,14 @@ mod tests { }, }; + let tx = TuxedoGenesisConfig::default().0.get(0).unwrap().clone(); + + assert_eq!(tx.outputs.get(1), Some(&genesis_multi_sig_utxo)); + + let tx_hash = BlakeTwo256::hash_of(&tx.encode()); let output_ref = OutputRef { - // Genesis UTXOs don't come from any real transaction, so just uze the zero hash - tx_hash:
::Hash::zero(), - index: 1 as u32, + tx_hash, + index: 1_u32, }; let encoded_utxo = diff --git a/wallet/src/amoeba.rs b/wallet/src/amoeba.rs index 7451aa7c9..4ed66b282 100644 --- a/wallet/src/amoeba.rs +++ b/wallet/src/amoeba.rs @@ -1,6 +1,6 @@ //! Toy off-chain process to create an amoeba and perform mitosis on it -use crate::fetch_storage; +use crate::rpc::fetch_storage; use std::{thread::sleep, time::Duration}; diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 3bb4ddb11..ef356934d 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -1,19 +1,11 @@ //! A simple CLI wallet. For now it is a toy just to start testing things out. -use anyhow::anyhow; use clap::Parser; -use jsonrpsee::{ - core::client::ClientT, - http_client::{HttpClient, HttpClientBuilder}, - rpc_params, -}; +use jsonrpsee::http_client::HttpClientBuilder; use parity_scale_codec::{Decode, Encode}; use runtime::OuterVerifier; use std::path::PathBuf; -use tuxedo_core::{ - types::{Output, OutputRef}, - verifier::*, -}; +use tuxedo_core::{types::OutputRef, verifier::*}; use sp_core::H256; @@ -70,15 +62,14 @@ async fn main() -> anyhow::Result<()> { log::debug!("Node's Genesis block::{:?}", node_genesis_hash); // Open the local database - let db = sync::open_db(db_path, node_genesis_hash, node_genesis_block)?; + let db = sync::open_db(db_path, node_genesis_hash, node_genesis_block.clone())?; let num_blocks = sync::height(&db)?.expect("db should be initialized automatically when opening."); log::info!("Number of blocks in the db: {num_blocks}"); - // The filter function that will determine whether the local database should - // track a given utxo is based on whether that utxo is privately owned by a - // key that is in our keystore. + // The filter function that will determine whether the local database should track a given utxo + // is based on whether that utxo is privately owned by a key that is in our keystore. let keystore_filter = |v: &OuterVerifier| -> bool { matches![ v, @@ -87,8 +78,8 @@ async fn main() -> anyhow::Result<()> { }; if !sled::Db::was_recovered(&db) { - // Before synchronizing init the database with the current Genesis utxos - sync::init_from_genesis(&db, &client, &keystore_filter).await?; + // This is a new instance, so we need to apply the genesis block to the database. + sync::apply_block(&db, node_genesis_block, node_genesis_hash, &keystore_filter).await?; } // Synchronize the wallet with attached node unless instructed otherwise. @@ -195,24 +186,6 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -//TODO move to rpc.rs -/// Fetch an output from chain storage given an OutputRef -pub async fn fetch_storage( - output_ref: &OutputRef, - client: &HttpClient, -) -> anyhow::Result> { - let ref_hex = hex::encode(output_ref.encode()); - let params = rpc_params![ref_hex]; - let rpc_response: Result, _> = client.request("state_getStorage", params).await; - - let response_hex = rpc_response?.ok_or(anyhow!("Data cannot be retrieved from storage"))?; - let response_hex = strip_0x_prefix(&response_hex); - let response_bytes = hex::decode(response_hex)?; - let utxo = Output::decode(&mut &response_bytes[..])?; - - Ok(utxo) -} - /// Parse a string into an H256 that represents a public key pub(crate) fn h256_from_string(s: &str) -> anyhow::Result { let s = strip_0x_prefix(s); diff --git a/wallet/src/money.rs b/wallet/src/money.rs index 7eefb9445..e0264f41a 100644 --- a/wallet/src/money.rs +++ b/wallet/src/money.rs @@ -1,6 +1,6 @@ //! Wallet features related to spending money and checking balances. -use crate::{cli::SpendArgs, fetch_storage, sync}; +use crate::{cli::SpendArgs, rpc::fetch_storage, sync}; use anyhow::anyhow; use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; diff --git a/wallet/src/output_filter.rs b/wallet/src/output_filter.rs index 458832ae1..13759a06f 100644 --- a/wallet/src/output_filter.rs +++ b/wallet/src/output_filter.rs @@ -74,7 +74,7 @@ mod tests { }; let my_filter = TestSigCheckFilter::build_filter(verifier).expect("Can build print filter"); - let _ = my_filter(&vec![output], &H256::zero()); + let _ = my_filter(&[output], &H256::zero()); } #[test] diff --git a/wallet/src/rpc.rs b/wallet/src/rpc.rs index f9b90af23..0d5466e6d 100644 --- a/wallet/src/rpc.rs +++ b/wallet/src/rpc.rs @@ -1,10 +1,16 @@ //! Strongly typed helper functions for communicating with the Node's //! RPC endpoint. +use crate::strip_0x_prefix; +use anyhow::anyhow; use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; use parity_scale_codec::{Decode, Encode}; use runtime::{opaque::Block as OpaqueBlock, Block}; use sp_core::H256; +use tuxedo_core::{ + types::{Output, OutputRef}, + Verifier, +}; /// Typed helper to get the Node's block hash at a particular height pub async fn node_get_block_hash(height: u32, client: &HttpClient) -> anyhow::Result> { @@ -36,3 +42,20 @@ pub async fn node_get_block(hash: H256, client: &HttpClient) -> anyhow::Result( + output_ref: &OutputRef, + client: &HttpClient, +) -> anyhow::Result> { + let ref_hex = hex::encode(output_ref.encode()); + let params = rpc_params![ref_hex]; + let rpc_response: Result, _> = client.request("state_getStorage", params).await; + + let response_hex = rpc_response?.ok_or(anyhow!("Data cannot be retrieved from storage"))?; + let response_hex = strip_0x_prefix(&response_hex); + let response_bytes = hex::decode(response_hex)?; + let utxo = Output::decode(&mut &response_bytes[..])?; + + Ok(utxo) +} diff --git a/wallet/src/sync.rs b/wallet/src/sync.rs index 2e6c3637b..0ade4fe6f 100644 --- a/wallet/src/sync.rs +++ b/wallet/src/sync.rs @@ -13,7 +13,7 @@ use std::path::PathBuf; -use crate::{fetch_storage, rpc}; +use crate::rpc; use anyhow::anyhow; use parity_scale_codec::{Decode, Encode}; use sled::Db; @@ -24,7 +24,6 @@ use tuxedo_core::{ verifier::SigCheck, }; -use futures::{stream, StreamExt, TryStreamExt}; use jsonrpsee::http_client::HttpClient; use runtime::{money::Coin, Block, OuterVerifier, Transaction}; @@ -40,66 +39,6 @@ const UNSPENT: &str = "unspent"; /// The identifier for the spent tree in the db. const SPENT: &str = "spent"; -/// TODO remove this constant. Instead we should just iterate output refs until we find one that doesn't exist. -pub const NUM_GENESIS_UTXOS: u32 = 1; - -pub(crate) async fn init_from_genesis bool>( - db: &Db, - client: &HttpClient, - filter: &F, -) -> anyhow::Result<()> { - let genesis_utxo_refs: Vec = (0..NUM_GENESIS_UTXOS) - .map(|utxo_index| OutputRef { - tx_hash: H256::zero(), - index: utxo_index, - }) - .collect(); - - let genesis_utxos = stream::iter( - genesis_utxo_refs - .iter() - //TODO Fetch storage will read the latest storage. We want to read genesis utxos from the genesis state. - .map(|utxo_ref| fetch_storage::(utxo_ref, client)), - ) - .buffer_unordered(5) - .try_collect::>() - .await?; - - log::debug!("The fetched genesis_utxos are {:?}", genesis_utxos); - - let filtered_outputs_and_refs = - genesis_utxos - .iter() - .zip(genesis_utxo_refs) - .filter_map(|(output, output_ref)| { - if filter(&output.verifier) { - Some((output, output_ref)) - } else { - None - } - }); - - for (output, output_ref) in filtered_outputs_and_refs { - // For now the wallet only supports simple coins, so skip anything else - let amount = match output.payload.extract::>() { - Ok(Coin(amount)) => amount, - Err(_) => continue, - }; - - // At this point we already know we have a coin that matches our filter. - // So we extract its owner. - match output.verifier { - OuterVerifier::SigCheck(SigCheck { owner_pubkey }) => { - // Add it to the global unspent_outputs table - add_unspent_output(db, &output_ref, &owner_pubkey, &amount)?; - } - _ => return Err(anyhow!("{:?}", ())), - } - } - - Ok(()) -} - /// Open a database at the given location intended for the given genesis block. /// /// If the database is already populated, make sure it is based on the expected genesis diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index 761ec3284..9091c8f4c 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -132,7 +132,7 @@ impl Default for Parent { Debug, TypeInfo, )] -pub struct KittyDNA(H256); +pub struct KittyDNA(pub H256); #[derive( Serialize, diff --git a/wardrobe/kitties/src/tests.rs b/wardrobe/kitties/src/tests.rs index 90b648de6..0b9c7fdfe 100644 --- a/wardrobe/kitties/src/tests.rs +++ b/wardrobe/kitties/src/tests.rs @@ -56,9 +56,9 @@ fn breed_happy_path_works() { let new_family = KittyData::default_family(); let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_family[1].clone().into(), new_family[2].clone().into(), @@ -71,9 +71,9 @@ fn breed_happy_path_works() { fn breed_wrong_input_type_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![Bogus.into(), Bogus.into()], + &[Bogus.into(), Bogus.into()], &[], // no peeks - &vec![], + &[], ); assert_eq!(result, Err(ConstraintCheckerError::BadlyTyped)); } @@ -82,9 +82,9 @@ fn breed_wrong_input_type_fails() { fn breed_wrong_output_type_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![Bogus.into(), Bogus.into(), Bogus.into()], + &[Bogus.into(), Bogus.into(), Bogus.into()], ); assert_eq!(result, Err(ConstraintCheckerError::BadlyTyped)); } @@ -93,9 +93,9 @@ fn breed_wrong_output_type_fails() { fn inputs_dont_contain_two_parents_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into()], + &[KittyData::default().into()], &[], // no peeks - &vec![], + &[], ); assert_eq!(result, Err(ConstraintCheckerError::TwoParentsDoNotExist)); } @@ -104,9 +104,9 @@ fn inputs_dont_contain_two_parents_fails() { fn outputs_dont_contain_all_family_members_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![KittyData::default().into()], + &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFamilyMembers)); } @@ -115,12 +115,12 @@ fn outputs_dont_contain_all_family_members_fails() { fn breed_two_dads_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![ + &[ KittyData::default_dad().into(), KittyData::default_dad().into(), ], &[], // no peeks - &vec![KittyData::default().into()], + &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::TwoDadsNotValid)); } @@ -129,9 +129,9 @@ fn breed_two_dads_fails() { fn breed_two_moms_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default().into()], + &[KittyData::default().into(), KittyData::default().into()], &[], // no peeks - &vec![KittyData::default().into()], + &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::TwoMomsNotValid)); } @@ -140,9 +140,9 @@ fn breed_two_moms_fails() { fn first_input_not_mom_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default_dad().into(), KittyData::default().into()], + &[KittyData::default_dad().into(), KittyData::default().into()], &[], // no peeks - &vec![], + &[], ); assert_eq!(result, Err(ConstraintCheckerError::TwoDadsNotValid)) } @@ -151,9 +151,9 @@ fn first_input_not_mom_fails() { fn first_output_not_mom_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ KittyData::default_dad().into(), KittyData::default().into(), KittyData::default_child().into(), @@ -169,9 +169,9 @@ fn breed_mom_when_she_gave_birth_recently_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![new_momma.into(), KittyData::default_dad().into()], + &[new_momma.into(), KittyData::default_dad().into()], &[], // no peeks - &vec![], + &[], ); assert_eq!(result, Err(ConstraintCheckerError::MomNotReadyYet)); } @@ -183,9 +183,9 @@ fn breed_dad_when_he_is_tired_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), tired_dadda.into()], + &[KittyData::default().into(), tired_dadda.into()], &[], // no peeks - &vec![], + &[], ); assert_eq!(result, Err(ConstraintCheckerError::DadTooTired)); } @@ -197,9 +197,9 @@ fn check_mom_breedings_overflow_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![test_mom.into(), KittyData::default_dad().into()], + &[test_mom.into(), KittyData::default_dad().into()], &[], // no peeks - &vec![], + &[], ); assert_eq!( result, @@ -214,9 +214,9 @@ fn check_dad_breedings_overflow_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), test_dad.into()], + &[KittyData::default().into(), test_dad.into()], &[], // no peeks - &vec![], + &[], ); assert_eq!( result, @@ -231,9 +231,9 @@ fn check_mom_free_breedings_zero_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![test_mom.into(), KittyData::default_dad().into()], + &[test_mom.into(), KittyData::default_dad().into()], &[], // no peeks - &vec![], + &[], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFreeBreedings)); } @@ -245,9 +245,9 @@ fn check_dad_free_breedings_zero_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), test_dad.into()], + &[KittyData::default().into(), test_dad.into()], &[], // no peeks - &vec![], + &[], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFreeBreedings)); } @@ -260,9 +260,9 @@ fn check_new_mom_free_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_mom.into(), new_family[1].clone().into(), new_family[2].clone().into(), @@ -282,9 +282,9 @@ fn check_new_dad_free_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_dad.into(), new_family[2].clone().into(), @@ -304,9 +304,9 @@ fn check_new_mom_num_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_mom.into(), new_family[1].clone().into(), new_family[2].clone().into(), @@ -326,9 +326,9 @@ fn check_new_dad_num_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_dad.into(), new_family[2].clone().into(), @@ -348,9 +348,9 @@ fn check_new_mom_dna_doesnt_match_old_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_mom.into(), new_family[1].clone().into(), new_family[2].clone().into(), @@ -370,9 +370,9 @@ fn check_new_dad_dna_doesnt_match_old_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_dad.into(), new_family[2].clone().into(), @@ -392,9 +392,9 @@ fn check_child_dna_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_family[1].clone().into(), new_child.into(), @@ -411,9 +411,9 @@ fn check_child_dad_parent_tired_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_family[1].clone().into(), new_child.into(), @@ -433,9 +433,9 @@ fn check_child_mom_parent_recently_gave_birth_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_family[1].clone().into(), new_child.into(), @@ -455,9 +455,9 @@ fn check_child_free_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_family[1].clone().into(), new_child.into(), @@ -477,9 +477,9 @@ fn check_child_num_breedings_non_zero_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker, - &vec![KittyData::default().into(), KittyData::default_dad().into()], + &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks - &vec![ + &[ new_family[0].clone().into(), new_family[1].clone().into(), new_child.into(), diff --git a/wardrobe/timestamp/src/cleanup_tests.rs b/wardrobe/timestamp/src/cleanup_tests.rs index 31c76fc65..9252eac2e 100644 --- a/wardrobe/timestamp/src/cleanup_tests.rs +++ b/wardrobe/timestamp/src/cleanup_tests.rs @@ -80,7 +80,7 @@ fn cleanup_timestamp_input_not_yet_ripe_for_cleaning() { #[test] fn cleanup_timestamp_multiple_happy_path() { - let old1 = Timestamp::new(1 * AlwaysBlockMillion::MINIMUM_TIME_INTERVAL, 1); + let old1 = Timestamp::new(AlwaysBlockMillion::MINIMUM_TIME_INTERVAL, 1); let old2 = Timestamp::new(2 * AlwaysBlockMillion::MINIMUM_TIME_INTERVAL, 2); let newer = Timestamp::new( 2 * AlwaysBlockMillion::MIN_TIME_BEFORE_CLEANUP, @@ -117,7 +117,7 @@ fn cleanup_timestamp_missing_input() { #[test] fn cleanup_timestamp_multiple_first_valid_second_invalid() { - let old = Timestamp::new(1 * AlwaysBlockMillion::MINIMUM_TIME_INTERVAL, 1); + let old = Timestamp::new(AlwaysBlockMillion::MINIMUM_TIME_INTERVAL, 1); let supposedly_old = Timestamp::new( 2 * AlwaysBlockMillion::MIN_TIME_BEFORE_CLEANUP, 2 * AlwaysBlockMillion::MIN_BLOCKS_BEFORE_CLEANUP, diff --git a/wardrobe/timestamp/src/lib.rs b/wardrobe/timestamp/src/lib.rs index a796b0d4a..c4ff5e4f8 100644 --- a/wardrobe/timestamp/src/lib.rs +++ b/wardrobe/timestamp/src/lib.rs @@ -6,10 +6,8 @@ //! In each block, the block author must include a single `SetTimestamp` transaction that peeks at the //! Timestamp UTXO that was created in the previous block, and creates a new one with an updated timestamp. //! -//! This piece currently features two prominent hacks which will need to be cleaned up in due course. -//! 1. It abuses the UpForGrabs verifier. This should be replaced with an Unspendable verifier and an eviction workflow. -//! 2. In block #1 it allows creating a new best timestamp without comsuming a previous one. -//! This should be removed once we are able to include a timestamp in the genesis block. +//! This piece currently features a prominent hack which will need to be cleaned up in due course. +//! It abuses the UpForGrabs verifier. This should be replaced with an Unspendable verifier and an eviction workflow. #![cfg_attr(not(feature = "std"), no_std)] @@ -240,7 +238,7 @@ impl, T: TimestampConfig + 'static> TuxedoInheren fn create_inherent( authoring_inherent_data: &InherentData, - previous_inherent: Option<(Transaction, H256)>, + previous_inherent: (Transaction, H256), ) -> tuxedo_core::types::Transaction { let current_timestamp: u64 = authoring_inherent_data .get_data(&sp_timestamp::INHERENT_IDENTIFIER) @@ -256,25 +254,13 @@ impl, T: TimestampConfig + 'static> TuxedoInheren "🕰️🖴 Local timestamp while creating inherent i:: {current_timestamp}" ); - let mut peeks = Vec::new(); - match (previous_inherent, T::block_height()) { - (None, 1) => { - // This is the first block hack case. - // We don't need any inputs, so just do nothing. - } - (None, _) => panic!("Attemping to construct timestamp inherent with no previous inherent (and not block 1)."), - (Some((_previous_inherent, previous_id)), _) => { - // This is the the normal case. We create a full previous to peek at. - - // We are given the entire previous inherent in case we need data from it or need to scrape the outputs. - // But out transactions are simple enough that we know we just need the one and only output. - peeks.push(OutputRef { - tx_hash: previous_id, - // There is always 1 output, so we know right where to find it. - index: 0, - }); - } - } + // We are given the entire previous inherent in case we need data from it or need to scrape the outputs. + // But out transactions are simple enough that we know we just need the one and only output. + let old_output = OutputRef { + tx_hash: previous_inherent.1, + // There is always 1 output, so we know right where to find it. + index: 0, + }; let new_output = Output { payload: new_timestamp.into(), @@ -283,7 +269,7 @@ impl, T: TimestampConfig + 'static> TuxedoInheren Transaction { inputs: Vec::new(), - peeks, + peeks: vec![old_output], outputs: vec![new_output], checker: Self::default(), } @@ -330,6 +316,24 @@ impl, T: TimestampConfig + 'static> TuxedoInheren .expect("Should be able to push some error"); } } + + #[cfg(feature = "std")] + fn genesis_transactions() -> Vec> { + let time = std::time::SystemTime::now() + .duration_since(std::time::SystemTime::UNIX_EPOCH) + .expect("Time is always after UNIX_EPOCH; qed") + .as_millis() as u64; + + vec![Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: vec![Output { + payload: Timestamp::new(time, 0).into(), + verifier: UpForGrabs.into(), + }], + checker: Self::default(), + }] + } } /// Allows users to voluntarily clean up old timestamps by showing that there