diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 10c7f52..5a8df5c 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -113,5 +113,10 @@ fn testnet_genesis( // Assign network admin rights. "key": Some(root_key), }, + "utxo":{ + // initial supply of 21 million to alice + "supply": 2100, + "owner": Some(get_account_id_from_seed::("Alice")), + } }) } diff --git a/pallets/utxo/src/lib.rs b/pallets/utxo/src/lib.rs index 1e9f374..2551ae6 100644 --- a/pallets/utxo/src/lib.rs +++ b/pallets/utxo/src/lib.rs @@ -6,188 +6,208 @@ pub use pallet::*; //#[frame_support::pallet] #[frame_support::pallet(dev_mode)] pub mod pallet { - use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; - use frame_system::{ensure_signed, pallet_prelude::*}; - #[cfg(feature = "std")] - use serde::{Deserialize, Serialize}; - - use sp_core::{ - sp_std::{collections::btree_map::BTreeMap, vec::Vec}, - H256, - }; - use sp_runtime::traits::{BlakeTwo256, Hash}; - - #[pallet::pallet] - pub struct Pallet(_); - - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching runtime event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } - - #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[derive( - Clone, - Encode, - Decode, - Eq, - PartialEq, - PartialOrd, - Ord, - RuntimeDebug, - Hash, - Default, - MaxEncodedLen, - TypeInfo, - )] - pub struct Utxo { - value: u64, - /// owner of the utxo accountID - owner: AccountId, - } - - #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Hash, Default, TypeInfo)] - pub struct TransactionInput { - /// id of the utxo to be spend - pub utxo_id: H256, - } - - #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Hash, Default, TypeInfo)] - pub struct Transaction { - pub(crate) inputs: Vec, - pub(crate) outputs: Vec>, - } - - impl Transaction - where - AccountId: Parameter + Member + MaxEncodedLen, - { - pub fn hash_input_utxo(&self, index: u64) -> H256 { - BlakeTwo256::hash_of(&(&self.encode(), index)) - } - } - - #[pallet::storage] - #[pallet::getter(fn utxo_store)] - pub(super) type UtxoStore = - StorageMap<_, Blake2_256, H256, Option>, ValueQuery>; - - #[pallet::event] - #[pallet::generate_deposit(pub (super) fn deposit_event)] - pub enum Event { - /// A user has successfully transferred a utxo. - UtxoTransferred, - } - - #[pallet::error] - pub enum Error { - DuplicatedTransaction, - InvalidTransaction, - OutputValueIsZero, - InputsNotSatisfied, - SignatureFailure, - } - - #[pallet::call] - impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight(100_000)] - pub fn transfer( - origin: OriginFor, - transaction: Transaction, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - // Check that the extrinsic was signed and get the signer. - let validity = validate_tx::(&transaction, who)?; - ensure!(validity.requires.is_empty(), Error::::InputsNotSatisfied); - - // Update storage. - update_utxo_store::(&transaction)?; - - // Emit an event. - Self::deposit_event(Event::UtxoTransferred); - - // Return a successful `DispatchResult` - Ok(()) - } - } - - pub fn validate_tx( - transaction: &Transaction, - from: T::AccountId, - ) -> Result { - ensure!(transaction.inputs.len() > 0, "Transaction input length is 0"); - ensure!(transaction.outputs.len() > 0, "Transaction output length is 0"); - - { - // check if input transactions are not duplicated - let mut map = BTreeMap::new(); - for input in &transaction.inputs { - map.insert(input.utxo_id, ()); - } - ensure!(map.len() == transaction.inputs.len(), "Duplicated Transaction Input"); - } - - { - // check if output transactions are not duplicated - let mut map = BTreeMap::new(); - for output in &transaction.outputs { - map.insert(&output.owner, ()); - } - ensure!(map.len() == transaction.outputs.len(), "Duplicated Transaction Output"); - } - - let mut missing_utxos = Vec::new(); - let mut created_utxos = Vec::new(); - - for input in &transaction.inputs { - //check if utxo exist in the store - if let Some(utxo) = UtxoStore::::get(input.utxo_id) { - // check if all utxo are from the same account - ensure!(from == utxo.owner, "Owner invalid"); - } else { - missing_utxos.push(input.utxo_id.as_fixed_bytes().to_vec()) - } - } - - // validate if output are valid (non 0) - let mut idx: u64 = 0; - for output in &transaction.outputs { - ensure!(output.value > 0, "Output value is 0"); - let hash = transaction.hash_input_utxo(idx); - idx = idx.saturating_add(1); - created_utxos.push(hash.clone().as_fixed_bytes().to_vec()) - } - - Ok(ValidTransaction { - priority: 1, - requires: missing_utxos, - provides: created_utxos, - longevity: 10, - propagate: true, - }) - } - - pub fn update_utxo_store(transaction: &Transaction) -> DispatchResult { - //remove outdated utxo - for input in &transaction.inputs { - UtxoStore::::remove(input.utxo_id); - } - - //add newly validated utxos - let mut idx: u32 = 0; - for output in &transaction.outputs { - // create a unique and deterministic hash for each uxto in output - // Do not use random here, as then the hash will be different for - // other nodes in the network. - let hash = transaction.hash_input_utxo(idx as u64); - idx = idx.saturating_add(1); - UtxoStore::::insert(hash, Some(output.clone())); - } - - Ok(()) - } + use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; + use frame_system::{ensure_signed, pallet_prelude::*}; + #[cfg(feature = "std")] + use serde::{Deserialize, Serialize}; + + use sp_core::{ + sp_std::{collections::btree_map::BTreeMap, vec::Vec}, + H256, + }; + use sp_runtime::traits::{BlakeTwo256, Hash}; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching runtime event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[derive( + Clone, + Encode, + Decode, + Eq, + PartialEq, + PartialOrd, + Ord, + RuntimeDebug, + Hash, + Default, + MaxEncodedLen, + TypeInfo, + )] + pub struct Utxo { + pub value: u64, + /// owner of the utxo accountID + pub owner: AccountId, + } + + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Hash, Default, TypeInfo)] + pub struct TransactionInput { + /// id of the utxo to be spend + pub utxo_id: H256, + } + + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Hash, Default, TypeInfo)] + pub struct Transaction { + pub(crate) inputs: Vec, + pub(crate) outputs: Vec>, + } + + impl Transaction + where + AccountId: Parameter + Member + MaxEncodedLen, + { + pub fn hash_input_utxo(&self, index: u64) -> H256 { + BlakeTwo256::hash_of(&(&self.encode(), index)) + } + } + + #[pallet::storage] + #[pallet::getter(fn utxo_store)] + pub(super) type UtxoStore = + StorageMap<_, Blake2_256, H256, Option>, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub (super) fn deposit_event)] + pub enum Event { + /// A user has successfully transferred a utxo. + UtxoTransferred, + } + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub supply: u64, + pub owner: Option, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + if let Some(account) = &self.owner { + let utxo = Utxo { + value: self.supply, + owner: account.clone() + }; + UtxoStore::::insert(H256::zero(), Some(utxo)); + } + } + } + + #[pallet::error] + pub enum Error { + DuplicatedTransaction, + InvalidTransaction, + OutputValueIsZero, + InputsNotSatisfied, + SignatureFailure, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(100_000)] + pub fn transfer( + origin: OriginFor, + transaction: Transaction, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + // Check that the extrinsic was signed and get the signer. + let validity = validate_tx::(&transaction, who)?; + ensure!(validity.requires.is_empty(), Error::::InputsNotSatisfied); + + // Update storage. + update_utxo_store::(&transaction)?; + + // Emit an event. + Self::deposit_event(Event::UtxoTransferred); + + // Return a successful `DispatchResult` + Ok(()) + } + } + + pub fn validate_tx( + transaction: &Transaction, + from: T::AccountId, + ) -> Result { + ensure!(transaction.inputs.len() > 0, "Transaction input length is 0"); + ensure!(transaction.outputs.len() > 0, "Transaction output length is 0"); + + { + // check if input transactions are not duplicated + let mut map = BTreeMap::new(); + for input in &transaction.inputs { + map.insert(input.utxo_id, ()); + } + ensure!(map.len() == transaction.inputs.len(), "Duplicated Transaction Input"); + } + + { + // check if output transactions are not duplicated + let mut map = BTreeMap::new(); + for output in &transaction.outputs { + map.insert(&output.owner, ()); + } + ensure!(map.len() == transaction.outputs.len(), "Duplicated Transaction Output"); + } + + let mut missing_utxos = Vec::new(); + let mut created_utxos = Vec::new(); + + for input in &transaction.inputs { + //check if utxo exist in the store + if let Some(utxo) = UtxoStore::::get(input.utxo_id) { + // check if all utxo are from the same account + ensure!(from == utxo.owner, "Owner invalid"); + } else { + missing_utxos.push(input.utxo_id.as_fixed_bytes().to_vec()) + } + } + + // validate if output are valid (non 0) + let mut idx: u64 = 0; + for output in &transaction.outputs { + ensure!(output.value > 0, "Output value is 0"); + let hash = transaction.hash_input_utxo(idx); + idx = idx.saturating_add(1); + created_utxos.push(hash.clone().as_fixed_bytes().to_vec()) + } + + Ok(ValidTransaction { + priority: 1, + requires: missing_utxos, + provides: created_utxos, + longevity: 10, + propagate: true, + }) + } + + pub fn update_utxo_store(transaction: &Transaction) -> DispatchResult { + //remove outdated utxo + for input in &transaction.inputs { + UtxoStore::::remove(input.utxo_id); + } + + //add newly validated utxos + let mut idx: u32 = 0; + for output in &transaction.outputs { + // create a unique and deterministic hash for each uxto in output + // Do not use random here, as then the hash will be different for + // other nodes in the network. + let hash = transaction.hash_input_utxo(idx as u64); + idx = idx.saturating_add(1); + UtxoStore::::insert(hash, Some(output.clone())); + } + + Ok(()) + } }