From b522b1bdbb01e4e96118c48d9ebbb60f5a54a45d Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Mon, 12 Feb 2024 13:40:45 +0530 Subject: [PATCH 01/14] Enhanced Kitty piece with 'name' field, extended FreeKittyConstraintChecker, and introduced TradableKitty for minting, property updates, and buying in the blockchain. Overall Summary: 1. Enhanced the Kitty piece by adding a 'name' field to the KittyData structure. 2. Extended FreeKittyConstraintChecker to support Mint and Breed functionalities. 3. Introduced a new TradableKitty piece with minting, property updating, and buying capabilities. Details: KittyData Enhancement: The KittyData structure now includes a 'name' field, allowing for the storage and retrieval of kitty names. FreeKittyConstraintChecker Extension in kitties piece : FreeKittyConstraintChecker has been expanded to support both existing Breed functionality and the newly added Mint functionality. Introduction of new piece TradableKitty: A new piece, TradableKitty, has been introduced. Functionalities include minting kitties, updating properties (name, price, availability for sale), and enabling buy operations. Integration with Existing Pieces: TradableKitty seamlessly integrates with both existing kitties and the Money piece, leveraging their functionalities. --- tuxedo-template-runtime/Cargo.toml | 1 + tuxedo-template-runtime/src/lib.rs | 5 + wardrobe/kitties/src/lib.rs | 60 +- wardrobe/kitties/src/tests.rs | 98 ++- wardrobe/tradable_kitties/Cargo.toml | 29 + wardrobe/tradable_kitties/src/lib.rs | 573 +++++++++++++++ wardrobe/tradable_kitties/src/tests.rs | 928 +++++++++++++++++++++++++ 7 files changed, 1655 insertions(+), 39 deletions(-) create mode 100644 wardrobe/tradable_kitties/Cargo.toml create mode 100644 wardrobe/tradable_kitties/src/lib.rs create mode 100644 wardrobe/tradable_kitties/src/tests.rs diff --git a/tuxedo-template-runtime/Cargo.toml b/tuxedo-template-runtime/Cargo.toml index 4949fb6cb..73fcb33da 100644 --- a/tuxedo-template-runtime/Cargo.toml +++ b/tuxedo-template-runtime/Cargo.toml @@ -36,6 +36,7 @@ sp-consensus-grandpa = { default_features = false, workspace = true } # Tuxedo Core and Pieces amoeba = { default-features = false, path = "../wardrobe/amoeba" } kitties = { default-features = false, path = "../wardrobe/kitties" } +tradable_kitties = { default-features = false, path = "../wardrobe/tradable_kitties" } money = { default-features = false, path = "../wardrobe/money" } poe = { default-features = false, path = "../wardrobe/poe" } runtime-upgrade = { default-features = false, path = "../wardrobe/runtime_upgrade" } diff --git a/tuxedo-template-runtime/src/lib.rs b/tuxedo-template-runtime/src/lib.rs index 6a2c8a389..f8eb74826 100644 --- a/tuxedo-template-runtime/src/lib.rs +++ b/tuxedo-template-runtime/src/lib.rs @@ -45,6 +45,7 @@ pub use money; pub use poe; pub use runtime_upgrade; pub use timestamp; +pub use tradable_kitties; /// 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 @@ -170,6 +171,8 @@ pub enum OuterConstraintChecker { Money(money::MoneyConstraintChecker<0>), /// Checks Free Kitty transactions FreeKittyConstraintChecker(kitties::FreeKittyConstraintChecker), + /// Checks Paid Kitty transactions + TradableKittyConstraintChecker(tradable_kitties::TradableKittyConstraintChecker<0>), /// Checks that an amoeba can split into two new amoebas AmoebaMitosis(amoeba::AmoebaMitosis), /// Checks that a single amoeba is simply removed from the state @@ -205,6 +208,8 @@ pub enum OuterConstraintChecker { Money(money::MoneyConstraintChecker<0>), /// Checks Free Kitty transactions FreeKittyConstraintChecker(kitties::FreeKittyConstraintChecker), + /// Checks Paid Kitty transactions + TradableKittyConstraintChecker(tradable_kitties::TradableKittyConstraintChecker<0>), /// Checks that an amoeba can split into two new amoebas AmoebaMitosis(amoeba::AmoebaMitosis), /// Checks that a single amoeba is simply removed from the state diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index 49d7d4162..3e2441f6d 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -50,7 +50,12 @@ mod tests; Debug, TypeInfo, )] -pub struct FreeKittyConstraintChecker; +pub enum FreeKittyConstraintChecker { + /// A mint transaction that creates kitty without parents. + Mint, + /// A typical Breed transaction where kitties are consumed and new family(Parents(mom,dad) and child) is created. + Breed, +} #[derive( Serialize, @@ -165,6 +170,7 @@ pub struct KittyData { pub free_breedings: u64, // Ignore in breed for money case pub dna: KittyDNA, pub num_breedings: u128, + pub name: [u8; 4], } impl KittyData { @@ -187,7 +193,7 @@ impl KittyData { v, ) .into()], - checker: FreeKittyConstraintChecker.into(), + checker: FreeKittyConstraintChecker::Mint.into(), } } } @@ -199,6 +205,7 @@ impl Default for KittyData { free_breedings: 2, dna: KittyDNA(H256::from_slice(b"mom_kitty_1asdfasdfasdfasdfasdfa")), num_breedings: 3, + name: *b"kity", } } } @@ -261,9 +268,15 @@ pub enum ConstraintCheckerError { TooManyBreedingsForKitty, /// Not enough free breedings available for these parents. NotEnoughFreeBreedings, + /// Incorrect number of outputs when it comes to Minting. + IncorrectNumberOfKittiesForMintOperation, + /// The transaction attempts to mint no Kitty. + MintingNothing, + /// Inputs(Parents) not required for mint. + MintingWithInputs, } -trait Breed { +pub trait Breed { /// The Cost to breed a kitty if it is not free. const COST: u128; /// Number of free breedings a kitty will have. @@ -510,18 +523,39 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { _peeks: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { - // Input must be a Mom and a Dad - ensure!(input_data.len() == 2, Self::Error::TwoParentsDoNotExist); - - let mom = KittyData::try_from(&input_data[0])?; - let dad = KittyData::try_from(&input_data[1])?; - KittyHelpers::can_breed(&mom, &dad)?; + match &self { + Self::Mint => { + // Make sure there are no inputs being consumed + ensure!( + input_data.is_empty(), + ConstraintCheckerError::MintingWithInputs + ); + // Make sure there is at least one output being minted + ensure!( + !output_data.is_empty(), + ConstraintCheckerError::MintingNothing + ); + // Make sure the outputs are the right type + for utxo in output_data { + let _utxo_kitty = utxo + .extract::() + .map_err(|_| ConstraintCheckerError::BadlyTyped)?; + } + Ok(0) + } + Self::Breed => { + ensure!(input_data.len() == 2, Self::Error::TwoParentsDoNotExist); + let mom = KittyData::try_from(&input_data[0])?; + let dad = KittyData::try_from(&input_data[1])?; + KittyHelpers::can_breed(&mom, &dad)?; - // Output must be Mom, Dad, Child - ensure!(output_data.len() == 3, Self::Error::NotEnoughFamilyMembers); + // Output must be Mom, Dad, Child + ensure!(output_data.len() == 3, Self::Error::NotEnoughFamilyMembers); - KittyHelpers::check_new_family(&mom, &dad, output_data)?; + KittyHelpers::check_new_family(&mom, &dad, output_data)?; - Ok(0) + Ok(0) + } + } } } diff --git a/wardrobe/kitties/src/tests.rs b/wardrobe/kitties/src/tests.rs index 0b9c7fdfe..76d4e22c8 100644 --- a/wardrobe/kitties/src/tests.rs +++ b/wardrobe/kitties/src/tests.rs @@ -24,6 +24,7 @@ impl KittyData { KittyData { parent: Parent::Mom(MomKittyStatus::RearinToGo), free_breedings: 2, + name: *b"bkty", dna: KittyDNA(BlakeTwo256::hash_of(&( mom.dna, dad.dna, @@ -50,12 +51,57 @@ impl KittyData { Box::new(vec![new_mom, new_dad, child]) } } +#[test] +fn mint_happy_path_works() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::Mint, + &[], + &[], // no peeks + &[KittyData::default().into(), KittyData::default_dad().into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn mint_with_input_fails() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::Mint, + &[KittyData::default().into(), KittyData::default_dad().into()], + &[], // no peeks + &[], + ); + assert_eq!(result, Err(ConstraintCheckerError::MintingWithInputs)); +} +#[test] +fn mint_without_output_fails() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::Mint, + &[], + &[], // no peeks + &[], + ); + assert_eq!(result, Err(ConstraintCheckerError::MintingNothing)); +} +#[test] +fn mint_with_wrong_output_type_fails() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::Mint, + &[], + &[], // no peeks + &[ + Bogus.into(), + KittyData::default().into(), + KittyData::default_dad().into(), + ], + ); + assert_eq!(result, Err(ConstraintCheckerError::BadlyTyped)); +} #[test] fn breed_happy_path_works() { let new_family = KittyData::default_family(); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -70,7 +116,7 @@ fn breed_happy_path_works() { #[test] fn breed_wrong_input_type_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[Bogus.into(), Bogus.into()], &[], // no peeks &[], @@ -81,7 +127,7 @@ fn breed_wrong_input_type_fails() { #[test] fn breed_wrong_output_type_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[Bogus.into(), Bogus.into(), Bogus.into()], @@ -92,7 +138,7 @@ fn breed_wrong_output_type_fails() { #[test] fn inputs_dont_contain_two_parents_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into()], &[], // no peeks &[], @@ -103,7 +149,7 @@ fn inputs_dont_contain_two_parents_fails() { #[test] fn outputs_dont_contain_all_family_members_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[KittyData::default().into()], @@ -114,7 +160,7 @@ fn outputs_dont_contain_all_family_members_fails() { #[test] fn breed_two_dads_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[ KittyData::default_dad().into(), KittyData::default_dad().into(), @@ -128,7 +174,7 @@ fn breed_two_dads_fails() { #[test] fn breed_two_moms_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default().into()], &[], // no peeks &[KittyData::default().into()], @@ -139,7 +185,7 @@ fn breed_two_moms_fails() { #[test] fn first_input_not_mom_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default_dad().into(), KittyData::default().into()], &[], // no peeks &[], @@ -150,7 +196,7 @@ fn first_input_not_mom_fails() { #[test] fn first_output_not_mom_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -168,7 +214,7 @@ fn breed_mom_when_she_gave_birth_recently_fails() { new_momma.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[new_momma.into(), KittyData::default_dad().into()], &[], // no peeks &[], @@ -182,7 +228,7 @@ fn breed_dad_when_he_is_tired_fails() { tired_dadda.parent = Parent::Dad(DadKittyStatus::Tired); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), tired_dadda.into()], &[], // no peeks &[], @@ -196,7 +242,7 @@ fn check_mom_breedings_overflow_fails() { test_mom.num_breedings = u128::MAX; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[test_mom.into(), KittyData::default_dad().into()], &[], // no peeks &[], @@ -213,7 +259,7 @@ fn check_dad_breedings_overflow_fails() { test_dad.num_breedings = u128::MAX; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), test_dad.into()], &[], // no peeks &[], @@ -230,7 +276,7 @@ fn check_mom_free_breedings_zero_fails() { test_mom.free_breedings = 0; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[test_mom.into(), KittyData::default_dad().into()], &[], // no peeks &[], @@ -244,7 +290,7 @@ fn check_dad_free_breedings_zero_fails() { test_dad.free_breedings = 0; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), test_dad.into()], &[], // no peeks &[], @@ -259,7 +305,7 @@ fn check_new_mom_free_breedings_incorrect_fails() { new_mom.free_breedings = 2; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -281,7 +327,7 @@ fn check_new_dad_free_breedings_incorrect_fails() { new_dad.free_breedings = 2; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -303,7 +349,7 @@ fn check_new_mom_num_breedings_incorrect_fails() { new_mom.num_breedings = 0; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -325,7 +371,7 @@ fn check_new_dad_num_breedings_incorrect_fails() { new_dad.num_breedings = 0; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -347,7 +393,7 @@ fn check_new_mom_dna_doesnt_match_old_fails() { new_mom.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -369,7 +415,7 @@ fn check_new_dad_dna_doesnt_match_old_fails() { new_dad.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -391,7 +437,7 @@ fn check_child_dna_incorrect_fails() { new_child.dna = KittyDNA(H256::zero()); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -410,7 +456,7 @@ fn check_child_dad_parent_tired_fails() { new_child.parent = Parent::Dad(DadKittyStatus::Tired); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -432,7 +478,7 @@ fn check_child_mom_parent_recently_gave_birth_fails() { new_child.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -454,7 +500,7 @@ fn check_child_free_breedings_incorrect_fails() { new_child.free_breedings = KittyHelpers::NUM_FREE_BREEDINGS + 1; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ @@ -476,7 +522,7 @@ fn check_child_num_breedings_non_zero_fails() { new_child.num_breedings = 42; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker, + &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], &[], // no peeks &[ diff --git a/wardrobe/tradable_kitties/Cargo.toml b/wardrobe/tradable_kitties/Cargo.toml new file mode 100644 index 000000000..de1d07067 --- /dev/null +++ b/wardrobe/tradable_kitties/Cargo.toml @@ -0,0 +1,29 @@ +[package] +description = "A Tuxedo piece that provides an NFT game loosely inspired by crypto kitties" +edition = "2021" +name = "tradable_kitties" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +parity-scale-codec = { features = [ "derive" ], workspace = true } +scale-info = { features = [ "derive" ], workspace = true } +serde = { features = [ "derive" ], workspace = true } +sp-core = { default_features = false, workspace = true } +sp-runtime = { default_features = false, workspace = true } +sp-std = { default_features = false, workspace = true } +tuxedo-core = { default-features = false, path = "../../tuxedo-core" } +money = { default-features = false, path = "../money/" } +kitties = { default-features = false, path = "../kitties/" } + +[features] +default = [ "std" ] +std = [ + "tuxedo-core/std", + "parity-scale-codec/std", + "sp-runtime/std", + "sp-std/std", + "sp-core/std", + "serde/std", +] diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs new file mode 100644 index 000000000..40360b925 --- /dev/null +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -0,0 +1,573 @@ +//! This module defines TradableKitty, a specialized type of kitty with additional features. +//! +//! TradableKitties are designed for trading, and they extend the functionality of the basic kitty. +//! Key features of TradableKitties include: +//! The TradableKitty module extends the basic functionality of kitties with additional features such as buying, updating properties, minting, and breeding. The provided validation functionality includes: +//! +//! - `Mint`: Create new TradableKitties, supporting the generation of kitties without parents. +//! - `Breed`: Consume kitties and create a new family, including parents (mom and dad) and a child. +//! - `UpdateProperties`: Update properties of a TradableKitty, including `is_available_for_sale`, `price`, and `name`. +//! A single API, `updateKittyProperties()`, is provided for updating these properties for below reasons: +//! 1. Updating atomically in a single transaction, ensuring consistency. +//! 2. Benfit of less number of transaction is reduced weight or gas fees. +//! - `Buy`: Enable users to purchase TradableKitties from others, facilitating secure and fair exchanges. +//! +//! +//! TradableKitties provide an enhanced user experience by introducing trading capabilities +//! and additional customization for kitty properties. + +#![cfg_attr(not(feature = "std"), no_std)] + +use kitties::Breed as BasicKittyBreed; +use kitties::ConstraintCheckerError; +use kitties::KittyData; +use kitties::KittyHelpers; +use money::{Coin, MoneyConstraintChecker}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; +use sp_runtime::transaction_validity::TransactionPriority; +use sp_std::prelude::*; +use tuxedo_core::{ + dynamic_typing::{DynamicallyTypedData, UtxoData}, + ensure, SimpleConstraintChecker, +}; + +#[cfg(test)] +mod tests; + +#[derive( + Serialize, + Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Encode, + Decode, + Hash, + Debug, + TypeInfo, +)] +pub struct TradableKittyData { + pub kitty_basic_data: KittyData, + pub price: Option, + pub is_available_for_sale: bool, +} + +impl Default for TradableKittyData { + fn default() -> Self { + Self { + kitty_basic_data: KittyData::default(), + price: None, + is_available_for_sale: false, + } + } +} + +impl TryFrom<&DynamicallyTypedData> for TradableKittyData { + type Error = TradableKittyConstraintCheckerError; + fn try_from(a: &DynamicallyTypedData) -> Result { + a.extract::() + .map_err(|_| TradableKittyConstraintCheckerError::BadlyTyped) + } +} + +impl UtxoData for TradableKittyData { + const TYPE_ID: [u8; 4] = *b"tdkt"; +} + +#[derive( + Serialize, + Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Encode, + Decode, + Hash, + Debug, + TypeInfo, +)] +pub enum TradableKittyConstraintCheckerError { + /// Dynamic typing issue. + /// This error doesn't discriminate between badly typed inputs and outputs. + BadlyTyped, + /// Needed when spending for breeding. + MinimumSpendAndBreedNotMet, + /// Need two parents to breed. + TwoParentsDoNotExist, + /// Incorrect number of outputs when it comes to breeding. + NotEnoughFamilyMembers, + /// Incorrect number of outputs when it comes to Minting. + IncorrectNumberOfKittiesForMintOperation, + /// Mom has recently given birth and isnt ready to breed. + MomNotReadyYet, + /// Dad cannot breed because he is still too tired. + DadTooTired, + /// Cannot have two moms when breeding. + TwoMomsNotValid, + /// Cannot have two dads when breeding. + TwoDadsNotValid, + /// New Mom after breeding should be in HadBirthRecently state. + NewMomIsStillRearinToGo, + /// New Dad after breeding should be in Tired state. + NewDadIsStillRearinToGo, + /// Number of free breedings of new parent is not correct. + NewParentFreeBreedingsIncorrect, + /// New parents DNA does not match the old one parent has to still be the same kitty. + NewParentDnaDoesntMatchOld, + /// New parent Breedings has not incremented or is incorrect. + NewParentNumberBreedingsIncorrect, + /// New child DNA is not correct given the protocol. + NewChildDnaIncorrect, + /// New child doesnt have the correct number of free breedings. + NewChildFreeBreedingsIncorrect, + /// New child has non zero breedings which is impossible because it was just born. + NewChildHasNonZeroBreedings, + /// New child parent info is either in Tired state or HadBirthRecently state which is not possible. + NewChildIncorrectParentInfo, + /// Too many breedings for this kitty can no longer breed. + TooManyBreedingsForKitty, + /// Not enough free breedings available for these parents. + NotEnoughFreeBreedings, + /// The transaction attempts to mint no Kitty. + MintingNothing, + /// Inputs(Parents) not required for mint. + MintingWithInputs, + /// No input for kitty Update. + InputMissingUpdatingNothing, + /// Mismatch innumberof inputs and number of outputs . + MismatchBetweenNumberOfInputAndUpdateUpadtingNothing, + /// TradableKitty Update can't have more than one outputs. + MultipleOutputsForKittyUpdateError, + /// Kitty Update has more than one inputs. + InValidNumberOfInputsForKittyUpdate, + /// Basic kitty properties cannot be updated. + KittyGenderCannotBeUpdated, + /// Basic kitty properties cannot be updated. + KittyDnaCannotBeUpdated, + /// Kitty FreeBreeding cannot be updated. + FreeBreedingCannotBeUpdated, + /// Kitty NumOfBreeding cannot be updated. + NumOfBreedingCannotBeUpdated, + /// Updated price of is incorrect. + UpdatedKittyIncorrectPrice, + + /// No input for kitty buy operation. + InputMissingBuyingNothing, + /// No output for kitty buy operation. + OutputMissingBuyingNothing, + /// Incorrect number of outputs buy operation. + IncorrectNumberOfInputKittiesForBuyOperation, + /// Incorrect number of outputs for buy operation. + IncorrectNumberOfOutputKittiesForBuyOperation, + /// Kitty not avilable for sale + KittyNotForSale, + /// Kitty price cant be none when it is avilable for sale + KittyPriceCantBeNone, + /// Kitty price cant be zero when it is avilable for sale + KittyPriceCantBeZero, + /// Not enough amount to buy kitty + InsufficientCollateralToBuyKitty, + + // From below money constraintchecker errors are added + /// The transaction attempts to spend without consuming any inputs. + /// Either the output value will exceed the input value, or if there are no outputs, + /// it is a waste of processing power, so it is not allowed. + SpendingNothing, + /// The value of the spent input coins is less than the value of the newly created + /// output coins. This would lead to money creation and is not allowed. + OutputsExceedInputs, + /// The value consumed or created by this transaction overflows the value type. + /// This could lead to problems like https://bitcointalk.org/index.php?topic=823.0 + ValueOverflow, + /// The transaction attempted to create a coin with zero value. This is not allowed + /// because it wastes state space. + ZeroValueCoin, +} + +impl From for TradableKittyConstraintCheckerError { + fn from(error: money::ConstraintCheckerError) -> Self { + match error { + money::ConstraintCheckerError::BadlyTyped => { + TradableKittyConstraintCheckerError::BadlyTyped + } + money::ConstraintCheckerError::MintingWithInputs => { + TradableKittyConstraintCheckerError::MintingWithInputs + } + money::ConstraintCheckerError::MintingNothing => { + TradableKittyConstraintCheckerError::MintingNothing + } + money::ConstraintCheckerError::SpendingNothing => { + TradableKittyConstraintCheckerError::SpendingNothing + } + money::ConstraintCheckerError::OutputsExceedInputs => { + TradableKittyConstraintCheckerError::OutputsExceedInputs + } + money::ConstraintCheckerError::ValueOverflow => { + TradableKittyConstraintCheckerError::ValueOverflow + } + money::ConstraintCheckerError::ZeroValueCoin => { + TradableKittyConstraintCheckerError::ZeroValueCoin + } + } + } +} + +// Implement From trait for mapping ConstraintCheckerError to TradableKittyConstraintCheckerError +impl From for TradableKittyConstraintCheckerError { + fn from(error: ConstraintCheckerError) -> Self { + match error { + ConstraintCheckerError::BadlyTyped => TradableKittyConstraintCheckerError::BadlyTyped, + ConstraintCheckerError::MinimumSpendAndBreedNotMet => { + TradableKittyConstraintCheckerError::MinimumSpendAndBreedNotMet + } + ConstraintCheckerError::TwoParentsDoNotExist => { + TradableKittyConstraintCheckerError::TwoParentsDoNotExist + } + ConstraintCheckerError::NotEnoughFamilyMembers => { + TradableKittyConstraintCheckerError::NotEnoughFamilyMembers + } + ConstraintCheckerError::IncorrectNumberOfKittiesForMintOperation => { + TradableKittyConstraintCheckerError::IncorrectNumberOfKittiesForMintOperation + } + ConstraintCheckerError::MomNotReadyYet => { + TradableKittyConstraintCheckerError::MomNotReadyYet + } + ConstraintCheckerError::DadTooTired => TradableKittyConstraintCheckerError::DadTooTired, + ConstraintCheckerError::TwoMomsNotValid => { + TradableKittyConstraintCheckerError::TwoMomsNotValid + } + ConstraintCheckerError::TwoDadsNotValid => { + TradableKittyConstraintCheckerError::TwoDadsNotValid + } + ConstraintCheckerError::NewMomIsStillRearinToGo => { + TradableKittyConstraintCheckerError::NewMomIsStillRearinToGo + } + ConstraintCheckerError::NewDadIsStillRearinToGo => { + TradableKittyConstraintCheckerError::NewDadIsStillRearinToGo + } + ConstraintCheckerError::NewParentFreeBreedingsIncorrect => { + TradableKittyConstraintCheckerError::NewParentFreeBreedingsIncorrect + } + ConstraintCheckerError::NewParentDnaDoesntMatchOld => { + TradableKittyConstraintCheckerError::NewParentDnaDoesntMatchOld + } + ConstraintCheckerError::NewParentNumberBreedingsIncorrect => { + TradableKittyConstraintCheckerError::NewParentNumberBreedingsIncorrect + } + ConstraintCheckerError::NewChildDnaIncorrect => { + TradableKittyConstraintCheckerError::NewChildDnaIncorrect + } + ConstraintCheckerError::NewChildFreeBreedingsIncorrect => { + TradableKittyConstraintCheckerError::NewChildFreeBreedingsIncorrect + } + ConstraintCheckerError::NewChildHasNonZeroBreedings => { + TradableKittyConstraintCheckerError::NewChildHasNonZeroBreedings + } + ConstraintCheckerError::NewChildIncorrectParentInfo => { + TradableKittyConstraintCheckerError::NewChildIncorrectParentInfo + } + ConstraintCheckerError::TooManyBreedingsForKitty => { + TradableKittyConstraintCheckerError::TooManyBreedingsForKitty + } + ConstraintCheckerError::NotEnoughFreeBreedings => { + TradableKittyConstraintCheckerError::NotEnoughFreeBreedings + } + ConstraintCheckerError::MintingNothing => { + TradableKittyConstraintCheckerError::MintingNothing + } + ConstraintCheckerError::MintingWithInputs => { + TradableKittyConstraintCheckerError::MintingWithInputs + } + } + } +} + +#[derive( + Serialize, + Deserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Clone, + Encode, + Decode, + Hash, + Debug, + TypeInfo, +)] +pub enum TradableKittyConstraintChecker { + /// A mint transaction that creates Tradable Kitties + Mint, + /// A typical Breed transaction where kitties are consumed and new family(Parents(mom,dad) and child) is created. + Breed, + ///Update various properties of kitty. + UpdateProperties, + ///Can buy a new kitty from others + Buy, +} + +pub trait Breed { + /// The Cost to breed a kitty. + const COST: u128; + type Error: Into; + fn can_breed(mom: &TradableKittyData, dad: &TradableKittyData) -> Result<(), Self::Error>; + fn check_new_family( + mom: &TradableKittyData, + dad: &TradableKittyData, + new_family: &[DynamicallyTypedData], + ) -> Result<(), Self::Error>; +} + +trait UpdateKittyProperty { + /// The Cost to update a kitty property if it is not free. + const COST: u128; + /// Error type for all Kitty errors. + type Error: Into; + + fn check_updated_kitty( + original_kitty: &TradableKittyData, + updated_kitty: &TradableKittyData, + ) -> Result<(), Self::Error>; +} + +trait Buy { + /// Error type for all Kitty errors. + type Error: Into; + + fn can_buy( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], + ) -> Result<(), Self::Error>; +} + +pub struct TradableKittyHelpers; + +impl UpdateKittyProperty for TradableKittyHelpers { + const COST: u128 = 5u128; + type Error = TradableKittyConstraintCheckerError; + + fn check_updated_kitty( + original_kitty: &TradableKittyData, + updated_kitty: &TradableKittyData, + ) -> Result<(), Self::Error> { + ensure!( + original_kitty.kitty_basic_data.parent == updated_kitty.kitty_basic_data.parent, + Self::Error::KittyGenderCannotBeUpdated, + ); + ensure!( + original_kitty.kitty_basic_data.free_breedings + == updated_kitty.kitty_basic_data.free_breedings, + Self::Error::FreeBreedingCannotBeUpdated, + ); + ensure!( + original_kitty.kitty_basic_data.dna == updated_kitty.kitty_basic_data.dna, + Self::Error::KittyDnaCannotBeUpdated, + ); + ensure!( + original_kitty.kitty_basic_data.num_breedings + == updated_kitty.kitty_basic_data.num_breedings, + Self::Error::NumOfBreedingCannotBeUpdated, + ); + + if !updated_kitty.is_available_for_sale && updated_kitty.price != None { + return Err(Self::Error::UpdatedKittyIncorrectPrice); + } + + if updated_kitty.is_available_for_sale + && (updated_kitty.price == None || updated_kitty.price.unwrap() == 0) + { + return Err(Self::Error::UpdatedKittyIncorrectPrice); + } + Ok(()) + } +} + +impl Breed for TradableKittyHelpers { + const COST: u128 = 5u128; + type Error = TradableKittyConstraintCheckerError; + fn can_breed(mom: &TradableKittyData, dad: &TradableKittyData) -> Result<(), Self::Error> { + KittyHelpers::can_breed(&mom.kitty_basic_data, &dad.kitty_basic_data)?; + Ok(()) + } + + fn check_new_family( + mom: &TradableKittyData, + dad: &TradableKittyData, + new_tradable_kitty_family: &[DynamicallyTypedData], + ) -> Result<(), Self::Error> { + let new_tradable_kitty_mom = TradableKittyData::try_from(&new_tradable_kitty_family[0])?; + let new_tradable_kitty_dad = TradableKittyData::try_from(&new_tradable_kitty_family[1])?; + let new_tradable_kitty_child = TradableKittyData::try_from(&new_tradable_kitty_family[2])?; + + let new_basic_kitty_mom: DynamicallyTypedData = + new_tradable_kitty_mom.kitty_basic_data.into(); + let new_basic_kitty_dad: DynamicallyTypedData = + new_tradable_kitty_dad.kitty_basic_data.into(); + let new_basic_kitty_child: DynamicallyTypedData = + new_tradable_kitty_child.kitty_basic_data.into(); + + let mut new_family: Vec = Vec::new(); + new_family.push(new_basic_kitty_mom); + new_family.push(new_basic_kitty_dad); + new_family.push(new_basic_kitty_child); + + KittyHelpers::check_new_family(&mom.kitty_basic_data, &dad.kitty_basic_data, &new_family)?; + Ok(()) + } +} + +impl Buy for TradableKittyHelpers { + type Error = TradableKittyConstraintCheckerError; + fn can_buy( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], + ) -> Result<(), Self::Error> { + let mut input_coin_data: Vec = Vec::new(); + let mut output_coin_data: Vec = Vec::new(); + let mut input_kitty_data: Vec = Vec::new(); + let mut output_kitty_data: Vec = Vec::new(); + + let mut total_input_amount: u128 = 0; + let mut total_price_of_kitty: u128 = 0; + + for utxo in input_data { + if let Ok(coin) = utxo.extract::>() { + let utxo_value = coin.0; + ensure!( + utxo_value > 0, + TradableKittyConstraintCheckerError::ZeroValueCoin + ); + input_coin_data.push(utxo.clone()); + total_input_amount = total_input_amount + .checked_add(utxo_value) + .ok_or(TradableKittyConstraintCheckerError::ValueOverflow)?; + + // Process Kitty + } else if let Ok(tradable_kitty) = utxo.extract::() { + if !tradable_kitty.is_available_for_sale { + return Err(Self::Error::KittyNotForSale); + } + let price = match tradable_kitty.price { + None => return Err(Self::Error::KittyPriceCantBeNone), + Some(p) => p, + }; + + input_kitty_data.push(utxo.clone()); + total_price_of_kitty = total_price_of_kitty + .checked_add(price) + .ok_or(TradableKittyConstraintCheckerError::ValueOverflow)?; + // Process TradableKittyData + // You can also use the `tradable_kitty` variable herex + } else { + return Err(Self::Error::BadlyTyped); + } + } + + // Need to filter only Coins and send to MoneyConstraintChecker + for utxo in output_data { + if let Ok(coin) = utxo.extract::>() { + let utxo_value = coin.0; + ensure!( + utxo_value > 0, + TradableKittyConstraintCheckerError::ZeroValueCoin + ); + output_coin_data.push(utxo.clone()); + // Process Coin + } else if let Ok(_tradable_kitty) = utxo.extract::() { + output_kitty_data.push(utxo.clone()); + } else { + return Err(Self::Error::BadlyTyped); + } + } + + ensure!( + !input_kitty_data.is_empty(), + TradableKittyConstraintCheckerError::InputMissingBuyingNothing + ); + + // Make sure there is at least one output being minted + ensure!( + !output_kitty_data.is_empty(), + TradableKittyConstraintCheckerError::OutputMissingBuyingNothing + ); + ensure!( + total_price_of_kitty <= total_input_amount, + TradableKittyConstraintCheckerError::InsufficientCollateralToBuyKitty + ); + + // Need to filter only Coins and send to MoneyConstraintChecker + MoneyConstraintChecker::<0>::Spend.check(&input_coin_data, &[], &output_coin_data)?; + Ok(()) + } +} + +impl SimpleConstraintChecker for TradableKittyConstraintChecker { + type Error = TradableKittyConstraintCheckerError; + + fn check( + &self, + input_data: &[DynamicallyTypedData], + _peeks: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], + ) -> Result { + match &self { + Self::Mint => { + // Make sure there are no inputs being consumed + ensure!( + input_data.is_empty(), + TradableKittyConstraintCheckerError::MintingWithInputs + ); + + // Make sure there is at least one output being minted + ensure!( + !output_data.is_empty(), + TradableKittyConstraintCheckerError::MintingNothing + ); + + // Make sure the outputs are the right type + for utxo in output_data { + let _utxo_kitty = utxo + .extract::() + .map_err(|_| TradableKittyConstraintCheckerError::BadlyTyped)?; + } + return Ok(0); + } + Self::Breed => { + ensure!(input_data.len() == 2, Self::Error::TwoParentsDoNotExist); + let mom = TradableKittyData::try_from(&input_data[0])?; + let dad = TradableKittyData::try_from(&input_data[1])?; + TradableKittyHelpers::::can_breed(&mom, &dad)?; + ensure!(output_data.len() == 3, Self::Error::NotEnoughFamilyMembers); + TradableKittyHelpers::::check_new_family(&mom, &dad, output_data)?; + return Ok(0); + } + Self::UpdateProperties => { + ensure!( + !input_data.is_empty(), + TradableKittyConstraintCheckerError::InputMissingUpdatingNothing + ); + + ensure!( + input_data.len() == output_data.len(), + TradableKittyConstraintCheckerError::MismatchBetweenNumberOfInputAndUpdateUpadtingNothing + ); + + let original_kitty = TradableKittyData::try_from(&input_data[0])?; + let updated_kitty = TradableKittyData::try_from(&output_data[0])?; + TradableKittyHelpers::::check_updated_kitty(&original_kitty, &updated_kitty)?; + } + Self::Buy => { + TradableKittyHelpers::::can_buy(input_data, output_data)?; + return Ok(0); + } + } + Ok(0) + } +} diff --git a/wardrobe/tradable_kitties/src/tests.rs b/wardrobe/tradable_kitties/src/tests.rs new file mode 100644 index 000000000..81dc5aadb --- /dev/null +++ b/wardrobe/tradable_kitties/src/tests.rs @@ -0,0 +1,928 @@ +//! Tests for the Crypto Kitties Piece + +use super::*; +use kitties::DadKittyStatus; +use kitties::KittyDNA; +use kitties::MomKittyStatus; +use kitties::Parent; +use sp_runtime::testing::H256; +use sp_runtime::traits::BlakeTwo256; +use sp_runtime::traits::Hash; + +/// A bogus data type used in tests for type validation +#[derive(Encode, Decode)] +struct Bogus; + +impl UtxoData for Bogus { + const TYPE_ID: [u8; 4] = *b"bogs"; +} + +impl TradableKittyData { + pub fn default_dad() -> Self { + let kitty_basic = KittyData { + parent: Parent::Dad(DadKittyStatus::RearinToGo), + ..Default::default() + }; + TradableKittyData { + kitty_basic_data: kitty_basic, + ..Default::default() + } + } + + pub fn default_child() -> Self { + let mom = Self::default(); + let dad = Self::default_dad(); + + let kitty_basic = KittyData { + parent: Parent::Mom(MomKittyStatus::RearinToGo), + free_breedings: 2, + name: *b"tkty", + dna: KittyDNA(BlakeTwo256::hash_of(&( + mom.kitty_basic_data.dna, + dad.kitty_basic_data.dna, + mom.kitty_basic_data.num_breedings + 1, + dad.kitty_basic_data.num_breedings + 1, + ))), + num_breedings: 0, + }; + + TradableKittyData { + kitty_basic_data: kitty_basic, + ..Default::default() + } + } + + pub fn default_family() -> Box> { + let mut new_mom: TradableKittyData = TradableKittyData::default(); + new_mom.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); + new_mom.kitty_basic_data.num_breedings += 1; + new_mom.kitty_basic_data.free_breedings -= 1; + + let mut new_dad = TradableKittyData::default_dad(); + new_dad.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::Tired); + new_dad.kitty_basic_data.num_breedings += 1; + new_dad.kitty_basic_data.free_breedings -= 1; + + let child = TradableKittyData::default_child(); + + Box::new(vec![new_mom, new_dad, child]) + } + + pub fn default_updated() -> Self { + let kitty_basic = KittyData { + name: *b"tomy", + ..Default::default() + }; + TradableKittyData { + kitty_basic_data: kitty_basic, + is_available_for_sale: true, + price: Some(200), + ..Default::default() + } + } +} + +// From below mint tradable kitty test cases start. + +#[test] +fn mint_happy_path_works() { + let result = TradableKittyConstraintChecker::<0>::Mint.check( + &[], + &[], // no peeks + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + ); + assert!(result.is_ok()); +} + +#[test] +fn mint_with_input_fails() { + let result = TradableKittyConstraintChecker::<0>::Mint.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::MintingWithInputs) + ); +} +#[test] +fn mint_without_output_fails() { + let result = TradableKittyConstraintChecker::<0>::Mint.check( + &[], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::MintingNothing) + ); +} +#[test] +fn mint_with_wrong_output_type_fails() { + let result = TradableKittyConstraintChecker::<0>::Mint.check( + &[], + &[], // no peeks + &[ + Bogus.into(), + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + ); + assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); +} + +// From below breed tradable kitty test cases start. + +#[test] +fn breed_happy_path_works() { + let new_family = TradableKittyData::default_family(); + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_family[1].clone().into(), + new_family[2].clone().into(), + ], + ); + assert!(result.is_ok()); +} + +#[test] +fn breed_wrong_input_type_fails() { + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[Bogus.into(), Bogus.into()], + &[], // no peeks + &[], + ); + assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); +} + +#[test] +fn breed_wrong_output_type_fails() { + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[Bogus.into(), Bogus.into(), Bogus.into()], + ); + assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); +} + +#[test] +fn inputs_dont_contain_two_parents_fails() { + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::TwoParentsDoNotExist) + ); +} + +#[test] +fn outputs_dont_contain_all_family_members_fails() { + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[TradableKittyData::default().into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NotEnoughFamilyMembers) + ); +} + +#[test] +fn breed_two_dads_fails() { + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default_dad().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[TradableKittyData::default().into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::TwoDadsNotValid) + ); +} + +#[test] +fn breed_two_moms_fails() { + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default().into(), + ], + &[], // no peeks + &[TradableKittyData::default().into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::TwoMomsNotValid) + ); +} + +#[test] +fn first_input_not_mom_fails() { + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default_dad().into(), + TradableKittyData::default().into(), + ], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::TwoDadsNotValid) + ) +} + +#[test] +fn first_output_not_mom_fails() { + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + TradableKittyData::default_dad().into(), + TradableKittyData::default().into(), + TradableKittyData::default_child().into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::TwoDadsNotValid) + ); +} + +#[test] +fn breed_mom_when_she_gave_birth_recently_fails() { + let mut new_momma = TradableKittyData::default(); + new_momma.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[new_momma.into(), TradableKittyData::default_dad().into()], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::MomNotReadyYet) + ); +} + +#[test] +fn breed_dad_when_he_is_tired_fails() { + let mut tired_dadda = TradableKittyData::default_dad(); + tired_dadda.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::Tired); + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[TradableKittyData::default().into(), tired_dadda.into()], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::DadTooTired) + ); +} + +#[test] +fn check_mom_breedings_overflow_fails() { + let mut test_mom = TradableKittyData::default(); + test_mom.kitty_basic_data.num_breedings = u128::MAX; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[test_mom.into(), TradableKittyData::default_dad().into()], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::TooManyBreedingsForKitty) + ); +} + +#[test] +fn check_dad_breedings_overflow_fails() { + let mut test_dad = TradableKittyData::default_dad(); + test_dad.kitty_basic_data.num_breedings = u128::MAX; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[TradableKittyData::default().into(), test_dad.into()], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::TooManyBreedingsForKitty) + ); +} + +#[test] +fn check_mom_free_breedings_zero_fails() { + let mut test_mom = TradableKittyData::default(); + test_mom.kitty_basic_data.free_breedings = 0; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[test_mom.into(), TradableKittyData::default_dad().into()], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NotEnoughFreeBreedings) + ); +} + +#[test] +fn check_dad_free_breedings_zero_fails() { + let mut test_dad = TradableKittyData::default_dad(); + test_dad.kitty_basic_data.free_breedings = 0; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[TradableKittyData::default().into(), test_dad.into()], + &[], // no peeks + &[], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NotEnoughFreeBreedings) + ); +} + +#[test] +fn check_new_mom_free_breedings_incorrect_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_mom = new_family[0].clone(); + new_mom.kitty_basic_data.free_breedings = 2; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_mom.into(), + new_family[1].clone().into(), + new_family[2].clone().into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewParentFreeBreedingsIncorrect) + ); +} + +#[test] +fn check_new_dad_free_breedings_incorrect_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_dad = new_family[1].clone(); + new_dad.kitty_basic_data.free_breedings = 2; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_dad.into(), + new_family[2].clone().into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewParentFreeBreedingsIncorrect) + ); +} + +#[test] +fn check_new_mom_num_breedings_incorrect_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_mom = new_family[0].clone(); + new_mom.kitty_basic_data.num_breedings = 0; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_mom.into(), + new_family[1].clone().into(), + new_family[2].clone().into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewParentNumberBreedingsIncorrect) + ); +} + +#[test] +fn check_new_dad_num_breedings_incorrect_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_dad = new_family[1].clone(); + new_dad.kitty_basic_data.num_breedings = 0; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_dad.into(), + new_family[2].clone().into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewParentNumberBreedingsIncorrect) + ); +} + +#[test] +fn check_new_mom_dna_doesnt_match_old_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_mom = new_family[0].clone(); + new_mom.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_mom.into(), + new_family[1].clone().into(), + new_family[2].clone().into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewParentDnaDoesntMatchOld) + ); +} + +#[test] +fn check_new_dad_dna_doesnt_match_old_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_dad = new_family[1].clone(); + new_dad.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_dad.into(), + new_family[2].clone().into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewParentDnaDoesntMatchOld) + ); +} + +#[test] +fn check_child_dna_incorrect_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_child = new_family[2].clone(); + new_child.kitty_basic_data.dna = KittyDNA(H256::zero()); + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_family[1].clone().into(), + new_child.into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewChildDnaIncorrect) + ); +} + +#[test] +fn check_child_dad_parent_tired_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_child = new_family[2].clone(); + new_child.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::Tired); + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_family[1].clone().into(), + new_child.into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewChildIncorrectParentInfo) + ); +} + +#[test] +fn check_child_mom_parent_recently_gave_birth_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_child = new_family[2].clone(); + new_child.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_family[1].clone().into(), + new_child.into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewChildIncorrectParentInfo) + ); +} + +#[test] +fn check_child_free_breedings_incorrect_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_child = new_family[2].clone(); + new_child.kitty_basic_data.free_breedings = KittyHelpers::NUM_FREE_BREEDINGS + 1; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_family[1].clone().into(), + new_child.into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewChildFreeBreedingsIncorrect) + ); +} + +#[test] +fn check_child_num_breedings_non_zero_fails() { + let new_family = TradableKittyData::default_family(); + let mut new_child = new_family[2].clone(); + new_child.kitty_basic_data.num_breedings = 42; + + let result = TradableKittyConstraintChecker::<0>::Breed.check( + &[ + TradableKittyData::default().into(), + TradableKittyData::default_dad().into(), + ], + &[], // no peeks + &[ + new_family[0].clone().into(), + new_family[1].clone().into(), + new_child.into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NewChildHasNonZeroBreedings) + ); +} + +// From below update tradable kitty properties test cases starts +#[test] +fn update_properties_happy_path_works() { + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[TradableKittyData::default_updated().into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn update_properties_update_no_input_fails() { + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[], + &[], // no peeks + &[TradableKittyData::default_updated().into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::InputMissingUpdatingNothing) + ); +} + +#[test] +fn update_properties_update_num_of_input_output_mismatch_fails() { + let mut updated_kitty = TradableKittyData::default(); + updated_kitty.kitty_basic_data.dna = + KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[ + TradableKittyData::default_updated().into(), + updated_kitty.into(), + ], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::MismatchBetweenNumberOfInputAndUpdateUpadtingNothing) + ); +} + +#[test] +fn update_properties_update_dna_fails() { + let mut updated_kitty = TradableKittyData::default(); + updated_kitty.kitty_basic_data.dna = + KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[updated_kitty.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::KittyDnaCannotBeUpdated) + ); +} + +#[test] +fn update_properties_update_gender_fails() { + let updated_kitty = TradableKittyData::default_dad(); + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[updated_kitty.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::KittyGenderCannotBeUpdated) + ); +} + +#[test] +fn update_properties_update_free_breedings_fails() { + let mut updated_kitty = TradableKittyData::default(); + updated_kitty.kitty_basic_data.free_breedings = 5; + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[updated_kitty.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::FreeBreedingCannotBeUpdated) + ); +} + +#[test] +fn update_properties_update_num_breedings_fails() { + let mut updated_kitty = TradableKittyData::default(); + updated_kitty.kitty_basic_data.num_breedings = 5; + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[updated_kitty.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::NumOfBreedingCannotBeUpdated) + ); +} + +#[test] +fn update_properties_non_none_price_when_is_avilable_for_sale_is_false_fails() { + let mut updated_kitty = TradableKittyData::default(); + updated_kitty.price = Some(100); + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[updated_kitty.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::UpdatedKittyIncorrectPrice) + ); +} + +#[test] +fn update_properties_none_price_when_is_avilable_for_sale_is_true_fails() { + let mut updated_kitty = TradableKittyData::default(); + updated_kitty.is_available_for_sale = true; + updated_kitty.price = None; + let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( + &[TradableKittyData::default().into()], + &[], // no peeks + &[updated_kitty.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::UpdatedKittyIncorrectPrice) + ); +} + +// From below buy tradable kitty test cases start. +#[test] +fn buy_happy_path_single_input_coinworks() { + let mut input_kitty = TradableKittyData::default(); + input_kitty.is_available_for_sale = true; + input_kitty.price = Some(100); + let output_kitty = input_kitty.clone(); + + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into()], + &[], // no peeks + &[output_kitty.into(), output_coin.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn buy_happy_path_multiple_input_coinworks() { + let mut input_kitty = TradableKittyData::default(); + input_kitty.is_available_for_sale = true; + input_kitty.price = Some(100); + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(10); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], // no peeks + &[output_kitty.into(), output_coin.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn buy_kityy_is_available_for_sale_false_fails() { + let mut input_kitty = TradableKittyData::default(); + input_kitty.price = Some(100); + let output_kitty = input_kitty.clone(); + + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into()], + &[], // no peeks + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::KittyNotForSale) + ); +} +#[test] +fn buy_kityy_is_available_for_sale_true_price_none_fails() { + let mut input_kitty = TradableKittyData::default(); + input_kitty.is_available_for_sale = true; + let output_kitty = input_kitty.clone(); + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into()], + &[], // no peeks + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::KittyPriceCantBeNone) + ); +} +#[test] +fn buy_kityy_wrong_input_type_fails() { + let mut input_kitty = TradableKittyData::default(); + input_kitty.is_available_for_sale = true; + input_kitty.price = Some(101); + let output_kitty = input_kitty.clone(); + + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into(), Bogus.into()], + &[], // no peeks + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); +} + +#[test] +fn buy_kityy_wrong_output_type_fails() { + let mut input_kitty = TradableKittyData::default(); + input_kitty.is_available_for_sale = true; + input_kitty.price = Some(101); + let output_kitty = input_kitty.clone(); + + let input_coin = Coin::<0>(100); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin.into()], + &[], // no peeks + &[output_kitty.into(), output_coin.into(), Bogus.into()], + ); + assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); +} + +#[test] +fn buy_kitty_less_money_than_price_of_kitty_fails() { + let mut input_kitty = TradableKittyData::default(); + input_kitty.is_available_for_sale = true; + input_kitty.price = Some(101); + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(100); + // let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into()], + &[], // no peeks + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::InsufficientCollateralToBuyKitty) + ); +} + +#[test] +fn buy_kitty_coin_output_value_exceeds_input_coin_value_fails() { + let mut input_kitty = TradableKittyData::default(); + input_kitty.is_available_for_sale = true; + input_kitty.price = Some(101); + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(100); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(300); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], // no peeks + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradableKittyConstraintCheckerError::OutputsExceedInputs) + ) +} From 2e0885d6ddf6ed6e5d7dc21a50ea3f7464a9f6fe Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Mon, 12 Feb 2024 14:48:21 +0530 Subject: [PATCH 02/14] Updated PKG nam and added better description for tradable_kitties cargo.toml Updated PKG nam and added better description for tradable_kitties cargo.toml --- tuxedo-template-runtime/Cargo.toml | 2 +- wardrobe/tradable_kitties/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tuxedo-template-runtime/Cargo.toml b/tuxedo-template-runtime/Cargo.toml index 73fcb33da..4daba6183 100644 --- a/tuxedo-template-runtime/Cargo.toml +++ b/tuxedo-template-runtime/Cargo.toml @@ -36,7 +36,7 @@ sp-consensus-grandpa = { default_features = false, workspace = true } # Tuxedo Core and Pieces amoeba = { default-features = false, path = "../wardrobe/amoeba" } kitties = { default-features = false, path = "../wardrobe/kitties" } -tradable_kitties = { default-features = false, path = "../wardrobe/tradable_kitties" } +tradable-kitties = { default-features = false, path = "../wardrobe/tradable_kitties" } money = { default-features = false, path = "../wardrobe/money" } poe = { default-features = false, path = "../wardrobe/poe" } runtime-upgrade = { default-features = false, path = "../wardrobe/runtime_upgrade" } diff --git a/wardrobe/tradable_kitties/Cargo.toml b/wardrobe/tradable_kitties/Cargo.toml index de1d07067..41bfbe6a6 100644 --- a/wardrobe/tradable_kitties/Cargo.toml +++ b/wardrobe/tradable_kitties/Cargo.toml @@ -1,7 +1,7 @@ [package] -description = "A Tuxedo piece that provides an NFT game loosely inspired by crypto kitties" +description = "A Tuxedo piece that provides an NFT game loosely inspired by crypto kitties which can be traded" edition = "2021" -name = "tradable_kitties" +name = "tradable-kitties" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,7 +13,7 @@ serde = { features = [ "derive" ], workspace = true } sp-core = { default_features = false, workspace = true } sp-runtime = { default_features = false, workspace = true } sp-std = { default_features = false, workspace = true } -tuxedo-core = { default-features = false, path = "../../tuxedo-core" } +tuxedo-core = { default-features = false, path = "../../tuxedo-core" } money = { default-features = false, path = "../money/" } kitties = { default-features = false, path = "../kitties/" } From 80d2e996e954d1e3ab29cb9b3b791dc1cd7d05ba Mon Sep 17 00:00:00 2001 From: Joshy Orndorff Date: Tue, 13 Feb 2024 11:56:58 -0500 Subject: [PATCH 03/14] toml sort --- tuxedo-template-runtime/Cargo.toml | 2 +- wardrobe/tradable_kitties/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tuxedo-template-runtime/Cargo.toml b/tuxedo-template-runtime/Cargo.toml index 4daba6183..de5c5c49c 100644 --- a/tuxedo-template-runtime/Cargo.toml +++ b/tuxedo-template-runtime/Cargo.toml @@ -36,11 +36,11 @@ sp-consensus-grandpa = { default_features = false, workspace = true } # Tuxedo Core and Pieces amoeba = { default-features = false, path = "../wardrobe/amoeba" } kitties = { default-features = false, path = "../wardrobe/kitties" } -tradable-kitties = { default-features = false, path = "../wardrobe/tradable_kitties" } money = { default-features = false, path = "../wardrobe/money" } poe = { default-features = false, path = "../wardrobe/poe" } runtime-upgrade = { default-features = false, path = "../wardrobe/runtime_upgrade" } timestamp = { default-features = false, path = "../wardrobe/timestamp" } +tradable-kitties = { default-features = false, path = "../wardrobe/tradable_kitties" } tuxedo-core = { default-features = false, path = "../tuxedo-core" } # Parachain related ones diff --git a/wardrobe/tradable_kitties/Cargo.toml b/wardrobe/tradable_kitties/Cargo.toml index 41bfbe6a6..3b1a57eed 100644 --- a/wardrobe/tradable_kitties/Cargo.toml +++ b/wardrobe/tradable_kitties/Cargo.toml @@ -7,6 +7,8 @@ version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +kitties = { default-features = false, path = "../kitties/" } +money = { default-features = false, path = "../money/" } parity-scale-codec = { features = [ "derive" ], workspace = true } scale-info = { features = [ "derive" ], workspace = true } serde = { features = [ "derive" ], workspace = true } @@ -14,8 +16,6 @@ sp-core = { default_features = false, workspace = true } sp-runtime = { default_features = false, workspace = true } sp-std = { default_features = false, workspace = true } tuxedo-core = { default-features = false, path = "../../tuxedo-core" } -money = { default-features = false, path = "../money/" } -kitties = { default-features = false, path = "../kitties/" } [features] default = [ "std" ] From 5461173179f21d24037a673dd26470056c39cb69 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Thu, 22 Feb 2024 15:38:31 +0530 Subject: [PATCH 04/14] Implement the design change Implement design change and addressed all the review comments. Mainly: 1. Now kitties can updated to tradable kitties and vice versa based on listForSale and DelistFromsale constraint checkers. 2. Support multiple inputs and outputs 3. Remove the is_avilabel_for_sale from tradable kitty 4. Add listForSale , DelistFromSale and Update name and updatePrice for tradable kitty 5. Add Update name for kitty piece. --- tuxedo-template-runtime/src/lib.rs | 2 +- wardrobe/kitties/src/lib.rs | 135 +++- wardrobe/kitties/src/tests.rs | 255 +++++- wardrobe/tradable_kitties/Cargo.toml | 2 + wardrobe/tradable_kitties/src/lib.rs | 698 +++++++--------- wardrobe/tradable_kitties/src/tests.rs | 1024 ++++++++++-------------- 6 files changed, 1010 insertions(+), 1106 deletions(-) diff --git a/tuxedo-template-runtime/src/lib.rs b/tuxedo-template-runtime/src/lib.rs index f8eb74826..3afe5fe0d 100644 --- a/tuxedo-template-runtime/src/lib.rs +++ b/tuxedo-template-runtime/src/lib.rs @@ -171,7 +171,7 @@ pub enum OuterConstraintChecker { Money(money::MoneyConstraintChecker<0>), /// Checks Free Kitty transactions FreeKittyConstraintChecker(kitties::FreeKittyConstraintChecker), - /// Checks Paid Kitty transactions + /// Checks tradable Kitty transactions TradableKittyConstraintChecker(tradable_kitties::TradableKittyConstraintChecker<0>), /// Checks that an amoeba can split into two new amoebas AmoebaMitosis(amoeba::AmoebaMitosis), diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index 3e2441f6d..d29362274 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -1,5 +1,21 @@ //! An NFT game inspired by cryptokitties. -//! This is a game which allows for kitties to be bred based on a few factors +//! This is a game which allows for kitties to be create,bred and update name of kitty. +//! +//! ## Features +//! +//! - **Create:** Generate a new kitty. +//! To submit a valid transaction for creating a kitty, adhere to the following structure: +//! 1. Input must be empty. +//! 2. Output must contain only the newly created kitty as a child. + +//! - **Update Name:** Modify the name of a kitty. +//! To submit a valid transaction for updating a kitty's name, adhere to the following structure: +//! 1. Input must be the kitty to be updated. +//! 2. Output must contain the kitty with the updated name. +//! +//! **Note:** All other properties such as DNA, parents, free breedings, etc., must remain unaltered in the output. +//! +//! - **Breed:** Breeds a new kitty using mom and dad based on below factors //! 1.) Mom and Tired have to be in a state where they are ready to breed //! 2.) Each Mom and Dad have some DNA and the child will have unique DNA combined from the both of them //! Linkable back to the Mom and Dad @@ -25,6 +41,7 @@ use sp_runtime::{ traits::{BlakeTwo256, Hash as HashT}, transaction_validity::TransactionPriority, }; +use sp_std::collections::btree_map::BTreeMap; use sp_std::prelude::*; use tuxedo_core::{ dynamic_typing::{DynamicallyTypedData, UtxoData}, @@ -51,9 +68,11 @@ mod tests; TypeInfo, )] pub enum FreeKittyConstraintChecker { - /// A mint transaction that creates kitty without parents. - Mint, - /// A typical Breed transaction where kitties are consumed and new family(Parents(mom,dad) and child) is created. + /// A transaction that creates kitty without parents. + Create, + /// A Transaction that updates kitty Name. + UpdateKittyName, + /// A transaction where kitties are consumed and new family(Parents(mom,dad) and child) is created. Breed, } @@ -193,7 +212,7 @@ impl KittyData { v, ) .into()], - checker: FreeKittyConstraintChecker::Mint.into(), + checker: FreeKittyConstraintChecker::Create.into(), } } } @@ -268,12 +287,22 @@ pub enum ConstraintCheckerError { TooManyBreedingsForKitty, /// Not enough free breedings available for these parents. NotEnoughFreeBreedings, - /// Incorrect number of outputs when it comes to Minting. - IncorrectNumberOfKittiesForMintOperation, - /// The transaction attempts to mint no Kitty. - MintingNothing, + /// The transaction attempts to create no Kitty. + CreatingNothing, /// Inputs(Parents) not required for mint. - MintingWithInputs, + CreatingWithInputs, + /// No input for kitty Update. + InvalidNumberOfInputOutput, + /// Updating nothing + OutputUtxoMissingError, + /// Name is not updated + KittyNameUnAltered, + /// Kitty FreeBreeding cannot be updated. + FreeBreedingCannotBeUpdated, + /// Kitty NumOfBreeding cannot be updated. + NumOfBreedingCannotBeUpdated, + /// Gender cannot be updated + KittyGenderCannotBeUpdated, } pub trait Breed { @@ -513,10 +542,7 @@ impl TryFrom<&DynamicallyTypedData> for KittyData { impl SimpleConstraintChecker for FreeKittyConstraintChecker { type Error = ConstraintCheckerError; - /// Checks: - /// - `input_data` is of length 2 - /// - `output_data` is of length 3 - /// + fn check( &self, input_data: &[DynamicallyTypedData], @@ -524,17 +550,19 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { output_data: &[DynamicallyTypedData], ) -> Result { match &self { - Self::Mint => { + Self::Create => { // Make sure there are no inputs being consumed ensure!( input_data.is_empty(), - ConstraintCheckerError::MintingWithInputs + ConstraintCheckerError::CreatingWithInputs ); + // Make sure there is at least one output being minted ensure!( !output_data.is_empty(), - ConstraintCheckerError::MintingNothing + ConstraintCheckerError::CreatingNothing ); + // Make sure the outputs are the right type for utxo in output_data { let _utxo_kitty = utxo @@ -544,18 +572,85 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { Ok(0) } Self::Breed => { + // Check that we are consuming at least one input ensure!(input_data.len() == 2, Self::Error::TwoParentsDoNotExist); + let mom = KittyData::try_from(&input_data[0])?; let dad = KittyData::try_from(&input_data[1])?; KittyHelpers::can_breed(&mom, &dad)?; - // Output must be Mom, Dad, Child ensure!(output_data.len() == 3, Self::Error::NotEnoughFamilyMembers); - KittyHelpers::check_new_family(&mom, &dad, output_data)?; - Ok(0) } + Self::UpdateKittyName => { + can_kitty_name_be_updated(input_data, output_data)?; + Ok(0) + } + } + } +} + +/// Checks: +/// - Input and output is of kittyType +/// - Only name is updated and ther basicproperties are not updated. +/// +pub fn can_kitty_name_be_updated( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + ensure!( + input_data.len() == output_data.len() && !input_data.is_empty(), + { ConstraintCheckerError::InvalidNumberOfInputOutput } + ); + + let mut map: BTreeMap = BTreeMap::new(); + + for utxo in input_data { + let utxo_kitty = utxo + .extract::() + .map_err(|_| ConstraintCheckerError::BadlyTyped)?; + map.insert(utxo_kitty.clone().dna, utxo_kitty); + } + + for utxo in output_data { + let utxo_output_kitty = utxo + .extract::() + .map_err(|_| ConstraintCheckerError::BadlyTyped)?; + + if let Some(input_kitty) = map.remove(&utxo_output_kitty.dna) { + // Element found, access the value + check_kitty_name_update(&input_kitty, &utxo_output_kitty)?; + } else { + return Err(ConstraintCheckerError::OutputUtxoMissingError); } } + return Ok(0); +} + +/// Checks: +/// - Private function used by can_kitty_name_be_updated. +/// - Only name is updated and ther basicproperties are not updated. +/// +fn check_kitty_name_update( + original_kitty: &KittyData, + updated_kitty: &KittyData, +) -> Result { + ensure!( + original_kitty != updated_kitty, + ConstraintCheckerError::KittyNameUnAltered + ); + ensure!( + original_kitty.free_breedings == updated_kitty.free_breedings, + ConstraintCheckerError::FreeBreedingCannotBeUpdated + ); + ensure!( + original_kitty.num_breedings == updated_kitty.num_breedings, + ConstraintCheckerError::NumOfBreedingCannotBeUpdated + ); + ensure!( + original_kitty.parent == updated_kitty.parent, + ConstraintCheckerError::KittyGenderCannotBeUpdated + ); + return Ok(0); } diff --git a/wardrobe/kitties/src/tests.rs b/wardrobe/kitties/src/tests.rs index 76d4e22c8..f881f3aae 100644 --- a/wardrobe/kitties/src/tests.rs +++ b/wardrobe/kitties/src/tests.rs @@ -51,43 +51,44 @@ impl KittyData { Box::new(vec![new_mom, new_dad, child]) } } + #[test] -fn mint_happy_path_works() { +fn create_happy_path_works() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::Mint, + &FreeKittyConstraintChecker::Create, &[], - &[], // no peeks + &[], &[KittyData::default().into(), KittyData::default_dad().into()], ); assert!(result.is_ok()); } #[test] -fn mint_with_input_fails() { +fn create_with_input_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::Mint, - &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &FreeKittyConstraintChecker::Create, + &[KittyData::default().into()], + &[], &[], ); - assert_eq!(result, Err(ConstraintCheckerError::MintingWithInputs)); + assert_eq!(result, Err(ConstraintCheckerError::CreatingWithInputs)); } #[test] -fn mint_without_output_fails() { +fn create_without_output_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::Mint, + &FreeKittyConstraintChecker::Create, &[], - &[], // no peeks + &[], &[], ); - assert_eq!(result, Err(ConstraintCheckerError::MintingNothing)); + assert_eq!(result, Err(ConstraintCheckerError::CreatingNothing)); } #[test] -fn mint_with_wrong_output_type_fails() { +fn create_with_wrong_output_type_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::Mint, + &FreeKittyConstraintChecker::Create, &[], - &[], // no peeks + &[], &[ Bogus.into(), KittyData::default().into(), @@ -103,7 +104,7 @@ fn breed_happy_path_works() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -118,7 +119,7 @@ fn breed_wrong_input_type_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[Bogus.into(), Bogus.into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::BadlyTyped)); @@ -129,7 +130,7 @@ fn breed_wrong_output_type_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[Bogus.into(), Bogus.into(), Bogus.into()], ); assert_eq!(result, Err(ConstraintCheckerError::BadlyTyped)); @@ -140,7 +141,7 @@ fn inputs_dont_contain_two_parents_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::TwoParentsDoNotExist)); @@ -151,7 +152,7 @@ fn outputs_dont_contain_all_family_members_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFamilyMembers)); @@ -165,7 +166,7 @@ fn breed_two_dads_fails() { KittyData::default_dad().into(), KittyData::default_dad().into(), ], - &[], // no peeks + &[], &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::TwoDadsNotValid)); @@ -176,7 +177,7 @@ fn breed_two_moms_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default().into()], - &[], // no peeks + &[], &[KittyData::default().into()], ); assert_eq!(result, Err(ConstraintCheckerError::TwoMomsNotValid)); @@ -187,7 +188,7 @@ fn first_input_not_mom_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default_dad().into(), KittyData::default().into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::TwoDadsNotValid)) @@ -198,7 +199,7 @@ fn first_output_not_mom_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ KittyData::default_dad().into(), KittyData::default().into(), @@ -216,7 +217,7 @@ fn breed_mom_when_she_gave_birth_recently_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[new_momma.into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::MomNotReadyYet)); @@ -230,7 +231,7 @@ fn breed_dad_when_he_is_tired_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), tired_dadda.into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::DadTooTired)); @@ -244,7 +245,7 @@ fn check_mom_breedings_overflow_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[test_mom.into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[], ); assert_eq!( @@ -261,7 +262,7 @@ fn check_dad_breedings_overflow_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), test_dad.into()], - &[], // no peeks + &[], &[], ); assert_eq!( @@ -278,7 +279,7 @@ fn check_mom_free_breedings_zero_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[test_mom.into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFreeBreedings)); @@ -292,7 +293,7 @@ fn check_dad_free_breedings_zero_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), test_dad.into()], - &[], // no peeks + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::NotEnoughFreeBreedings)); @@ -307,7 +308,7 @@ fn check_new_mom_free_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_mom.into(), new_family[1].clone().into(), @@ -329,7 +330,7 @@ fn check_new_dad_free_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_dad.into(), @@ -351,7 +352,7 @@ fn check_new_mom_num_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_mom.into(), new_family[1].clone().into(), @@ -373,7 +374,7 @@ fn check_new_dad_num_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_dad.into(), @@ -395,7 +396,7 @@ fn check_new_mom_dna_doesnt_match_old_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_mom.into(), new_family[1].clone().into(), @@ -417,7 +418,7 @@ fn check_new_dad_dna_doesnt_match_old_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_dad.into(), @@ -439,7 +440,7 @@ fn check_child_dna_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -458,7 +459,7 @@ fn check_child_dad_parent_tired_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -480,7 +481,7 @@ fn check_child_mom_parent_recently_gave_birth_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -502,7 +503,7 @@ fn check_child_free_breedings_incorrect_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -524,7 +525,7 @@ fn check_child_num_breedings_non_zero_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Breed, &[KittyData::default().into(), KittyData::default_dad().into()], - &[], // no peeks + &[], &[ new_family[0].clone().into(), new_family[1].clone().into(), @@ -536,3 +537,175 @@ fn check_child_num_breedings_non_zero_fails() { Err(ConstraintCheckerError::NewChildHasNonZeroBreedings) ); } + +#[test] +fn update_name_happy_path_works() { + let input = KittyData::default_dad(); + let mut output = KittyData::default_dad(); + output.name = *b"kty1"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[input.into()], + &[], + &[output.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn update_name_happy_path_with_multiple_input_sworks() { + let input1 = KittyData::default_dad(); + let mut input2 = KittyData::default(); + input2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialaroci")); + let mut output1 = input1.clone(); + let mut output2 = input2.clone(); + + output1.name = *b"kty1"; + output2.name = *b"kty2"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[input1.into(), input2.into()], + &[], + &[output1.into(), output2.into()], + ); + assert!(result.is_ok()); +} + +#[test] +fn update_name_inputs_and_outputs_number_mismatch_fails() { + let input1 = KittyData::default_dad(); + let input2 = KittyData::default_dad(); + let mut output1 = input1.clone(); + let mut output2 = input2.clone(); + + output1.name = *b"kty1"; + output2.name = *b"kty2"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[input1.into(), input2.into()], + &[], + &[output1.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::InvalidNumberOfInputOutput) + ); +} +#[test] +fn update_name_no_inputs_fails() { + let output = KittyData::default_dad(); + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::InvalidNumberOfInputOutput) + ); +} + +#[test] +fn update_name_no_output_fails() { + let input = KittyData::default_dad(); + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[input.into()], + &[], + &[], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::InvalidNumberOfInputOutput) + ); +} +#[test] +fn update_name_dna_update_fails() { + let input = KittyData::default_dad(); + let mut output = input.clone(); + output.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + output.name = *b"kty1"; + + let input1 = KittyData::default_dad(); + let mut output1 = input1.clone(); + output1.name = *b"kty2"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[input1.into(), input.into()], + &[], + &[output1.into(), output.into()], + ); + assert_eq!(result, Err(ConstraintCheckerError::OutputUtxoMissingError)); +} + +#[test] +fn update_name_name_unupdated_path_fails() { + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[KittyData::default_dad().into()], + &[], + &[KittyData::default_dad().into()], + ); + assert_eq!(result, Err(ConstraintCheckerError::KittyNameUnAltered)); +} + +#[test] +fn update_name_free_breeding_updated_path_fails() { + let mut output = KittyData::default_dad(); + output.name = *b"kty1"; + output.free_breedings += 1; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[KittyData::default().into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::FreeBreedingCannotBeUpdated) + ); +} + +#[test] +fn update_name_num_of_breeding_updated_path_fails() { + let mut output = KittyData::default_dad(); + output.name = *b"kty1"; + output.num_breedings += 1; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[KittyData::default().into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::NumOfBreedingCannotBeUpdated) + ); +} + +#[test] +fn update_name_gender_updated_path_fails() { + let input = KittyData::default(); + let mut output = KittyData::default_dad(); + output.name = *b"kty1"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::KittyGenderCannotBeUpdated) + ); +} diff --git a/wardrobe/tradable_kitties/Cargo.toml b/wardrobe/tradable_kitties/Cargo.toml index 41bfbe6a6..fe978f820 100644 --- a/wardrobe/tradable_kitties/Cargo.toml +++ b/wardrobe/tradable_kitties/Cargo.toml @@ -26,4 +26,6 @@ std = [ "sp-std/std", "sp-core/std", "serde/std", + "money/std", + "kitties/std", ] diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs index 40360b925..160bcb9f3 100644 --- a/wardrobe/tradable_kitties/src/lib.rs +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -1,41 +1,38 @@ -//! This module defines TradableKitty, a specialized type of kitty with additional features. +//! This module defines `TradableKitty`, a specialized type of kitty designed for trading with unique features. //! -//! TradableKitties are designed for trading, and they extend the functionality of the basic kitty. -//! Key features of TradableKitties include: -//! The TradableKitty module extends the basic functionality of kitties with additional features such as buying, updating properties, minting, and breeding. The provided validation functionality includes: +//! ## Features //! -//! - `Mint`: Create new TradableKitties, supporting the generation of kitties without parents. -//! - `Breed`: Consume kitties and create a new family, including parents (mom and dad) and a child. -//! - `UpdateProperties`: Update properties of a TradableKitty, including `is_available_for_sale`, `price`, and `name`. -//! A single API, `updateKittyProperties()`, is provided for updating these properties for below reasons: -//! 1. Updating atomically in a single transaction, ensuring consistency. -//! 2. Benfit of less number of transaction is reduced weight or gas fees. -//! - `Buy`: Enable users to purchase TradableKitties from others, facilitating secure and fair exchanges. +//! - **ListKittyForSale:** Convert basic kitties into tradable kitties, adding a `Price` field. +//! - **DelistKittyFromSale:** Transform tradable kitties back into regular kitties when owners decide not to sell. +//! - **UpdateKittyPrice:** Allow owners to modify the price of TradableKitties. +//! - **UpdateKittyName:** Permit owners to update the name of TradableKitties. +//! - **Buy:** Enable users to securely purchase TradableKitties from others, ensuring fair exchanges. //! //! -//! TradableKitties provide an enhanced user experience by introducing trading capabilities -//! and additional customization for kitty properties. +//! TradableKitties enrich the user experience by introducing advanced trading capabilities. #![cfg_attr(not(feature = "std"), no_std)] -use kitties::Breed as BasicKittyBreed; -use kitties::ConstraintCheckerError; -use kitties::KittyData; -use kitties::KittyHelpers; -use money::{Coin, MoneyConstraintChecker}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::transaction_validity::TransactionPriority; +use sp_std::collections::btree_map::BTreeMap; use sp_std::prelude::*; use tuxedo_core::{ dynamic_typing::{DynamicallyTypedData, UtxoData}, ensure, SimpleConstraintChecker, }; +use kitties::{KittyDNA, KittyData}; +use money::ConstraintCheckerError as MoneyError; +use money::{Coin, MoneyConstraintChecker}; + #[cfg(test)] mod tests; +/// A tradableKittyData, required for trading the kitty.. +/// It contains optional price of tradable-kitty in addition to the basic kitty data. #[derive( Serialize, Deserialize, @@ -51,9 +48,10 @@ mod tests; TypeInfo, )] pub struct TradableKittyData { + /// Basic kitty data composed from kitties piece pub kitty_basic_data: KittyData, + /// Price of the tradable kitty pub price: Option, - pub is_available_for_sale: bool, } impl Default for TradableKittyData { @@ -61,16 +59,15 @@ impl Default for TradableKittyData { Self { kitty_basic_data: KittyData::default(), price: None, - is_available_for_sale: false, } } } impl TryFrom<&DynamicallyTypedData> for TradableKittyData { - type Error = TradableKittyConstraintCheckerError; + type Error = TradeableKittyError; fn try_from(a: &DynamicallyTypedData) -> Result { a.extract::() - .map_err(|_| TradableKittyConstraintCheckerError::BadlyTyped) + .map_err(|_| TradeableKittyError::BadlyTyped) } } @@ -78,6 +75,7 @@ impl UtxoData for TradableKittyData { const TYPE_ID: [u8; 4] = *b"tdkt"; } +/// Reasons that tradable kitty opertaion may go wrong. #[derive( Serialize, Deserialize, @@ -92,202 +90,50 @@ impl UtxoData for TradableKittyData { Debug, TypeInfo, )] -pub enum TradableKittyConstraintCheckerError { +pub enum TradeableKittyError { + /// Error in the underlying money piece. + MoneyError(money::ConstraintCheckerError), + /// Error in the underlying kitty piece. + KittyError(kitties::ConstraintCheckerError), /// Dynamic typing issue. /// This error doesn't discriminate between badly typed inputs and outputs. BadlyTyped, - /// Needed when spending for breeding. - MinimumSpendAndBreedNotMet, - /// Need two parents to breed. - TwoParentsDoNotExist, - /// Incorrect number of outputs when it comes to breeding. - NotEnoughFamilyMembers, - /// Incorrect number of outputs when it comes to Minting. - IncorrectNumberOfKittiesForMintOperation, - /// Mom has recently given birth and isnt ready to breed. - MomNotReadyYet, - /// Dad cannot breed because he is still too tired. - DadTooTired, - /// Cannot have two moms when breeding. - TwoMomsNotValid, - /// Cannot have two dads when breeding. - TwoDadsNotValid, - /// New Mom after breeding should be in HadBirthRecently state. - NewMomIsStillRearinToGo, - /// New Dad after breeding should be in Tired state. - NewDadIsStillRearinToGo, - /// Number of free breedings of new parent is not correct. - NewParentFreeBreedingsIncorrect, - /// New parents DNA does not match the old one parent has to still be the same kitty. - NewParentDnaDoesntMatchOld, - /// New parent Breedings has not incremented or is incorrect. - NewParentNumberBreedingsIncorrect, - /// New child DNA is not correct given the protocol. - NewChildDnaIncorrect, - /// New child doesnt have the correct number of free breedings. - NewChildFreeBreedingsIncorrect, - /// New child has non zero breedings which is impossible because it was just born. - NewChildHasNonZeroBreedings, - /// New child parent info is either in Tired state or HadBirthRecently state which is not possible. - NewChildIncorrectParentInfo, - /// Too many breedings for this kitty can no longer breed. - TooManyBreedingsForKitty, - /// Not enough free breedings available for these parents. - NotEnoughFreeBreedings, - /// The transaction attempts to mint no Kitty. - MintingNothing, - /// Inputs(Parents) not required for mint. - MintingWithInputs, - /// No input for kitty Update. - InputMissingUpdatingNothing, - /// Mismatch innumberof inputs and number of outputs . - MismatchBetweenNumberOfInputAndUpdateUpadtingNothing, - /// TradableKitty Update can't have more than one outputs. - MultipleOutputsForKittyUpdateError, - /// Kitty Update has more than one inputs. - InValidNumberOfInputsForKittyUpdate, - /// Basic kitty properties cannot be updated. - KittyGenderCannotBeUpdated, - /// Basic kitty properties cannot be updated. - KittyDnaCannotBeUpdated, - /// Kitty FreeBreeding cannot be updated. - FreeBreedingCannotBeUpdated, - /// Kitty NumOfBreeding cannot be updated. - NumOfBreedingCannotBeUpdated, - /// Updated price of is incorrect. - UpdatedKittyIncorrectPrice, - - /// No input for kitty buy operation. - InputMissingBuyingNothing, - /// No output for kitty buy operation. - OutputMissingBuyingNothing, - /// Incorrect number of outputs buy operation. - IncorrectNumberOfInputKittiesForBuyOperation, - /// Incorrect number of outputs for buy operation. - IncorrectNumberOfOutputKittiesForBuyOperation, - /// Kitty not avilable for sale + /// output missing updating nothing. + OutputUtxoMissingError, + /// Input missing for the transaction. + InputMissingError, + /// Not enough amount to buy a kitty. + InsufficientCollateralToBuyKitty, + /// The number of input vs number of output doesn't match for a transaction. + NumberOfInputOutputMismatch, + /// Kitty basic properties such as DNA, free breeding, and a number of breedings, are altered error. + KittyBasicPropertiesAltered, + /// Kitty not available for sale.Occur when price is None. KittyNotForSale, - /// Kitty price cant be none when it is avilable for sale + /// Kitty price cant be none when it is available for sale. KittyPriceCantBeNone, - /// Kitty price cant be zero when it is avilable for sale - KittyPriceCantBeZero, - /// Not enough amount to buy kitty - InsufficientCollateralToBuyKitty, - - // From below money constraintchecker errors are added - /// The transaction attempts to spend without consuming any inputs. - /// Either the output value will exceed the input value, or if there are no outputs, - /// it is a waste of processing power, so it is not allowed. - SpendingNothing, - /// The value of the spent input coins is less than the value of the newly created - /// output coins. This would lead to money creation and is not allowed. - OutputsExceedInputs, - /// The value consumed or created by this transaction overflows the value type. - /// This could lead to problems like https://bitcointalk.org/index.php?topic=823.0 - ValueOverflow, - /// The transaction attempted to create a coin with zero value. This is not allowed - /// because it wastes state space. - ZeroValueCoin, + /// Kitty price is unaltered for kitty price update transactions. + KittyPriceUnaltered, } -impl From for TradableKittyConstraintCheckerError { +impl From for TradeableKittyError { fn from(error: money::ConstraintCheckerError) -> Self { - match error { - money::ConstraintCheckerError::BadlyTyped => { - TradableKittyConstraintCheckerError::BadlyTyped - } - money::ConstraintCheckerError::MintingWithInputs => { - TradableKittyConstraintCheckerError::MintingWithInputs - } - money::ConstraintCheckerError::MintingNothing => { - TradableKittyConstraintCheckerError::MintingNothing - } - money::ConstraintCheckerError::SpendingNothing => { - TradableKittyConstraintCheckerError::SpendingNothing - } - money::ConstraintCheckerError::OutputsExceedInputs => { - TradableKittyConstraintCheckerError::OutputsExceedInputs - } - money::ConstraintCheckerError::ValueOverflow => { - TradableKittyConstraintCheckerError::ValueOverflow - } - money::ConstraintCheckerError::ZeroValueCoin => { - TradableKittyConstraintCheckerError::ZeroValueCoin - } - } + TradeableKittyError::MoneyError(error) } } -// Implement From trait for mapping ConstraintCheckerError to TradableKittyConstraintCheckerError -impl From for TradableKittyConstraintCheckerError { - fn from(error: ConstraintCheckerError) -> Self { - match error { - ConstraintCheckerError::BadlyTyped => TradableKittyConstraintCheckerError::BadlyTyped, - ConstraintCheckerError::MinimumSpendAndBreedNotMet => { - TradableKittyConstraintCheckerError::MinimumSpendAndBreedNotMet - } - ConstraintCheckerError::TwoParentsDoNotExist => { - TradableKittyConstraintCheckerError::TwoParentsDoNotExist - } - ConstraintCheckerError::NotEnoughFamilyMembers => { - TradableKittyConstraintCheckerError::NotEnoughFamilyMembers - } - ConstraintCheckerError::IncorrectNumberOfKittiesForMintOperation => { - TradableKittyConstraintCheckerError::IncorrectNumberOfKittiesForMintOperation - } - ConstraintCheckerError::MomNotReadyYet => { - TradableKittyConstraintCheckerError::MomNotReadyYet - } - ConstraintCheckerError::DadTooTired => TradableKittyConstraintCheckerError::DadTooTired, - ConstraintCheckerError::TwoMomsNotValid => { - TradableKittyConstraintCheckerError::TwoMomsNotValid - } - ConstraintCheckerError::TwoDadsNotValid => { - TradableKittyConstraintCheckerError::TwoDadsNotValid - } - ConstraintCheckerError::NewMomIsStillRearinToGo => { - TradableKittyConstraintCheckerError::NewMomIsStillRearinToGo - } - ConstraintCheckerError::NewDadIsStillRearinToGo => { - TradableKittyConstraintCheckerError::NewDadIsStillRearinToGo - } - ConstraintCheckerError::NewParentFreeBreedingsIncorrect => { - TradableKittyConstraintCheckerError::NewParentFreeBreedingsIncorrect - } - ConstraintCheckerError::NewParentDnaDoesntMatchOld => { - TradableKittyConstraintCheckerError::NewParentDnaDoesntMatchOld - } - ConstraintCheckerError::NewParentNumberBreedingsIncorrect => { - TradableKittyConstraintCheckerError::NewParentNumberBreedingsIncorrect - } - ConstraintCheckerError::NewChildDnaIncorrect => { - TradableKittyConstraintCheckerError::NewChildDnaIncorrect - } - ConstraintCheckerError::NewChildFreeBreedingsIncorrect => { - TradableKittyConstraintCheckerError::NewChildFreeBreedingsIncorrect - } - ConstraintCheckerError::NewChildHasNonZeroBreedings => { - TradableKittyConstraintCheckerError::NewChildHasNonZeroBreedings - } - ConstraintCheckerError::NewChildIncorrectParentInfo => { - TradableKittyConstraintCheckerError::NewChildIncorrectParentInfo - } - ConstraintCheckerError::TooManyBreedingsForKitty => { - TradableKittyConstraintCheckerError::TooManyBreedingsForKitty - } - ConstraintCheckerError::NotEnoughFreeBreedings => { - TradableKittyConstraintCheckerError::NotEnoughFreeBreedings - } - ConstraintCheckerError::MintingNothing => { - TradableKittyConstraintCheckerError::MintingNothing - } - ConstraintCheckerError::MintingWithInputs => { - TradableKittyConstraintCheckerError::MintingWithInputs - } - } +impl From for TradeableKittyError { + fn from(error: kitties::ConstraintCheckerError) -> Self { + TradeableKittyError::KittyError(error) } } +/// The main constraint checker for the trdable kitty piece. Allows below : +/// Listing kitty for sale +/// Delisting kitty from sale +/// Update kitty price +/// Update kitty name +/// Buy tradable kitty #[derive( Serialize, Deserialize, @@ -303,213 +149,240 @@ impl From for TradableKittyConstraintCheckerError { TypeInfo, )] pub enum TradableKittyConstraintChecker { - /// A mint transaction that creates Tradable Kitties - Mint, - /// A typical Breed transaction where kitties are consumed and new family(Parents(mom,dad) and child) is created. - Breed, - ///Update various properties of kitty. - UpdateProperties, - ///Can buy a new kitty from others + /// List kitty for sale, means kitty will converted to tradable kitty once transaction is executed + ListKittyForSale, + /// Delist kitty from sale, means tradable kitty will converted back to kitty + DelistKittyFromSale, + /// Update price of tradable kitty. + UpdateKittyPrice, + // Update name of kitty + UpdateKittyName, + /// For buying a new kitty from others Buy, } -pub trait Breed { - /// The Cost to breed a kitty. - const COST: u128; - type Error: Into; - fn can_breed(mom: &TradableKittyData, dad: &TradableKittyData) -> Result<(), Self::Error>; - fn check_new_family( - mom: &TradableKittyData, - dad: &TradableKittyData, - new_family: &[DynamicallyTypedData], - ) -> Result<(), Self::Error>; +/// Extracts basic kitty data from a list of dynamically typed TradableKitty data, populating basic kitty data list. +fn extract_basic_kitty_list( + tradable_kitty_data: &[DynamicallyTypedData], + kitty_data_list: &mut Vec, +) -> Result<(), TradeableKittyError> { + for utxo in tradable_kitty_data { + if let Ok(tradable_kitty) = utxo.extract::() { + kitty_data_list.push(tradable_kitty.kitty_basic_data.clone().into()); + } else { + return Err(TradeableKittyError::BadlyTyped); + } + } + Ok(()) } -trait UpdateKittyProperty { - /// The Cost to update a kitty property if it is not free. - const COST: u128; - /// Error type for all Kitty errors. - type Error: Into; +/// checks if buying the kitty is possible of not. It depends on Money piece to validat spending of coins. +fn check_can_buy( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result<(), TradeableKittyError> { + let mut input_coin_data: Vec = Vec::new(); + let mut output_coin_data: Vec = Vec::new(); + let mut input_kitty_data: Vec = Vec::new(); + let mut output_kitty_data: Vec = Vec::new(); + + let mut total_input_amount: u128 = 0; + let mut total_price_of_kitty: u128 = 0; + + // Map to verify that output_kitty is same as input_kitty based on the dna after buy operation + let mut dna_to_tdkitty_map: BTreeMap = BTreeMap::new(); + + // Seperate the coin and tdkitty in to seperate vecs from the input_data . + for utxo in input_data { + if let Ok(coin) = utxo.extract::>() { + let utxo_value = coin.0; + + ensure!( + utxo_value > 0, + TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) + ); + input_coin_data.push(utxo.clone()); + total_input_amount = total_input_amount + .checked_add(utxo_value) + .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; + + // Process Kitty + } else if let Ok(td_input_kitty) = utxo.extract::() { + // Trying to buy kitty which is not listed for sale. + let price = match td_input_kitty.price { + None => return Err(TradeableKittyError::KittyNotForSale), + Some(p) => p, + }; + + input_kitty_data.push(utxo.clone()); + dna_to_tdkitty_map.insert(td_input_kitty.clone().kitty_basic_data.dna, td_input_kitty); + total_price_of_kitty = total_price_of_kitty + .checked_add(price) + .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; + } else { + return Err(TradeableKittyError::BadlyTyped); + } + } - fn check_updated_kitty( - original_kitty: &TradableKittyData, - updated_kitty: &TradableKittyData, - ) -> Result<(), Self::Error>; -} + // Seperate the coin and tdkitty in to seperate vecs from the output_data . + for utxo in output_data { + if let Ok(coin) = utxo.extract::>() { + let utxo_value = coin.0; + ensure!( + utxo_value > 0, + TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) + ); + output_coin_data.push(utxo.clone()); + // Process Coin + } else if let Ok(td_output_kitty) = utxo.extract::() { + match dna_to_tdkitty_map.remove(&td_output_kitty.kitty_basic_data.dna) { + Some(found_kitty) => { + // During buy opertaion, basic kitty properties cant be updated in the same transaction. + ensure!( + found_kitty.kitty_basic_data == td_output_kitty.kitty_basic_data, // basic kitty data is unaltered + TradeableKittyError::KittyBasicPropertiesAltered // this need to be chan + ); + } + None => { + return Err(TradeableKittyError::OutputUtxoMissingError); + } + }; + output_kitty_data.push(utxo.clone()); + } else { + return Err(TradeableKittyError::BadlyTyped); + } + } -trait Buy { - /// Error type for all Kitty errors. - type Error: Into; + ensure!( + input_kitty_data.len() == output_kitty_data.len() && !input_kitty_data.is_empty(), + { TradeableKittyError::NumberOfInputOutputMismatch } + ); - fn can_buy( - input_data: &[DynamicallyTypedData], - output_data: &[DynamicallyTypedData], - ) -> Result<(), Self::Error>; + ensure!( + total_price_of_kitty <= total_input_amount, + TradeableKittyError::InsufficientCollateralToBuyKitty + ); + + // Filterd coins sent to MoneyConstraintChecker for money validation. + MoneyConstraintChecker::<0>::Spend.check(&input_coin_data, &[], &output_coin_data)?; + Ok(()) } -pub struct TradableKittyHelpers; - -impl UpdateKittyProperty for TradableKittyHelpers { - const COST: u128 = 5u128; - type Error = TradableKittyConstraintCheckerError; - - fn check_updated_kitty( - original_kitty: &TradableKittyData, - updated_kitty: &TradableKittyData, - ) -> Result<(), Self::Error> { - ensure!( - original_kitty.kitty_basic_data.parent == updated_kitty.kitty_basic_data.parent, - Self::Error::KittyGenderCannotBeUpdated, - ); - ensure!( - original_kitty.kitty_basic_data.free_breedings - == updated_kitty.kitty_basic_data.free_breedings, - Self::Error::FreeBreedingCannotBeUpdated, - ); - ensure!( - original_kitty.kitty_basic_data.dna == updated_kitty.kitty_basic_data.dna, - Self::Error::KittyDnaCannotBeUpdated, - ); - ensure!( - original_kitty.kitty_basic_data.num_breedings - == updated_kitty.kitty_basic_data.num_breedings, - Self::Error::NumOfBreedingCannotBeUpdated, - ); - - if !updated_kitty.is_available_for_sale && updated_kitty.price != None { - return Err(Self::Error::UpdatedKittyIncorrectPrice); - } +/// checks if kitty price updates is possible of not. +/// Price of multiple kitties can be updated in the same txn. +fn check_kitty_price_update( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + ensure!( + input_data.len() == output_data.len() && !input_data.is_empty(), + { TradeableKittyError::NumberOfInputOutputMismatch } + ); + + let mut dna_to_tdkitty_map: BTreeMap = BTreeMap::new(); + + for utxo in input_data { + let td_input_kitty = utxo + .extract::() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + dna_to_tdkitty_map.insert(td_input_kitty.clone().kitty_basic_data.dna, td_input_kitty); + } + + for utxo in output_data { + let td_output_kitty = utxo + .extract::() + .map_err(|_| TradeableKittyError::BadlyTyped)?; - if updated_kitty.is_available_for_sale - && (updated_kitty.price == None || updated_kitty.price.unwrap() == 0) + if let Some(found_kitty) = dna_to_tdkitty_map.remove(&td_output_kitty.kitty_basic_data.dna) { - return Err(Self::Error::UpdatedKittyIncorrectPrice); + // Element found, access the value + ensure!( + found_kitty.kitty_basic_data == td_output_kitty.kitty_basic_data, // basic kitty data is unaltered + TradeableKittyError::KittyBasicPropertiesAltered // this need to be chan + ); + match td_output_kitty.price { + Some(_) => { + ensure!( + found_kitty.price != td_output_kitty.price, // kitty ptice is unaltered + TradeableKittyError::KittyPriceUnaltered // this need to be chan + ); + } + None => return Err(TradeableKittyError::KittyPriceCantBeNone), + }; + } else { + return Err(TradeableKittyError::OutputUtxoMissingError); } - Ok(()) } + return Ok(0); } -impl Breed for TradableKittyHelpers { - const COST: u128 = 5u128; - type Error = TradableKittyConstraintCheckerError; - fn can_breed(mom: &TradableKittyData, dad: &TradableKittyData) -> Result<(), Self::Error> { - KittyHelpers::can_breed(&mom.kitty_basic_data, &dad.kitty_basic_data)?; - Ok(()) - } +/// Wrapper function for checking conversion from basic kitty to tradable kitty. +/// Multiple kitties can be converted in the same txn. +fn check_can_list_kitty_for_sale( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + check_kitty_tdkitty_interconversion(&input_data, &output_data)?; + return Ok(0); +} - fn check_new_family( - mom: &TradableKittyData, - dad: &TradableKittyData, - new_tradable_kitty_family: &[DynamicallyTypedData], - ) -> Result<(), Self::Error> { - let new_tradable_kitty_mom = TradableKittyData::try_from(&new_tradable_kitty_family[0])?; - let new_tradable_kitty_dad = TradableKittyData::try_from(&new_tradable_kitty_family[1])?; - let new_tradable_kitty_child = TradableKittyData::try_from(&new_tradable_kitty_family[2])?; - - let new_basic_kitty_mom: DynamicallyTypedData = - new_tradable_kitty_mom.kitty_basic_data.into(); - let new_basic_kitty_dad: DynamicallyTypedData = - new_tradable_kitty_dad.kitty_basic_data.into(); - let new_basic_kitty_child: DynamicallyTypedData = - new_tradable_kitty_child.kitty_basic_data.into(); - - let mut new_family: Vec = Vec::new(); - new_family.push(new_basic_kitty_mom); - new_family.push(new_basic_kitty_dad); - new_family.push(new_basic_kitty_child); - - KittyHelpers::check_new_family(&mom.kitty_basic_data, &dad.kitty_basic_data, &new_family)?; - Ok(()) - } +/// Wrapper function for checking conversion from tradable kitty to basic kitty. +/// Multiple kitties can be converted from tradable to non-tradable in the same txn. +fn check_can_delist_kitty_from_sale( + input_data: &[DynamicallyTypedData], + output_data: &[DynamicallyTypedData], +) -> Result { + // Below is conversion from tradable kitty to kitty, reverse of the ListKittyForSale, + // hence input params are rebversed + check_kitty_tdkitty_interconversion(&output_data, &input_data)?; + return Ok(0); } -impl Buy for TradableKittyHelpers { - type Error = TradableKittyConstraintCheckerError; - fn can_buy( - input_data: &[DynamicallyTypedData], - output_data: &[DynamicallyTypedData], - ) -> Result<(), Self::Error> { - let mut input_coin_data: Vec = Vec::new(); - let mut output_coin_data: Vec = Vec::new(); - let mut input_kitty_data: Vec = Vec::new(); - let mut output_kitty_data: Vec = Vec::new(); - - let mut total_input_amount: u128 = 0; - let mut total_price_of_kitty: u128 = 0; - - for utxo in input_data { - if let Ok(coin) = utxo.extract::>() { - let utxo_value = coin.0; - ensure!( - utxo_value > 0, - TradableKittyConstraintCheckerError::ZeroValueCoin - ); - input_coin_data.push(utxo.clone()); - total_input_amount = total_input_amount - .checked_add(utxo_value) - .ok_or(TradableKittyConstraintCheckerError::ValueOverflow)?; - - // Process Kitty - } else if let Ok(tradable_kitty) = utxo.extract::() { - if !tradable_kitty.is_available_for_sale { - return Err(Self::Error::KittyNotForSale); - } - let price = match tradable_kitty.price { - None => return Err(Self::Error::KittyPriceCantBeNone), - Some(p) => p, - }; +/// Validaes inter-conversion b/w both kitty & tradable kitty.Used by listForSale & delistFromSale functions. +fn check_kitty_tdkitty_interconversion( + kitty_data: &[DynamicallyTypedData], + tradable_kitty_data: &[DynamicallyTypedData], +) -> Result { + ensure!( + kitty_data.len() == tradable_kitty_data.len() && !kitty_data.is_empty(), + { TradeableKittyError::NumberOfInputOutputMismatch } + ); + + let mut map: BTreeMap = BTreeMap::new(); + + for utxo in kitty_data { + let utxo_kitty = utxo + .extract::() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + map.insert(utxo_kitty.clone().dna, utxo_kitty); + } - input_kitty_data.push(utxo.clone()); - total_price_of_kitty = total_price_of_kitty - .checked_add(price) - .ok_or(TradableKittyConstraintCheckerError::ValueOverflow)?; - // Process TradableKittyData - // You can also use the `tradable_kitty` variable herex - } else { - return Err(Self::Error::BadlyTyped); - } - } + for utxo in tradable_kitty_data { + let utxo_tradable_kitty = utxo + .extract::() + .map_err(|_| TradeableKittyError::BadlyTyped)?; - // Need to filter only Coins and send to MoneyConstraintChecker - for utxo in output_data { - if let Ok(coin) = utxo.extract::>() { - let utxo_value = coin.0; + match map.remove(&utxo_tradable_kitty.kitty_basic_data.dna) { + Some(kitty) => { ensure!( - utxo_value > 0, - TradableKittyConstraintCheckerError::ZeroValueCoin + kitty == utxo_tradable_kitty.kitty_basic_data, // basic kitty data is unaltered + TradeableKittyError::KittyBasicPropertiesAltered // this need to be chan ); - output_coin_data.push(utxo.clone()); - // Process Coin - } else if let Ok(_tradable_kitty) = utxo.extract::() { - output_kitty_data.push(utxo.clone()); - } else { - return Err(Self::Error::BadlyTyped); + let _ = match utxo_tradable_kitty.price { + Some(_) => {} + None => return Err(TradeableKittyError::KittyPriceCantBeNone), + }; } - } - - ensure!( - !input_kitty_data.is_empty(), - TradableKittyConstraintCheckerError::InputMissingBuyingNothing - ); - - // Make sure there is at least one output being minted - ensure!( - !output_kitty_data.is_empty(), - TradableKittyConstraintCheckerError::OutputMissingBuyingNothing - ); - ensure!( - total_price_of_kitty <= total_input_amount, - TradableKittyConstraintCheckerError::InsufficientCollateralToBuyKitty - ); - - // Need to filter only Coins and send to MoneyConstraintChecker - MoneyConstraintChecker::<0>::Spend.check(&input_coin_data, &[], &output_coin_data)?; - Ok(()) + None => { + return Err(TradeableKittyError::InputMissingError); + } + }; } + return Ok(0); } impl SimpleConstraintChecker for TradableKittyConstraintChecker { - type Error = TradableKittyConstraintCheckerError; + type Error = TradeableKittyError; fn check( &self, @@ -518,53 +391,28 @@ impl SimpleConstraintChecker for TradableKittyConstraintChecker Result { match &self { - Self::Mint => { - // Make sure there are no inputs being consumed - ensure!( - input_data.is_empty(), - TradableKittyConstraintCheckerError::MintingWithInputs - ); - - // Make sure there is at least one output being minted - ensure!( - !output_data.is_empty(), - TradableKittyConstraintCheckerError::MintingNothing - ); - - // Make sure the outputs are the right type - for utxo in output_data { - let _utxo_kitty = utxo - .extract::() - .map_err(|_| TradableKittyConstraintCheckerError::BadlyTyped)?; - } + Self::ListKittyForSale => { + check_can_list_kitty_for_sale(&input_data, &output_data)?; return Ok(0); } - Self::Breed => { - ensure!(input_data.len() == 2, Self::Error::TwoParentsDoNotExist); - let mom = TradableKittyData::try_from(&input_data[0])?; - let dad = TradableKittyData::try_from(&input_data[1])?; - TradableKittyHelpers::::can_breed(&mom, &dad)?; - ensure!(output_data.len() == 3, Self::Error::NotEnoughFamilyMembers); - TradableKittyHelpers::::check_new_family(&mom, &dad, output_data)?; - return Ok(0); + Self::DelistKittyFromSale => { + check_can_delist_kitty_from_sale(&input_data, &output_data)?; } - Self::UpdateProperties => { - ensure!( - !input_data.is_empty(), - TradableKittyConstraintCheckerError::InputMissingUpdatingNothing - ); - - ensure!( - input_data.len() == output_data.len(), - TradableKittyConstraintCheckerError::MismatchBetweenNumberOfInputAndUpdateUpadtingNothing - ); - - let original_kitty = TradableKittyData::try_from(&input_data[0])?; - let updated_kitty = TradableKittyData::try_from(&output_data[0])?; - TradableKittyHelpers::::check_updated_kitty(&original_kitty, &updated_kitty)?; + Self::UpdateKittyPrice => { + check_kitty_price_update(input_data, output_data)?; + } + Self::UpdateKittyName => { + let mut input_basic_kitty_data: Vec = Vec::new(); + let mut output_basic_kitty_data: Vec = Vec::new(); + let _ = extract_basic_kitty_list(&input_data, &mut input_basic_kitty_data)?; + let _ = extract_basic_kitty_list(&output_data, &mut output_basic_kitty_data)?; + kitties::can_kitty_name_be_updated( + &input_basic_kitty_data, + &output_basic_kitty_data, + )?; } Self::Buy => { - TradableKittyHelpers::::can_buy(input_data, output_data)?; + check_can_buy::(input_data, output_data)?; return Ok(0); } } diff --git a/wardrobe/tradable_kitties/src/tests.rs b/wardrobe/tradable_kitties/src/tests.rs index 81dc5aadb..8bba38a6c 100644 --- a/wardrobe/tradable_kitties/src/tests.rs +++ b/wardrobe/tradable_kitties/src/tests.rs @@ -3,11 +3,8 @@ use super::*; use kitties::DadKittyStatus; use kitties::KittyDNA; -use kitties::MomKittyStatus; use kitties::Parent; use sp_runtime::testing::H256; -use sp_runtime::traits::BlakeTwo256; -use sp_runtime::traits::Hash; /// A bogus data type used in tests for type validation #[derive(Encode, Decode)] @@ -18,765 +15,518 @@ impl UtxoData for Bogus { } impl TradableKittyData { - pub fn default_dad() -> Self { - let kitty_basic = KittyData { + pub fn default_kitty() -> KittyData { + KittyData { parent: Parent::Dad(DadKittyStatus::RearinToGo), ..Default::default() - }; - TradableKittyData { - kitty_basic_data: kitty_basic, - ..Default::default() - } - } - - pub fn default_child() -> Self { - let mom = Self::default(); - let dad = Self::default_dad(); - - let kitty_basic = KittyData { - parent: Parent::Mom(MomKittyStatus::RearinToGo), - free_breedings: 2, - name: *b"tkty", - dna: KittyDNA(BlakeTwo256::hash_of(&( - mom.kitty_basic_data.dna, - dad.kitty_basic_data.dna, - mom.kitty_basic_data.num_breedings + 1, - dad.kitty_basic_data.num_breedings + 1, - ))), - num_breedings: 0, - }; - - TradableKittyData { - kitty_basic_data: kitty_basic, - ..Default::default() } } - pub fn default_family() -> Box> { - let mut new_mom: TradableKittyData = TradableKittyData::default(); - new_mom.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); - new_mom.kitty_basic_data.num_breedings += 1; - new_mom.kitty_basic_data.free_breedings -= 1; - - let mut new_dad = TradableKittyData::default_dad(); - new_dad.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::Tired); - new_dad.kitty_basic_data.num_breedings += 1; - new_dad.kitty_basic_data.free_breedings -= 1; - - let child = TradableKittyData::default_child(); - - Box::new(vec![new_mom, new_dad, child]) - } - - pub fn default_updated() -> Self { + pub fn default_tradable_kitty() -> Self { let kitty_basic = KittyData { - name: *b"tomy", + parent: Parent::Dad(DadKittyStatus::RearinToGo), ..Default::default() }; TradableKittyData { kitty_basic_data: kitty_basic, - is_available_for_sale: true, - price: Some(200), + price: Some(100), ..Default::default() } } } -// From below mint tradable kitty test cases start. - #[test] -fn mint_happy_path_works() { - let result = TradableKittyConstraintChecker::<0>::Mint.check( - &[], - &[], // no peeks - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], +fn list_kitty_for_sale_happy_path_works() { + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[TradableKittyData::default_kitty().into()], + &[], + &[TradableKittyData::default_tradable_kitty().into()], ); assert!(result.is_ok()); } #[test] -fn mint_with_input_fails() { - let result = TradableKittyConstraintChecker::<0>::Mint.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::MintingWithInputs) - ); -} -#[test] -fn mint_without_output_fails() { - let result = TradableKittyConstraintChecker::<0>::Mint.check( - &[], - &[], // no peeks - &[], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::MintingNothing) - ); -} -#[test] -fn mint_with_wrong_output_type_fails() { - let result = TradableKittyConstraintChecker::<0>::Mint.check( - &[], - &[], // no peeks - &[ - Bogus.into(), - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - ); - assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); -} - -// From below breed tradable kitty test cases start. +fn list_kitty_for_sale_multiple_input_happy_path_works() { + let input1 = TradableKittyData::default_kitty(); + let mut input2 = TradableKittyData::default_kitty(); + input2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadola")); + let mut output1 = TradableKittyData::default_tradable_kitty(); + let mut output2 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + output2.kitty_basic_data = input2.clone(); -#[test] -fn breed_happy_path_works() { - let new_family = TradableKittyData::default_family(); - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_family[1].clone().into(), - new_family[2].clone().into(), - ], + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into(), output2.into()], ); assert!(result.is_ok()); } #[test] -fn breed_wrong_input_type_fails() { - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[Bogus.into(), Bogus.into()], - &[], // no peeks - &[], - ); - assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); -} +fn list_kitty_for_sale_different_num_of_input_output_path_fails() { + let mut input1 = TradableKittyData::default_kitty(); + let input2 = TradableKittyData::default_kitty(); + input1.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input2.clone(); -#[test] -fn breed_wrong_output_type_fails() { - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[Bogus.into(), Bogus.into(), Bogus.into()], + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) ); - assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); } #[test] -fn inputs_dont_contain_two_parents_fails() { - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[TradableKittyData::default().into()], - &[], // no peeks +fn list_kitty_for_sale_input_missing_path_fails() { + let output = TradableKittyData::default_tradable_kitty(); + + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[], + &[], + &[output.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::TwoParentsDoNotExist) + Err(TradeableKittyError::NumberOfInputOutputMismatch) ); } - #[test] -fn outputs_dont_contain_all_family_members_fails() { - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[TradableKittyData::default().into()], +fn list_kitty_for_sale_out_put_missing_path_fails() { + let input1 = TradableKittyData::default_kitty(); + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input1.into()], + &[], + &[], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::NotEnoughFamilyMembers) + Err(TradeableKittyError::NumberOfInputOutputMismatch) ); } #[test] -fn breed_two_dads_fails() { - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default_dad().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[TradableKittyData::default().into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::TwoDadsNotValid) +fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { + let input1 = TradableKittyData::default_kitty(); + let input2 = TradableKittyData::default_kitty(); + + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into(), Bogus.into()], ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] -fn breed_two_moms_fails() { - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default().into(), - ], - &[], // no peeks - &[TradableKittyData::default().into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::TwoMomsNotValid) +fn list_kitty_for_sale_with_wrong_input_type_fails() { + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[Bogus.into()], + &[], + &[TradableKittyData::default_tradable_kitty().into()], ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] -fn first_input_not_mom_fails() { - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default_dad().into(), - TradableKittyData::default().into(), - ], - &[], // no peeks - &[], +fn list_kitty_for_sale_with_input_missing_fails() { + let input1 = TradableKittyData::default_kitty(); + let input2 = TradableKittyData::default_kitty(); + + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + let mut output2 = TradableKittyData::default_tradable_kitty(); + output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into(), output2.into()], ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::TwoDadsNotValid) - ) + assert_eq!(result, Err(TradeableKittyError::InputMissingError)); } #[test] -fn first_output_not_mom_fails() { - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - TradableKittyData::default_dad().into(), - TradableKittyData::default().into(), - TradableKittyData::default_child().into(), - ], +fn list_for_sale_with_basic_property_changed_fails() { + let input1 = TradableKittyData::default_kitty(); + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + output1.kitty_basic_data.free_breedings += 1; + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input1.into()], + &[], + &[output1.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::TwoDadsNotValid) + Err(TradeableKittyError::KittyBasicPropertiesAltered) ); } #[test] -fn breed_mom_when_she_gave_birth_recently_fails() { - let mut new_momma = TradableKittyData::default(); - new_momma.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); - - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[new_momma.into(), TradableKittyData::default_dad().into()], - &[], // no peeks - &[], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::MomNotReadyYet) +fn list_for_sale_with_price_none_fails() { + let input1 = TradableKittyData::default_kitty(); + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + output1.price = None; + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input1.into()], + &[], + &[output1.into()], ); + assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeNone)); } -#[test] -fn breed_dad_when_he_is_tired_fails() { - let mut tired_dadda = TradableKittyData::default_dad(); - tired_dadda.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::Tired); +// From below delist tradable kitty test cases start. - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[TradableKittyData::default().into(), tired_dadda.into()], - &[], // no peeks - &[], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::DadTooTired) +#[test] +fn delist_kitty_from_sale_happy_path_works() { + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[TradableKittyData::default_tradable_kitty().into()], + &[], + &[TradableKittyData::default_kitty().into()], ); + assert!(result.is_ok()); } #[test] -fn check_mom_breedings_overflow_fails() { - let mut test_mom = TradableKittyData::default(); - test_mom.kitty_basic_data.num_breedings = u128::MAX; +fn delist_kitty_from_sale_multiple_input_happy_path_works() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input2.kitty_basic_data = output2.clone(); - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[test_mom.into(), TradableKittyData::default_dad().into()], - &[], // no peeks - &[], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::TooManyBreedingsForKitty) + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into(), output2.into()], ); + assert!(result.is_ok()); } - +////////////////////// #[test] -fn check_dad_breedings_overflow_fails() { - let mut test_dad = TradableKittyData::default_dad(); - test_dad.kitty_basic_data.num_breedings = u128::MAX; +fn delist_kitty_from_sale_different_num_of_input_output_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input2.kitty_basic_data = output2; - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[TradableKittyData::default().into(), test_dad.into()], - &[], // no peeks - &[], + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::TooManyBreedingsForKitty) + Err(TradeableKittyError::NumberOfInputOutputMismatch) ); } #[test] -fn check_mom_free_breedings_zero_fails() { - let mut test_mom = TradableKittyData::default(); - test_mom.kitty_basic_data.free_breedings = 0; - - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[test_mom.into(), TradableKittyData::default_dad().into()], - &[], // no peeks +fn delist_kitty_from_sale_input_missing_fails() { + let output = TradableKittyData::default_kitty(); + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( &[], + &[], + &[output.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::NotEnoughFreeBreedings) + Err(TradeableKittyError::NumberOfInputOutputMismatch) ); } - #[test] -fn check_dad_free_breedings_zero_fails() { - let mut test_dad = TradableKittyData::default_dad(); - test_dad.kitty_basic_data.free_breedings = 0; - - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[TradableKittyData::default().into(), test_dad.into()], - &[], // no peeks +fn delist_kitty_from_sale_out_put_missing_path_fails() { + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[TradableKittyData::default_tradable_kitty().into()], + &[], &[], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::NotEnoughFreeBreedings) + Err(TradeableKittyError::NumberOfInputOutputMismatch) ); } #[test] -fn check_new_mom_free_breedings_incorrect_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_mom = new_family[0].clone(); - new_mom.kitty_basic_data.free_breedings = 2; +fn delist_kitty_from_sale_with_wrong_output_type_ampoung_valid_output_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let mut input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data = output1.clone(); + input2.kitty_basic_data = output2.clone(); - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_mom.into(), - new_family[1].clone().into(), - new_family[2].clone().into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewParentFreeBreedingsIncorrect) + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[input1.into(), input2.into()], + &[], + &[output1.into(), Bogus.into()], ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] -fn check_new_dad_free_breedings_incorrect_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_dad = new_family[1].clone(); - new_dad.kitty_basic_data.free_breedings = 2; - - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_dad.into(), - new_family[2].clone().into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewParentFreeBreedingsIncorrect) +fn delist_from_sale_with_wrong_input_type_fails() { + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[Bogus.into()], + &[], + &[TradableKittyData::default_kitty().into()], ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] -fn check_new_mom_num_breedings_incorrect_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_mom = new_family[0].clone(); - new_mom.kitty_basic_data.num_breedings = 0; - - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_mom.into(), - new_family[1].clone().into(), - new_family[2].clone().into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewParentNumberBreedingsIncorrect) +fn delist_from_sale_with_input_missing_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadocz")); + let mut input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data = output1.clone(); + input2.kitty_basic_data = output2.clone(); + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[input1.clone().into(), input1.into()], + &[], + &[output1.into(), output2.into()], ); + assert_eq!(result, Err(TradeableKittyError::InputMissingError)); } +// From below update tradable kitty name test cases starts #[test] -fn check_new_dad_num_breedings_incorrect_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_dad = new_family[1].clone(); - new_dad.kitty_basic_data.num_breedings = 0; +fn update_name_happy_path_works() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"tdkt"; - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_dad.into(), - new_family[2].clone().into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewParentNumberBreedingsIncorrect) + let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + &[input.into()], + &[], + &[output.into()], ); + assert!(result.is_ok()); } #[test] -fn check_new_mom_dna_doesnt_match_old_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_mom = new_family[0].clone(); - new_mom.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); +fn update_name_invalid_type_in_input_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"tdkt"; - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_mom.into(), - new_family[1].clone().into(), - new_family[2].clone().into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewParentDnaDoesntMatchOld) + let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + &[input.into(), Bogus.into()], + &[], + &[output.clone().into(), output.into()], ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } - #[test] -fn check_new_dad_dna_doesnt_match_old_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_dad = new_family[1].clone(); - new_dad.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); +fn update_name_invalid_type_in_output_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"tdkt"; - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_dad.into(), - new_family[2].clone().into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewParentDnaDoesntMatchOld) + let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + &[input.clone().into(), input.into()], + &[], + &[output.into(), Bogus.into()], ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } - +//// Price update test cases #[test] -fn check_child_dna_incorrect_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_child = new_family[2].clone(); - new_child.kitty_basic_data.dna = KittyDNA(H256::zero()); +fn update_price_happy_path_works() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_family[1].clone().into(), - new_child.into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewChildDnaIncorrect) + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into()], + &[], + &[output.into()], ); + assert!(result.is_ok()); } #[test] -fn check_child_dad_parent_tired_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_child = new_family[2].clone(); - new_child.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::Tired); +fn update_price_multiple_input_happy_path_works() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + let mut output1 = input1.clone(); + output1.price = Some(700); - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_family[1].clone().into(), - new_child.into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewChildIncorrectParentInfo) + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into(), input1.into()], + &[], + &[output.into(), output1.into()], ); + assert!(result.is_ok()); } #[test] -fn check_child_mom_parent_recently_gave_birth_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_child = new_family[2].clone(); - new_child.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); +fn update_price_output_missing_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_family[1].clone().into(), - new_child.into(), - ], + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into(), input1.into()], + &[], + &[output.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::NewChildIncorrectParentInfo) + Err(TradeableKittyError::NumberOfInputOutputMismatch) ); } #[test] -fn check_child_free_breedings_incorrect_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_child = new_family[2].clone(); - new_child.kitty_basic_data.free_breedings = KittyHelpers::NUM_FREE_BREEDINGS + 1; +fn update_price_input_missing_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + let mut output1 = input1.clone(); + output1.price = Some(700); - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_family[1].clone().into(), - new_child.into(), - ], + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into()], + &[], + &[output.into(), output1.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::NewChildFreeBreedingsIncorrect) + Err(TradeableKittyError::NumberOfInputOutputMismatch) ); } #[test] -fn check_child_num_breedings_non_zero_fails() { - let new_family = TradableKittyData::default_family(); - let mut new_child = new_family[2].clone(); - new_child.kitty_basic_data.num_breedings = 42; +fn update_price_bad_input_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); - let result = TradableKittyConstraintChecker::<0>::Breed.check( - &[ - TradableKittyData::default().into(), - TradableKittyData::default_dad().into(), - ], - &[], // no peeks - &[ - new_family[0].clone().into(), - new_family[1].clone().into(), - new_child.into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::NewChildHasNonZeroBreedings) + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[Bogus.into()], + &[], + &[output.into()], ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } -// From below update tradable kitty properties test cases starts #[test] -fn update_properties_happy_path_works() { - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[TradableKittyData::default().into()], - &[], // no peeks - &[TradableKittyData::default_updated().into()], - ); - assert!(result.is_ok()); -} +fn update_price_bad_output_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); -#[test] -fn update_properties_update_no_input_fails() { - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[], - &[], // no peeks - &[TradableKittyData::default_updated().into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::InputMissingUpdatingNothing) + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into(), input1.into()], + &[], + &[output.into(), Bogus.into()], ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] -fn update_properties_update_num_of_input_output_mismatch_fails() { - let mut updated_kitty = TradableKittyData::default(); - updated_kitty.kitty_basic_data.dna = - KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[TradableKittyData::default().into()], - &[], // no peeks - &[ - TradableKittyData::default_updated().into(), - updated_kitty.into(), - ], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::MismatchBetweenNumberOfInputAndUpdateUpadtingNothing) - ); -} +fn update_price_different_dna_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); -#[test] -fn update_properties_update_dna_fails() { - let mut updated_kitty = TradableKittyData::default(); - updated_kitty.kitty_basic_data.dna = - KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoci")); - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[TradableKittyData::default().into()], - &[], // no peeks - &[updated_kitty.into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::KittyDnaCannotBeUpdated) - ); -} + output.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); -#[test] -fn update_properties_update_gender_fails() { - let updated_kitty = TradableKittyData::default_dad(); - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[TradableKittyData::default().into()], - &[], // no peeks - &[updated_kitty.into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::KittyGenderCannotBeUpdated) + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.clone().into()], + &[], + &[output.into()], ); + assert_eq!(result, Err(TradeableKittyError::OutputUtxoMissingError)); } #[test] -fn update_properties_update_free_breedings_fails() { - let mut updated_kitty = TradableKittyData::default(); - updated_kitty.kitty_basic_data.free_breedings = 5; - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[TradableKittyData::default().into()], - &[], // no peeks - &[updated_kitty.into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::FreeBreedingCannotBeUpdated) +fn update_price_map_remove_check_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); + // check that only 1 instance with duplicate dna is inserted and when 1st is removed , 2nd one fails. + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.clone().into(), input.into()], + &[], + &[output.clone().into(), output.into()], ); + assert_eq!(result, Err(TradeableKittyError::OutputUtxoMissingError)); } #[test] -fn update_properties_update_num_breedings_fails() { - let mut updated_kitty = TradableKittyData::default(); - updated_kitty.kitty_basic_data.num_breedings = 5; - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[TradableKittyData::default().into()], - &[], // no peeks - &[updated_kitty.into()], +fn update_price_basic_properties_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); + output.kitty_basic_data.free_breedings += 1; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into()], + &[], + &[output.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::NumOfBreedingCannotBeUpdated) + Err(TradeableKittyError::KittyBasicPropertiesAltered) ); } #[test] -fn update_properties_non_none_price_when_is_avilable_for_sale_is_false_fails() { - let mut updated_kitty = TradableKittyData::default(); - updated_kitty.price = Some(100); - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[TradableKittyData::default().into()], - &[], // no peeks - &[updated_kitty.into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::UpdatedKittyIncorrectPrice) +fn update_price_not_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let output = input.clone(); + + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into()], + &[], + &[output.into()], ); + assert_eq!(result, Err(TradeableKittyError::KittyPriceUnaltered)); } #[test] -fn update_properties_none_price_when_is_avilable_for_sale_is_true_fails() { - let mut updated_kitty = TradableKittyData::default(); - updated_kitty.is_available_for_sale = true; - updated_kitty.price = None; - let result = TradableKittyConstraintChecker::<0>::UpdateProperties.check( - &[TradableKittyData::default().into()], - &[], // no peeks - &[updated_kitty.into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::UpdatedKittyIncorrectPrice) +fn update_price_to_null_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = None; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into()], + &[], + &[output.into()], ); + assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeNone)); } // From below buy tradable kitty test cases start. + #[test] fn buy_happy_path_single_input_coinworks() { - let mut input_kitty = TradableKittyData::default(); - input_kitty.is_available_for_sale = true; + let mut input_kitty = TradableKittyData::default_tradable_kitty(); input_kitty.price = Some(100); let output_kitty = input_kitty.clone(); @@ -785,7 +535,7 @@ fn buy_happy_path_single_input_coinworks() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin.into()], - &[], // no peeks + &[], &[output_kitty.into(), output_coin.into()], ); assert!(result.is_ok()); @@ -793,8 +543,7 @@ fn buy_happy_path_single_input_coinworks() { #[test] fn buy_happy_path_multiple_input_coinworks() { - let mut input_kitty = TradableKittyData::default(); - input_kitty.is_available_for_sale = true; + let mut input_kitty = TradableKittyData::default_tradable_kitty(); input_kitty.price = Some(100); let output_kitty = input_kitty.clone(); @@ -804,16 +553,16 @@ fn buy_happy_path_multiple_input_coinworks() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into(), input_coin2.into()], - &[], // no peeks + &[], &[output_kitty.into(), output_coin.into()], ); assert!(result.is_ok()); } #[test] -fn buy_kityy_is_available_for_sale_false_fails() { - let mut input_kitty = TradableKittyData::default(); - input_kitty.price = Some(100); +fn buy_kityy_with_price_none_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = None; let output_kitty = input_kitty.clone(); let input_coin = Coin::<0>(100); @@ -821,36 +570,16 @@ fn buy_kityy_is_available_for_sale_false_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin.into()], - &[], // no peeks + &[], &[output_kitty.into(), output_coin.into()], ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::KittyNotForSale) - ); + assert_eq!(result, Err(TradeableKittyError::KittyNotForSale)); } -#[test] -fn buy_kityy_is_available_for_sale_true_price_none_fails() { - let mut input_kitty = TradableKittyData::default(); - input_kitty.is_available_for_sale = true; - let output_kitty = input_kitty.clone(); - let input_coin = Coin::<0>(100); - let output_coin = Coin::<0>(100); - let result = TradableKittyConstraintChecker::<0>::Buy.check( - &[input_kitty.into(), input_coin.into()], - &[], // no peeks - &[output_kitty.into(), output_coin.into()], - ); - assert_eq!( - result, - Err(TradableKittyConstraintCheckerError::KittyPriceCantBeNone) - ); -} #[test] fn buy_kityy_wrong_input_type_fails() { - let mut input_kitty = TradableKittyData::default(); - input_kitty.is_available_for_sale = true; + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = Some(101); let output_kitty = input_kitty.clone(); @@ -859,16 +588,15 @@ fn buy_kityy_wrong_input_type_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin.into(), Bogus.into()], - &[], // no peeks + &[], &[output_kitty.into(), output_coin.into()], ); - assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] fn buy_kityy_wrong_output_type_fails() { - let mut input_kitty = TradableKittyData::default(); - input_kitty.is_available_for_sale = true; + let mut input_kitty = TradableKittyData::default_tradable_kitty(); input_kitty.price = Some(101); let output_kitty = input_kitty.clone(); @@ -877,16 +605,15 @@ fn buy_kityy_wrong_output_type_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin.into()], - &[], // no peeks + &[], &[output_kitty.into(), output_coin.into(), Bogus.into()], ); - assert_eq!(result, Err(TradableKittyConstraintCheckerError::BadlyTyped)); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] -fn buy_kitty_less_money_than_price_of_kitty_fails() { - let mut input_kitty = TradableKittyData::default(); - input_kitty.is_available_for_sale = true; +fn buy_kitty_less_money_than_price_of_kityy_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); input_kitty.price = Some(101); let output_kitty = input_kitty.clone(); @@ -896,19 +623,18 @@ fn buy_kitty_less_money_than_price_of_kitty_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into()], - &[], // no peeks + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::InsufficientCollateralToBuyKitty) + Err(TradeableKittyError::InsufficientCollateralToBuyKitty) ); } #[test] fn buy_kitty_coin_output_value_exceeds_input_coin_value_fails() { - let mut input_kitty = TradableKittyData::default(); - input_kitty.is_available_for_sale = true; + let mut input_kitty = TradableKittyData::default_tradable_kitty(); input_kitty.price = Some(101); let output_kitty = input_kitty.clone(); @@ -918,11 +644,71 @@ fn buy_kitty_coin_output_value_exceeds_input_coin_value_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into(), input_coin2.into()], - &[], // no peeks + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::MoneyError( + MoneyError::OutputsExceedInputs + )) + ) +} + +#[test] +fn buy_kitty_input_zero_coin_value_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = Some(101); + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(0); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(300); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!( result, - Err(TradableKittyConstraintCheckerError::OutputsExceedInputs) + Err(TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin)) ) } +#[test] +fn buy_kitty_output_zero_coin_value_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = Some(101); + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(100); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(0); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin)) + ) +} + +#[test] +fn buy_kitty_basic_kitty_fails() { + let input_kitty = TradableKittyData::default_kitty(); + let output_kitty = input_kitty.clone(); + + let input_coin1 = Coin::<0>(100); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(0); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[input_kitty.into(), input_coin1.into(), input_coin2.into()], + &[], + &[output_kitty.into(), output_coin.into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)) +} From 1f5855bb191f13975fae0144887af88c2cda26b8 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Thu, 22 Feb 2024 16:48:58 +0530 Subject: [PATCH 05/14] Submit the fmt fix submitted the fmt fix cargo fmt --all --- wardrobe/kitties/src/tests.rs | 34 ++++----- wardrobe/tradable_kitties/src/tests.rs | 99 ++++++++++++-------------- 2 files changed, 60 insertions(+), 73 deletions(-) diff --git a/wardrobe/kitties/src/tests.rs b/wardrobe/kitties/src/tests.rs index f881f3aae..2281328f5 100644 --- a/wardrobe/kitties/src/tests.rs +++ b/wardrobe/kitties/src/tests.rs @@ -57,7 +57,7 @@ fn create_happy_path_works() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Create, &[], - &[], + &[], &[KittyData::default().into(), KittyData::default_dad().into()], ); assert!(result.is_ok()); @@ -68,19 +68,15 @@ fn create_with_input_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Create, &[KittyData::default().into()], - &[], + &[], &[], ); assert_eq!(result, Err(ConstraintCheckerError::CreatingWithInputs)); } #[test] fn create_without_output_fails() { - let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::Create, - &[], - &[], - &[], - ); + let result = + FreeKittyConstraintChecker::check(&FreeKittyConstraintChecker::Create, &[], &[], &[]); assert_eq!(result, Err(ConstraintCheckerError::CreatingNothing)); } #[test] @@ -88,7 +84,7 @@ fn create_with_wrong_output_type_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::Create, &[], - &[], + &[], &[ Bogus.into(), KittyData::default().into(), @@ -547,7 +543,7 @@ fn update_name_happy_path_works() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[input.into()], - &[], + &[], &[output.into()], ); assert!(result.is_ok()); @@ -567,7 +563,7 @@ fn update_name_happy_path_with_multiple_input_sworks() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[input1.into(), input2.into()], - &[], + &[], &[output1.into(), output2.into()], ); assert!(result.is_ok()); @@ -586,7 +582,7 @@ fn update_name_inputs_and_outputs_number_mismatch_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[input1.into(), input2.into()], - &[], + &[], &[output1.into()], ); assert_eq!( @@ -601,7 +597,7 @@ fn update_name_no_inputs_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[], - &[], + &[], &[output.into()], ); assert_eq!( @@ -617,7 +613,7 @@ fn update_name_no_output_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[input.into()], - &[], + &[], &[], ); assert_eq!( @@ -639,7 +635,7 @@ fn update_name_dna_update_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[input1.into(), input.into()], - &[], + &[], &[output1.into(), output.into()], ); assert_eq!(result, Err(ConstraintCheckerError::OutputUtxoMissingError)); @@ -650,7 +646,7 @@ fn update_name_name_unupdated_path_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[KittyData::default_dad().into()], - &[], + &[], &[KittyData::default_dad().into()], ); assert_eq!(result, Err(ConstraintCheckerError::KittyNameUnAltered)); @@ -665,7 +661,7 @@ fn update_name_free_breeding_updated_path_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[KittyData::default().into()], - &[], + &[], &[output.into()], ); assert_eq!( @@ -683,7 +679,7 @@ fn update_name_num_of_breeding_updated_path_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[KittyData::default().into()], - &[], + &[], &[output.into()], ); assert_eq!( @@ -701,7 +697,7 @@ fn update_name_gender_updated_path_fails() { let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, &[input.into()], - &[], + &[], &[output.into()], ); assert_eq!( diff --git a/wardrobe/tradable_kitties/src/tests.rs b/wardrobe/tradable_kitties/src/tests.rs index 8bba38a6c..799e37eb6 100644 --- a/wardrobe/tradable_kitties/src/tests.rs +++ b/wardrobe/tradable_kitties/src/tests.rs @@ -39,7 +39,7 @@ impl TradableKittyData { fn list_kitty_for_sale_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[TradableKittyData::default_kitty().into()], - &[], + &[], &[TradableKittyData::default_tradable_kitty().into()], ); assert!(result.is_ok()); @@ -57,7 +57,7 @@ fn list_kitty_for_sale_multiple_input_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[input1.into(), input2.into()], - &[], + &[], &[output1.into(), output2.into()], ); assert!(result.is_ok()); @@ -73,7 +73,7 @@ fn list_kitty_for_sale_different_num_of_input_output_path_fails() { let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[input1.into(), input2.into()], - &[], + &[], &[output1.into()], ); assert_eq!( @@ -86,11 +86,8 @@ fn list_kitty_for_sale_different_num_of_input_output_path_fails() { fn list_kitty_for_sale_input_missing_path_fails() { let output = TradableKittyData::default_tradable_kitty(); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( - &[], - &[], - &[output.into()], - ); + let result = + TradableKittyConstraintChecker::<0>::ListKittyForSale.check(&[], &[], &[output.into()]); assert_eq!( result, Err(TradeableKittyError::NumberOfInputOutputMismatch) @@ -99,11 +96,8 @@ fn list_kitty_for_sale_input_missing_path_fails() { #[test] fn list_kitty_for_sale_out_put_missing_path_fails() { let input1 = TradableKittyData::default_kitty(); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( - &[input1.into()], - &[], - &[], - ); + let result = + TradableKittyConstraintChecker::<0>::ListKittyForSale.check(&[input1.into()], &[], &[]); assert_eq!( result, Err(TradeableKittyError::NumberOfInputOutputMismatch) @@ -119,7 +113,7 @@ fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { output1.kitty_basic_data = input1.clone(); let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[input1.into(), input2.into()], - &[], + &[], &[output1.into(), Bogus.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -129,7 +123,7 @@ fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { fn list_kitty_for_sale_with_wrong_input_type_fails() { let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[Bogus.into()], - &[], + &[], &[TradableKittyData::default_tradable_kitty().into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -146,7 +140,7 @@ fn list_kitty_for_sale_with_input_missing_fails() { output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[input1.into(), input2.into()], - &[], + &[], &[output1.into(), output2.into()], ); assert_eq!(result, Err(TradeableKittyError::InputMissingError)); @@ -160,7 +154,7 @@ fn list_for_sale_with_basic_property_changed_fails() { output1.kitty_basic_data.free_breedings += 1; let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[input1.into()], - &[], + &[], &[output1.into()], ); assert_eq!( @@ -177,7 +171,7 @@ fn list_for_sale_with_price_none_fails() { output1.price = None; let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[input1.into()], - &[], + &[], &[output1.into()], ); assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeNone)); @@ -189,7 +183,7 @@ fn list_for_sale_with_price_none_fails() { fn delist_kitty_from_sale_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( &[TradableKittyData::default_tradable_kitty().into()], - &[], + &[], &[TradableKittyData::default_kitty().into()], ); assert!(result.is_ok()); @@ -206,7 +200,7 @@ fn delist_kitty_from_sale_multiple_input_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( &[input1.into(), input2.into()], - &[], + &[], &[output1.into(), output2.into()], ); assert!(result.is_ok()); @@ -223,7 +217,7 @@ fn delist_kitty_from_sale_different_num_of_input_output_fails() { let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( &[input1.into(), input2.into()], - &[], + &[], &[output1.into()], ); assert_eq!( @@ -235,11 +229,8 @@ fn delist_kitty_from_sale_different_num_of_input_output_fails() { #[test] fn delist_kitty_from_sale_input_missing_fails() { let output = TradableKittyData::default_kitty(); - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( - &[], - &[], - &[output.into()], - ); + let result = + TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check(&[], &[], &[output.into()]); assert_eq!( result, Err(TradeableKittyError::NumberOfInputOutputMismatch) @@ -249,7 +240,7 @@ fn delist_kitty_from_sale_input_missing_fails() { fn delist_kitty_from_sale_out_put_missing_path_fails() { let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( &[TradableKittyData::default_tradable_kitty().into()], - &[], + &[], &[], ); assert_eq!( @@ -270,7 +261,7 @@ fn delist_kitty_from_sale_with_wrong_output_type_ampoung_valid_output_fails() { let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( &[input1.into(), input2.into()], - &[], + &[], &[output1.into(), Bogus.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -280,7 +271,7 @@ fn delist_kitty_from_sale_with_wrong_output_type_ampoung_valid_output_fails() { fn delist_from_sale_with_wrong_input_type_fails() { let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( &[Bogus.into()], - &[], + &[], &[TradableKittyData::default_kitty().into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -297,7 +288,7 @@ fn delist_from_sale_with_input_missing_fails() { input2.kitty_basic_data = output2.clone(); let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( &[input1.clone().into(), input1.into()], - &[], + &[], &[output1.into(), output2.into()], ); assert_eq!(result, Err(TradeableKittyError::InputMissingError)); @@ -312,7 +303,7 @@ fn update_name_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( &[input.into()], - &[], + &[], &[output.into()], ); assert!(result.is_ok()); @@ -326,7 +317,7 @@ fn update_name_invalid_type_in_input_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( &[input.into(), Bogus.into()], - &[], + &[], &[output.clone().into(), output.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -339,7 +330,7 @@ fn update_name_invalid_type_in_output_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( &[input.clone().into(), input.into()], - &[], + &[], &[output.into(), Bogus.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -353,7 +344,7 @@ fn update_price_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into()], - &[], + &[], &[output.into()], ); assert!(result.is_ok()); @@ -371,7 +362,7 @@ fn update_price_multiple_input_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into(), input1.into()], - &[], + &[], &[output.into(), output1.into()], ); assert!(result.is_ok()); @@ -387,7 +378,7 @@ fn update_price_output_missing_path_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into(), input1.into()], - &[], + &[], &[output.into()], ); assert_eq!( @@ -408,7 +399,7 @@ fn update_price_input_missing_path_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into()], - &[], + &[], &[output.into(), output1.into()], ); assert_eq!( @@ -425,7 +416,7 @@ fn update_price_bad_input_path_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[Bogus.into()], - &[], + &[], &[output.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -441,7 +432,7 @@ fn update_price_bad_output_path_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into(), input1.into()], - &[], + &[], &[output.into(), Bogus.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -457,7 +448,7 @@ fn update_price_different_dna_path_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.clone().into()], - &[], + &[], &[output.into()], ); assert_eq!(result, Err(TradeableKittyError::OutputUtxoMissingError)); @@ -471,7 +462,7 @@ fn update_price_map_remove_check_path_fails() { // check that only 1 instance with duplicate dna is inserted and when 1st is removed , 2nd one fails. let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.clone().into(), input.into()], - &[], + &[], &[output.clone().into(), output.into()], ); assert_eq!(result, Err(TradeableKittyError::OutputUtxoMissingError)); @@ -486,7 +477,7 @@ fn update_price_basic_properties_updated_path_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into()], - &[], + &[], &[output.into()], ); assert_eq!( @@ -502,7 +493,7 @@ fn update_price_not_updated_path_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into()], - &[], + &[], &[output.into()], ); assert_eq!(result, Err(TradeableKittyError::KittyPriceUnaltered)); @@ -516,7 +507,7 @@ fn update_price_to_null_updated_path_fails() { let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into()], - &[], + &[], &[output.into()], ); assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeNone)); @@ -535,7 +526,7 @@ fn buy_happy_path_single_input_coinworks() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert!(result.is_ok()); @@ -553,7 +544,7 @@ fn buy_happy_path_multiple_input_coinworks() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into(), input_coin2.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert!(result.is_ok()); @@ -570,7 +561,7 @@ fn buy_kityy_with_price_none_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!(result, Err(TradeableKittyError::KittyNotForSale)); @@ -588,7 +579,7 @@ fn buy_kityy_wrong_input_type_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin.into(), Bogus.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -605,7 +596,7 @@ fn buy_kityy_wrong_output_type_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin.into()], - &[], + &[], &[output_kitty.into(), output_coin.into(), Bogus.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); @@ -623,7 +614,7 @@ fn buy_kitty_less_money_than_price_of_kityy_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!( @@ -644,7 +635,7 @@ fn buy_kitty_coin_output_value_exceeds_input_coin_value_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into(), input_coin2.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!( @@ -667,7 +658,7 @@ fn buy_kitty_input_zero_coin_value_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into(), input_coin2.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!( @@ -687,7 +678,7 @@ fn buy_kitty_output_zero_coin_value_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into(), input_coin2.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!( @@ -707,7 +698,7 @@ fn buy_kitty_basic_kitty_fails() { let result = TradableKittyConstraintChecker::<0>::Buy.check( &[input_kitty.into(), input_coin1.into(), input_coin2.into()], - &[], + &[], &[output_kitty.into(), output_coin.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)) From 4d4501b12be2aad37ecdb02de1850ba8ba6f4de9 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Sun, 25 Feb 2024 14:05:23 +0530 Subject: [PATCH 06/14] Implement in-order requirement between input and output for multi input txns Implemented below : 1. Removed the out-of-order b/w input & output for Implemented the in-order for below : 1. ListKittyForSale 2. DelistKittyForSale 3. UpdateName 4.UpdatePrice For Breed operation, only 1 tradable kitty can be traded at the same time in the same txn. --- wardrobe/kitties/src/lib.rs | 80 +++++---- wardrobe/kitties/src/tests.rs | 45 ++++- wardrobe/tradable_kitties/src/lib.rs | 217 ++++++++++++++----------- wardrobe/tradable_kitties/src/tests.rs | 191 ++++++++++++++++++++-- 4 files changed, 389 insertions(+), 144 deletions(-) diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index d29362274..23de2e51f 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -6,23 +6,26 @@ //! - **Create:** Generate a new kitty. //! To submit a valid transaction for creating a kitty, adhere to the following structure: //! 1. Input must be empty. -//! 2. Output must contain only the newly created kitty as a child. - +//! 2. Output must contain only the newly created kittities as a child. +//! +//! **Note 1:** Multiple kitties can be created at the same time in the same txn.. +//! //! - **Update Name:** Modify the name of a kitty. //! To submit a valid transaction for updating a kitty's name, adhere to the following structure: //! 1. Input must be the kitty to be updated. //! 2. Output must contain the kitty with the updated name. //! -//! **Note:** All other properties such as DNA, parents, free breedings, etc., must remain unaltered in the output. +//! **Note 1:** All other properties such as DNA, parents, free breedings, etc., must remain unaltered in the output. +//! **Note 2:** The input and output kitties must follow same order. //! //! - **Breed:** Breeds a new kitty using mom and dad based on below factors -//! 1.) Mom and Tired have to be in a state where they are ready to breed +//! 1.) Mom and Dad have to be in a state where they are ready to breed //! 2.) Each Mom and Dad have some DNA and the child will have unique DNA combined from the both of them //! Linkable back to the Mom and Dad //! 3.) The game also allows Kitties to have a cooling off period inbetween breeding before they can be bred again. //! 4.) A rest operation allows for a Mom Kitty and a Dad Kitty to be cooled off //! -//! In order to submit a valid transaction you must strutucture it as follows: +//! In order to submit a valid transaction you must structure it as follows: //! 1.) Input must contain 1 mom and 1 dad //! 2.) Output must contain Mom, Dad, and newly created Child //! 3.) A child's DNA is calculated by: @@ -41,7 +44,7 @@ use sp_runtime::{ traits::{BlakeTwo256, Hash as HashT}, transaction_validity::TransactionPriority, }; -use sp_std::collections::btree_map::BTreeMap; +use sp_std::collections::btree_set::BTreeSet; // For checking the uniqueness of input and output based on dna. use sp_std::prelude::*; use tuxedo_core::{ dynamic_typing::{DynamicallyTypedData, UtxoData}, @@ -53,6 +56,10 @@ use tuxedo_core::{ #[cfg(test)] mod tests; +/// The main constraint checker for the kitty piece. Allows below : +/// Create : Allows creation of kitty without parents, Multiple kitties can be created in same txn. +/// UpdateKittyName : Allows updating the name of the kitty s, Multiple kitty name can be updated in same txn. +/// Breed : Allows breeding of kitty. #[derive( Serialize, Deserialize, @@ -68,14 +75,15 @@ mod tests; TypeInfo, )] pub enum FreeKittyConstraintChecker { - /// A transaction that creates kitty without parents. + /// Txn that creates kitty without parents.Multiple kitties can be created at the same time. Create, - /// A Transaction that updates kitty Name. + /// Txn that updates kitty Name. Multiple kitty names can be updated. input & output must follow the same order. UpdateKittyName, - /// A transaction where kitties are consumed and new family(Parents(mom,dad) and child) is created. + /// Txn where kitties are consumed and new family(Parents(mom,dad) and child) is created. Breed, } +/// Dad kitty status with respect to breeding. #[derive( Serialize, Deserialize, @@ -93,10 +101,13 @@ pub enum FreeKittyConstraintChecker { )] pub enum DadKittyStatus { #[default] + /// Can breed. RearinToGo, + /// Can't breed due to tired. Tired, } +/// Mad kitty status with respect to breeding. #[derive( Serialize, Deserialize, @@ -114,10 +125,13 @@ pub enum DadKittyStatus { )] pub enum MomKittyStatus { #[default] + /// Can breed. RearinToGo, + /// Can't breed due to recent child kitty delivery. HadBirthRecently, } +/// Parent stuct contains 1 mom kitty and 1 dad kitty. #[derive( Serialize, Deserialize, @@ -170,6 +184,12 @@ impl Default for Parent { )] pub struct KittyDNA(pub H256); +/// Kitty data contains basic informationsuch as below : +/// parent: 1 mom kitty and 1 dad kitty. +/// free_breedings: Free breeding allowed on a kitty. +/// dna :Its a unique per kitty. +/// num_breedings: number of free breedings are remaining. +/// name: Name of kitty. #[derive( Serialize, Deserialize, @@ -233,6 +253,7 @@ impl UtxoData for KittyData { const TYPE_ID: [u8; 4] = *b"Kitt"; } +/// Reasons that kitty opertaion may go wrong. #[derive( Serialize, Deserialize, @@ -293,8 +314,10 @@ pub enum ConstraintCheckerError { CreatingWithInputs, /// No input for kitty Update. InvalidNumberOfInputOutput, - /// Updating nothing - OutputUtxoMissingError, + /// Duplicate kitty found i.e based on the DNA. + DuplicateKittyFound, + /// Dna mismatch between input and output. + DnaMismatchBetweenInputAndOutput, /// Name is not updated KittyNameUnAltered, /// Kitty FreeBreeding cannot be updated. @@ -593,8 +616,8 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { /// Checks: /// - Input and output is of kittyType -/// - Only name is updated and ther basicproperties are not updated. -/// +/// - Only name is updated and ther basic properties are not updated. +/// - Order between input and output must be same. pub fn can_kitty_name_be_updated( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], @@ -603,34 +626,35 @@ pub fn can_kitty_name_be_updated( input_data.len() == output_data.len() && !input_data.is_empty(), { ConstraintCheckerError::InvalidNumberOfInputOutput } ); + let mut dna_to_kitty_set: BTreeSet = BTreeSet::new(); - let mut map: BTreeMap = BTreeMap::new(); - - for utxo in input_data { - let utxo_kitty = utxo + for i in 0..input_data.len() { + let utxo_input_kitty = input_data[i] + .clone() .extract::() .map_err(|_| ConstraintCheckerError::BadlyTyped)?; - map.insert(utxo_kitty.clone().dna, utxo_kitty); - } - for utxo in output_data { - let utxo_output_kitty = utxo + if dna_to_kitty_set.contains(&utxo_input_kitty.dna) { + return Err(ConstraintCheckerError::DuplicateKittyFound); + } else { + dna_to_kitty_set.insert(utxo_input_kitty.clone().dna); + } + + let utxo_output_kitty = output_data[i] + .clone() .extract::() .map_err(|_| ConstraintCheckerError::BadlyTyped)?; - - if let Some(input_kitty) = map.remove(&utxo_output_kitty.dna) { - // Element found, access the value - check_kitty_name_update(&input_kitty, &utxo_output_kitty)?; - } else { - return Err(ConstraintCheckerError::OutputUtxoMissingError); + if utxo_input_kitty.dna != utxo_output_kitty.dna { + return Err(ConstraintCheckerError::DnaMismatchBetweenInputAndOutput); } + check_kitty_name_update(&utxo_input_kitty, &utxo_output_kitty)?; } return Ok(0); } /// Checks: /// - Private function used by can_kitty_name_be_updated. -/// - Only name is updated and ther basicproperties are not updated. +/// - Only name is updated and ther basic properties are not updated. /// fn check_kitty_name_update( original_kitty: &KittyData, diff --git a/wardrobe/kitties/src/tests.rs b/wardrobe/kitties/src/tests.rs index 2281328f5..1b760cb63 100644 --- a/wardrobe/kitties/src/tests.rs +++ b/wardrobe/kitties/src/tests.rs @@ -628,17 +628,54 @@ fn update_name_dna_update_fails() { output.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); output.name = *b"kty1"; - let input1 = KittyData::default_dad(); + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::DnaMismatchBetweenInputAndOutput) + ); +} + +#[test] +fn update_name_duplicate_dna_update_fails() { + let input = KittyData::default_dad(); + let mut output = input.clone(); + output.name = *b"kty1"; + + let result = FreeKittyConstraintChecker::check( + &FreeKittyConstraintChecker::UpdateKittyName, + &[input.clone().into(), input.into()], + &[], + &[output.clone().into(), output.into()], + ); + assert_eq!(result, Err(ConstraintCheckerError::DuplicateKittyFound)); +} + +#[test] +fn update_name_out_of_order_input_and_output_fails() { + let input = KittyData::default_dad(); + let mut output = input.clone(); + output.name = *b"kty1"; + + let mut input1 = KittyData::default_dad(); + input1.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); let mut output1 = input1.clone(); output1.name = *b"kty2"; let result = FreeKittyConstraintChecker::check( &FreeKittyConstraintChecker::UpdateKittyName, - &[input1.into(), input.into()], + &[input.clone().into(), input1.into()], &[], - &[output1.into(), output.into()], + &[output1.clone().into(), output.into()], + ); + assert_eq!( + result, + Err(ConstraintCheckerError::DnaMismatchBetweenInputAndOutput) ); - assert_eq!(result, Err(ConstraintCheckerError::OutputUtxoMissingError)); } #[test] diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs index 160bcb9f3..12ef91fd2 100644 --- a/wardrobe/tradable_kitties/src/lib.rs +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -1,33 +1,39 @@ -//! This module defines `TradableKitty`, a specialized type of kitty designed for trading with unique features. +//! # TradableKitty Module +//! +//! The `TradableKitty` module defines a specialized type of kitty tailored for trading with unique features. //! //! ## Features //! +//! The module supports multiple tradable kitties in the same transactions. Note that the input and output kitties must follow the same order. +//! //! - **ListKittyForSale:** Convert basic kitties into tradable kitties, adding a `Price` field. //! - **DelistKittyFromSale:** Transform tradable kitties back into regular kitties when owners decide not to sell. //! - **UpdateKittyPrice:** Allow owners to modify the price of TradableKitties. //! - **UpdateKittyName:** Permit owners to update the name of TradableKitties. -//! - **Buy:** Enable users to securely purchase TradableKitties from others, ensuring fair exchanges. //! +//! *Note: Only one kitty can be traded at a time.* +//! +//! - **Buy:** Enable users to securely purchase TradableKitty from others, ensuring fair exchanges. //! //! TradableKitties enrich the user experience by introducing advanced trading capabilities. +//! #![cfg_attr(not(feature = "std"), no_std)] +use kitties::{KittyDNA, KittyData}; +use money::ConstraintCheckerError as MoneyError; +use money::{Coin, MoneyConstraintChecker}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::transaction_validity::TransactionPriority; -use sp_std::collections::btree_map::BTreeMap; +use sp_std::collections::btree_set::BTreeSet; // For checking the uniqueness of input and output based on dna. use sp_std::prelude::*; use tuxedo_core::{ dynamic_typing::{DynamicallyTypedData, UtxoData}, ensure, SimpleConstraintChecker, }; -use kitties::{KittyDNA, KittyData}; -use money::ConstraintCheckerError as MoneyError; -use money::{Coin, MoneyConstraintChecker}; - #[cfg(test)] mod tests; @@ -98,21 +104,25 @@ pub enum TradeableKittyError { /// Dynamic typing issue. /// This error doesn't discriminate between badly typed inputs and outputs. BadlyTyped, - /// output missing updating nothing. - OutputUtxoMissingError, /// Input missing for the transaction. InputMissingError, /// Not enough amount to buy a kitty. InsufficientCollateralToBuyKitty, /// The number of input vs number of output doesn't match for a transaction. NumberOfInputOutputMismatch, + /// Can't buy more than one kitty at a time. + CannotBuyMoreThanOneKittyAtTime, /// Kitty basic properties such as DNA, free breeding, and a number of breedings, are altered error. KittyBasicPropertiesAltered, /// Kitty not available for sale.Occur when price is None. KittyNotForSale, /// Kitty price cant be none when it is available for sale. KittyPriceCantBeNone, - /// Kitty price is unaltered for kitty price update transactions. + /// Duplicate kitty foundi.e based on the DNA. + DuplicateKittyFound, + /// Duplicate tradable kitty foundi.e based on the DNA. + DuplicateTradableKittyFound, + /// Kitty price is unaltered is not allowed for kitty price update transactions. KittyPriceUnaltered, } @@ -129,11 +139,11 @@ impl From for TradeableKittyError { } /// The main constraint checker for the trdable kitty piece. Allows below : -/// Listing kitty for sale -/// Delisting kitty from sale -/// Update kitty price -/// Update kitty name -/// Buy tradable kitty +/// Listing kitty for sale : multiple kiities are allowed, provided input and output in same order. +/// Delisting kitty from sale : multiple tradable kiities are allowed, provided input and output in same order. +/// Update kitty price : multiple tradable kiities are allowed, provided input and output in same order. +/// Update kitty name : multiple tradable kiities are allowed, provided input and output in same order. +/// Buy tradable kitty : multiple tradable kiities are not allowed, Only single kiity operation is allowed. #[derive( Serialize, Deserialize, @@ -176,23 +186,20 @@ fn extract_basic_kitty_list( Ok(()) } -/// checks if buying the kitty is possible of not. It depends on Money piece to validat spending of coins. +/// Checks if buying the kitty is possible of not. It depends on Money piece to validate spending of coins. fn check_can_buy( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result<(), TradeableKittyError> { let mut input_coin_data: Vec = Vec::new(); let mut output_coin_data: Vec = Vec::new(); - let mut input_kitty_data: Vec = Vec::new(); - let mut output_kitty_data: Vec = Vec::new(); + let mut input_ktty_to_be_traded: Option = None; + let mut output_ktty_to_be_traded: Option = None; let mut total_input_amount: u128 = 0; let mut total_price_of_kitty: u128 = 0; - // Map to verify that output_kitty is same as input_kitty based on the dna after buy operation - let mut dna_to_tdkitty_map: BTreeMap = BTreeMap::new(); - - // Seperate the coin and tdkitty in to seperate vecs from the input_data . + // Seperate the coin and tradable kitty in to seperate vecs from the input_data . for utxo in input_data { if let Ok(coin) = utxo.extract::>() { let utxo_value = coin.0; @@ -205,25 +212,36 @@ fn check_can_buy( total_input_amount = total_input_amount .checked_add(utxo_value) .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; - - // Process Kitty } else if let Ok(td_input_kitty) = utxo.extract::() { - // Trying to buy kitty which is not listed for sale. - let price = match td_input_kitty.price { - None => return Err(TradeableKittyError::KittyNotForSale), - Some(p) => p, + // Process tradable kitty + // Checking if more than 1 kitty is sent for trading. + match input_ktty_to_be_traded { + None => { + // 1st tradable kitty is received in the input. + input_ktty_to_be_traded = Some(td_input_kitty.clone()); + let price = match td_input_kitty.price { + None => return Err(TradeableKittyError::KittyNotForSale), + Some(p) => p, + }; + total_price_of_kitty = total_price_of_kitty + .checked_add(price) + .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; + } + Some(_) => { + // More than 1 tradable kitty are sent for trading. + return Err(TradeableKittyError::CannotBuyMoreThanOneKittyAtTime); + } }; - - input_kitty_data.push(utxo.clone()); - dna_to_tdkitty_map.insert(td_input_kitty.clone().kitty_basic_data.dna, td_input_kitty); - total_price_of_kitty = total_price_of_kitty - .checked_add(price) - .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; } else { return Err(TradeableKittyError::BadlyTyped); } } + // Ensuring we found kitty in the input + ensure!(input_ktty_to_be_traded != None, { + TradeableKittyError::InputMissingError + }); + // Seperate the coin and tdkitty in to seperate vecs from the output_data . for utxo in output_data { if let Ok(coin) = utxo.extract::>() { @@ -235,29 +253,33 @@ fn check_can_buy( output_coin_data.push(utxo.clone()); // Process Coin } else if let Ok(td_output_kitty) = utxo.extract::() { - match dna_to_tdkitty_map.remove(&td_output_kitty.kitty_basic_data.dna) { - Some(found_kitty) => { - // During buy opertaion, basic kitty properties cant be updated in the same transaction. + // Checking if more than 1 kitty in output is sent for trading. + match output_ktty_to_be_traded { + None => { + // 1st tradable kitty is received in the output. + output_ktty_to_be_traded = Some(td_output_kitty.clone()); ensure!( - found_kitty.kitty_basic_data == td_output_kitty.kitty_basic_data, // basic kitty data is unaltered + input_ktty_to_be_traded.clone().unwrap().kitty_basic_data + == td_output_kitty.kitty_basic_data, // basic kitty data is unaltered TradeableKittyError::KittyBasicPropertiesAltered // this need to be chan ); } - None => { - return Err(TradeableKittyError::OutputUtxoMissingError); + Some(_) => { + // More than 1 tradable kitty are sent in output for trading. + return Err(TradeableKittyError::CannotBuyMoreThanOneKittyAtTime); } }; - output_kitty_data.push(utxo.clone()); } else { return Err(TradeableKittyError::BadlyTyped); } } - ensure!( - input_kitty_data.len() == output_kitty_data.len() && !input_kitty_data.is_empty(), - { TradeableKittyError::NumberOfInputOutputMismatch } - ); + // Ensuring we found kitty in the output + ensure!(output_ktty_to_be_traded != None, { + TradeableKittyError::InputMissingError + }); + // Ensuring total money sent is enough to buy the kitty ensure!( total_price_of_kitty <= total_input_amount, TradeableKittyError::InsufficientCollateralToBuyKitty @@ -268,8 +290,8 @@ fn check_can_buy( Ok(()) } -/// checks if kitty price updates is possible of not. -/// Price of multiple kitties can be updated in the same txn. +/// checks if tradable kitty price updates is possible of not. +/// Price of multiple tradable kitties can be updated in the same txn. fn check_kitty_price_update( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], @@ -279,40 +301,39 @@ fn check_kitty_price_update( { TradeableKittyError::NumberOfInputOutputMismatch } ); - let mut dna_to_tdkitty_map: BTreeMap = BTreeMap::new(); - - for utxo in input_data { - let td_input_kitty = utxo + let mut dna_to_tdkitty_set: BTreeSet = BTreeSet::new(); // to check the uniqueness in input + //input td-kitty and output td kitties need to be in same order. + for i in 0..input_data.len() { + let utxo_input_tradable_kitty = input_data[i] .extract::() .map_err(|_| TradeableKittyError::BadlyTyped)?; - dna_to_tdkitty_map.insert(td_input_kitty.clone().kitty_basic_data.dna, td_input_kitty); - } - for utxo in output_data { - let td_output_kitty = utxo + if dna_to_tdkitty_set.contains(&utxo_input_tradable_kitty.kitty_basic_data.dna) { + return Err(TradeableKittyError::DuplicateTradableKittyFound); + } else { + dna_to_tdkitty_set.insert(utxo_input_tradable_kitty.clone().kitty_basic_data.dna); + } + + let utxo_output_tradable_kitty = output_data[i] .extract::() .map_err(|_| TradeableKittyError::BadlyTyped)?; - if let Some(found_kitty) = dna_to_tdkitty_map.remove(&td_output_kitty.kitty_basic_data.dna) - { - // Element found, access the value - ensure!( - found_kitty.kitty_basic_data == td_output_kitty.kitty_basic_data, // basic kitty data is unaltered - TradeableKittyError::KittyBasicPropertiesAltered // this need to be chan - ); - match td_output_kitty.price { - Some(_) => { - ensure!( - found_kitty.price != td_output_kitty.price, // kitty ptice is unaltered - TradeableKittyError::KittyPriceUnaltered // this need to be chan - ); - } - None => return Err(TradeableKittyError::KittyPriceCantBeNone), - }; - } else { - return Err(TradeableKittyError::OutputUtxoMissingError); - } + ensure!( + utxo_input_tradable_kitty.kitty_basic_data + == utxo_output_tradable_kitty.kitty_basic_data, + TradeableKittyError::KittyBasicPropertiesAltered + ); + match utxo_output_tradable_kitty.price { + Some(_) => { + ensure!( + utxo_input_tradable_kitty.price != utxo_output_tradable_kitty.price, // kitty ptice is unaltered + TradeableKittyError::KittyPriceUnaltered + ); + } + None => return Err(TradeableKittyError::KittyPriceCantBeNone), + }; } + return Ok(0); } @@ -348,36 +369,40 @@ fn check_kitty_tdkitty_interconversion( { TradeableKittyError::NumberOfInputOutputMismatch } ); - let mut map: BTreeMap = BTreeMap::new(); + let mut dna_to_tdkitty_set: BTreeSet = BTreeSet::new(); + let mut dna_to_kitty_set: BTreeSet = BTreeSet::new(); - for utxo in kitty_data { - let utxo_kitty = utxo + for i in 0..kitty_data.len() { + let utxo_kitty = kitty_data[i] .extract::() .map_err(|_| TradeableKittyError::BadlyTyped)?; - map.insert(utxo_kitty.clone().dna, utxo_kitty); - } - for utxo in tradable_kitty_data { - let utxo_tradable_kitty = utxo + if dna_to_kitty_set.contains(&utxo_kitty.dna) { + return Err(TradeableKittyError::DuplicateKittyFound); + } else { + dna_to_kitty_set.insert(utxo_kitty.clone().dna); + } + + let utxo_tradable_kitty = tradable_kitty_data[i] .extract::() .map_err(|_| TradeableKittyError::BadlyTyped)?; - match map.remove(&utxo_tradable_kitty.kitty_basic_data.dna) { - Some(kitty) => { - ensure!( - kitty == utxo_tradable_kitty.kitty_basic_data, // basic kitty data is unaltered - TradeableKittyError::KittyBasicPropertiesAltered // this need to be chan - ); - let _ = match utxo_tradable_kitty.price { - Some(_) => {} - None => return Err(TradeableKittyError::KittyPriceCantBeNone), - }; - } - None => { - return Err(TradeableKittyError::InputMissingError); - } - }; + if dna_to_tdkitty_set.contains(&utxo_tradable_kitty.kitty_basic_data.dna) { + return Err(TradeableKittyError::DuplicateTradableKittyFound); + } else { + dna_to_tdkitty_set.insert(utxo_tradable_kitty.clone().kitty_basic_data.dna); + } + + ensure!( + utxo_kitty == utxo_tradable_kitty.kitty_basic_data, + TradeableKittyError::KittyBasicPropertiesAltered + ); + ensure!( + utxo_tradable_kitty.price != None, // basic kitty data is unaltered + TradeableKittyError::KittyPriceCantBeNone // this need to be chan + ); } + return Ok(0); } diff --git a/wardrobe/tradable_kitties/src/tests.rs b/wardrobe/tradable_kitties/src/tests.rs index 799e37eb6..366f48a93 100644 --- a/wardrobe/tradable_kitties/src/tests.rs +++ b/wardrobe/tradable_kitties/src/tests.rs @@ -34,7 +34,7 @@ impl TradableKittyData { } } } - +// List kitty UT startes from here #[test] fn list_kitty_for_sale_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( @@ -63,6 +63,27 @@ fn list_kitty_for_sale_multiple_input_happy_path_works() { assert!(result.is_ok()); } +#[test] +fn list_kitty_for_sale_multiple_out_of_order_input_fails() { + let input1 = TradableKittyData::default_kitty(); + let mut input2 = TradableKittyData::default_kitty(); + input2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadola")); + let mut output1 = TradableKittyData::default_tradable_kitty(); + let mut output2 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + output2.kitty_basic_data = input2.clone(); + + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input1.into(), input2.into()], + &[], + &[output2.into(), output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + #[test] fn list_kitty_for_sale_different_num_of_input_output_path_fails() { let mut input1 = TradableKittyData::default_kitty(); @@ -93,6 +114,7 @@ fn list_kitty_for_sale_input_missing_path_fails() { Err(TradeableKittyError::NumberOfInputOutputMismatch) ); } + #[test] fn list_kitty_for_sale_out_put_missing_path_fails() { let input1 = TradableKittyData::default_kitty(); @@ -107,14 +129,12 @@ fn list_kitty_for_sale_out_put_missing_path_fails() { #[test] fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { let input1 = TradableKittyData::default_kitty(); - let input2 = TradableKittyData::default_kitty(); - let mut output1 = TradableKittyData::default_tradable_kitty(); output1.kitty_basic_data = input1.clone(); let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( - &[input1.into(), input2.into()], + &[input1.into()], &[], - &[output1.into(), Bogus.into()], + &[Bogus.into()], ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } @@ -132,6 +152,25 @@ fn list_kitty_for_sale_with_wrong_input_type_fails() { #[test] fn list_kitty_for_sale_with_input_missing_fails() { let input1 = TradableKittyData::default_kitty(); + + let mut output1 = TradableKittyData::default_tradable_kitty(); + output1.kitty_basic_data = input1.clone(); + let mut output2 = TradableKittyData::default_tradable_kitty(); + output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[], + &[], + &[output1.into(), output2.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::NumberOfInputOutputMismatch) + ); +} + +#[test] +fn list_kitty_for_sale_with_duplicate_input_fails() { + let input1 = TradableKittyData::default_kitty(); let input2 = TradableKittyData::default_kitty(); let mut output1 = TradableKittyData::default_tradable_kitty(); @@ -143,19 +182,36 @@ fn list_kitty_for_sale_with_input_missing_fails() { &[], &[output1.into(), output2.into()], ); - assert_eq!(result, Err(TradeableKittyError::InputMissingError)); + assert_eq!(result, Err(TradeableKittyError::DuplicateKittyFound)); } #[test] -fn list_for_sale_with_basic_property_changed_fails() { +fn list_kitty_for_sale_with_duplicate_output_fails() { let input1 = TradableKittyData::default_kitty(); + let input2 = TradableKittyData::default_kitty(); + let mut output1 = TradableKittyData::default_tradable_kitty(); output1.kitty_basic_data = input1.clone(); - output1.kitty_basic_data.free_breedings += 1; + let mut output2 = TradableKittyData::default_tradable_kitty(); + output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( - &[input1.into()], + &[input1.into(), input2.into()], &[], - &[output1.into()], + &[output1.clone().into(), output1.into()], + ); + assert_eq!(result, Err(TradeableKittyError::DuplicateKittyFound)); +} + +#[test] +fn list_for_sale_with_basic_property_changed_fails() { + let input = TradableKittyData::default_kitty(); + let mut output = TradableKittyData::default_tradable_kitty(); + output.kitty_basic_data = input.clone(); + output.kitty_basic_data.free_breedings += 1; + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[input.into()], + &[], + &[output.into()], ); assert_eq!( result, @@ -205,7 +261,27 @@ fn delist_kitty_from_sale_multiple_input_happy_path_works() { ); assert!(result.is_ok()); } -////////////////////// + +#[test] +fn delist_kitty_from_sale_multiple_input_out_of_order_path_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + let input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input2.kitty_basic_data = output2.clone(); + + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[input1.into(), input2.into()], + &[], + &[output2.into(), output1.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + #[test] fn delist_kitty_from_sale_different_num_of_input_output_fails() { let output1 = TradableKittyData::default_kitty(); @@ -278,7 +354,7 @@ fn delist_from_sale_with_wrong_input_type_fails() { } #[test] -fn delist_from_sale_with_input_missing_fails() { +fn delist_from_sale_with_duplicate_input_fails() { let output1 = TradableKittyData::default_kitty(); let mut output2 = TradableKittyData::default_kitty(); output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadocz")); @@ -291,7 +367,27 @@ fn delist_from_sale_with_input_missing_fails() { &[], &[output1.into(), output2.into()], ); - assert_eq!(result, Err(TradeableKittyError::InputMissingError)); + assert_eq!( + result, + Err(TradeableKittyError::DuplicateTradableKittyFound) + ); +} + +#[test] +fn delist_from_sale_with_duplicate_output_fails() { + let output1 = TradableKittyData::default_kitty(); + let mut output2 = TradableKittyData::default_kitty(); + output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadocz")); + let mut input1 = TradableKittyData::default_tradable_kitty(); + let mut input2 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data = output1.clone(); + input2.kitty_basic_data = output2.clone(); + let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + &[input1.clone().into(), input2.into()], + &[], + &[output1.clone().into(), output1.into()], + ); + assert_eq!(result, Err(TradeableKittyError::DuplicateKittyFound)); } // From below update tradable kitty name test cases starts @@ -368,6 +464,27 @@ fn update_price_multiple_input_happy_path_works() { assert!(result.is_ok()); } +#[test] +fn update_price_multiple_input_out_of_order_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.price = Some(500); + let mut input1 = TradableKittyData::default_tradable_kitty(); + input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); + let mut output1 = input1.clone(); + output1.price = Some(700); + + let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + &[input.into(), input1.into()], + &[], + &[output1.into(), output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); +} + #[test] fn update_price_output_missing_path_fails() { let input = TradableKittyData::default_tradable_kitty(); @@ -451,11 +568,14 @@ fn update_price_different_dna_path_fails() { &[], &[output.into()], ); - assert_eq!(result, Err(TradeableKittyError::OutputUtxoMissingError)); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) + ); } #[test] -fn update_price_map_remove_check_path_fails() { +fn update_price_duplicate_input_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); output.price = Some(500); @@ -465,7 +585,10 @@ fn update_price_map_remove_check_path_fails() { &[], &[output.clone().into(), output.into()], ); - assert_eq!(result, Err(TradeableKittyError::OutputUtxoMissingError)); + assert_eq!( + result, + Err(TradeableKittyError::DuplicateTradableKittyFound) + ); } #[test] @@ -549,6 +672,41 @@ fn buy_happy_path_multiple_input_coinworks() { ); assert!(result.is_ok()); } +#[test] +fn buy_path_multiple_kitty_fails() { + let mut input_kitty = TradableKittyData::default_tradable_kitty(); + input_kitty.price = Some(100); + let output_kitty = input_kitty.clone(); + + let mut input_kitty1 = TradableKittyData::default_tradable_kitty(); + input_kitty1.kitty_basic_data.dna = + KittyDNA(H256::from_slice(b"superkalifragislisticexpialadolx")); + input_kitty1.price = Some(1); + let output_kitty1 = input_kitty1.clone(); + + let input_coin1 = Coin::<0>(10); + let input_coin2 = Coin::<0>(90); + let output_coin = Coin::<0>(100); + + let result = TradableKittyConstraintChecker::<0>::Buy.check( + &[ + input_kitty.into(), + input_kitty1.into(), + input_coin1.into(), + input_coin2.into(), + ], + &[], + &[ + output_kitty.into(), + output_kitty1.into(), + output_coin.into(), + ], + ); + assert_eq!( + result, + Err(TradeableKittyError::CannotBuyMoreThanOneKittyAtTime) + ); +} #[test] fn buy_kityy_with_price_none_fails() { @@ -666,6 +824,7 @@ fn buy_kitty_input_zero_coin_value_fails() { Err(TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin)) ) } + #[test] fn buy_kitty_output_zero_coin_value_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); From 32be05fcecd52a9bcc1ba8f2c17516e9f31591f3 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Fri, 1 Mar 2024 21:02:33 +0530 Subject: [PATCH 07/14] Code improvement Code Improvement --- wardrobe/kitties/src/lib.rs | 4 ++-- wardrobe/tradable_kitties/src/lib.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index 23de2e51f..5be0399a3 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -649,7 +649,7 @@ pub fn can_kitty_name_be_updated( } check_kitty_name_update(&utxo_input_kitty, &utxo_output_kitty)?; } - return Ok(0); + Ok(0) } /// Checks: @@ -676,5 +676,5 @@ fn check_kitty_name_update( original_kitty.parent == updated_kitty.parent, ConstraintCheckerError::KittyGenderCannotBeUpdated ); - return Ok(0); + Ok(0) } diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs index 12ef91fd2..cc081b182 100644 --- a/wardrobe/tradable_kitties/src/lib.rs +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -334,7 +334,7 @@ fn check_kitty_price_update( }; } - return Ok(0); + Ok(0) } /// Wrapper function for checking conversion from basic kitty to tradable kitty. @@ -344,7 +344,7 @@ fn check_can_list_kitty_for_sale( output_data: &[DynamicallyTypedData], ) -> Result { check_kitty_tdkitty_interconversion(&input_data, &output_data)?; - return Ok(0); + Ok(0) } /// Wrapper function for checking conversion from tradable kitty to basic kitty. @@ -356,7 +356,7 @@ fn check_can_delist_kitty_from_sale( // Below is conversion from tradable kitty to kitty, reverse of the ListKittyForSale, // hence input params are rebversed check_kitty_tdkitty_interconversion(&output_data, &input_data)?; - return Ok(0); + Ok(0) } /// Validaes inter-conversion b/w both kitty & tradable kitty.Used by listForSale & delistFromSale functions. @@ -403,7 +403,7 @@ fn check_kitty_tdkitty_interconversion( ); } - return Ok(0); + Ok(0) } impl SimpleConstraintChecker for TradableKittyConstraintChecker { From 7b0a12ecb296c443fec264efd8e6db4d99f986c9 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Fri, 8 Mar 2024 12:13:37 +0530 Subject: [PATCH 08/14] Implement the review comments Implement review comments : Main updates : 1. Price of kitty of mage mandatory. 2. DNA duplicate checks removed. 3. Documentation is revisited. 4. Test cases are updated. --- tuxedo-template-runtime/src/lib.rs | 8 +- wardrobe/kitties/src/lib.rs | 116 +++++----- wardrobe/kitties/src/tests.rs | 31 +-- wardrobe/tradable_kitties/src/lib.rs | 295 ++++++++++--------------- wardrobe/tradable_kitties/src/tests.rs | 268 ++++++++++++---------- 5 files changed, 328 insertions(+), 390 deletions(-) diff --git a/tuxedo-template-runtime/src/lib.rs b/tuxedo-template-runtime/src/lib.rs index 3afe5fe0d..dd9c4e59b 100644 --- a/tuxedo-template-runtime/src/lib.rs +++ b/tuxedo-template-runtime/src/lib.rs @@ -170,9 +170,9 @@ pub enum OuterConstraintChecker { /// Checks monetary transactions in a basic fungible cryptocurrency Money(money::MoneyConstraintChecker<0>), /// Checks Free Kitty transactions - FreeKittyConstraintChecker(kitties::FreeKittyConstraintChecker), + FreeKitty(kitties::FreeKittyConstraintChecker), /// Checks tradable Kitty transactions - TradableKittyConstraintChecker(tradable_kitties::TradableKittyConstraintChecker<0>), + TradableKitty(tradable_kitties::TradableKittyConstraintChecker<0>), /// Checks that an amoeba can split into two new amoebas AmoebaMitosis(amoeba::AmoebaMitosis), /// Checks that a single amoeba is simply removed from the state @@ -207,9 +207,9 @@ pub enum OuterConstraintChecker { /// Checks monetary transactions in a basic fungible cryptocurrency Money(money::MoneyConstraintChecker<0>), /// Checks Free Kitty transactions - FreeKittyConstraintChecker(kitties::FreeKittyConstraintChecker), + FreeKitty(kitties::FreeKittyConstraintChecker), /// Checks Paid Kitty transactions - TradableKittyConstraintChecker(tradable_kitties::TradableKittyConstraintChecker<0>), + TradableKitty(tradable_kitties::TradableKittyConstraintChecker<0>), /// Checks that an amoeba can split into two new amoebas AmoebaMitosis(amoeba::AmoebaMitosis), /// Checks that a single amoeba is simply removed from the state diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index 5be0399a3..caaf04436 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -1,37 +1,36 @@ -//! An NFT game inspired by cryptokitties. -//! This is a game which allows for kitties to be create,bred and update name of kitty. +//! An NFT game inspired by Cryptokitties. +//! This is a game that allows for the creation, breeding, and updating of the name of kitties. //! //! ## Features //! //! - **Create:** Generate a new kitty. //! To submit a valid transaction for creating a kitty, adhere to the following structure: -//! 1. Input must be empty. -//! 2. Output must contain only the newly created kittities as a child. +//! 1. The input must be empty. +//! 2. The output must contain only the newly created kitties as a child. //! -//! **Note 1:** Multiple kitties can be created at the same time in the same txn.. +//! **Note 1:** Multiple kitties can be created at the same time in the same transaction. //! //! - **Update Name:** Modify the name of a kitty. //! To submit a valid transaction for updating a kitty's name, adhere to the following structure: -//! 1. Input must be the kitty to be updated. -//! 2. Output must contain the kitty with the updated name. +//! 1. The input must be the kitty to be updated. +//! 2. The output must contain the kitty with the updated name. //! -//! **Note 1:** All other properties such as DNA, parents, free breedings, etc., must remain unaltered in the output. -//! **Note 2:** The input and output kitties must follow same order. +//! **Note 1:** All other properties, such as DNA, parents, free breedings, etc., must remain unaltered in the output. +//! **Note 2:** The input and output kitties must follow the same order. //! -//! - **Breed:** Breeds a new kitty using mom and dad based on below factors -//! 1.) Mom and Dad have to be in a state where they are ready to breed -//! 2.) Each Mom and Dad have some DNA and the child will have unique DNA combined from the both of them -//! Linkable back to the Mom and Dad -//! 3.) The game also allows Kitties to have a cooling off period inbetween breeding before they can be bred again. -//! 4.) A rest operation allows for a Mom Kitty and a Dad Kitty to be cooled off +//! - **Breed:** Breed a new kitty using mom and dad based on the factors below: +//! 1. Mom and Dad have to be in a state where they are ready to breed. +//! 2. Each Mom and Dad have some DNA, and the child will have unique DNA combined from both of them, linkable back to the Mom and Dad. +//! 3. The game also allows kitties to have a cooling-off period in between breeding before they can be bred again. +//! 4. A rest operation allows for a Mom Kitty and a Dad Kitty to cool off. //! -//! In order to submit a valid transaction you must structure it as follows: -//! 1.) Input must contain 1 mom and 1 dad -//! 2.) Output must contain Mom, Dad, and newly created Child -//! 3.) A child's DNA is calculated by: +//! In order to submit a valid breed transaction, you must structure it as follows: +//! 1. The input must contain 1 mom and 1 dad. +//! 2. The output must contain Mom, Dad, and the newly created Child. +//! 3. A child's DNA is calculated by: //! BlakeTwo256::hash_of(MomDna, DadDna, MomCurrNumBreedings, DadCurrNumberBreedings) //! -//! There are a only a finite amount of free breedings available before it starts to cost money +//! There are only a finite amount of free breedings available before it starts to cost money //! to breed kitties. #![cfg_attr(not(feature = "std"), no_std)] @@ -44,7 +43,6 @@ use sp_runtime::{ traits::{BlakeTwo256, Hash as HashT}, transaction_validity::TransactionPriority, }; -use sp_std::collections::btree_set::BTreeSet; // For checking the uniqueness of input and output based on dna. use sp_std::prelude::*; use tuxedo_core::{ dynamic_typing::{DynamicallyTypedData, UtxoData}, @@ -56,10 +54,10 @@ use tuxedo_core::{ #[cfg(test)] mod tests; -/// The main constraint checker for the kitty piece. Allows below : -/// Create : Allows creation of kitty without parents, Multiple kitties can be created in same txn. -/// UpdateKittyName : Allows updating the name of the kitty s, Multiple kitty name can be updated in same txn. -/// Breed : Allows breeding of kitty. +/// The main constraint checker for the kitty piece. Allows the following: +/// Create: Allows the creation of a kitty without parents. Multiple kitties can be created in the same transaction. +/// UpdateKittyName: Allows updating the names of the kitties. Multiple kitty names can be updated in the same transaction. +/// Breed: Allows the breeding of kitties. #[derive( Serialize, Deserialize, @@ -75,15 +73,15 @@ mod tests; TypeInfo, )] pub enum FreeKittyConstraintChecker { - /// Txn that creates kitty without parents.Multiple kitties can be created at the same time. + /// Transaction that creates a kitty without parents. Multiple kitties can be created at the same time Create, - /// Txn that updates kitty Name. Multiple kitty names can be updated. input & output must follow the same order. + /// Transaction that updates kitty names. Multiple kitty names can be updated. Input and output must follow the same order UpdateKittyName, - /// Txn where kitties are consumed and new family(Parents(mom,dad) and child) is created. + /// Transaction where kitties are consumed, and a new family (parents: mom, dad, and child) is created. Breed, } -/// Dad kitty status with respect to breeding. +/// Dad Kitty's breeding status. #[derive( Serialize, Deserialize, @@ -107,7 +105,7 @@ pub enum DadKittyStatus { Tired, } -/// Mad kitty status with respect to breeding. +/// Mom Kitty's breeding status. #[derive( Serialize, Deserialize, @@ -127,11 +125,11 @@ pub enum MomKittyStatus { #[default] /// Can breed. RearinToGo, - /// Can't breed due to recent child kitty delivery. + /// Can't breed due to a recent delivery of kittens. HadBirthRecently, } -/// Parent stuct contains 1 mom kitty and 1 dad kitty. +/// The parent structure contains 1 mom kitty and 1 dad kitty. #[derive( Serialize, Deserialize, @@ -184,11 +182,11 @@ impl Default for Parent { )] pub struct KittyDNA(pub H256); -/// Kitty data contains basic informationsuch as below : +/// Kitty data contains basic information such as below: /// parent: 1 mom kitty and 1 dad kitty. /// free_breedings: Free breeding allowed on a kitty. -/// dna :Its a unique per kitty. -/// num_breedings: number of free breedings are remaining. +/// dna: It's unique per kitty. +/// num_breedings: Number of free breedings remaining. /// name: Name of kitty. #[derive( Serialize, @@ -310,13 +308,11 @@ pub enum ConstraintCheckerError { NotEnoughFreeBreedings, /// The transaction attempts to create no Kitty. CreatingNothing, - /// Inputs(Parents) not required for mint. + /// Inputs (Parents) are not required for kitty creation. CreatingWithInputs, - /// No input for kitty Update. - InvalidNumberOfInputOutput, - /// Duplicate kitty found i.e based on the DNA. - DuplicateKittyFound, - /// Dna mismatch between input and output. + /// The number of inputs does not match the number of outputs for a transaction. + NumberOfInputOutputMismatch, + /// DNA mismatch between input and output. DnaMismatchBetweenInputAndOutput, /// Name is not updated KittyNameUnAltered, @@ -324,7 +320,7 @@ pub enum ConstraintCheckerError { FreeBreedingCannotBeUpdated, /// Kitty NumOfBreeding cannot be updated. NumOfBreedingCannotBeUpdated, - /// Gender cannot be updated + /// Gender cannot be updated. KittyGenderCannotBeUpdated, } @@ -574,19 +570,19 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { ) -> Result { match &self { Self::Create => { - // Make sure there are no inputs being consumed + // Ensure that no inputs are being consumed. ensure!( input_data.is_empty(), ConstraintCheckerError::CreatingWithInputs ); - // Make sure there is at least one output being minted + // Ensure that at least one kitty is being created. ensure!( !output_data.is_empty(), ConstraintCheckerError::CreatingNothing ); - // Make sure the outputs are the right type + // Ensure the outputs are the right type. for utxo in output_data { let _utxo_kitty = utxo .extract::() @@ -595,13 +591,13 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { Ok(0) } Self::Breed => { - // Check that we are consuming at least one input + // Check that we are consuming at least one input. ensure!(input_data.len() == 2, Self::Error::TwoParentsDoNotExist); let mom = KittyData::try_from(&input_data[0])?; let dad = KittyData::try_from(&input_data[1])?; KittyHelpers::can_breed(&mom, &dad)?; - // Output must be Mom, Dad, Child + // Output must be Mom, Dad, and Child. ensure!(output_data.len() == 3, Self::Error::NotEnoughFamilyMembers); KittyHelpers::check_new_family(&mom, &dad, output_data)?; Ok(0) @@ -615,18 +611,17 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { } /// Checks: -/// - Input and output is of kittyType -/// - Only name is updated and ther basic properties are not updated. -/// - Order between input and output must be same. +/// - Input and output are of kittyType. +/// - Only the name is updated, and other basic properties are not modified. +/// - The order between input and output must be the same. pub fn can_kitty_name_be_updated( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { ensure!( input_data.len() == output_data.len() && !input_data.is_empty(), - { ConstraintCheckerError::InvalidNumberOfInputOutput } + { ConstraintCheckerError::NumberOfInputOutputMismatch } ); - let mut dna_to_kitty_set: BTreeSet = BTreeSet::new(); for i in 0..input_data.len() { let utxo_input_kitty = input_data[i] @@ -634,32 +629,27 @@ pub fn can_kitty_name_be_updated( .extract::() .map_err(|_| ConstraintCheckerError::BadlyTyped)?; - if dna_to_kitty_set.contains(&utxo_input_kitty.dna) { - return Err(ConstraintCheckerError::DuplicateKittyFound); - } else { - dna_to_kitty_set.insert(utxo_input_kitty.clone().dna); - } - let utxo_output_kitty = output_data[i] .clone() .extract::() .map_err(|_| ConstraintCheckerError::BadlyTyped)?; - if utxo_input_kitty.dna != utxo_output_kitty.dna { - return Err(ConstraintCheckerError::DnaMismatchBetweenInputAndOutput); - } check_kitty_name_update(&utxo_input_kitty, &utxo_output_kitty)?; } Ok(0) } /// Checks: -/// - Private function used by can_kitty_name_be_updated. -/// - Only name is updated and ther basic properties are not updated. +/// - This is a private function used by can_kitty_name_be_updated. +/// - Only the name is updated, and other basic properties are not updated. /// fn check_kitty_name_update( original_kitty: &KittyData, updated_kitty: &KittyData, ) -> Result { + ensure!( + original_kitty.dna == updated_kitty.dna, + ConstraintCheckerError::DnaMismatchBetweenInputAndOutput + ); ensure!( original_kitty != updated_kitty, ConstraintCheckerError::KittyNameUnAltered diff --git a/wardrobe/kitties/src/tests.rs b/wardrobe/kitties/src/tests.rs index 1b760cb63..4a2850956 100644 --- a/wardrobe/kitties/src/tests.rs +++ b/wardrobe/kitties/src/tests.rs @@ -1,13 +1,7 @@ //! Tests for the Crypto Kitties Piece use super::*; -/// A bogus data type used in tests for type validation -#[derive(Encode, Decode)] -struct Bogus; - -impl UtxoData for Bogus { - const TYPE_ID: [u8; 4] = *b"bogs"; -} +use tuxedo_core::dynamic_typing::testing::Bogus; impl KittyData { pub fn default_dad() -> Self { @@ -587,9 +581,10 @@ fn update_name_inputs_and_outputs_number_mismatch_fails() { ); assert_eq!( result, - Err(ConstraintCheckerError::InvalidNumberOfInputOutput) + Err(ConstraintCheckerError::NumberOfInputOutputMismatch) ); } + #[test] fn update_name_no_inputs_fails() { let output = KittyData::default_dad(); @@ -602,7 +597,7 @@ fn update_name_no_inputs_fails() { ); assert_eq!( result, - Err(ConstraintCheckerError::InvalidNumberOfInputOutput) + Err(ConstraintCheckerError::NumberOfInputOutputMismatch) ); } @@ -618,9 +613,10 @@ fn update_name_no_output_fails() { ); assert_eq!( result, - Err(ConstraintCheckerError::InvalidNumberOfInputOutput) + Err(ConstraintCheckerError::NumberOfInputOutputMismatch) ); } + #[test] fn update_name_dna_update_fails() { let input = KittyData::default_dad(); @@ -640,21 +636,6 @@ fn update_name_dna_update_fails() { ); } -#[test] -fn update_name_duplicate_dna_update_fails() { - let input = KittyData::default_dad(); - let mut output = input.clone(); - output.name = *b"kty1"; - - let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, - &[input.clone().into(), input.into()], - &[], - &[output.clone().into(), output.into()], - ); - assert_eq!(result, Err(ConstraintCheckerError::DuplicateKittyFound)); -} - #[test] fn update_name_out_of_order_input_and_output_fails() { let input = KittyData::default_dad(); diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs index cc081b182..d7b72fedf 100644 --- a/wardrobe/tradable_kitties/src/lib.rs +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -1,33 +1,32 @@ //! # TradableKitty Module //! +//! This Tuxedo piece codifies additional features that work with the Kitties piece. +//! This piece should not and cannot be used without that piece. //! The `TradableKitty` module defines a specialized type of kitty tailored for trading with unique features. //! -//! ## Features +//! TradableKitties enrich the user experience by introducing trading/selling capabilities. +//! The following are features supported: //! -//! The module supports multiple tradable kitties in the same transactions. Note that the input and output kitties must follow the same order. -//! -//! - **ListKittyForSale:** Convert basic kitties into tradable kitties, adding a `Price` field. +//! - **ListKittyForSale:** Convert basic kitties into tradable kitties, adding a `price` field. //! - **DelistKittyFromSale:** Transform tradable kitties back into regular kitties when owners decide not to sell. -//! - **UpdateKittyPrice:** Allow owners to modify the price of TradableKitties. -//! - **UpdateKittyName:** Permit owners to update the name of TradableKitties. +//! - **UpdateKittyPrice:** Allow owners to modify the `price` of TradableKitties. +//! - **UpdateKittyName:** Permit owners to update the `name` of TradableKitties. //! //! *Note: Only one kitty can be traded at a time.* //! //! - **Buy:** Enable users to securely purchase TradableKitty from others, ensuring fair exchanges. +//! Make sure to place the kitty first and then coins in the inputs and outputs. //! -//! TradableKitties enrich the user experience by introducing advanced trading capabilities. //! #![cfg_attr(not(feature = "std"), no_std)] -use kitties::{KittyDNA, KittyData}; -use money::ConstraintCheckerError as MoneyError; -use money::{Coin, MoneyConstraintChecker}; +use kitties::KittyData; +use money::{Coin, ConstraintCheckerError as MoneyError, MoneyConstraintChecker}; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::transaction_validity::TransactionPriority; -use sp_std::collections::btree_set::BTreeSet; // For checking the uniqueness of input and output based on dna. use sp_std::prelude::*; use tuxedo_core::{ dynamic_typing::{DynamicallyTypedData, UtxoData}, @@ -37,8 +36,11 @@ use tuxedo_core::{ #[cfg(test)] mod tests; -/// A tradableKittyData, required for trading the kitty.. -/// It contains optional price of tradable-kitty in addition to the basic kitty data. +/// The default price of a kitten is 10. +const DEFAULT_KITTY_PRICE: u128 = 10; + +/// A `TradableKittyData` is required for trading the kitty. +/// It includes the `price` of the kitty, in addition to the basic `KittyData`. #[derive( Serialize, Deserialize, @@ -54,17 +56,17 @@ mod tests; TypeInfo, )] pub struct TradableKittyData { - /// Basic kitty data composed from kitties piece + /// Basic `KittyData` composed from the `kitties` piece. pub kitty_basic_data: KittyData, - /// Price of the tradable kitty - pub price: Option, + /// Price of the `TradableKitty` + pub price: u128, } impl Default for TradableKittyData { fn default() -> Self { Self { kitty_basic_data: KittyData::default(), - price: None, + price: DEFAULT_KITTY_PRICE, } } } @@ -97,32 +99,24 @@ impl UtxoData for TradableKittyData { TypeInfo, )] pub enum TradeableKittyError { - /// Error in the underlying money piece. + /// Error in the underlying `money` piece. MoneyError(money::ConstraintCheckerError), - /// Error in the underlying kitty piece. + /// Error in the underlying `kitties` piece. KittyError(kitties::ConstraintCheckerError), /// Dynamic typing issue. /// This error doesn't discriminate between badly typed inputs and outputs. BadlyTyped, /// Input missing for the transaction. InputMissingError, - /// Not enough amount to buy a kitty. + /// Not enough amount to buy a `kitty`. InsufficientCollateralToBuyKitty, /// The number of input vs number of output doesn't match for a transaction. NumberOfInputOutputMismatch, - /// Can't buy more than one kitty at a time. - CannotBuyMoreThanOneKittyAtTime, - /// Kitty basic properties such as DNA, free breeding, and a number of breedings, are altered error. + /// Kitty basic properties such as `DNA`, `free breeding`, and the `number of breedings`, are altered error. KittyBasicPropertiesAltered, - /// Kitty not available for sale.Occur when price is None. - KittyNotForSale, - /// Kitty price cant be none when it is available for sale. - KittyPriceCantBeNone, - /// Duplicate kitty foundi.e based on the DNA. - DuplicateKittyFound, - /// Duplicate tradable kitty foundi.e based on the DNA. - DuplicateTradableKittyFound, - /// Kitty price is unaltered is not allowed for kitty price update transactions. + /// Kitty `price` can't be zero when it is available for sale. + KittyPriceCantBeZero, + /// Kitty `price` is unaltered and is not allowed for kitty price update transactions. KittyPriceUnaltered, } @@ -138,12 +132,14 @@ impl From for TradeableKittyError { } } -/// The main constraint checker for the trdable kitty piece. Allows below : -/// Listing kitty for sale : multiple kiities are allowed, provided input and output in same order. -/// Delisting kitty from sale : multiple tradable kiities are allowed, provided input and output in same order. -/// Update kitty price : multiple tradable kiities are allowed, provided input and output in same order. -/// Update kitty name : multiple tradable kiities are allowed, provided input and output in same order. -/// Buy tradable kitty : multiple tradable kiities are not allowed, Only single kiity operation is allowed. +/// The main constraint checker for the tradable kitty piece. Allows the following: +/// Listing kitty for sale: Multiple kitties are allowed, provided input and output are in the same order. +/// Delisting kitty from sale: Multiple tradable kitties are allowed, provided input and output are in the same order. +/// Update kitty price: Multiple tradable kitties are allowed, provided input and output are in the same order. +/// Update kitty name: Multiple tradable kitties are allowed, provided input and output are in the same order. +/// Buy tradable kitty: Multiple tradable kitties are not allowed. Only a single kitty operation is allowed. +/// For buying a kitty, you need to send the kitty first, and then coins in both input and output of the transaction. + #[derive( Serialize, Deserialize, @@ -159,19 +155,19 @@ impl From for TradeableKittyError { TypeInfo, )] pub enum TradableKittyConstraintChecker { - /// List kitty for sale, means kitty will converted to tradable kitty once transaction is executed + /// List the kitty for sale. This means the kitty will be converted to a tradable kitty once the transaction is executed.. ListKittyForSale, - /// Delist kitty from sale, means tradable kitty will converted back to kitty + /// Delist the kitty from sale, This means tradable kitty will converted back to kitty. DelistKittyFromSale, - /// Update price of tradable kitty. + /// Update the `price` of tradable kitty. UpdateKittyPrice, - // Update name of kitty + // Update the name of the kitty. UpdateKittyName, - /// For buying a new kitty from others + /// For buying a new kitty from other owners. Buy, } -/// Extracts basic kitty data from a list of dynamically typed TradableKitty data, populating basic kitty data list. +/// Extract basic kitty data from a list of dynamically typed `TradableKitty` data, populating a list with basic kitty data. fn extract_basic_kitty_list( tradable_kitty_data: &[DynamicallyTypedData], kitty_data_list: &mut Vec, @@ -186,112 +182,85 @@ fn extract_basic_kitty_list( Ok(()) } -/// Checks if buying the kitty is possible of not. It depends on Money piece to validate spending of coins. +/// Checks if buying the kitty is possible or not. It depends on the Money variable to validate the spending of coins. +/// Make sure to place the kitty first and then the coins in the transaction. fn check_can_buy( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], -) -> Result<(), TradeableKittyError> { +) -> Result { let mut input_coin_data: Vec = Vec::new(); let mut output_coin_data: Vec = Vec::new(); - let mut input_ktty_to_be_traded: Option = None; - let mut output_ktty_to_be_traded: Option = None; + let input_kitty_to_be_traded: Option; let mut total_input_amount: u128 = 0; - let mut total_price_of_kitty: u128 = 0; - - // Seperate the coin and tradable kitty in to seperate vecs from the input_data . - for utxo in input_data { - if let Ok(coin) = utxo.extract::>() { - let utxo_value = coin.0; - - ensure!( - utxo_value > 0, - TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) - ); - input_coin_data.push(utxo.clone()); - total_input_amount = total_input_amount - .checked_add(utxo_value) - .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; - } else if let Ok(td_input_kitty) = utxo.extract::() { - // Process tradable kitty - // Checking if more than 1 kitty is sent for trading. - match input_ktty_to_be_traded { - None => { - // 1st tradable kitty is received in the input. - input_ktty_to_be_traded = Some(td_input_kitty.clone()); - let price = match td_input_kitty.price { - None => return Err(TradeableKittyError::KittyNotForSale), - Some(p) => p, - }; - total_price_of_kitty = total_price_of_kitty - .checked_add(price) - .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; - } - Some(_) => { - // More than 1 tradable kitty are sent for trading. - return Err(TradeableKittyError::CannotBuyMoreThanOneKittyAtTime); - } - }; - } else { - return Err(TradeableKittyError::BadlyTyped); - } + let total_price_of_kitty: u128; + + if let Ok(td_input_kitty) = input_data[0].extract::() { + ensure!( + td_input_kitty.price != 0, + TradeableKittyError::KittyPriceCantBeZero + ); + input_kitty_to_be_traded = Some(td_input_kitty.clone()); + total_price_of_kitty = td_input_kitty.price; + } else { + return Err(TradeableKittyError::BadlyTyped); } - // Ensuring we found kitty in the input - ensure!(input_ktty_to_be_traded != None, { - TradeableKittyError::InputMissingError - }); - - // Seperate the coin and tdkitty in to seperate vecs from the output_data . - for utxo in output_data { - if let Ok(coin) = utxo.extract::>() { - let utxo_value = coin.0; - ensure!( - utxo_value > 0, - TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) - ); - output_coin_data.push(utxo.clone()); - // Process Coin - } else if let Ok(td_output_kitty) = utxo.extract::() { - // Checking if more than 1 kitty in output is sent for trading. - match output_ktty_to_be_traded { - None => { - // 1st tradable kitty is received in the output. - output_ktty_to_be_traded = Some(td_output_kitty.clone()); - ensure!( - input_ktty_to_be_traded.clone().unwrap().kitty_basic_data - == td_output_kitty.kitty_basic_data, // basic kitty data is unaltered - TradeableKittyError::KittyBasicPropertiesAltered // this need to be chan - ); - } - Some(_) => { - // More than 1 tradable kitty are sent in output for trading. - return Err(TradeableKittyError::CannotBuyMoreThanOneKittyAtTime); - } - }; - } else { - return Err(TradeableKittyError::BadlyTyped); - } + if let Ok(td_output_kitty) = output_data[0].extract::() { + ensure!( + input_kitty_to_be_traded.clone().unwrap().kitty_basic_data + == td_output_kitty.kitty_basic_data, + TradeableKittyError::KittyBasicPropertiesAltered + ); + } else { + return Err(TradeableKittyError::BadlyTyped); + } + + for i in 1..input_data.len() { + let coin = input_data[i] + .clone() + .extract::>() + .map_err(|_| TradeableKittyError::BadlyTyped)?; + + let utxo_value = coin.0; + ensure!( + utxo_value > 0, + TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) + ); + input_coin_data.push(input_data[i].clone()); + total_input_amount = total_input_amount + .checked_add(utxo_value) + .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; } - // Ensuring we found kitty in the output - ensure!(output_ktty_to_be_traded != None, { - TradeableKittyError::InputMissingError - }); + for i in 1..output_data.len() { + let coin = output_data[i] + .clone() + .extract::>() + .map_err(|_| TradeableKittyError::BadlyTyped)?; - // Ensuring total money sent is enough to buy the kitty + let utxo_value = coin.0; + ensure!( + utxo_value > 0, + TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) + ); + ensure!( + utxo_value > 0, + TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) + ); + output_coin_data.push(output_data[i].clone()); + } ensure!( total_price_of_kitty <= total_input_amount, TradeableKittyError::InsufficientCollateralToBuyKitty ); - // Filterd coins sent to MoneyConstraintChecker for money validation. - MoneyConstraintChecker::<0>::Spend.check(&input_coin_data, &[], &output_coin_data)?; - Ok(()) + // Filtered coins are sent to MoneyConstraintChecker for money validation. + Ok(MoneyConstraintChecker::<0>::Spend.check(&input_coin_data, &[], &output_coin_data)?) } -/// checks if tradable kitty price updates is possible of not. -/// Price of multiple tradable kitties can be updated in the same txn. +/// Checks if updates to the prices of tradable kitties are possible or not. +/// Prices of multiple tradable kitties can be updated in the same transaction. fn check_kitty_price_update( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], @@ -300,20 +269,11 @@ fn check_kitty_price_update( input_data.len() == output_data.len() && !input_data.is_empty(), { TradeableKittyError::NumberOfInputOutputMismatch } ); - - let mut dna_to_tdkitty_set: BTreeSet = BTreeSet::new(); // to check the uniqueness in input - //input td-kitty and output td kitties need to be in same order. for i in 0..input_data.len() { let utxo_input_tradable_kitty = input_data[i] .extract::() .map_err(|_| TradeableKittyError::BadlyTyped)?; - if dna_to_tdkitty_set.contains(&utxo_input_tradable_kitty.kitty_basic_data.dna) { - return Err(TradeableKittyError::DuplicateTradableKittyFound); - } else { - dna_to_tdkitty_set.insert(utxo_input_tradable_kitty.clone().kitty_basic_data.dna); - } - let utxo_output_tradable_kitty = output_data[i] .extract::() .map_err(|_| TradeableKittyError::BadlyTyped)?; @@ -323,22 +283,21 @@ fn check_kitty_price_update( == utxo_output_tradable_kitty.kitty_basic_data, TradeableKittyError::KittyBasicPropertiesAltered ); - match utxo_output_tradable_kitty.price { - Some(_) => { - ensure!( - utxo_input_tradable_kitty.price != utxo_output_tradable_kitty.price, // kitty ptice is unaltered - TradeableKittyError::KittyPriceUnaltered - ); - } - None => return Err(TradeableKittyError::KittyPriceCantBeNone), - }; + ensure!( + utxo_output_tradable_kitty.price != 0, + TradeableKittyError::KittyPriceCantBeZero + ); + ensure!( + utxo_input_tradable_kitty.price != utxo_output_tradable_kitty.price, + TradeableKittyError::KittyPriceUnaltered + ); } Ok(0) } -/// Wrapper function for checking conversion from basic kitty to tradable kitty. -/// Multiple kitties can be converted in the same txn. +/// Wrapper function for verifying the conversion from basic kitties to tradable kitties. +/// Multiple kitties can be converted in a single transaction. fn check_can_list_kitty_for_sale( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], @@ -347,19 +306,19 @@ fn check_can_list_kitty_for_sale( Ok(0) } -/// Wrapper function for checking conversion from tradable kitty to basic kitty. -/// Multiple kitties can be converted from tradable to non-tradable in the same txn. +/// Wrapper function for verifying the conversion from tradable kitties to basic kitties. +/// Multiple tradable kitties can be converted in a single transaction. fn check_can_delist_kitty_from_sale( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { - // Below is conversion from tradable kitty to kitty, reverse of the ListKittyForSale, - // hence input params are rebversed + // Below is the conversion from tradable kitty to regular kitty, the reverse of the ListKittyForSale. + // Hence, input parameters are reversed. check_kitty_tdkitty_interconversion(&output_data, &input_data)?; Ok(0) } -/// Validaes inter-conversion b/w both kitty & tradable kitty.Used by listForSale & delistFromSale functions. +/// Validates inter-conversion between both regular kitties and tradable kitties, as used by the `listForSale` and `delistFromSale` functions. fn check_kitty_tdkitty_interconversion( kitty_data: &[DynamicallyTypedData], tradable_kitty_data: &[DynamicallyTypedData], @@ -369,37 +328,22 @@ fn check_kitty_tdkitty_interconversion( { TradeableKittyError::NumberOfInputOutputMismatch } ); - let mut dna_to_tdkitty_set: BTreeSet = BTreeSet::new(); - let mut dna_to_kitty_set: BTreeSet = BTreeSet::new(); - for i in 0..kitty_data.len() { let utxo_kitty = kitty_data[i] .extract::() .map_err(|_| TradeableKittyError::BadlyTyped)?; - if dna_to_kitty_set.contains(&utxo_kitty.dna) { - return Err(TradeableKittyError::DuplicateKittyFound); - } else { - dna_to_kitty_set.insert(utxo_kitty.clone().dna); - } - let utxo_tradable_kitty = tradable_kitty_data[i] .extract::() .map_err(|_| TradeableKittyError::BadlyTyped)?; - if dna_to_tdkitty_set.contains(&utxo_tradable_kitty.kitty_basic_data.dna) { - return Err(TradeableKittyError::DuplicateTradableKittyFound); - } else { - dna_to_tdkitty_set.insert(utxo_tradable_kitty.clone().kitty_basic_data.dna); - } - ensure!( utxo_kitty == utxo_tradable_kitty.kitty_basic_data, TradeableKittyError::KittyBasicPropertiesAltered ); ensure!( - utxo_tradable_kitty.price != None, // basic kitty data is unaltered - TradeableKittyError::KittyPriceCantBeNone // this need to be chan + utxo_tradable_kitty.price != 0, + TradeableKittyError::KittyPriceCantBeZero ); } @@ -418,7 +362,6 @@ impl SimpleConstraintChecker for TradableKittyConstraintChecker { check_can_list_kitty_for_sale(&input_data, &output_data)?; - return Ok(0); } Self::DelistKittyFromSale => { check_can_delist_kitty_from_sale(&input_data, &output_data)?; @@ -437,8 +380,8 @@ impl SimpleConstraintChecker for TradableKittyConstraintChecker { - check_can_buy::(input_data, output_data)?; - return Ok(0); + let priority = check_can_buy::(input_data, output_data)?; + return Ok(priority); } } Ok(0) diff --git a/wardrobe/tradable_kitties/src/tests.rs b/wardrobe/tradable_kitties/src/tests.rs index 366f48a93..e8e9b4eac 100644 --- a/wardrobe/tradable_kitties/src/tests.rs +++ b/wardrobe/tradable_kitties/src/tests.rs @@ -1,18 +1,12 @@ -//! Tests for the Crypto Kitties Piece +//! Tests for the Tradable Kitty Piece use super::*; use kitties::DadKittyStatus; use kitties::KittyDNA; +use kitties::MomKittyStatus; use kitties::Parent; use sp_runtime::testing::H256; - -/// A bogus data type used in tests for type validation -#[derive(Encode, Decode)] -struct Bogus; - -impl UtxoData for Bogus { - const TYPE_ID: [u8; 4] = *b"bogs"; -} +use tuxedo_core::dynamic_typing::testing::Bogus; impl TradableKittyData { pub fn default_kitty() -> KittyData { @@ -29,12 +23,12 @@ impl TradableKittyData { }; TradableKittyData { kitty_basic_data: kitty_basic, - price: Some(100), + price: 100, ..Default::default() } } } -// List kitty UT startes from here +// listKittyForSale UT startes from here. #[test] fn list_kitty_for_sale_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( @@ -126,29 +120,6 @@ fn list_kitty_for_sale_out_put_missing_path_fails() { ); } -#[test] -fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { - let input1 = TradableKittyData::default_kitty(); - let mut output1 = TradableKittyData::default_tradable_kitty(); - output1.kitty_basic_data = input1.clone(); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( - &[input1.into()], - &[], - &[Bogus.into()], - ); - assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); -} - -#[test] -fn list_kitty_for_sale_with_wrong_input_type_fails() { - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( - &[Bogus.into()], - &[], - &[TradableKittyData::default_tradable_kitty().into()], - ); - assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); -} - #[test] fn list_kitty_for_sale_with_input_missing_fails() { let input1 = TradableKittyData::default_kitty(); @@ -169,24 +140,30 @@ fn list_kitty_for_sale_with_input_missing_fails() { } #[test] -fn list_kitty_for_sale_with_duplicate_input_fails() { +fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { let input1 = TradableKittyData::default_kitty(); - let input2 = TradableKittyData::default_kitty(); - let mut output1 = TradableKittyData::default_tradable_kitty(); output1.kitty_basic_data = input1.clone(); - let mut output2 = TradableKittyData::default_tradable_kitty(); - output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( - &[input1.into(), input2.into()], + &[input1.into()], &[], - &[output1.into(), output2.into()], + &[Bogus.into()], ); - assert_eq!(result, Err(TradeableKittyError::DuplicateKittyFound)); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] -fn list_kitty_for_sale_with_duplicate_output_fails() { +fn list_kitty_for_sale_with_wrong_input_type_fails() { + let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + &[Bogus.into()], + &[], + &[TradableKittyData::default_tradable_kitty().into()], + ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); +} + +#[test] +fn list_for_sale_multiple_inputs_with_basic_property_changed_fails() { let input1 = TradableKittyData::default_kitty(); let input2 = TradableKittyData::default_kitty(); @@ -197,9 +174,12 @@ fn list_kitty_for_sale_with_duplicate_output_fails() { let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[input1.into(), input2.into()], &[], - &[output1.clone().into(), output1.into()], + &[output1.clone().into(), output2.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyBasicPropertiesAltered) ); - assert_eq!(result, Err(TradeableKittyError::DuplicateKittyFound)); } #[test] @@ -220,21 +200,20 @@ fn list_for_sale_with_basic_property_changed_fails() { } #[test] -fn list_for_sale_with_price_none_fails() { +fn list_for_sale_with_price_zero_fails() { let input1 = TradableKittyData::default_kitty(); let mut output1 = TradableKittyData::default_tradable_kitty(); output1.kitty_basic_data = input1.clone(); - output1.price = None; + output1.price = 0; let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( &[input1.into()], &[], &[output1.into()], ); - assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeNone)); + assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeZero)); } -// From below delist tradable kitty test cases start. - +// delistKittyFromSale UT starts from here. #[test] fn delist_kitty_from_sale_happy_path_works() { let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( @@ -354,7 +333,7 @@ fn delist_from_sale_with_wrong_input_type_fails() { } #[test] -fn delist_from_sale_with_duplicate_input_fails() { +fn delist_from_sale_with_basic_property_update_fails() { let output1 = TradableKittyData::default_kitty(); let mut output2 = TradableKittyData::default_kitty(); output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadocz")); @@ -363,31 +342,14 @@ fn delist_from_sale_with_duplicate_input_fails() { input1.kitty_basic_data = output1.clone(); input2.kitty_basic_data = output2.clone(); let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( - &[input1.clone().into(), input1.into()], + &[input1.clone().into(), input2.into()], &[], - &[output1.into(), output2.into()], + &[output1.clone().into(), output1.into()], ); assert_eq!( result, - Err(TradeableKittyError::DuplicateTradableKittyFound) - ); -} - -#[test] -fn delist_from_sale_with_duplicate_output_fails() { - let output1 = TradableKittyData::default_kitty(); - let mut output2 = TradableKittyData::default_kitty(); - output2.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadocz")); - let mut input1 = TradableKittyData::default_tradable_kitty(); - let mut input2 = TradableKittyData::default_tradable_kitty(); - input1.kitty_basic_data = output1.clone(); - input2.kitty_basic_data = output2.clone(); - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( - &[input1.clone().into(), input2.into()], - &[], - &[output1.clone().into(), output1.into()], + Err(TradeableKittyError::KittyBasicPropertiesAltered) ); - assert_eq!(result, Err(TradeableKittyError::DuplicateKittyFound)); } // From below update tradable kitty name test cases starts @@ -418,6 +380,7 @@ fn update_name_invalid_type_in_input_fails() { ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } + #[test] fn update_name_invalid_type_in_output_fails() { let input = TradableKittyData::default_tradable_kitty(); @@ -431,12 +394,93 @@ fn update_name_invalid_type_in_output_fails() { ); assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } -//// Price update test cases + +#[test] +fn update_name_dna_update_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); + output.kitty_basic_data.name = *b"tdkt"; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyError( + kitties::ConstraintCheckerError::DnaMismatchBetweenInputAndOutput + )) + ); +} + +#[test] +fn update_name_free_breeding_update_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"kty1"; + output.kitty_basic_data.free_breedings += 1; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyError( + kitties::ConstraintCheckerError::FreeBreedingCannotBeUpdated + )) + ); +} + +#[test] +fn update_name_num_of_breeding_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = input.clone(); + output.kitty_basic_data.name = *b"kty1"; + output.kitty_basic_data.num_breedings += 1; + + let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyError( + kitties::ConstraintCheckerError::NumOfBreedingCannotBeUpdated + )) + ); +} + +#[test] +fn update_name_gender_updated_path_fails() { + let input = TradableKittyData::default_tradable_kitty(); + let mut output = TradableKittyData::default_tradable_kitty(); + output.kitty_basic_data.name = *b"tdk1"; + output.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::RearinToGo); + + let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + &[input.into()], + &[], + &[output.into()], + ); + assert_eq!( + result, + Err(TradeableKittyError::KittyError( + kitties::ConstraintCheckerError::KittyGenderCannotBeUpdated + )) + ); +} + +//// Price update UT starts from here. #[test] fn update_price_happy_path_works() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into()], @@ -450,11 +494,11 @@ fn update_price_happy_path_works() { fn update_price_multiple_input_happy_path_works() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; let mut input1 = TradableKittyData::default_tradable_kitty(); input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); let mut output1 = input1.clone(); - output1.price = Some(700); + output1.price = 700; let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into(), input1.into()], @@ -468,11 +512,11 @@ fn update_price_multiple_input_happy_path_works() { fn update_price_multiple_input_out_of_order_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; let mut input1 = TradableKittyData::default_tradable_kitty(); input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); let mut output1 = input1.clone(); - output1.price = Some(700); + output1.price = 700; let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into(), input1.into()], @@ -489,7 +533,7 @@ fn update_price_multiple_input_out_of_order_fails() { fn update_price_output_missing_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; let mut input1 = TradableKittyData::default_tradable_kitty(); input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); @@ -508,11 +552,11 @@ fn update_price_output_missing_path_fails() { fn update_price_input_missing_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; let mut input1 = TradableKittyData::default_tradable_kitty(); input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); let mut output1 = input1.clone(); - output1.price = Some(700); + output1.price = 700; let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into()], @@ -529,7 +573,7 @@ fn update_price_input_missing_path_fails() { fn update_price_bad_input_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[Bogus.into()], @@ -543,7 +587,7 @@ fn update_price_bad_input_path_fails() { fn update_price_bad_output_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; let mut input1 = TradableKittyData::default_tradable_kitty(); input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); @@ -559,7 +603,7 @@ fn update_price_bad_output_path_fails() { fn update_price_different_dna_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; output.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); @@ -574,28 +618,11 @@ fn update_price_different_dna_path_fails() { ); } -#[test] -fn update_price_duplicate_input_path_fails() { - let input = TradableKittyData::default_tradable_kitty(); - let mut output = input.clone(); - output.price = Some(500); - // check that only 1 instance with duplicate dna is inserted and when 1st is removed , 2nd one fails. - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( - &[input.clone().into(), input.into()], - &[], - &[output.clone().into(), output.into()], - ); - assert_eq!( - result, - Err(TradeableKittyError::DuplicateTradableKittyFound) - ); -} - #[test] fn update_price_basic_properties_updated_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = Some(500); + output.price = 500; output.kitty_basic_data.free_breedings += 1; let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( @@ -623,25 +650,25 @@ fn update_price_not_updated_path_fails() { } #[test] -fn update_price_to_null_updated_path_fails() { +fn update_price_to_zero_updated_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let mut output = input.clone(); - output.price = None; + output.price = 0; let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( &[input.into()], &[], &[output.into()], ); - assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeNone)); + assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeZero)); } -// From below buy tradable kitty test cases start. +// Buy tradable Kitty UT starts from here. #[test] fn buy_happy_path_single_input_coinworks() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(100); + input_kitty.price = 100; let output_kitty = input_kitty.clone(); let input_coin = Coin::<0>(100); @@ -658,7 +685,7 @@ fn buy_happy_path_single_input_coinworks() { #[test] fn buy_happy_path_multiple_input_coinworks() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(100); + input_kitty.price = 100; let output_kitty = input_kitty.clone(); let input_coin1 = Coin::<0>(10); @@ -672,16 +699,17 @@ fn buy_happy_path_multiple_input_coinworks() { ); assert!(result.is_ok()); } + #[test] fn buy_path_multiple_kitty_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(100); + input_kitty.price = 100; let output_kitty = input_kitty.clone(); let mut input_kitty1 = TradableKittyData::default_tradable_kitty(); input_kitty1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadolx")); - input_kitty1.price = Some(1); + input_kitty1.price = 1; let output_kitty1 = input_kitty1.clone(); let input_coin1 = Coin::<0>(10); @@ -702,16 +730,13 @@ fn buy_path_multiple_kitty_fails() { output_coin.into(), ], ); - assert_eq!( - result, - Err(TradeableKittyError::CannotBuyMoreThanOneKittyAtTime) - ); + assert_eq!(result, Err(TradeableKittyError::BadlyTyped)); } #[test] fn buy_kityy_with_price_none_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = None; + input_kitty.price = 0; let output_kitty = input_kitty.clone(); let input_coin = Coin::<0>(100); @@ -722,14 +747,14 @@ fn buy_kityy_with_price_none_fails() { &[], &[output_kitty.into(), output_coin.into()], ); - assert_eq!(result, Err(TradeableKittyError::KittyNotForSale)); + assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeZero)); } #[test] fn buy_kityy_wrong_input_type_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(101); + input_kitty.price = 101; let output_kitty = input_kitty.clone(); let input_coin = Coin::<0>(100); @@ -746,7 +771,7 @@ fn buy_kityy_wrong_input_type_fails() { #[test] fn buy_kityy_wrong_output_type_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(101); + input_kitty.price = 101; let output_kitty = input_kitty.clone(); let input_coin = Coin::<0>(100); @@ -763,11 +788,10 @@ fn buy_kityy_wrong_output_type_fails() { #[test] fn buy_kitty_less_money_than_price_of_kityy_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(101); + input_kitty.price = 101; let output_kitty = input_kitty.clone(); let input_coin1 = Coin::<0>(100); - // let input_coin2 = Coin::<0>(90); let output_coin = Coin::<0>(100); let result = TradableKittyConstraintChecker::<0>::Buy.check( @@ -784,7 +808,7 @@ fn buy_kitty_less_money_than_price_of_kityy_fails() { #[test] fn buy_kitty_coin_output_value_exceeds_input_coin_value_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(101); + input_kitty.price = 101; let output_kitty = input_kitty.clone(); let input_coin1 = Coin::<0>(100); @@ -807,7 +831,7 @@ fn buy_kitty_coin_output_value_exceeds_input_coin_value_fails() { #[test] fn buy_kitty_input_zero_coin_value_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(101); + input_kitty.price = 101; let output_kitty = input_kitty.clone(); let input_coin1 = Coin::<0>(0); @@ -828,7 +852,7 @@ fn buy_kitty_input_zero_coin_value_fails() { #[test] fn buy_kitty_output_zero_coin_value_fails() { let mut input_kitty = TradableKittyData::default_tradable_kitty(); - input_kitty.price = Some(101); + input_kitty.price = 101; let output_kitty = input_kitty.clone(); let input_coin1 = Coin::<0>(100); From 389576b78fe21c558ad35130530a89ab421d2c91 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Fri, 8 Mar 2024 14:21:56 +0530 Subject: [PATCH 09/14] Fix clippy issues Fixed clippy issues --- wardrobe/kitties/src/lib.rs | 7 ++++--- wardrobe/tradable_kitties/src/lib.rs | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index caaf04436..93f777da7 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -623,16 +623,17 @@ pub fn can_kitty_name_be_updated( { ConstraintCheckerError::NumberOfInputOutputMismatch } ); - for i in 0..input_data.len() { - let utxo_input_kitty = input_data[i] + for (input, output) in input_data.iter().zip(output_data.iter()) { + let utxo_input_kitty = input .clone() .extract::() .map_err(|_| ConstraintCheckerError::BadlyTyped)?; - let utxo_output_kitty = output_data[i] + let utxo_output_kitty = output .clone() .extract::() .map_err(|_| ConstraintCheckerError::BadlyTyped)?; + check_kitty_name_update(&utxo_input_kitty, &utxo_output_kitty)?; } Ok(0) diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs index d7b72fedf..19c26900d 100644 --- a/wardrobe/tradable_kitties/src/lib.rs +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -216,8 +216,8 @@ fn check_can_buy( return Err(TradeableKittyError::BadlyTyped); } - for i in 1..input_data.len() { - let coin = input_data[i] + for coin_data in input_data.iter().skip(1) { + let coin = coin_data .clone() .extract::>() .map_err(|_| TradeableKittyError::BadlyTyped)?; @@ -227,14 +227,14 @@ fn check_can_buy( utxo_value > 0, TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) ); - input_coin_data.push(input_data[i].clone()); + input_coin_data.push(coin_data.clone()); total_input_amount = total_input_amount .checked_add(utxo_value) .ok_or(TradeableKittyError::MoneyError(MoneyError::ValueOverflow))?; } - for i in 1..output_data.len() { - let coin = output_data[i] + for coin_data in output_data.iter().skip(1) { + let coin = coin_data .clone() .extract::>() .map_err(|_| TradeableKittyError::BadlyTyped)?; @@ -248,7 +248,7 @@ fn check_can_buy( utxo_value > 0, TradeableKittyError::MoneyError(MoneyError::ZeroValueCoin) ); - output_coin_data.push(output_data[i].clone()); + output_coin_data.push(coin_data.clone()); } ensure!( total_price_of_kitty <= total_input_amount, @@ -302,7 +302,7 @@ fn check_can_list_kitty_for_sale( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { - check_kitty_tdkitty_interconversion(&input_data, &output_data)?; + check_kitty_tdkitty_interconversion(input_data, output_data)?; Ok(0) } @@ -314,7 +314,7 @@ fn check_can_delist_kitty_from_sale( ) -> Result { // Below is the conversion from tradable kitty to regular kitty, the reverse of the ListKittyForSale. // Hence, input parameters are reversed. - check_kitty_tdkitty_interconversion(&output_data, &input_data)?; + check_kitty_tdkitty_interconversion(output_data, input_data)?; Ok(0) } @@ -361,10 +361,10 @@ impl SimpleConstraintChecker for TradableKittyConstraintChecker Result { match &self { Self::ListKittyForSale => { - check_can_list_kitty_for_sale(&input_data, &output_data)?; + check_can_list_kitty_for_sale(input_data, output_data)?; } Self::DelistKittyFromSale => { - check_can_delist_kitty_from_sale(&input_data, &output_data)?; + check_can_delist_kitty_from_sale(input_data, output_data)?; } Self::UpdateKittyPrice => { check_kitty_price_update(input_data, output_data)?; @@ -372,8 +372,8 @@ impl SimpleConstraintChecker for TradableKittyConstraintChecker { let mut input_basic_kitty_data: Vec = Vec::new(); let mut output_basic_kitty_data: Vec = Vec::new(); - let _ = extract_basic_kitty_list(&input_data, &mut input_basic_kitty_data)?; - let _ = extract_basic_kitty_list(&output_data, &mut output_basic_kitty_data)?; + extract_basic_kitty_list(input_data, &mut input_basic_kitty_data)?; + extract_basic_kitty_list(output_data, &mut output_basic_kitty_data)?; kitties::can_kitty_name_be_updated( &input_basic_kitty_data, &output_basic_kitty_data, From c77120b924dc9fcc2b0ff13a586457f1b89d56d7 Mon Sep 17 00:00:00 2001 From: Matteo Muraca <56828990+muraca@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:33:02 +0100 Subject: [PATCH 10/14] improve Kitties documentation --- wardrobe/kitties/src/lib.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index 93f777da7..b1f702876 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -1,32 +1,32 @@ //! An NFT game inspired by Cryptokitties. -//! This is a game that allows for the creation, breeding, and updating of the name of kitties. +//! In this game, Kitties can be created, bred or renamed. //! //! ## Features //! -//! - **Create:** Generate a new kitty. -//! To submit a valid transaction for creating a kitty, adhere to the following structure: +//! - **Create:** Generate new kitties from scratch. +//! To submit a valid transaction for creating kitties, adhere to the following structure: //! 1. The input must be empty. -//! 2. The output must contain only the newly created kitties as a child. +//! 2. The output must contain only the newly created kitties. //! //! **Note 1:** Multiple kitties can be created at the same time in the same transaction. //! -//! - **Update Name:** Modify the name of a kitty. -//! To submit a valid transaction for updating a kitty's name, adhere to the following structure: -//! 1. The input must be the kitty to be updated. -//! 2. The output must contain the kitty with the updated name. +//! - **Update Name:** Modify the name of one or more kitties. +//! To submit a valid transaction for updating some kitties' names, adhere to the following structure: +//! 1. The input must be the kitties to update. +//! 2. The output must contain the kitties with the updated names. //! //! **Note 1:** All other properties, such as DNA, parents, free breedings, etc., must remain unaltered in the output. //! **Note 2:** The input and output kitties must follow the same order. //! -//! - **Breed:** Breed a new kitty using mom and dad based on the factors below: +//! - **Breed:** Breed a new kitty using Mom and Dad based on the factors below: //! 1. Mom and Dad have to be in a state where they are ready to breed. -//! 2. Each Mom and Dad have some DNA, and the child will have unique DNA combined from both of them, linkable back to the Mom and Dad. +//! 2. The child's unique DNA combined from Mom's and Dad's, linkable back to them. //! 3. The game also allows kitties to have a cooling-off period in between breeding before they can be bred again. //! 4. A rest operation allows for a Mom Kitty and a Dad Kitty to cool off. //! //! In order to submit a valid breed transaction, you must structure it as follows: -//! 1. The input must contain 1 mom and 1 dad. -//! 2. The output must contain Mom, Dad, and the newly created Child. +//! 1. The input must contain 1 Mom and 1 Dad, in a `RearinToGo` state. +//! 2. The output must contain Mom, Dad, and the newly created Child. Mom and Dad's state must be updated to `HadBirthRecently` and `Tired`. //! 3. A child's DNA is calculated by: //! BlakeTwo256::hash_of(MomDna, DadDna, MomCurrNumBreedings, DadCurrNumberBreedings) //! @@ -101,7 +101,7 @@ pub enum DadKittyStatus { #[default] /// Can breed. RearinToGo, - /// Can't breed due to tired. + /// Can't breed due to tiredness. Tired, } @@ -129,7 +129,7 @@ pub enum MomKittyStatus { HadBirthRecently, } -/// The parent structure contains 1 mom kitty and 1 dad kitty. +/// The parent structure contains 1 Mom Kitty and 1 Dad Kitty. #[derive( Serialize, Deserialize, From d57f9b10adfe9badc39823d6e7275b816272b33f Mon Sep 17 00:00:00 2001 From: Matteo Muraca <56828990+muraca@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:08:44 +0100 Subject: [PATCH 11/14] improve TradableKitties documentation --- wardrobe/kitties/src/lib.rs | 11 +++-------- wardrobe/tradable_kitties/src/lib.rs | 13 +++++-------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index b1f702876..1c73a40d0 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -610,10 +610,8 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { } } -/// Checks: -/// - Input and output are of kittyType. -/// - Only the name is updated, and other basic properties are not modified. -/// - The order between input and output must be the same. +/// Checks if input and output contain a list of KittyData in the same order. +/// The KittyData in the output list can have different names from the inputs, but other properties must be unmodified. pub fn can_kitty_name_be_updated( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], @@ -639,10 +637,7 @@ pub fn can_kitty_name_be_updated( Ok(0) } -/// Checks: -/// - This is a private function used by can_kitty_name_be_updated. -/// - Only the name is updated, and other basic properties are not updated. -/// +/// Checks if only the name is updated, and other basic properties remain the same. fn check_kitty_name_update( original_kitty: &KittyData, updated_kitty: &KittyData, diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs index 19c26900d..ff689e9b6 100644 --- a/wardrobe/tradable_kitties/src/lib.rs +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -1,11 +1,8 @@ //! # TradableKitty Module //! //! This Tuxedo piece codifies additional features that work with the Kitties piece. -//! This piece should not and cannot be used without that piece. -//! The `TradableKitty` module defines a specialized type of kitty tailored for trading with unique features. -//! -//! TradableKitties enrich the user experience by introducing trading/selling capabilities. -//! The following are features supported: +//! This piece should not and cannot be used without the Kitties and Money pieces. +//! The introduced features are: //! //! - **ListKittyForSale:** Convert basic kitties into tradable kitties, adding a `price` field. //! - **DelistKittyFromSale:** Transform tradable kitties back into regular kitties when owners decide not to sell. @@ -39,8 +36,8 @@ mod tests; /// The default price of a kitten is 10. const DEFAULT_KITTY_PRICE: u128 = 10; -/// A `TradableKittyData` is required for trading the kitty. -/// It includes the `price` of the kitty, in addition to the basic `KittyData`. +/// A `TradableKittyData` is required for trading the Kitty. +/// It includes the `price` of the Kitty, in addition to the basic `KittyData` provided by the Kitties piece. #[derive( Serialize, Deserialize, @@ -155,7 +152,7 @@ impl From for TradeableKittyError { TypeInfo, )] pub enum TradableKittyConstraintChecker { - /// List the kitty for sale. This means the kitty will be converted to a tradable kitty once the transaction is executed.. + /// List the kitty for sale. This means the kitty will be converted to a tradable kitty once the transaction is executed. ListKittyForSale, /// Delist the kitty from sale, This means tradable kitty will converted back to kitty. DelistKittyFromSale, From db5748d74623715f69201a1985dd0ddf79ef6f81 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Wed, 13 Mar 2024 13:23:05 +0530 Subject: [PATCH 12/14] Review comments fix Fixed review comments from Matteo Muraca --- wardrobe/kitties/src/lib.rs | 55 +++++----- wardrobe/kitties/src/tests.rs | 22 ++-- wardrobe/tradable_kitties/src/lib.rs | 136 +++++++++++++------------ wardrobe/tradable_kitties/src/tests.rs | 87 ++++++++-------- 4 files changed, 153 insertions(+), 147 deletions(-) diff --git a/wardrobe/kitties/src/lib.rs b/wardrobe/kitties/src/lib.rs index 93f777da7..1a9977862 100644 --- a/wardrobe/kitties/src/lib.rs +++ b/wardrobe/kitties/src/lib.rs @@ -1,32 +1,32 @@ //! An NFT game inspired by Cryptokitties. -//! This is a game that allows for the creation, breeding, and updating of the name of kitties. +//! In this game, Kitties can be created, bred or renamed. //! //! ## Features //! -//! - **Create:** Generate a new kitty. -//! To submit a valid transaction for creating a kitty, adhere to the following structure: +//! - **Create:** Generate new kitties from scratch. +//! To submit a valid transaction for creating kitties, adhere to the following structure: //! 1. The input must be empty. -//! 2. The output must contain only the newly created kitties as a child. +//! 2. The output must contain only the newly created kitties. //! //! **Note 1:** Multiple kitties can be created at the same time in the same transaction. //! -//! - **Update Name:** Modify the name of a kitty. -//! To submit a valid transaction for updating a kitty's name, adhere to the following structure: -//! 1. The input must be the kitty to be updated. -//! 2. The output must contain the kitty with the updated name. +//! - **Update Name:** Modify the name of one or more kitties. +//! To submit a valid transaction for updating some kitties' names, adhere to the following structure: +//! 1. The input must be the kitties to update. +//! 2. The output must contain the kitties with the updated names. //! //! **Note 1:** All other properties, such as DNA, parents, free breedings, etc., must remain unaltered in the output. //! **Note 2:** The input and output kitties must follow the same order. //! -//! - **Breed:** Breed a new kitty using mom and dad based on the factors below: +//! - **Breed:** Breed a new kitty using Mom and Dad based on the factors below: //! 1. Mom and Dad have to be in a state where they are ready to breed. -//! 2. Each Mom and Dad have some DNA, and the child will have unique DNA combined from both of them, linkable back to the Mom and Dad. +//! 2. The child's unique DNA combined from Mom's and Dad's, linkable back to them. //! 3. The game also allows kitties to have a cooling-off period in between breeding before they can be bred again. //! 4. A rest operation allows for a Mom Kitty and a Dad Kitty to cool off. //! //! In order to submit a valid breed transaction, you must structure it as follows: -//! 1. The input must contain 1 mom and 1 dad. -//! 2. The output must contain Mom, Dad, and the newly created Child. +//! 1. The input must contain 1 Mom and 1 Dad, in a `RearinToGo` state. +//! 2. The output must contain Mom, Dad, and the newly created Child. Mom and Dad's state must be updated to `HadBirthRecently` and `Tired`. //! 3. A child's DNA is calculated by: //! BlakeTwo256::hash_of(MomDna, DadDna, MomCurrNumBreedings, DadCurrNumberBreedings) //! @@ -56,7 +56,7 @@ mod tests; /// The main constraint checker for the kitty piece. Allows the following: /// Create: Allows the creation of a kitty without parents. Multiple kitties can be created in the same transaction. -/// UpdateKittyName: Allows updating the names of the kitties. Multiple kitty names can be updated in the same transaction. +/// UpdateKittiesName: Allows updating the names of the kitties. Multiple kitty names can be updated in the same transaction. /// Breed: Allows the breeding of kitties. #[derive( Serialize, @@ -76,7 +76,7 @@ pub enum FreeKittyConstraintChecker { /// Transaction that creates a kitty without parents. Multiple kitties can be created at the same time Create, /// Transaction that updates kitty names. Multiple kitty names can be updated. Input and output must follow the same order - UpdateKittyName, + UpdateKittiesName, /// Transaction where kitties are consumed, and a new family (parents: mom, dad, and child) is created. Breed, } @@ -101,7 +101,7 @@ pub enum DadKittyStatus { #[default] /// Can breed. RearinToGo, - /// Can't breed due to tired. + /// Can't breed due to tiredness. Tired, } @@ -129,7 +129,7 @@ pub enum MomKittyStatus { HadBirthRecently, } -/// The parent structure contains 1 mom kitty and 1 dad kitty. +/// The parent structure contains 1 Mom Kitty and 1 Dad Kitty. #[derive( Serialize, Deserialize, @@ -184,9 +184,9 @@ pub struct KittyDNA(pub H256); /// Kitty data contains basic information such as below: /// parent: 1 mom kitty and 1 dad kitty. -/// free_breedings: Free breeding allowed on a kitty. +/// free_breedings: Maximum free breeding allowed for a kitty. /// dna: It's unique per kitty. -/// num_breedings: Number of free breedings remaining. +/// num_breedings: Current count of remaining free breedings. /// name: Name of kitty. #[derive( Serialize, @@ -602,19 +602,17 @@ impl SimpleConstraintChecker for FreeKittyConstraintChecker { KittyHelpers::check_new_family(&mom, &dad, output_data)?; Ok(0) } - Self::UpdateKittyName => { - can_kitty_name_be_updated(input_data, output_data)?; + Self::UpdateKittiesName => { + can_kitties_name_be_updated(input_data, output_data)?; Ok(0) } } } } -/// Checks: -/// - Input and output are of kittyType. -/// - Only the name is updated, and other basic properties are not modified. -/// - The order between input and output must be the same. -pub fn can_kitty_name_be_updated( +/// Checks if input and output contain a list of KittyData in the same order. +/// The KittyData in the output list can have different names from the inputs, but other properties must be unmodified. +pub fn can_kitties_name_be_updated( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { @@ -625,12 +623,10 @@ pub fn can_kitty_name_be_updated( for (input, output) in input_data.iter().zip(output_data.iter()) { let utxo_input_kitty = input - .clone() .extract::() .map_err(|_| ConstraintCheckerError::BadlyTyped)?; let utxo_output_kitty = output - .clone() .extract::() .map_err(|_| ConstraintCheckerError::BadlyTyped)?; @@ -639,10 +635,7 @@ pub fn can_kitty_name_be_updated( Ok(0) } -/// Checks: -/// - This is a private function used by can_kitty_name_be_updated. -/// - Only the name is updated, and other basic properties are not updated. -/// +/// Checks if only the name is updated, and other basic properties remain the same. fn check_kitty_name_update( original_kitty: &KittyData, updated_kitty: &KittyData, diff --git a/wardrobe/kitties/src/tests.rs b/wardrobe/kitties/src/tests.rs index 4a2850956..531310bcb 100644 --- a/wardrobe/kitties/src/tests.rs +++ b/wardrobe/kitties/src/tests.rs @@ -535,7 +535,7 @@ fn update_name_happy_path_works() { output.name = *b"kty1"; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[input.into()], &[], &[output.into()], @@ -555,7 +555,7 @@ fn update_name_happy_path_with_multiple_input_sworks() { output2.name = *b"kty2"; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[input1.into(), input2.into()], &[], &[output1.into(), output2.into()], @@ -574,7 +574,7 @@ fn update_name_inputs_and_outputs_number_mismatch_fails() { output2.name = *b"kty2"; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[input1.into(), input2.into()], &[], &[output1.into()], @@ -590,7 +590,7 @@ fn update_name_no_inputs_fails() { let output = KittyData::default_dad(); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[], &[], &[output.into()], @@ -606,7 +606,7 @@ fn update_name_no_output_fails() { let input = KittyData::default_dad(); let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[input.into()], &[], &[], @@ -625,7 +625,7 @@ fn update_name_dna_update_fails() { output.name = *b"kty1"; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[input.into()], &[], &[output.into()], @@ -648,7 +648,7 @@ fn update_name_out_of_order_input_and_output_fails() { output1.name = *b"kty2"; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[input.clone().into(), input1.into()], &[], &[output1.clone().into(), output.into()], @@ -662,7 +662,7 @@ fn update_name_out_of_order_input_and_output_fails() { #[test] fn update_name_name_unupdated_path_fails() { let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[KittyData::default_dad().into()], &[], &[KittyData::default_dad().into()], @@ -677,7 +677,7 @@ fn update_name_free_breeding_updated_path_fails() { output.free_breedings += 1; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[KittyData::default().into()], &[], &[output.into()], @@ -695,7 +695,7 @@ fn update_name_num_of_breeding_updated_path_fails() { output.num_breedings += 1; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[KittyData::default().into()], &[], &[output.into()], @@ -713,7 +713,7 @@ fn update_name_gender_updated_path_fails() { output.name = *b"kty1"; let result = FreeKittyConstraintChecker::check( - &FreeKittyConstraintChecker::UpdateKittyName, + &FreeKittyConstraintChecker::UpdateKittiesName, &[input.into()], &[], &[output.into()], diff --git a/wardrobe/tradable_kitties/src/lib.rs b/wardrobe/tradable_kitties/src/lib.rs index 19c26900d..fe17d881d 100644 --- a/wardrobe/tradable_kitties/src/lib.rs +++ b/wardrobe/tradable_kitties/src/lib.rs @@ -1,22 +1,18 @@ //! # TradableKitty Module //! //! This Tuxedo piece codifies additional features that work with the Kitties piece. -//! This piece should not and cannot be used without that piece. -//! The `TradableKitty` module defines a specialized type of kitty tailored for trading with unique features. +//! This piece should not and cannot be used without the Kitties and Money pieces. +//! The introduced features are: //! -//! TradableKitties enrich the user experience by introducing trading/selling capabilities. -//! The following are features supported: -//! -//! - **ListKittyForSale:** Convert basic kitties into tradable kitties, adding a `price` field. -//! - **DelistKittyFromSale:** Transform tradable kitties back into regular kitties when owners decide not to sell. -//! - **UpdateKittyPrice:** Allow owners to modify the `price` of TradableKitties. -//! - **UpdateKittyName:** Permit owners to update the `name` of TradableKitties. -//! -//! *Note: Only one kitty can be traded at a time.* +//! - **ListKittiesForSale:** Convert basic kitties into tradable kitties, adding a `price` field. +//! - **DelistKittiesFromSale:** Transform tradable kitties back into regular kitties when owners decide not to sell. +//! - **UpdateKittiesPrice:** Allow owners to modify the `price` of TradableKitties. +//! - **UpdateKittiesName:** Permit owners to update the `name` of TradableKitties. //! //! - **Buy:** Enable users to securely purchase TradableKitty from others, ensuring fair exchanges. //! Make sure to place the kitty first and then coins in the inputs and outputs. //! +//! *Note: Only one kitty can be bought at a time.* //! #![cfg_attr(not(feature = "std"), no_std)] @@ -39,8 +35,8 @@ mod tests; /// The default price of a kitten is 10. const DEFAULT_KITTY_PRICE: u128 = 10; -/// A `TradableKittyData` is required for trading the kitty. -/// It includes the `price` of the kitty, in addition to the basic `KittyData`. +/// A `TradableKittyData` is required for trading the Kitty. +/// It includes the `price` of the Kitty, in addition to the basic `KittyData` provided by the Kitties piece. #[derive( Serialize, Deserialize, @@ -108,6 +104,8 @@ pub enum TradeableKittyError { BadlyTyped, /// Input missing for the transaction. InputMissingError, + /// Output missing for the transaction. + OutputMissingError, /// Not enough amount to buy a `kitty`. InsufficientCollateralToBuyKitty, /// The number of input vs number of output doesn't match for a transaction. @@ -155,39 +153,32 @@ impl From for TradeableKittyError { TypeInfo, )] pub enum TradableKittyConstraintChecker { - /// List the kitty for sale. This means the kitty will be converted to a tradable kitty once the transaction is executed.. - ListKittyForSale, - /// Delist the kitty from sale, This means tradable kitty will converted back to kitty. - DelistKittyFromSale, - /// Update the `price` of tradable kitty. - UpdateKittyPrice, - // Update the name of the kitty. - UpdateKittyName, + /// List the kitties for sale. This means the kitties will be converted to a tradable kitties once the transaction is executed. + ListKittiesForSale, + /// Delist the kitties from sale, This means tradable kitties will converted back to kitties. + DelistKittiesFromSale, + /// Update the `price` of tradable kitties. + UpdateKittiesPrice, + // Update the name of the kitties. + UpdateKittiesName, /// For buying a new kitty from other owners. Buy, } -/// Extract basic kitty data from a list of dynamically typed `TradableKitty` data, populating a list with basic kitty data. -fn extract_basic_kitty_list( - tradable_kitty_data: &[DynamicallyTypedData], - kitty_data_list: &mut Vec, -) -> Result<(), TradeableKittyError> { - for utxo in tradable_kitty_data { - if let Ok(tradable_kitty) = utxo.extract::() { - kitty_data_list.push(tradable_kitty.kitty_basic_data.clone().into()); - } else { - return Err(TradeableKittyError::BadlyTyped); - } - } - Ok(()) -} - /// Checks if buying the kitty is possible or not. It depends on the Money variable to validate the spending of coins. /// Make sure to place the kitty first and then the coins in the transaction. fn check_can_buy( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { + ensure!(!input_data.is_empty(), { + TradeableKittyError::InputMissingError + }); + + ensure!(!output_data.is_empty(), { + TradeableKittyError::OutputMissingError + }); + let mut input_coin_data: Vec = Vec::new(); let mut output_coin_data: Vec = Vec::new(); let input_kitty_to_be_traded: Option; @@ -261,14 +252,13 @@ fn check_can_buy( /// Checks if updates to the prices of tradable kitties are possible or not. /// Prices of multiple tradable kitties can be updated in the same transaction. -fn check_kitty_price_update( +fn check_kitties_price_update( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { - ensure!( - input_data.len() == output_data.len() && !input_data.is_empty(), - { TradeableKittyError::NumberOfInputOutputMismatch } - ); + ensure!(input_data.len() == output_data.len(), { + TradeableKittyError::NumberOfInputOutputMismatch + }); for i in 0..input_data.len() { let utxo_input_tradable_kitty = input_data[i] .extract::() @@ -298,35 +288,34 @@ fn check_kitty_price_update( /// Wrapper function for verifying the conversion from basic kitties to tradable kitties. /// Multiple kitties can be converted in a single transaction. -fn check_can_list_kitty_for_sale( +fn check_can_list_kitties_for_sale( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { - check_kitty_tdkitty_interconversion(input_data, output_data)?; + check_kitties_tdkitties_interconversion(input_data, output_data)?; Ok(0) } /// Wrapper function for verifying the conversion from tradable kitties to basic kitties. /// Multiple tradable kitties can be converted in a single transaction. -fn check_can_delist_kitty_from_sale( +fn check_can_delist_kitties_from_sale( input_data: &[DynamicallyTypedData], output_data: &[DynamicallyTypedData], ) -> Result { - // Below is the conversion from tradable kitty to regular kitty, the reverse of the ListKittyForSale. + // Below is the conversion from tradable kitty to regular kitty, the reverse of the ListKittiesForSale. // Hence, input parameters are reversed. - check_kitty_tdkitty_interconversion(output_data, input_data)?; + check_kitties_tdkitties_interconversion(output_data, input_data)?; Ok(0) } /// Validates inter-conversion between both regular kitties and tradable kitties, as used by the `listForSale` and `delistFromSale` functions. -fn check_kitty_tdkitty_interconversion( +fn check_kitties_tdkitties_interconversion( kitty_data: &[DynamicallyTypedData], tradable_kitty_data: &[DynamicallyTypedData], ) -> Result { - ensure!( - kitty_data.len() == tradable_kitty_data.len() && !kitty_data.is_empty(), - { TradeableKittyError::NumberOfInputOutputMismatch } - ); + ensure!(kitty_data.len() == tradable_kitty_data.len(), { + TradeableKittyError::NumberOfInputOutputMismatch + }); for i in 0..kitty_data.len() { let utxo_kitty = kitty_data[i] @@ -360,21 +349,42 @@ impl SimpleConstraintChecker for TradableKittyConstraintChecker Result { match &self { - Self::ListKittyForSale => { - check_can_list_kitty_for_sale(input_data, output_data)?; + Self::ListKittiesForSale => { + check_can_list_kitties_for_sale(input_data, output_data)?; } - Self::DelistKittyFromSale => { - check_can_delist_kitty_from_sale(input_data, output_data)?; + Self::DelistKittiesFromSale => { + check_can_delist_kitties_from_sale(input_data, output_data)?; } - Self::UpdateKittyPrice => { - check_kitty_price_update(input_data, output_data)?; + Self::UpdateKittiesPrice => { + check_kitties_price_update(input_data, output_data)?; } - Self::UpdateKittyName => { - let mut input_basic_kitty_data: Vec = Vec::new(); - let mut output_basic_kitty_data: Vec = Vec::new(); - extract_basic_kitty_list(input_data, &mut input_basic_kitty_data)?; - extract_basic_kitty_list(output_data, &mut output_basic_kitty_data)?; - kitties::can_kitty_name_be_updated( + Self::UpdateKittiesName => { + let result: Result, _> = input_data + .iter() + .map(|utxo| utxo.extract::()) + .collect::, _>>() + .map(|vec| { + vec.into_iter() + .map(|tdkitty| tdkitty.kitty_basic_data.clone().into()) + .collect() + }); + + let input_basic_kitty_data = result.map_err(|_| TradeableKittyError::BadlyTyped)?; + + let result: Result, _> = output_data + .iter() + .map(|utxo| utxo.extract::()) + .collect::, _>>() + .map(|vec| { + vec.into_iter() + .map(|tdkitty| tdkitty.kitty_basic_data.clone().into()) + .collect() + }); + + let output_basic_kitty_data = + result.map_err(|_| TradeableKittyError::BadlyTyped)?; + + kitties::can_kitties_name_be_updated( &input_basic_kitty_data, &output_basic_kitty_data, )?; diff --git a/wardrobe/tradable_kitties/src/tests.rs b/wardrobe/tradable_kitties/src/tests.rs index e8e9b4eac..f84fe193d 100644 --- a/wardrobe/tradable_kitties/src/tests.rs +++ b/wardrobe/tradable_kitties/src/tests.rs @@ -28,10 +28,10 @@ impl TradableKittyData { } } } -// listKittyForSale UT startes from here. +// ListKittiesForSale UT startes from here. #[test] fn list_kitty_for_sale_happy_path_works() { - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[TradableKittyData::default_kitty().into()], &[], &[TradableKittyData::default_tradable_kitty().into()], @@ -49,7 +49,7 @@ fn list_kitty_for_sale_multiple_input_happy_path_works() { output1.kitty_basic_data = input1.clone(); output2.kitty_basic_data = input2.clone(); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[input1.into(), input2.into()], &[], &[output1.into(), output2.into()], @@ -67,7 +67,7 @@ fn list_kitty_for_sale_multiple_out_of_order_input_fails() { output1.kitty_basic_data = input1.clone(); output2.kitty_basic_data = input2.clone(); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[input1.into(), input2.into()], &[], &[output2.into(), output1.into()], @@ -86,7 +86,7 @@ fn list_kitty_for_sale_different_num_of_input_output_path_fails() { let mut output1 = TradableKittyData::default_tradable_kitty(); output1.kitty_basic_data = input2.clone(); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[input1.into(), input2.into()], &[], &[output1.into()], @@ -102,7 +102,7 @@ fn list_kitty_for_sale_input_missing_path_fails() { let output = TradableKittyData::default_tradable_kitty(); let result = - TradableKittyConstraintChecker::<0>::ListKittyForSale.check(&[], &[], &[output.into()]); + TradableKittyConstraintChecker::<0>::ListKittiesForSale.check(&[], &[], &[output.into()]); assert_eq!( result, Err(TradeableKittyError::NumberOfInputOutputMismatch) @@ -113,7 +113,7 @@ fn list_kitty_for_sale_input_missing_path_fails() { fn list_kitty_for_sale_out_put_missing_path_fails() { let input1 = TradableKittyData::default_kitty(); let result = - TradableKittyConstraintChecker::<0>::ListKittyForSale.check(&[input1.into()], &[], &[]); + TradableKittyConstraintChecker::<0>::ListKittiesForSale.check(&[input1.into()], &[], &[]); assert_eq!( result, Err(TradeableKittyError::NumberOfInputOutputMismatch) @@ -128,7 +128,7 @@ fn list_kitty_for_sale_with_input_missing_fails() { output1.kitty_basic_data = input1.clone(); let mut output2 = TradableKittyData::default_tradable_kitty(); output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[], &[], &[output1.into(), output2.into()], @@ -144,7 +144,7 @@ fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { let input1 = TradableKittyData::default_kitty(); let mut output1 = TradableKittyData::default_tradable_kitty(); output1.kitty_basic_data = input1.clone(); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[input1.into()], &[], &[Bogus.into()], @@ -154,7 +154,7 @@ fn list_for_sale_with_wrong_output_type_amoung_valid_output_fails() { #[test] fn list_kitty_for_sale_with_wrong_input_type_fails() { - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[Bogus.into()], &[], &[TradableKittyData::default_tradable_kitty().into()], @@ -171,7 +171,7 @@ fn list_for_sale_multiple_inputs_with_basic_property_changed_fails() { output1.kitty_basic_data = input1.clone(); let mut output2 = TradableKittyData::default_tradable_kitty(); output2.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[input1.into(), input2.into()], &[], &[output1.clone().into(), output2.into()], @@ -188,7 +188,7 @@ fn list_for_sale_with_basic_property_changed_fails() { let mut output = TradableKittyData::default_tradable_kitty(); output.kitty_basic_data = input.clone(); output.kitty_basic_data.free_breedings += 1; - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[input.into()], &[], &[output.into()], @@ -205,7 +205,7 @@ fn list_for_sale_with_price_zero_fails() { let mut output1 = TradableKittyData::default_tradable_kitty(); output1.kitty_basic_data = input1.clone(); output1.price = 0; - let result = TradableKittyConstraintChecker::<0>::ListKittyForSale.check( + let result = TradableKittyConstraintChecker::<0>::ListKittiesForSale.check( &[input1.into()], &[], &[output1.into()], @@ -213,10 +213,10 @@ fn list_for_sale_with_price_zero_fails() { assert_eq!(result, Err(TradeableKittyError::KittyPriceCantBeZero)); } -// delistKittyFromSale UT starts from here. +// DelistKittiesFromSale UT starts from here. #[test] fn delist_kitty_from_sale_happy_path_works() { - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( &[TradableKittyData::default_tradable_kitty().into()], &[], &[TradableKittyData::default_kitty().into()], @@ -233,7 +233,7 @@ fn delist_kitty_from_sale_multiple_input_happy_path_works() { let mut input2 = TradableKittyData::default_tradable_kitty(); input2.kitty_basic_data = output2.clone(); - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( &[input1.into(), input2.into()], &[], &[output1.into(), output2.into()], @@ -250,7 +250,7 @@ fn delist_kitty_from_sale_multiple_input_out_of_order_path_fails() { let mut input2 = TradableKittyData::default_tradable_kitty(); input2.kitty_basic_data = output2.clone(); - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( &[input1.into(), input2.into()], &[], &[output2.into(), output1.into()], @@ -270,7 +270,7 @@ fn delist_kitty_from_sale_different_num_of_input_output_fails() { let mut input2 = TradableKittyData::default_tradable_kitty(); input2.kitty_basic_data = output2; - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( &[input1.into(), input2.into()], &[], &[output1.into()], @@ -284,8 +284,11 @@ fn delist_kitty_from_sale_different_num_of_input_output_fails() { #[test] fn delist_kitty_from_sale_input_missing_fails() { let output = TradableKittyData::default_kitty(); - let result = - TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check(&[], &[], &[output.into()]); + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( + &[], + &[], + &[output.into()], + ); assert_eq!( result, Err(TradeableKittyError::NumberOfInputOutputMismatch) @@ -293,7 +296,7 @@ fn delist_kitty_from_sale_input_missing_fails() { } #[test] fn delist_kitty_from_sale_out_put_missing_path_fails() { - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( &[TradableKittyData::default_tradable_kitty().into()], &[], &[], @@ -314,7 +317,7 @@ fn delist_kitty_from_sale_with_wrong_output_type_ampoung_valid_output_fails() { input1.kitty_basic_data = output1.clone(); input2.kitty_basic_data = output2.clone(); - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( &[input1.into(), input2.into()], &[], &[output1.into(), Bogus.into()], @@ -324,7 +327,7 @@ fn delist_kitty_from_sale_with_wrong_output_type_ampoung_valid_output_fails() { #[test] fn delist_from_sale_with_wrong_input_type_fails() { - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( &[Bogus.into()], &[], &[TradableKittyData::default_kitty().into()], @@ -341,7 +344,7 @@ fn delist_from_sale_with_basic_property_update_fails() { let mut input2 = TradableKittyData::default_tradable_kitty(); input1.kitty_basic_data = output1.clone(); input2.kitty_basic_data = output2.clone(); - let result = TradableKittyConstraintChecker::<0>::DelistKittyFromSale.check( + let result = TradableKittyConstraintChecker::<0>::DelistKittiesFromSale.check( &[input1.clone().into(), input2.into()], &[], &[output1.clone().into(), output1.into()], @@ -359,7 +362,7 @@ fn update_name_happy_path_works() { let mut output = input.clone(); output.kitty_basic_data.name = *b"tdkt"; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( &[input.into()], &[], &[output.into()], @@ -373,7 +376,7 @@ fn update_name_invalid_type_in_input_fails() { let mut output = input.clone(); output.kitty_basic_data.name = *b"tdkt"; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( &[input.into(), Bogus.into()], &[], &[output.clone().into(), output.into()], @@ -387,7 +390,7 @@ fn update_name_invalid_type_in_output_fails() { let mut output = input.clone(); output.kitty_basic_data.name = *b"tdkt"; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( &[input.clone().into(), input.into()], &[], &[output.into(), Bogus.into()], @@ -402,7 +405,7 @@ fn update_name_dna_update_fails() { output.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoca")); output.kitty_basic_data.name = *b"tdkt"; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( &[input.into()], &[], &[output.into()], @@ -422,7 +425,7 @@ fn update_name_free_breeding_update_fails() { output.kitty_basic_data.name = *b"kty1"; output.kitty_basic_data.free_breedings += 1; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( &[input.into()], &[], &[output.into()], @@ -442,7 +445,7 @@ fn update_name_num_of_breeding_updated_path_fails() { output.kitty_basic_data.name = *b"kty1"; output.kitty_basic_data.num_breedings += 1; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( &[input.into()], &[], &[output.into()], @@ -462,7 +465,7 @@ fn update_name_gender_updated_path_fails() { output.kitty_basic_data.name = *b"tdk1"; output.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::RearinToGo); - let result = TradableKittyConstraintChecker::<0>::UpdateKittyName.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesName.check( &[input.into()], &[], &[output.into()], @@ -482,7 +485,7 @@ fn update_price_happy_path_works() { let mut output = input.clone(); output.price = 500; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into()], &[], &[output.into()], @@ -500,7 +503,7 @@ fn update_price_multiple_input_happy_path_works() { let mut output1 = input1.clone(); output1.price = 700; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into(), input1.into()], &[], &[output.into(), output1.into()], @@ -518,7 +521,7 @@ fn update_price_multiple_input_out_of_order_fails() { let mut output1 = input1.clone(); output1.price = 700; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into(), input1.into()], &[], &[output1.into(), output.into()], @@ -537,7 +540,7 @@ fn update_price_output_missing_path_fails() { let mut input1 = TradableKittyData::default_tradable_kitty(); input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into(), input1.into()], &[], &[output.into()], @@ -558,7 +561,7 @@ fn update_price_input_missing_path_fails() { let mut output1 = input1.clone(); output1.price = 700; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into()], &[], &[output.into(), output1.into()], @@ -575,7 +578,7 @@ fn update_price_bad_input_path_fails() { let mut output = input.clone(); output.price = 500; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[Bogus.into()], &[], &[output.into()], @@ -591,7 +594,7 @@ fn update_price_bad_output_path_fails() { let mut input1 = TradableKittyData::default_tradable_kitty(); input1.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into(), input1.into()], &[], &[output.into(), Bogus.into()], @@ -607,7 +610,7 @@ fn update_price_different_dna_path_fails() { output.kitty_basic_data.dna = KittyDNA(H256::from_slice(b"superkalifragislisticexpialadoai")); - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.clone().into()], &[], &[output.into()], @@ -625,7 +628,7 @@ fn update_price_basic_properties_updated_path_fails() { output.price = 500; output.kitty_basic_data.free_breedings += 1; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into()], &[], &[output.into()], @@ -641,7 +644,7 @@ fn update_price_not_updated_path_fails() { let input = TradableKittyData::default_tradable_kitty(); let output = input.clone(); - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into()], &[], &[output.into()], @@ -655,7 +658,7 @@ fn update_price_to_zero_updated_path_fails() { let mut output = input.clone(); output.price = 0; - let result = TradableKittyConstraintChecker::<0>::UpdateKittyPrice.check( + let result = TradableKittyConstraintChecker::<0>::UpdateKittiesPrice.check( &[input.into()], &[], &[output.into()], From 9c4f9030b7910c77ed08a5466fdca3ac487bc577 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Mon, 1 Apr 2024 20:32:56 +0530 Subject: [PATCH 13/14] web service implementation Web service implementation --- Cargo.toml | 2 + tuxedo-core/src/executive.rs | 11 +- webservice-wallet-external-signing/Cargo.toml | 36 + webservice-wallet-external-signing/Dockerfile | 35 + webservice-wallet-external-signing/README.md | 418 ++++++ .../src/amoeba.rs | 127 ++ webservice-wallet-external-signing/src/cli.rs | 388 ++++++ .../src/keystore.rs | 138 ++ .../src/kitty.rs | 1235 +++++++++++++++++ .../src/main.rs | 350 +++++ .../src/money.rs | 340 +++++ .../src/output_filter.rs | 137 ++ webservice-wallet-external-signing/src/rpc.rs | 61 + .../blockHandler/blockServicehandler.rs | 68 + .../keyHandler/keyServicehandler.rs | 63 + .../kittyHandler/kittyServicehandler.rs | 1146 +++++++++++++++ .../moneyHandler/moneyServicehandler.rs | 124 ++ .../src/sync.rs | 1199 ++++++++++++++++ .../src/timestamp.rs | 27 + .../Cargo.toml | 36 + .../Dockerfile | 35 + .../README.md | 299 ++++ .../src/TradableKitties.rs | 443 ++++++ .../src/amoeba.rs | 127 ++ .../src/cli.rs | 388 ++++++ .../src/keystore.rs | 138 ++ .../src/kitty.rs | 1045 ++++++++++++++ .../src/main.rs | 311 +++++ .../src/money.rs | 339 +++++ .../src/output_filter.rs | 137 ++ .../src/req_resp.rs | 24 + .../src/rpc.rs | 61 + .../blockHandler/blockServicehandler.rs | 74 + .../keyHandler/keyServicehandler.rs | 69 + .../kittyHandler/kittyServicehandler.rs | 841 +++++++++++ .../moneyHandler/moneyServicehandler.rs | 68 + .../src/sync.rs | 1096 +++++++++++++++ .../src/timestamp.rs | 27 + 38 files changed, 11460 insertions(+), 3 deletions(-) create mode 100644 webservice-wallet-external-signing/Cargo.toml create mode 100644 webservice-wallet-external-signing/Dockerfile create mode 100644 webservice-wallet-external-signing/README.md create mode 100644 webservice-wallet-external-signing/src/amoeba.rs create mode 100644 webservice-wallet-external-signing/src/cli.rs create mode 100644 webservice-wallet-external-signing/src/keystore.rs create mode 100644 webservice-wallet-external-signing/src/kitty.rs create mode 100644 webservice-wallet-external-signing/src/main.rs create mode 100644 webservice-wallet-external-signing/src/money.rs create mode 100644 webservice-wallet-external-signing/src/output_filter.rs create mode 100644 webservice-wallet-external-signing/src/rpc.rs create mode 100644 webservice-wallet-external-signing/src/serviceHandlers/blockHandler/blockServicehandler.rs create mode 100644 webservice-wallet-external-signing/src/serviceHandlers/keyHandler/keyServicehandler.rs create mode 100644 webservice-wallet-external-signing/src/serviceHandlers/kittyHandler/kittyServicehandler.rs create mode 100644 webservice-wallet-external-signing/src/serviceHandlers/moneyHandler/moneyServicehandler.rs create mode 100644 webservice-wallet-external-signing/src/sync.rs create mode 100644 webservice-wallet-external-signing/src/timestamp.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/Cargo.toml create mode 100644 webservice-wallet-with-inbuilt-key-store/Dockerfile create mode 100644 webservice-wallet-with-inbuilt-key-store/README.md create mode 100644 webservice-wallet-with-inbuilt-key-store/src/TradableKitties.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/amoeba.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/cli.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/keystore.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/kitty.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/main.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/money.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/output_filter.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/req_resp.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/rpc.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/blockHandler/blockServicehandler.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/keyHandler/keyServicehandler.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/kittyHandler/kittyServicehandler.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/moneyHandler/moneyServicehandler.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/sync.rs create mode 100644 webservice-wallet-with-inbuilt-key-store/src/timestamp.rs diff --git a/Cargo.toml b/Cargo.toml index 524a68b43..7e5116435 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ members = [ "tuxedo-parachain-core/register_validate_block", "tuxedo-parachain-core", "wallet", + "webservice-wallet-external-signing", + #"webservice-wallet-with-inbuilt-key-store", "wardrobe/amoeba", "wardrobe/money", "wardrobe/parachain", diff --git a/tuxedo-core/src/executive.rs b/tuxedo-core/src/executive.rs index aaec674c2..d4c07ff95 100644 --- a/tuxedo-core/src/executive.rs +++ b/tuxedo-core/src/executive.rs @@ -45,7 +45,7 @@ impl>, V: Verifier, C: ConstraintChecker pub fn validate_tuxedo_transaction( transaction: &Transaction, ) -> Result> { - debug!( + log::info!( target: LOG_TARGET, "validating tuxedo transaction", ); @@ -54,6 +54,11 @@ impl>, V: Verifier, C: ConstraintChecker // Duplicate peeks are allowed, although they are inefficient and wallets should not create such transactions { let input_set: BTreeSet<_> = transaction.inputs.iter().map(|o| o.encode()).collect(); + log::info!( + target: LOG_TARGET, + "input_set.len() {} and transaction.inputs.len() {}",input_set.len(), + transaction.inputs.len() + ); ensure!( input_set.len() == transaction.inputs.len(), UtxoError::DuplicateInput @@ -107,7 +112,7 @@ impl>, V: Verifier, C: ConstraintChecker index: index as u32, }; - debug!( + log::info!( target: LOG_TARGET, "Checking for pre-existing output {:?}", output_ref ); @@ -133,7 +138,7 @@ impl>, V: Verifier, C: ConstraintChecker // If any of the inputs are missing, we cannot make any more progress // If they are all present, we may proceed to call the constraint checker if !missing_inputs.is_empty() { - debug!( + log::info!( target: LOG_TARGET, "Transaction is valid but still has missing inputs. Returning early.", ); diff --git a/webservice-wallet-external-signing/Cargo.toml b/webservice-wallet-external-signing/Cargo.toml new file mode 100644 index 000000000..68ba3558f --- /dev/null +++ b/webservice-wallet-external-signing/Cargo.toml @@ -0,0 +1,36 @@ +[package] +description = "A simple example / template wallet built for the tuxedo template runtime" +edition = "2021" +license = "Apache-2.0" +name = "tuxedo-template-web-service-wallet" +repository = "https://github.com/Off-Narrative-Labs/Tuxedo" +version = "1.0.0-dev" + +[dependencies] +runtime = { package = "tuxedo-template-runtime", path = "../tuxedo-template-runtime" } +tuxedo-core = { path = "../tuxedo-core" } + +anyhow = { workspace = true } +clap = { features = [ "derive" ], workspace = true } +directories = { workspace = true } +env_logger = { workspace = true } +futures = { workspace = true } +hex = { workspace = true } +hex-literal = { workspace = true } +jsonrpsee = { features = [ "http-client" ], workspace = true } +log = { workspace = true } +parity-scale-codec = { workspace = true } +serde_json = { workspace = true } +sled = { workspace = true } +tokio = { features = [ "full" ], workspace = true } + +rand = "0.8" + +sc-keystore = { workspace = true } +sp-core = { workspace = true } +sp-keystore = { workspace = true } +sp-runtime = { workspace = true } + +axum = "0.5.16" +serde = { version = "1.0", features = ["derive"] } +tower-http = { version = "0.3.4", features = ["cors"] } diff --git a/webservice-wallet-external-signing/Dockerfile b/webservice-wallet-external-signing/Dockerfile new file mode 100644 index 000000000..b685c5129 --- /dev/null +++ b/webservice-wallet-external-signing/Dockerfile @@ -0,0 +1,35 @@ +# This is a multi-stage docker file. See https://docs.docker.com/build/building/multi-stage/ +# for details about this pattern. + + + +# For the build stage, we use an image provided by Parity +FROM docker.io/paritytech/ci-linux:production as builder +WORKDIR /wallet +#TODO The Workdir and Copy command is different here than in the node... +COPY . . +RUN cargo build --locked --release -p tuxedo-template-wallet + + +# For the second stage, we use a minimal Ubuntu image +FROM docker.io/library/ubuntu:20.04 +LABEL description="Tuxedo Templet Wallet" + +COPY --from=builder /wallet/target/release/tuxedo-template-wallet /usr/local/bin + +RUN useradd -m -u 1000 -U -s /bin/sh -d /node-dev node-dev && \ + mkdir -p /wallet-data /node-dev/.local/share && \ + chown -R node-dev:node-dev /wallet-data && \ + # Make the wallet data directory available outside the container. + ln -s /wallet-data /node-dev/.local/share/tuxedo-template-wallet && \ + # unclutter and minimize the attack surface + rm -rf /usr/bin /usr/sbin && \ + # check if executable works in this container + /usr/local/bin/tuxedo-template-wallet --version + +USER node-dev + +EXPOSE 9944 +VOLUME ["/wallet-data"] + +ENTRYPOINT ["/usr/local/bin/tuxedo-template-wallet"] \ No newline at end of file diff --git a/webservice-wallet-external-signing/README.md b/webservice-wallet-external-signing/README.md new file mode 100644 index 000000000..03c8a7621 --- /dev/null +++ b/webservice-wallet-external-signing/README.md @@ -0,0 +1,418 @@ +# Tuxedo web service functionality + +A REST API for communicating with Tuxedo node template. + +## Overview + +This is a service built on Axum to support the decentralized application (DApp) built on the Tuxedo Blockchain that allows users to create, trade, breed, and manage virtual cats known as "Kitties". This README provides an overview of the available operations and REST APIs for interacting with the Cryptokitties platform. + +Like many UTXO wallets, this web service synchronizes a local-to-the-wallet database of UTXOs that exist on the current best chain.Let's call this as Indexer from now on. +The Indexer does not sync the entire blockchain state. +Rather, it syncs a subset of the state that it considers "relevant". +Currently, the Indexer syncs all relevant UTXOs i.e. Coins, KittyData, TradableKittyData, Timestamps. +However, the Indexer is designed so that this notion of "relevance" is generalizable. +This design allows developers building chains with Tuxedo to extend the Indexer for their own needs. +However, because this is a rest API-based web service, it is likely to be used by DApps which will leverage the REST API to achieve results. + +The overall idea behind the web service architecture: https://github.com/mlabs-haskell/TuxedoDapp/issues/35 + +Links : +**Sequence dig for API flow:** https://github.com/mlabs-haskell/TuxedoDapp/issues/35#issuecomment-2020211287 + +**Algorithm to create the redeemer:** https://github.com/mlabs-haskell/TuxedoDapp/issues/35#issuecomment-2015171702 + +**The overall procedure required from DApp**: https://github.com/mlabs-haskell/TuxedoDapp/issues/35#issuecomment-2011277263 + +**Difference between signed transaction and unsigned transaction example:** https://github.com/mlabs-haskell/TuxedoDapp/issues/35#issuecomment-2020399526 + +## REST Documentation + +Webservice can be run by using + +```sh +$ cargo run +``` + +## Guided tour for REST APIS usage + +This guided tour shows REST apis usage and curl command used to hit the endpoints : + +### Minting coins + +Rest apis for minting coins + +**end point:**: mint-coins + +**Amount to mint:** 6000 + +**Public_key of owner:** d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +```sh +$ curl -X POST -H "Content-Type: application/json" -d '{"amount": 6000,"owner_public_key":"d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}' http://localhost:3000/mint-coins + +``` + +### Get all coins + +Rest apis for getting all the coins stored in the web service. Basically web service stores all the coin UTXO which are synced from the genesis block to the current height. + +**end point:**: get-all-coins + +```sh +$ curl -X GET -H "Content-Type: application/json" http://localhost:3000/get-all-coins + +``` + +### Get all owned coins + +Rest API for getting all the coins owned by a particular user or public key in the web service. Web service stores all the coin utxos which are synced from the genesis block to the current height. Webservice will filter the coin UTXO filtered by the supplied public jey. + +**end point:**:get-owned-coins + +**Public_key of owner:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-owned-coins + +``` + +### Create kitty: + +Rest API for creating the kitty + +**end point:**:create-kitty + +**Name of kitty to be created:**:amit + +**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +**Returns:** Created kitty. + + +```sh +$ curl -X POST -H "Content-Type: application/json" -d '{"name": "amit","owner_public_key":"d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}' http://localhost:3000/create-kitty + +``` + +### Get all kitties: + +Rest API forgetting all the kitties stored in the local db. It returns all the kitties irrespective of onwer. + +**end point:**:get-all-kitty-list + +**Returns:** All basic kitties irrespective of owner. + +```sh +$ curl -X GET -H "Content-Type: application/json" http://localhost:3000/get-all-kitty-list + +``` + +### Get owned kitties: + +Rest API forgetting all the owned kitties by any particular owner i.e. public key stored in the local db. + +**end point:**:get-owned-kitty-list + +**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example : d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +**Returns:** All the kitties owned by the user i.e public key. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "owner_public_key: 563b6da067f38dc194cbe41ce0b840a985dcbef92b1e5b0a6e04f35544ddfd16" http://localhost:3000/get-owned-kitty-list + +``` +### Get kitty details by DNA : + +Rest API for getting all the details of the kitty by DNA. + +**end point:**:get-kitty-by-dna + +**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de + +**Returns:** The kitty whose DNA matches, else None. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de" http://localhost:3000/get-kitty-by-dna + +``` +## From now on all the below APIS will have two API Calls in Sequence for one operation: + +**1. Get Transaction and Input UTXO List:** + Retrieves the transaction and input list required for generating the Redeemer by the web DApp. This call is not routed to the blockchain but is handled entirely by the web service. + + **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service :** + Sends the signed transaction to the blockchain via web service for verification and validation using the verifier and constraint checker, respectively. + + +### List kitty for sale : +Rest API used for listing a Kitty for sale, converting it into a TradableKitty with an associated price. + +**1. Get Transaction and Input UTXO List for list kitty for sale:** + +**end point:**:get-txn-and-inpututxolist-for-listkitty-forsale + +**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de + +**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example : d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +**kitty-price:** Price of the kitty + +**Returns:** Transaction for listing a kitty for sale without redeemer. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 394bd079207af3e0b1a9b1eb1dc40d5d5694bd1fd904d56b96d6fad0039b1f7c" -H "kitty-price: 100" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-listkitty-forsale + +``` + **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** + + **end point:**:listkitty-for-sale + +**signed_transaction:**: Send the signed transaction. i.e all inputs should have redeemer to prove the ownership of spending or usage. + +**Returns:** Tradable kitty . + + ```sh +$ curl -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0x0367d974927186bdeb3f1f1c111352711d9e1106a68bde6e4cfd0e64722e4f3a","index":0},"redeemer":[198, 69, 78, 148, 249, 1, 63, 2, 217, 105, 106, 87, 179, 252, 24, 66, 129, 190, 253, 17, 31, 87, 71, 231, 100, 31, 9, 81, 93, 141, 7, 81, 155, 0, 27, 38, 87, 16, 30, 55, 164, 220, 174, 37, 207, 163, 82, 216, 155, 195, 166, 253, 67, 95, 47, 240, 74, 20, 108, 160, 185, 71, 199, 129]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"TradableKitty":"ListKittiesForSale"}},"input_utxo_list":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}]}' \ + http://localhost:3000/listkitty-for-sale + +``` + +### Tradable kitty name update : +Rest API is used for updating the name of tradable kitty. + +**1. Get Transaction and Input UTXO List for name update of tradable kitty:** + +**end point:**:get-txn-and-inpututxolist-for-td-kitty-name-update + +**DNA of tradable kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de + +**Public_key of owner of tradable kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +**kitty-new-name:** New name of the kitty + +**Returns:** Transaction with tradable kitty name update without redeemer. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 394bd079207af3e0b1a9b1eb1dc40d5d5694bd1fd904d56b96d6fad0039b1f7c" -H "kitty-new-name: jbbl" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-td-kitty-name-update + +``` + **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** + + **end point:**:update-td-kitty-name + +**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. + + **Returns:** Tradable kitty with updated name. + + ```sh +$ curl -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0xb696b071fdbdca1adcec9149d21a167a04d851693e97b70900ac7547e23c0d0e","index":0},"redeemer":[232, 135, 109, 225, 49, 100, 3, 154, 233, 14, 37, 46, 219, 87, 87, 126, 194, 46, 21, 194, 58, 138, 235, 176, 121, 59, 164, 20, 98, 31, 165, 109, 121, 81, 63, 97, 243, 214, 105, 123, 163, 143, 8, 179, 52, 18, 168, 140, 193, 238, 120, 215, 59, 174, 231, 168, 22, 92, 124, 114, 78, 51, 15, 129]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,98,98,108,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"TradableKitty":"UpdateKittiesName"}},"input_utxo_list":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}]}' \ + http://localhost:3000/update-td-kitty-name + +``` +### Tradable kitty price update : +Rest API is used for updating the price of tradable kitty. + +**1. Get Transaction and Input UTXO List for price update of tradable kitty:** + +**end point:**:get-txn-and-inpututxolist-for-td-kitty-name-update + +**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de + +**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +**kitty-new-name:** New name of the kitty + +**Returns:** Transaction with tradable kitty price update without redeemer. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 394bd079207af3e0b1a9b1eb1dc40d5d5694bd1fd904d56b96d6fad0039b1f7c" -H "kitty-new-name: jbbl" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-td-kitty-name-update + +``` + **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** + + **end point:**:update-td-kitty-name + +**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. + + **Returns:** Tradable kitty with updated price. + + ```sh +$ curl -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0xb696b071fdbdca1adcec9149d21a167a04d851693e97b70900ac7547e23c0d0e","index":0},"redeemer":[232, 135, 109, 225, 49, 100, 3, 154, 233, 14, 37, 46, 219, 87, 87, 126, 194, 46, 21, 194, 58, 138, 235, 176, 121, 59, 164, 20, 98, 31, 165, 109, 121, 81, 63, 97, 243, 214, 105, 123, 163, 143, 8, 179, 52, 18, 168, 140, 193, 238, 120, 215, 59, 174, 231, 168, 22, 92, 124, 114, 78, 51, 15, 129]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,98,98,108,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"TradableKitty":"UpdateKittiesName"}},"input_utxo_list":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}]}' \ + http://localhost:3000/update-td-kitty-name +``` + +### De-List kitty from sale : +Rest API is used for removing a tradable Kitty from the sale, converting it back to a Basic Kitty without an associated price. + +**1. Get Transaction and Input UTXO List for delist-kitty-from-sale:** + +**end point:**:get-txn-and-inpututxolist-for-delist-kitty-from-sale + +**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de + +**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + + **Returns:** Transaction with a delisted kitty without redeemer.. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna:95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-delist-kitty-from-sale + +``` + **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** + + **end point:**:update-td-kitty-name + +**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. + + **Returns:** Basic kitty. + + ```sh +$ curl -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0xe680ce989ddaa35c7ed9f3ec1f48ff956457e00a9f4635bd97f2e682cf7e300a","index":0},"redeemer":[74, 200, 62, 251, 42, 74, 130, 155, 97, 200, 209, 13, 99, 178, 179, 5, 181, 124, 177, 221, 67, 131, 151, 81, 188, 224, 7, 56, 253, 244, 36, 76, 23, 177, 67, 218, 177, 229, 88, 178, 78, 42, 182, 143, 133, 172, 75, 96, 169, 132, 83, 203, 16, 210, 96, 190, 19, 118, 84, 78, 40, 56, 236, 128]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,98,98,108],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"TradableKitty":"DelistKittiesFromSale"}},"input_utxo_list":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,98,98,108,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}]}' \ + http://localhost:3000/delist-kitty-from-sale + +``` + +### kitty name update : +Rest API is used for updating the name of basic kitty. + +**1. Get Transaction and Input UTXO List for name update of kitty:** + +**end point:**:get-txn-and-inpututxolist-for-kitty-name-update + +**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de + +**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +**kitty-new-name:** New name of the kitty + +**Returns:** Transaction with kitty name update without redeemer. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de" -H "kitty-new-name: jram" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-kitty-name-update + +``` + **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** + + **end point:**:update-kitty-name + +**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. + +**Returns:** Kitty with an updated name. + + ```sh +$ curl -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0x9492d8c80fb5a8cf2720c0072d00c91c821502894fa4482a9c99fc027bf22daf","index":0},"redeemer":[132, 84, 163, 3, 64, 12, 74, 150, 176, 70, 223, 124, 252, 222, 23, 187, 141, 55, 207, 97, 55, 172, 128, 201, 147, 148, 8, 228, 108, 113, 36, 24, 10, 118, 178, 195, 8, 124, 127, 238, 172, 23, 127, 249, 203, 109, 196, 101, 76, 64, 162, 102, 184, 93, 63, 187, 193, 247, 129, 94, 44, 84, 200, 141]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,114,97,109],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"FreeKitty":"UpdateKittiesName"}}}' \ + http://localhost:3000/update-kitty-name + +``` + +### Breed kitty : +Rest API is used for breeding a new Kitty from two parent Kitties, creating a child DNA based on both + +**1. Get Transaction and Input UTXO List for breed kitty:** + +**end point:**:get-txn-and-inpututxolist-for-breed-kitty + +**DNA of mom kitty:** Input the DNA of kitty. Note it should start without 0X. Example e9243fb13a45a51d221cfca21a1a197aa35a1f0723cae3497fda971c825cb1d6 + +**DNA of dad kitty:** Input the DNA of kitty. Note it should start without 0X. Example 9741b6456f4b82bb243adfe5e887de9ce3a70e01d7ab39c0f9f565b24a2b059b + +**Public_key of the owner of kitties:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +**"child-kitty-name** Name of child kitty + +**Returns:** Transaction with breeding info such as mom, dad, child i.e. new family without a redeemer. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "mom-dna: e9243fb13a45a51d221cfca21a1a197aa35a1f0723cae3497fda971c825cb1d6" -H "dad-dna: 9741b6456f4b82bb243adfe5e887de9ce3a70e01d7ab39c0f9f565b24a2b059b" -H "child-kitty-name: jram" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-breed-kitty + +``` + **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** + + **end point:**:breed-kitty + +**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. + +**Returns:** New family. I.e Mom kitty, Dad kitty and Child kitty. The mom and dad will have breeding status updated EX: From raringToGo to Tired or hadRecentBirth. + + ```sh +$ curl -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0x8f83929cfc36c5ea445787421278f0688a2e7b482e71bd75d5ac7f36028c575b","index":0},"redeemer":[238, 126, 35, 95, 5, 149, 96, 160, 143, 172, 139, 56, 130, 116, 141, 93, 52, 181, 62, 9, 81, 32, 56, 199, 30, 48, 28, 186, 247, 72, 180, 125, 163, 197, 198, 5, 254, 86, 113, 164, 20, 112, 49, 37, 217, 91, 175, 248, 183, 126, 250, 169, 118, 165, 213, 242, 27, 47, 249, 32, 158, 89, 232, 141]},{"output_ref":{"tx_hash":"0x6bb11e2df46081e9252787342116b0b32be9d3302ca1dac535df85642ba46242","index":0},"redeemer":[112, 18, 73, 37, 101, 45, 254, 161, 83, 84, 12, 135, 125, 65, 6, 235, 200, 84, 16, 109, 12, 247, 240, 52, 116, 11, 46, 109, 86, 241, 69, 26, 223, 154, 215, 190, 247, 110, 248, 75, 246, 71, 126, 223, 23, 180, 233, 209, 98, 9, 178, 82, 46, 52, 110, 251, 52, 223, 232, 182, 82, 226, 5, 143]}],"peeks":[],"outputs":[{"payload":{"data":[0,1,1,0,0,0,0,0,0,0,233,36,63,177,58,69,165,29,34,28,252,162,26,26,25,122,163,90,31,7,35,202,227,73,127,218,151,28,130,92,177,214,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}},{"payload":{"data":[1,1,1,0,0,0,0,0,0,0,151,65,182,69,111,75,130,187,36,58,223,229,232,135,222,156,227,167,14,1,215,171,57,192,249,245,101,178,74,43,5,155,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,116,105],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}},{"payload":{"data":[0,0,2,0,0,0,0,0,0,0,191,64,163,127,195,246,227,90,81,218,5,243,219,78,156,51,82,162,4,192,66,249,180,130,64,229,219,239,136,216,243,153,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,114,97,109],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"FreeKitty":"Breed"}}}' \ + http://localhost:3000/breed-kitty + +``` +**The output message of breed looks like the below :** + ```sh +$O/P message : +{"message":"Kitty breeding done successfully","mom_kitty":{"parent":{"Mom":"HadBirthRecently"},"free_breedings":1,"dna":"0xe9243fb13a45a51d221cfca21a1a197aa35a1f0723cae3497fda971c825cb1d6","num_breedings":1,"name":[97,109,105,116]},"dad_kitty":{"parent":{"Dad":"Tired"},"free_breedings":1,"dna":"0x9741b6456f4b82bb243adfe5e887de9ce3a70e01d7ab39c0f9f565b24a2b059b","num_breedings":1,"name":[97,109,116,105]},"child_kitty":{"parent":{"Mom":"RearinToGo"},"free_breedings":2,"dna":"0xbf40a37fc3f6e35a51da05f3db4e9c3352a204c042f9b48240e5dbef88d8f399","num_breedings":0,"name":[106,114,97,109]}} + +``` + +### Buy tradable kitty : +Rest API that allows buying a Tradable Kitty from a seller using cryptocurrency i.e money/coin + +**1. Get Transaction and Input UTXO List for buying tradable kitty:** + +**end point:**:get-txn-and-inpututxolist-for-buy-kitty + +**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de + +**input-coins:** Reference of input coins owned by the buyer to be used for buying. We can input multiple input coins. EX: 4d732d8b0d0995151617c5c3beb600dc07a9e1be9fc8e95d9c792be42d65911000000000 + +**output_amount:** The amount to be paid for transaction which should be >= price of kitty. + +**buyer_public_key:** Public key of buyer i.e owner of coins used for buying: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 + +**seller_public_key:** Public key of seller i.e owner of kitty to be sold: Note it should start without 0X. Example: fab33c8c12f8df78fa515faa2fcc4bbf7829804a4d187984e13253660a9c1223 + +**Returns:** Transaction containing coins and kitty used in trading along with public keys of owner without redeemer. + +```sh +$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: bc147303f7d0a361ac22a50bf2ca2ec513d926a327ed678827c90d6512feadd6" -H "input-coins: 4d732d8b0d0995151617c5c3beb600dc07a9e1be9fc8e95d9c792be42d65911000000000" -H "output_amount: 200" -H "buyer_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" -H "seller_public_key: fab33c8c12f8df78fa515faa2fcc4bbf7829804a4d187984e13253660a9c1223"http://localhost:3000/get-txn-and-inpututxolist-for-buy-kitty + +``` + **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** + + **end point:**:/buy_kitty + +**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. + + **Returns:** Traded kitty with success or fail message. + + ```sh +$ curl -X POST \ + -H "Content-Type: application/json" \ + -d '{ + "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0x9bffe2abf274e0008f3f34af60cd083e909f884f2064e10f25ca46166306ae81","index":0},"redeemer":[134, 152, 55, 235, 162, 163, 255, 144, 247, 94, 237, 234, 127, 220, 149, 66, 226, 223, 43, 116, 16, 156, 165, 251, 221, 234, 13, 136, 132, 189, 187, 27, 206, 197, 48, 23, 188, 43, 41, 94, 103, 242, 174, 100, 249, 158, 206, 55, 88, 199, 103, 246, 227, 126, 138, 252, 205, 7, 132, 3, 112, 239, 52, 129]},{"output_ref":{"tx_hash":"0x4d732d8b0d0995151617c5c3beb600dc07a9e1be9fc8e95d9c792be42d659110","index":0},"redeemer":[166, 2, 32, 88, 200, 30, 54, 252, 155, 169, 122, 237, 29, 44, 33, 22, 102, 77, 71, 128, 35, 214, 84, 147, 193, 59, 45, 110, 69, 52, 25, 75, 5, 248, 227, 232, 110, 165, 177, 178, 218, 240, 235, 61, 25, 248, 242, 132, 106, 115, 62, 88, 57, 238, 39, 150, 202, 64, 237, 111, 147, 210, 215, 131]}],"peeks":[],"outputs":[{"payload":{"data":[0,0,2,0,0,0,0,0,0,0,188,20,115,3,247,208,163,97,172,34,165,11,242,202,46,197,19,217,38,163,39,237,103,136,39,201,13,101,18,254,173,214,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}},{"payload":{"data":[200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[99,111,105,0]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xfab33c8c12f8df78fa515faa2fcc4bbf7829804a4d187984e13253660a9c1223"}}}],"checker":{"TradableKitty":"Buy"}}}' \ + http://localhost:3000/buy_kitty +``` + +After the transaction is success.We can verify the kitty is transferred to buyer and coins are transferred to the seller using the below rest APIS : + + ```sh +$ curl -X GET -H "Content-Type: application/json" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-owned-kitty-list + + +curl -X GET -H "Content-Type: application/json" -H "owner_public_key: fab33c8c12f8df78fa515faa2fcc4bbf7829804a4d187984e13253660a9c1223" http://localhost:3000/get-owned-kitty-list + +``` + +My test results of buy kitty: https://github.com/mlabs-haskell/TuxedoDapp/issues/27#issuecomment-2029302071 + +Please also see the below link for how to achieve the buy transaction which involves of signing from both buyer and seller in the same transaction : https://github.com/Off-Narrative-Labs/Tuxedo/issues/169 diff --git a/webservice-wallet-external-signing/src/amoeba.rs b/webservice-wallet-external-signing/src/amoeba.rs new file mode 100644 index 000000000..4ed66b282 --- /dev/null +++ b/webservice-wallet-external-signing/src/amoeba.rs @@ -0,0 +1,127 @@ +//! Toy off-chain process to create an amoeba and perform mitosis on it + +use crate::rpc::fetch_storage; + +use std::{thread::sleep, time::Duration}; + +use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; +use parity_scale_codec::Encode; +use runtime::{ + amoeba::{AmoebaCreation, AmoebaDetails, AmoebaMitosis}, + OuterVerifier, Transaction, +}; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use tuxedo_core::{ + types::{Input, Output, OutputRef}, + verifier::UpForGrabs, +}; + +pub async fn amoeba_demo(client: &HttpClient) -> anyhow::Result<()> { + // Construct a simple amoeba spawning transaction (no signature required) + let eve = AmoebaDetails { + generation: 0, + four_bytes: *b"eve_", + }; + let spawn_tx = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: vec![Output { + payload: eve.into(), + verifier: UpForGrabs.into(), + }], + checker: AmoebaCreation.into(), + }; + + // Calculate the OutputRef which also serves as the storage location + let eve_ref = OutputRef { + tx_hash: ::hash_of(&spawn_tx.encode()), + index: 0, + }; + + // Send the transaction + let spawn_hex = hex::encode(spawn_tx.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + println!("Node's response to spawn transaction: {:?}", spawn_response); + + // Wait a few seconds to make sure a block has been authored. + sleep(Duration::from_secs(3)); + + // Check that the amoeba is in storage and print its details + let eve_from_storage: AmoebaDetails = fetch_storage::(&eve_ref, client) + .await? + .payload + .extract()?; + println!("Eve Amoeba retrieved from storage: {:?}", eve_from_storage); + + // Create a mitosis transaction on the Eve amoeba + let cain = AmoebaDetails { + generation: 1, + four_bytes: *b"cain", + }; + let able = AmoebaDetails { + generation: 1, + four_bytes: *b"able", + }; + let mitosis_tx = Transaction { + inputs: vec![Input { + output_ref: eve_ref, + redeemer: Vec::new(), + }], + peeks: Vec::new(), + outputs: vec![ + Output { + payload: cain.into(), + verifier: UpForGrabs.into(), + }, + Output { + payload: able.into(), + verifier: UpForGrabs.into(), + }, + ], + checker: AmoebaMitosis.into(), + }; + + // Calculate the two OutputRefs for the daughters + let cain_ref = OutputRef { + tx_hash: ::hash_of(&mitosis_tx.encode()), + index: 0, + }; + let able_ref = OutputRef { + tx_hash: ::hash_of(&mitosis_tx.encode()), + index: 1, + }; + + // Send the mitosis transaction + let mitosis_hex = hex::encode(mitosis_tx.encode()); + let params = rpc_params![mitosis_hex]; + let mitosis_response: Result = + client.request("author_submitExtrinsic", params).await; + println!( + "Node's response to mitosis transaction: {:?}", + mitosis_response + ); + + // Wait a few seconds to make sure a block has been authored. + sleep(Duration::from_secs(3)); + + // Check that the daughters are in storage and print their details + let cain_from_storage: AmoebaDetails = fetch_storage::(&cain_ref, client) + .await? + .payload + .extract()?; + println!( + "Cain Amoeba retrieved from storage: {:?}", + cain_from_storage + ); + let able_from_storage: AmoebaDetails = fetch_storage::(&able_ref, client) + .await? + .payload + .extract()?; + println!( + "Able Amoeba retrieved from storage: {:?}", + able_from_storage + ); + + Ok(()) +} diff --git a/webservice-wallet-external-signing/src/cli.rs b/webservice-wallet-external-signing/src/cli.rs new file mode 100644 index 000000000..3d133421c --- /dev/null +++ b/webservice-wallet-external-signing/src/cli.rs @@ -0,0 +1,388 @@ +//! Tuxedo Template Wallet's Command Line Interface. +//! +//! Built with clap's derive macros. + +use std::path::PathBuf; + +use clap::{ArgAction::Append, Args, Parser, Subcommand}; +use sp_core::H256; +use tuxedo_core::types::OutputRef; + +use crate::{h256_from_string, keystore::SHAWN_PUB_KEY, output_ref_from_string, DEFAULT_ENDPOINT}; + +/// The default number of coins to be minted. +pub const DEFAULT_MINT_VALUE: &str = "100"; + +/// Default recipient is SHAWN_KEY and output amount is 0 +pub const DEFAULT_RECIPIENT: &str = "d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 0"; + +/// The default name of the kitty to be created. Show be 4 character long +pub const DEFAULT_KITTY_NAME: &str = " "; + +/// The default gender of the kitty to be created. +pub const DEFAULT_KITTY_GENDER: &str = "female"; + + + +fn parse_recipient_coins(s: &str) -> Result<(H256, Vec), &'static str> { + println!("In parse_recipient_coins"); + let parts: Vec<&str> = s.split_whitespace().collect(); + if parts.len() >= 2 { + let recipient = h256_from_string(parts[0]); + let coins = parts[1..].iter().filter_map(|&c| c.parse().ok()).collect(); + match recipient { + Ok(r) => { + println!("Recipient: {}", r); + return Ok((r, coins)); + }, + _ => {}, + }; + } + println!("Sending the error value "); + Err("Invalid input format") +} + + + + +/// The wallet's main CLI struct +#[derive(Debug, Parser)] +#[command(about, version)] +pub struct Cli { + #[arg(long, short, default_value_t = DEFAULT_ENDPOINT.to_string())] + /// RPC endpoint of the node that this wallet will connect to. + pub endpoint: String, + + #[arg(long, short)] + /// Path where the wallet data is stored. Default value is platform specific. + pub path: Option, + + #[arg(long, verbatim_doc_comment)] + /// Skip the initial sync that the wallet typically performs with the node. + /// The wallet will use the latest data it had previously synced. + pub no_sync: bool, + + #[arg(long)] + /// A temporary directory will be created to store the configuration and will be deleted at the end of the process. + /// path will be ignored if this is set. + pub tmp: bool, + + #[arg(long, verbatim_doc_comment)] + /// Specify a development wallet instance, using a temporary directory (like --tmp). + /// The keystore will contain the development key Shawn. + pub dev: bool, + + #[command(subcommand)] + pub command: Option, +} + +/// The tasks supported by the wallet +#[derive(Debug, Subcommand)] +pub enum Command { + /// Print the block based on block height. + /// get the block hash ad print the block. + GetBlock { + /// Input the blockheight to be retrived. + block_height: Option, // fixme + }, + + /* + /// Demonstrate creating an amoeba and performing mitosis on it. + AmoebaDemo, + */ + /// Mint coins , optionally amount and publicKey of owner can be passed + /// if amount is not passed , 100 coins are minted + /// If publickKey of owner is not passed , then by default SHAWN_PUB_KEY is used. + #[command(verbatim_doc_comment)] + MintCoins(MintCoinArgs), + + /// Verify that a particular coin exists. + /// Show its value and owner from both chain storage and the local database. + #[command(verbatim_doc_comment)] + VerifyCoin { + /// A hex-encoded output reference + #[arg(value_parser = output_ref_from_string)] + output_ref: OutputRef, + }, + + //Some(Command::MintCoins { amount }) => money::mint_coins(&db, &client, &keystore,amount).await, + /// Spend some coins. + /// For now, all outputs in a single transaction go to the same recipient. + // FixMe: #62 + #[command(verbatim_doc_comment)] + SpendCoins(SpendArgs), + + /// Insert a private key into the keystore to later use when signing transactions. + InsertKey { + /// Seed phrase of the key to insert. + seed: String, + // /// Height from which the blockchain should be scanned to sync outputs + // /// belonging to this address. If non is provided, no re-syncing will + // /// happen and this key will be treated like a new key. + // sync_height: Option, + }, + + /// Generate a private key using either some or no password and insert into the keystore. + GenerateKey { + /// Initialize a public/private key pair with a password + password: Option, + }, + + /// Show public information about all the keys in the keystore. + ShowKeys, + + /// Remove a specific key from the keystore. + /// WARNING! This will permanently delete the private key information. + /// Make sure your keys are backed up somewhere safe. + #[command(verbatim_doc_comment)] + RemoveKey { + /// The public key to remove + #[arg(value_parser = h256_from_string)] + pub_key: H256, + }, + + /// For each key tracked by the wallet, shows the sum of all UTXO values owned by that key. + /// This sum is sometimes known as the "balance". + #[command(verbatim_doc_comment)] + ShowBalance, + + /// Show the complete list of UTXOs known to the wallet. + ShowAllOutputs, + + /// Show the latest on-chain timestamp. + ShowTimestamp, + + /// Create Kitty without mom and dad. + CreateKitty(CreateKittyArgs), + + /// Verify that a particular kitty exists. + /// Show its details and owner from both chain storage and the local database. + + #[command(verbatim_doc_comment)] + VerifyKitty { + /// A hex-encoded output reference + #[arg(value_parser = output_ref_from_string)] + output_ref: OutputRef, + }, + + /// Breed Kitties. + #[command(verbatim_doc_comment)] + BreedKitty(BreedKittyArgs), + + /// List Kitty for sale. + ListKittyForSale(ListKittyForSaleArgs), + + /// Delist Kitty from sale. + DelistKittyFromSale(DelistKittyFromSaleArgs), + + /// Update kitty name. + UpdateKittyName(UpdateKittyNameArgs), + + /// Update kitty price. Applicable only to tradable kitties + UpdateKittyPrice(UpdateKittyPriceArgs), + + /// Buy Kitty. + #[command(verbatim_doc_comment)] + BuyKitty(BuyKittyArgs), + + /// Show all kitties key tracked by the wallet. + #[command(verbatim_doc_comment)] + ShowAllKitties, + + /// ShowOwnedKitties. + /// Shows the kitties owned by owner. + #[command(verbatim_doc_comment)] + ShowOwnedKitties(ShowOwnedKittyArgs), +} + +#[derive(Debug, Args)] +pub struct MintCoinArgs { + /// Pass the amount to be minted. + #[arg(long, short, verbatim_doc_comment, action = Append,default_value = DEFAULT_MINT_VALUE)] + pub amount: u128, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +/* +// Old implementation +#[derive(Debug, Args)] +pub struct SpendArgs { + /// An input to be consumed by this transaction. This argument may be specified multiple times. + /// They must all be coins. + #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] + pub input: Vec, + + // /// All inputs to the transaction will be from this same sender. + // /// When not specified, inputs from any owner are chosen indiscriminantly + // #[arg(long, short, value_parser = h256_from_string)] + // sender: Option, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the recipient. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub recipient: H256, + + // The `action = Append` allows us to accept the same value multiple times. + /// An output amount. For the transaction to be valid, the outputs must add up to less than the sum of the inputs. + /// The wallet will not enforce this and will gladly send an invalid which will then be rejected by the node. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub output_amount: Vec, +} +*/ + +#[derive(Debug, Args,Clone)] +pub struct SpendArgs { + /// An input to be consumed by this transaction. This argument may be specified multiple times. + /// They must all be coins. + #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] + pub input: Vec, + + /// Variable number of recipients and their associated coins. + /// For example, "--recipients 0x1234 1 2 --recipients 0x5678 3 4 6" + // #[arg(long, short, verbatim_doc_comment, value_parser = parse_recipient_coins, action = Append)] + // pub recipients: Vec<(H256, Vec)>, + #[arg(long, short, verbatim_doc_comment, value_parser = parse_recipient_coins, action = Append, + default_value = DEFAULT_RECIPIENT)] + pub recipients: Vec<(H256, Vec)>, +} + +#[derive(Debug, Args)] +pub struct CreateKittyArgs { + + /// Pass the name of the kitty to be minted. + /// If kitty name is not passed ,name is choosen randomly from predefine name vector. + #[arg(long, short, action = Append, default_value = DEFAULT_KITTY_NAME)] + pub kitty_name: String, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct ShowOwnedKittyArgs { + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct BreedKittyArgs { + /// Name of Mom to be used for breeding . + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub mom_name: String, + + /// Name of Dad to be used for breeding . + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub dad_name: String, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct UpdateKittyNameArgs { + /// Current name of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub current_name: String, + + /// New name of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub new_name: String, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct UpdateKittyPriceArgs { + /// Current name of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub current_name: String, + + /// Price of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub price: u128, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct BuyKittyArgs { + /// An input to be consumed by this transaction. This argument may be specified multiple times. + /// They must all be coins. + #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] + pub input: Vec, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the seller of tradable kitty. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub seller: H256, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner of tradable kitty. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, + + /// Name of kitty to be bought. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub kitty_name: String, + + // The `action = Append` allows us to accept the same value multiple times. + /// An output amount. For the transaction to be valid, the outputs must add up to less than the sum of the inputs. + /// The wallet will not enforce this and will gladly send an invalid which will then be rejected by the node. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub output_amount: Vec, +} + +#[derive(Debug, Args)] +pub struct ListKittyForSaleArgs { + /// Pass the name of the kitty to be listed for sale. + #[arg(long, short, verbatim_doc_comment, action = Append, default_value = DEFAULT_KITTY_NAME)] + pub name: String, + + /// Price of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub price: u128, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct DelistKittyFromSaleArgs { + /// Pass the name of the kitty to be delisted or removed from the sale . + #[arg(long, short, verbatim_doc_comment, action = Append, default_value = DEFAULT_KITTY_NAME)] + pub name: String, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} diff --git a/webservice-wallet-external-signing/src/keystore.rs b/webservice-wallet-external-signing/src/keystore.rs new file mode 100644 index 000000000..34e6bb8d9 --- /dev/null +++ b/webservice-wallet-external-signing/src/keystore.rs @@ -0,0 +1,138 @@ +//! Wallet's local keystore. +//! +//! This is a thin wrapper around sc-cli for use in tuxedo wallets + +use anyhow::anyhow; +use parity_scale_codec::Encode; +use sc_keystore::LocalKeystore; +use sp_core::{ + crypto::Pair as PairT, + sr25519::{Pair, Public}, + H256, +}; +use sp_keystore::Keystore; +use sp_runtime::KeyTypeId; +use std::path::Path; +use crate::get_local_keystore; +/// A KeyTypeId to use in the keystore for Tuxedo transactions. We'll use this everywhere +/// until it becomes clear that there is a reason to use multiple of them +const KEY_TYPE: KeyTypeId = KeyTypeId(*b"_tux"); + +/// A default seed phrase for signing inputs when none is provided +/// Corresponds to the default pubkey. +pub const SHAWN_PHRASE: &str = + "news slush supreme milk chapter athlete soap sausage put clutch what kitten"; + +/// The public key corresponding to the default seed above. +pub const SHAWN_PUB_KEY: &str = "d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"; + +/// Insert the example "Shawn" key into the keystore for the current session only. +pub fn insert_development_key_for_this_session(keystore: &LocalKeystore) -> anyhow::Result<()> { + keystore.sr25519_generate_new(KEY_TYPE, Some(SHAWN_PHRASE))?; + Ok(()) +} + +/// Sign a given message with the private key that corresponds to the given public key. +/// +/// Returns an error if the keystore itself errors, or does not contain the requested key. +pub fn sign_with( + keystore: &LocalKeystore, + public: &Public, + message: &[u8], +) -> anyhow::Result> { + + let sig = keystore + .sr25519_sign(KEY_TYPE, public, message)? + .ok_or(anyhow!("Key doesn't exist in keystore"))?; + + Ok(sig.encode()) +} + +/// Insert the private key associated with the given seed into the keystore for later use. +pub fn insert_key(keystore: &LocalKeystore, seed: &str) -> anyhow::Result<()> { + // We need to provide a public key to the keystore manually, so let's calculate it. + let public_key = Pair::from_phrase(seed, None)?.0.public(); + println!("The generated public key is {:?}", public_key); + keystore + .insert(KEY_TYPE, seed, public_key.as_ref()) + .map_err(|()| anyhow!("Error inserting key"))?; + Ok(()) +} + +/// Generate a new key from system entropy and insert it into the keystore, optionally +/// protected by a password. +/// +/// TODO there is no password support when using keys later when signing. +/* +pub fn generate_key(keystore: &LocalKeystore, password: Option) -> anyhow::Result<()> { + + let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); + println!("Generated public key is {:?}", pair.public()); + println!("Generated Phrase is {}", phrase); + keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; + Ok(()) +} + + +pub async fn generate_key(password: Option) -> anyhow::Result<(String, String)> { + let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); + let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); + println!("Generated public key is {:?}", pair.public()); + println!("Generated Phrase is {}", phrase); + keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; + insert_key(&keystore,&phrase.as_str()); + + get_keys(&keystore)?.for_each(|pubkey| { + println!("key: 0x{}", hex::encode(pubkey)); + }); + + Ok((pair.public().to_string(), phrase)) +} + +pub fn get_keys(keystore: &LocalKeystore) -> anyhow::Result>> { + Ok(keystore.keys(KEY_TYPE)?.into_iter()) +} +*/ +pub async fn generate_key(password: Option) -> anyhow::Result<(String, String)> { + let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); + let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); + println!("Generated public key is {:?}", pair.public()); + println!("Generated Phrase is {}", phrase); + + // Insert the generated key pair into the keystore + + keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; + insert_key(&keystore,&phrase.as_str()); + get_keys().await?.for_each(|pubkey| { + println!("key: 0x{}", hex::encode(pubkey)); + }); + + let public_key_hex = hex::encode(pair.public()); + + Ok((public_key_hex.to_string(), phrase)) +} + + +/// Check whether a specific key is in the keystore +pub fn has_key(keystore: &LocalKeystore, pubkey: &H256) -> bool { + keystore.has_keys(&[(pubkey.encode(), KEY_TYPE)]) +} + +pub async fn get_keys() -> anyhow::Result>> { + let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); + + Ok(keystore.keys(KEY_TYPE)?.into_iter()) +} + + +/// Caution. Removes key from keystore. Call with care. +pub fn remove_key(keystore_path: &Path, pub_key: &H256) -> anyhow::Result<()> { + // The keystore doesn't provide an API for removing keys, so we + // remove them from the filesystem directly + let filename = format!("{}{}", hex::encode(KEY_TYPE.0), hex::encode(pub_key.0)); + let path = keystore_path.join(filename); + + std::fs::remove_file(path)?; + + Ok(()) +} diff --git a/webservice-wallet-external-signing/src/kitty.rs b/webservice-wallet-external-signing/src/kitty.rs new file mode 100644 index 000000000..f0f91dfca --- /dev/null +++ b/webservice-wallet-external-signing/src/kitty.rs @@ -0,0 +1,1235 @@ +use crate::money::get_coin_from_storage; +use crate::rpc::fetch_storage; +use crate::sync; + +//use crate::cli::BreedArgs; +use tuxedo_core::{ + dynamic_typing::UtxoData, + types::{Input, Output, OutputRef}, + verifier::Sr25519Signature, +}; + +use anyhow::anyhow; +use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; +use parity_scale_codec::Encode; +use rand::distributions::Alphanumeric; +use rand::Rng; +use sc_keystore::LocalKeystore; +use sled::Db; +use sp_core::sr25519::Public; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use crate::get_local_keystore; + +use runtime::{ + kitties::{ + DadKittyStatus, FreeKittyConstraintChecker, KittyDNA, KittyData, + MomKittyStatus, Parent, + }, + money::{Coin}, + tradable_kitties::{TradableKittyConstraintChecker, TradableKittyData}, + OuterVerifier, Transaction, +}; + +use crate::cli::DEFAULT_KITTY_NAME; + +use crate::cli::{ + BreedKittyArgs, BuyKittyArgs, CreateKittyArgs, DelistKittyFromSaleArgs, + UpdateKittyNameArgs, UpdateKittyPriceArgs, +}; +use parity_scale_codec::Decode; + +static MALE_KITTY_NAMES: [&str; 50] = [ + "Whis", "Purr", "Feli", "Leo", "Maxi", "Osca", "Simb", "Char", "Milo", "Tige", "Jasp", + "Smokey", "Oliv", "Loki", "Boot", "Gizm", "Rock", "Budd", "Shad", "Zeus", "Tedd", "Samm", + "Rust", "Tom", "Casp", "Blue", "Coop", "Coco", "Mitt", "Bent", "Geor", "Luck", "Rome", "Moch", + "Muff", "Ches", "Whis", "Appl", "Hunt", "Toby", "Finn", "Frod", "Sale", "Kobe", "Dext", "Jinx", + "Mick", "Pump", "Thor", "Sunn", +]; + +// Female kitty names +static FEMALE_KITTY_NAMES: [&str; 50] = [ + "Luna", "Mist", "Cleo", "Bell", "Lucy", "Nala", "Zoe", "Dais", "Lily", "Mia", "Chlo", "Stel", + "Coco", "Will", "Ruby", "Grac", "Sash", "Moll", "Lola", "Zara", "Mist", "Ange", "Rosi", "Soph", + "Zeld", "Layl", "Ambe", "Prin", "Cind", "Moch", "Zara", "Dais", "Cinn", "Oliv", "Peac", "Pixi", + "Harl", "Mimi", "Pipe", "Whis", "Cher", "Fion", "Kiki", "Suga", "Peac", "Ange", "Mapl", "Zigg", + "Lily", "Nova", +]; + +pub fn generate_random_string(length: usize) -> String { + let rng = rand::thread_rng(); + let random_string: String = rng + .sample_iter(&Alphanumeric) + .take(length) + .map(char::from) + .collect(); + random_string +} + +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub enum Gender { + Male, + Female, +} + +fn create_tx_input_based_on_td_kittyName( + db: &Db, + name: String, +) -> anyhow::Result<(TradableKittyData, Input)> { + let tradable_kitty = + crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, name.clone()); + let Some((tradable_kitty_info, out_ref)) = tradable_kitty.unwrap() else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let input = Input { + output_ref: out_ref, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + Ok((tradable_kitty_info, input)) +} + +fn create_tx_input_based_on_kitty_name<'a>( + db: &Db, + name: String, +) -> anyhow::Result<(KittyData, Input)> { + //let kitty = crate::sync::get_kitty_from_local_db_based_on_name(&db, name.clone()); + //let name_extractor_kitty_data = move |kitty: &'a KittyData| -> &'a [u8; 4] { &kitty.name }; + + let kitty = crate::sync::get_kitty_from_local_db_based_on_name(&db, name.clone()); + let Some((kitty_info, out_ref)) = kitty.unwrap() else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let input = Input { + output_ref: out_ref, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + Ok((kitty_info, input)) +} + +fn create_tx_input_based_on_td_kitty_name<'a>( + db: &Db, + name: String, +) -> anyhow::Result<(TradableKittyData, Input)> { + let kitty = crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, name.clone()); + let Some((kitty_info, out_ref)) = kitty.unwrap() else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let input = Input { + output_ref: out_ref, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + Ok((kitty_info, input)) +} + +fn print_new_output(transaction: &Transaction) -> anyhow::Result<()> { + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_ref = OutputRef { + tx_hash, + index: i as u32, + }; + match output.payload.type_id { + KittyData::TYPE_ID => { + let new_kitty = output.payload.extract::()?.dna.0; + print!( + "Created {:?} basic Kitty {:?}. ", + hex::encode(new_ref.encode()), + new_kitty + ); + } + TradableKittyData::TYPE_ID => { + let new_kitty = output + .payload + .extract::()? + .kitty_basic_data + .dna + .0; + print!( + "Created {:?} TradableKitty {:?}. ", + hex::encode(new_ref.encode()), + new_kitty + ); + } + Coin::<0>::TYPE_ID => { + let amount = output.payload.extract::>()?.0; + print!( + "Created {:?} worth {amount}. ", + hex::encode(new_ref.encode()) + ); + } + + _ => continue, + } + crate::pretty_print_verifier(&output.verifier); + } + Ok(()) +} + +async fn send_signed_tx( + transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result<()> { + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + + println!("Node's response to spawn transaction: {:?}", spawn_response); + match spawn_response { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), + } +} + +async fn send_tx( + transaction: &mut Transaction, + client: &HttpClient, + local_keystore: Option<&LocalKeystore>, +) -> anyhow::Result<()> { + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + + let _ = match local_keystore { + Some(ks) => { + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(ks, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + // insert the proof + input.redeemer = redeemer; + } + } + None => {} + }; + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + + println!("Node's response to spawn transaction: {:?}", spawn_response); + match spawn_response { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), + } +} + + + +fn gen_random_gender() -> Gender { + // Create a local random number generator + let mut rng = rand::thread_rng(); + + // Generate a random number between 0 and 1 + let random_number = rng.gen_range(0..=1); + + // We Use the random number to determine the gender + match random_number { + 0 => Gender::Male, + _ => Gender::Female, + } +} + +fn get_random_kitty_name_from_pre_defined_vec(g: Gender, name_slice: &mut [u8; 4]) { + // Create a local random number generator + let mut rng = rand::thread_rng(); + + // Generate a random number between 0 and 1 + let random_number = rng.gen_range(0..=50); + + // We Use the random number to determine the gender + + let name = match g { + Gender::Male => MALE_KITTY_NAMES[random_number], + Gender::Female => FEMALE_KITTY_NAMES[random_number], + }; + //name.to_string() + name_slice.copy_from_slice(name.clone().as_bytes()); +} + +fn convert_name_string_tostr_slice( + name: String, + name_slice: &mut [u8; 4],) -> anyhow::Result<()> { + if name.len() != 4 { + return Err(anyhow!( + "Please input a name of length 4 characters. Current length: {}", + name.len() + )); + } + + name_slice.copy_from_slice(name.clone().as_bytes()); + return Ok(()); +} + +fn validate_kitty_name_from_db( + db: &Db, + owner_pubkey: &H256, + name: String, + name_slice: &mut [u8; 4], +) -> anyhow::Result<()> { + + + match crate::sync::is_kitty_name_duplicate(&db, name.clone(), &owner_pubkey) { + Ok(Some(true)) => { + println!("Kitty name is duplicate , select another name"); + return Err(anyhow!( + "Please input a non-duplicate name of length 4 characters" + )); + } + _ => {} + }; + convert_name_string_tostr_slice(name,name_slice)?; + + return Ok(()); +} + +fn create_new_family( + new_mom: &mut KittyData, + new_dad: &mut KittyData, + new_child: &mut KittyData, +) -> anyhow::Result<()> { + new_mom.parent = Parent::Mom(MomKittyStatus::RearinToGo); + if new_mom.num_breedings >= 0 { + new_mom.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); + } + new_mom.num_breedings = new_mom.num_breedings.checked_add(1).expect("REASON"); + new_mom.free_breedings = new_mom.free_breedings.checked_sub(1).expect("REASON"); + + new_dad.parent = Parent::Dad(DadKittyStatus::RearinToGo); + if new_dad.num_breedings >= 0 { + new_dad.parent = Parent::Dad(DadKittyStatus::Tired); + } + + new_dad.num_breedings = new_dad.num_breedings.checked_add(1).expect("REASON"); + new_dad.free_breedings = new_dad.free_breedings.checked_sub(1).expect("REASON"); + + let child_gender = match gen_random_gender() { + Gender::Male => Parent::dad(), + Gender::Female => Parent::mom(), + }; + + let child = KittyData { + parent: child_gender, + free_breedings: 2, + name: *b"tomy", // Name of child kitty need to be generated in better way + dna: KittyDNA(BlakeTwo256::hash_of(&( + new_mom.dna.clone(), + new_dad.dna.clone(), + new_mom.num_breedings, + new_dad.num_breedings, + ))), + num_breedings: 0, + // price: None, + // is_available_for_sale: false, + }; + *new_child = child; + Ok(()) +} + +pub async fn create_kitty( + db: &Db, + client: &HttpClient, + args: CreateKittyArgs, +) -> anyhow::Result> { + let mut kitty_name = [0; 4]; + + let g = gen_random_gender(); + let gender = match g { + Gender::Male => Parent::dad(), + Gender::Female => Parent::mom(), + }; + + convert_name_string_tostr_slice(args.kitty_name.clone(), &mut kitty_name)?; + // Generate a random string of length 5 + let random_string = generate_random_string(5) + args.kitty_name.as_str(); + let dna_preimage: &[u8] = random_string.as_bytes(); + + let child_kitty = KittyData { + parent: gender, + dna: KittyDNA(BlakeTwo256::hash(dna_preimage)), + name: kitty_name, // Default value for now, as the provided code does not specify a value + ..Default::default() + }; + + println!("DNA of created kitty is {:?}",child_kitty.dna); + + // Create the Output + let output = Output { + payload: child_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + // Create the Transaction + let mut transaction = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: vec![output], + checker: FreeKittyConstraintChecker::Create.into(), + }; + + send_tx(&mut transaction, &client, None).await?; + print_new_output(&transaction)?; + Ok(Some(child_kitty)) +} + +pub async fn send_txn_with_td_kitty_as_output( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result> { + let tradable_kitty: TradableKittyData = signed_transaction.outputs[0].payload + .extract::() + .map_err(|_| anyhow!("Invalid output, Expected TradableKittyData"))?; + + send_signed_tx(&signed_transaction, &client).await?; + print_new_output(&signed_transaction)?; + Ok(Some(tradable_kitty)) +} + +pub async fn send_txn_with_kitty_as_output( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result> { + let kitty: KittyData = signed_transaction.outputs[0].payload + .extract::() + .map_err(|_| anyhow!("Invalid output, Expected KittyData"))?; + + send_signed_tx(&signed_transaction, &client).await?; + print_new_output(&signed_transaction)?; + Ok(Some(kitty)) +} + +pub async fn list_kitty_for_sale( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result> { + send_txn_with_td_kitty_as_output(&signed_transaction,&client).await +} + +pub async fn update_kitty_name( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result> { + send_txn_with_kitty_as_output(&signed_transaction,&client).await +} + +pub async fn update_td_kitty_name( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result> { + send_txn_with_td_kitty_as_output(&signed_transaction,&client).await +} + +pub async fn update_td_kitty_price( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result> { + send_txn_with_td_kitty_as_output(&signed_transaction,&client).await +} + +pub async fn delist_kitty_from_sale( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result> { + send_txn_with_kitty_as_output(&signed_transaction,&client).await +} + + +pub async fn breed_kitty( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result>> { + let mom_kitty: KittyData = signed_transaction.outputs[0].payload + .extract::() + .map_err(|_| anyhow!("Invalid output, Expected KittyData"))?; + + let dad_kitty: KittyData = signed_transaction.outputs[1].payload + .extract::() + .map_err(|_| anyhow!("Invalid output, Expected KittyData"))?; + + let child_kitty: KittyData = signed_transaction.outputs[2].payload + .extract::() + .map_err(|_| anyhow!("Invalid output, Expected KittyData"))?; + + send_signed_tx(&signed_transaction, &client).await?; + print_new_output(&signed_transaction)?; + Ok(vec![ + mom_kitty.clone(), + dad_kitty.clone(), + child_kitty.clone(), + ].into()) +} + +pub async fn buy_kitty( + signed_transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result> { + let traded_kitty: TradableKittyData = signed_transaction.outputs[0].payload + .extract::() + .map_err(|_| anyhow!("Invalid output, Expected TradableKittyData"))?; + + + send_signed_tx(&signed_transaction, &client).await?; + print_new_output(&signed_transaction)?; + Ok(Some(traded_kitty)) +} + +pub async fn buy_kitty1( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: BuyKittyArgs, +) -> anyhow::Result> { + log::info!("The Buy kittyArgs are:: {:?}", args); + + let Ok((kitty_info, kitty_ref)) = + create_tx_input_based_on_td_kittyName(db, args.kitty_name.clone()) + else { + return Err(anyhow!("No kitty with name {} in localdb", args.kitty_name)); + }; + + let inputs: Vec = vec![kitty_ref]; + // Create the KittyData + let output_kitty = kitty_info.clone(); + + let output = Output { + payload: output_kitty.into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::Buy.into(), + }; + + // Construct each output and then push to the transactions for Money + let mut total_output_amount = 0; + for amount in &args.output_amount { + let output = Output { + payload: Coin::<0>::new(*amount).into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.seller, + }), + }; + total_output_amount += amount; + transaction.outputs.push(output); + if total_output_amount >= kitty_info.price.into() { + break; + } + } + + let mut total_input_amount = 0; + let mut all_input_refs = args.input; + for output_ref in &all_input_refs { + let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( + "user-specified output ref not found in local database" + ))?; + total_input_amount += amount; + } + + if total_input_amount < total_output_amount { + match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { + Some(more_inputs) => { + all_input_refs.extend(more_inputs); + } + None => Err(anyhow!( + "Not enough value in database to construct transaction" + ))?, + } + } + + // Make sure each input decodes and is still present in the node's storage, + // and then push to transaction. + for output_ref in &all_input_refs { + get_coin_from_storage(output_ref, client).await?; + transaction.inputs.push(Input { + output_ref: output_ref.clone(), + redeemer: vec![], // We will sign the total transaction so this should be empty + }); + } + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + + Ok(Some(kitty_info)) +} + + +pub async fn update_kitty_price( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: UpdateKittyPriceArgs, +) -> anyhow::Result> { + log::info!("The set_kitty_property are:: {:?}", args); + let Ok((input_kitty_info, input_kitty_ref)) = + create_tx_input_based_on_td_kitty_name(db, args.current_name.clone()) + else { + return Err(anyhow!( + "No kitty with name {} in localdb", + args.current_name + )); + }; + + let inputs: Vec = vec![input_kitty_ref]; + let mut updated_kitty: TradableKittyData = input_kitty_info; + updated_kitty.price = args.price; + let output = Output { + payload: updated_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::UpdateKittiesPrice.into(), + }; + + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + Ok(Some(updated_kitty)) +} + +/// Apply a transaction to the local database, storing the new coins. +pub(crate) fn apply_transaction( + db: &Db, + tx_hash: ::Output, + index: u32, + output: &Output, +) -> anyhow::Result<()> { + let kitty_detail: KittyData = output.payload.extract()?; + + let output_ref = OutputRef { tx_hash, index }; + println!("in kitty:apply_transaction output_ref = {:?}", output_ref); + match output.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + // Add it to the global unspent_outputs table + crate::sync::add_fresh_kitty_to_db(db, &output_ref, &owner_pubkey, &kitty_detail) + } + _ => Err(anyhow!("{:?}", ())), + } +} + +pub(crate) fn convert_kitty_name_string(kitty: &KittyData) -> Option { + if let Ok(kitty_name) = std::str::from_utf8(&kitty.name) { + return Some(kitty_name.to_string()); + } else { + println!("Invalid UTF-8 data in the Kittyname"); + } + None +} + +/// Given an output ref, fetch the details about this coin from the node's +/// storage. +// Need to revisit this for tradableKitty +pub async fn get_kitty_from_storage( + output_ref: &OutputRef, + client: &HttpClient, +) -> anyhow::Result<(KittyData, OuterVerifier)> { + let utxo = fetch_storage::(output_ref, client).await?; + + let kitty_in_storage: KittyData = utxo.payload.extract()?; + + Ok((kitty_in_storage, utxo.verifier)) +} + +/// Apply a transaction to the local database, storing the new coins. +pub(crate) fn apply_td_transaction( + db: &Db, + tx_hash: ::Output, + index: u32, + output: &Output, +) -> anyhow::Result<()> { + let tradable_kitty_detail: TradableKittyData = output.payload.extract()?; + + let output_ref = OutputRef { tx_hash, index }; + println!( + "in Tradable kitty:apply_transaction output_ref = {:?}", + output_ref + ); + match output.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + // Add it to the global unspent_outputs table + crate::sync::add_fresh_tradable_kitty_to_db( + db, + &output_ref, + &owner_pubkey, + &tradable_kitty_detail, + ) + } + _ => Err(anyhow!("{:?}", ())), + } +} + +pub(crate) fn convert_td_kitty_name_string(tradable_kitty: &TradableKittyData) -> Option { + if let Ok(kitty_name) = std::str::from_utf8(&tradable_kitty.kitty_basic_data.name) { + return Some(kitty_name.to_string()); + } else { + println!("Invalid UTF-8 data in the Kittyname"); + } + None +} + +/// Given an output ref, fetch the details about this coin from the node's +/// storage. +pub async fn get_td_kitty_from_storage( + output_ref: &OutputRef, + client: &HttpClient, +) -> anyhow::Result<(TradableKittyData, OuterVerifier)> { + let utxo = fetch_storage::(output_ref, client).await?; + let kitty_in_storage: TradableKittyData = utxo.payload.extract()?; + + Ok((kitty_in_storage, utxo.verifier)) +} + +////////////////////////////////////////////////////////////////// +// Below is for web server handling +////////////////////////////////////////////////////////////////// + + +async fn send_tx_with_signed_inputs( + transaction: &mut Transaction, + client: &HttpClient, + local_keystore: Option<&LocalKeystore>, + signed_inputs: Vec, +) -> anyhow::Result<()> { + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + transaction.inputs = signed_inputs; + + let _ = match local_keystore { + Some(ks) => { + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(ks, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + // insert the proof + input.redeemer = redeemer; + } + } + None => {} + }; + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + + println!("Node's response to spawn transaction: {:?}", spawn_response); + match spawn_response { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), + } +} + +pub async fn create_txn_for_list_kitty( + db: &Db, + dna: &str, + price:u128, + public_key:H256, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + + let mut found_kitty: Option<(KittyData, OutputRef)> = None; + + if let Ok(Some((kitty_info, out_ref))) = crate::sync::get_kitty_from_local_db_based_on_dna(&db,dna) { + found_kitty = Some((kitty_info, out_ref)); + } else { + return Err(anyhow!("No kitty with DNA {} in localdb", dna)); + } + + let input = Input { + output_ref: found_kitty.clone().unwrap().1, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + let inputs: Vec = vec![input]; + + let tradable_kitty = TradableKittyData { + kitty_basic_data: found_kitty.unwrap().0, + price: price, + }; + + // Create the Output + let output = Output { + payload: tradable_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: public_key, + }), + }; + + // Create the Transaction + let transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::ListKittiesForSale.into(), + }; + print_debug_signed_txn_with_local_ks(transaction.clone()).await?; + + Ok(Some(transaction)) +} + +pub async fn create_txn_for_delist_kitty( + db: &Db, + dna: &str, + public_key:H256, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + + let mut found_kitty: Option<(TradableKittyData, OutputRef)> = None; + + if let Ok(Some((td_kitty_info, out_ref))) = crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,dna) { + found_kitty = Some((td_kitty_info, out_ref)); + } else { + return Err(anyhow!("No kitty with DNA {} in localdb", dna)); + } + + let input = Input { + output_ref: found_kitty.clone().unwrap().1, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + let inputs: Vec = vec![input]; + let kitty = found_kitty.unwrap().0.kitty_basic_data; + + let output = Output { + payload: kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: public_key, + }), + }; + + // Create the Transaction + let transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::DelistKittiesFromSale.into(), + }; + print_debug_signed_txn_with_local_ks(transaction.clone()).await?; + Ok(Some(transaction)) +} + +pub async fn create_txn_for_kitty_name_update( + db: &Db, + dna: &str, + new_name: String, + public_key:H256, +) -> anyhow::Result> { + let mut found_kitty: Option<(KittyData, OutputRef)> = None; + + if let Ok(Some((kitty_info, out_ref))) = + crate::sync::get_kitty_from_local_db_based_on_dna(&db,dna) + { + found_kitty = Some((kitty_info, out_ref)); + } else { + return Err(anyhow!("No kitty with DNA {} in localdb", dna)); + } + + let input = Input { + output_ref: found_kitty.clone().unwrap().1, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + let inputs: Vec = vec![input]; + + let mut kitty_name = [0; 4]; + convert_name_string_tostr_slice(new_name,&mut kitty_name)?; + + // found_kitty.name = new_name + let mut kitty = found_kitty.clone().unwrap().0; + kitty.name = kitty_name;// Name updated + + + // Create the Output + let output = Output { + payload: kitty.into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: public_key, + }), + }; + + // Create the Transaction + let transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: FreeKittyConstraintChecker::UpdateKittiesName.into(), + }; + + print_debug_signed_txn_with_local_ks(transaction.clone()).await?; + Ok(Some(transaction)) +} + + +pub async fn create_txn_for_td_kitty_name_update( + db: &Db, + dna: &str, + new_name: String, + public_key:H256, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + let mut found_kitty: Option<(TradableKittyData, OutputRef)> = None; + + if let Ok(Some((kitty_info, out_ref))) = + crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,dna) + { + found_kitty = Some((kitty_info, out_ref)); + } else { + return Err(anyhow!("No kitty with DNA {} in localdb", dna)); + } + + let input = Input { + output_ref: found_kitty.clone().unwrap().1, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + + let inputs: Vec = vec![input]; + + let mut kitty_name = [0; 4]; + convert_name_string_tostr_slice(new_name,&mut kitty_name)?; + + let mut td_kitty = found_kitty.clone().unwrap().0; + td_kitty.kitty_basic_data.name = kitty_name;// Name updated + + // Create the Output + let output = Output { + payload: td_kitty.into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: public_key, + }), + }; + + // Create the Transaction + let transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::UpdateKittiesName.into(), + }; + + print_debug_signed_txn_with_local_ks(transaction.clone()).await?; + Ok(Some(transaction)) +} + + +pub async fn create_txn_for_td_kitty_price_update( + db: &Db, + dna: &str, + new_price: u128, + public_key:H256, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + let mut found_kitty: Option<(TradableKittyData, OutputRef)> = None; + + if let Ok(Some((kitty_info, out_ref))) = + crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,dna) + { + found_kitty = Some((kitty_info, out_ref)); + } else { + return Err(anyhow!("No kitty with DNA {} in localdb", dna)); + } + + let input = Input { + output_ref: found_kitty.clone().unwrap().1, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + + let inputs: Vec = vec![input]; + + let mut td_kitty = found_kitty.clone().unwrap().0; + td_kitty.price = new_price;// price updated + + // Create the Output + let output = Output { + payload: td_kitty.into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: public_key, + }), + }; + + // Create the Transaction + let transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::UpdateKittiesPrice.into(), + }; + print_debug_signed_txn_with_local_ks(transaction.clone()).await?; + Ok(Some(transaction)) +} + +pub async fn create_txn_for_breed_kitty( + db: &Db, + mom_dna: &str, + dad_dna: &str, + child_name: String, + owner_public_key:H256, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + let mut mom_kitty: Option<(KittyData, OutputRef)> = None; + + if let Ok(Some((kitty_info, out_ref))) = + crate::sync::get_kitty_from_local_db_based_on_dna(&db,mom_dna) + { + mom_kitty = Some((kitty_info, out_ref)); + } else { + return Err(anyhow!("No kitty with MOM DNA {} in localdb", mom_dna)); + } + + let mom_input = Input { + output_ref: mom_kitty.clone().unwrap().1, + redeemer: vec![], + }; + + let mut dad_kitty: Option<(KittyData, OutputRef)> = None; + + if let Ok(Some((kitty_info, out_ref))) = + crate::sync::get_kitty_from_local_db_based_on_dna(&db,dad_dna) + { + dad_kitty = Some((kitty_info, out_ref)); + } else { + return Err(anyhow!("No kitty with DAD DNA {} in localdb", dad_dna)); + } + + let dad_input = Input { + output_ref: dad_kitty.clone().unwrap().1, + redeemer: vec![], + }; + + let inputs: Vec = vec![mom_input,dad_input]; + + let mut child_kitty_name = [0; 4]; + convert_name_string_tostr_slice(child_name,&mut child_kitty_name)?; + + let mut new_mom: KittyData = mom_kitty.clone().unwrap().0; + + let mut new_dad = dad_kitty.clone().unwrap().0; + + let mut child_kitty: KittyData = Default::default(); + + create_new_family(&mut new_mom, &mut new_dad, &mut child_kitty)?; + // Create the Output mom + println!("New mom Dna = {:?}", new_mom.dna); + println!("New Dad Dna = {:?}", new_dad.dna); + println!("Child Dna = {:?}", child_kitty.dna); + child_kitty.name = child_kitty_name; + + let output_mom = Output { + payload: new_mom.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: owner_public_key, + }), + }; + + // Create the Output dada + let output_dad = Output { + payload: new_dad.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: owner_public_key, + }), + }; + + let output_child = Output { + payload: child_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: owner_public_key, + }), + }; + + let new_family = Box::new(vec![output_mom, output_dad, output_child]); + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: (&[ + new_family[0].clone(), + new_family[1].clone(), + new_family[2].clone(), + ]) + .to_vec(), + checker: FreeKittyConstraintChecker::Breed.into(), + }; + + print_debug_signed_txn_with_local_ks(transaction.clone()).await?; + Ok(Some(transaction)) +} + + + + +pub async fn create_txn_for_buy_kitty( + db: &Db, + input_coins:Vec, + kitty_dna: &str, + buyer_public_key:H256, + seller_public_key:H256, + output_amount: & Vec, + client: &HttpClient, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + let mut found_kitty: Option<(TradableKittyData, OutputRef)> = None; + + if let Ok(Some((kitty_info, out_ref))) = + crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,kitty_dna) { + found_kitty = Some((kitty_info, out_ref)); + } else { + return Err(anyhow!("No kitty with DNA {} in localdb", kitty_dna)); + } + + let input = Input { + output_ref: found_kitty.clone().unwrap().1, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + + let inputs: Vec = vec![input]; + + let mut output_kitty = found_kitty.clone().unwrap().0; + + let output = Output { + payload: output_kitty.into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: buyer_public_key, + }), + }; + + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::Buy.into(), + }; + + // Construct each output and then push to the transactions for Money + let mut total_output_amount = 0; + for amount in output_amount { + let output = Output { + payload: Coin::<0>::new(*amount).into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: seller_public_key, + }), + }; + total_output_amount += amount; + transaction.outputs.push(output); + if total_output_amount >= found_kitty.clone().unwrap().0.price.into() { + break; + } + } + + let mut total_input_amount = 0; + let mut all_input_refs = input_coins; + for output_ref in &all_input_refs { + let (_buyer_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( + "user-specified output ref not found in local database" + ))?; + total_input_amount += amount; + } + + if total_input_amount < total_output_amount { + match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { + Some(more_inputs) => { + all_input_refs.extend(more_inputs); + } + None => Err(anyhow!( + "Not enough value in database to construct transaction" + ))?, + } + } + + // Make sure each input decodes and is still present in the node's storage, + // and then push to transaction. + for output_ref in &all_input_refs { + get_coin_from_storage(output_ref, client).await?; + transaction.inputs.push(Input { + output_ref: output_ref.clone(), + redeemer: vec![], // We will sign the total transaction so this should be empty + }); + } + + print_debug_signed_txn_with_local_ks(transaction.clone()).await?; // this is just for debug purpose. + Ok(Some(transaction)) +} + + +pub async fn create_inpututxo_list( + transaction: &mut Transaction, + client: &HttpClient, +) -> anyhow::Result>>> { + let mut utxo_list: Vec> = Vec::new(); + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + utxo_list.push(utxo); + } + Ok(Some(utxo_list)) +} + + +// Below function will not be used in real usage , it is just for test purpose + +use crate::HttpClientBuilder; +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; +pub async fn print_debug_signed_txn_with_local_ks( + mut transaction:Transaction, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + + let stripped_encoded_transaction = transaction.clone().encode(); + let local_keystore = get_local_keystore().await.expect("Key store error"); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let client = client_result.unwrap(); + + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, &client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(&local_keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + // insert the proof + input.redeemer = redeemer; + } + println!("signed_transaction {:?}",transaction.clone()); + Ok(Some(transaction.clone())) +} \ No newline at end of file diff --git a/webservice-wallet-external-signing/src/main.rs b/webservice-wallet-external-signing/src/main.rs new file mode 100644 index 000000000..9a436de86 --- /dev/null +++ b/webservice-wallet-external-signing/src/main.rs @@ -0,0 +1,350 @@ +//! A simple CLI wallet. For now it is a toy just to start testing things out. + + +use jsonrpsee::http_client::HttpClientBuilder; +use jsonrpsee::{http_client::HttpClient}; +use parity_scale_codec::{Decode}; +use runtime::OuterVerifier; +use std::path::PathBuf; +use sled::Db; +//use crate::kitty::{create_kitty,list_kitty_for_sale}; +use tuxedo_core::{types::OutputRef, verifier::*}; +use sp_core::H256; +use sc_keystore::LocalKeystore; + +//mod amoeba; +mod cli; +mod keystore; +mod kitty; +mod money; +mod output_filter; +mod rpc; +mod sync; +mod timestamp; + +//use moneyServicehandler::{MintCoinsRequest, MintCoinsResponse}; +mod serviceHandlers { + + pub mod blockHandler { + pub mod blockServicehandler; + } + + pub mod moneyHandler { + pub mod moneyServicehandler; + } + + pub mod kittyHandler { + pub mod kittyServicehandler; + } + + pub mod keyHandler { + pub mod keyServicehandler; + } +} + +use serviceHandlers::keyHandler::keyServicehandler::{ + debug_generate_key, + debug_get_keys, +}; + +use serviceHandlers::moneyHandler::moneyServicehandler::{ + mint_coins, + get_all_coins, + get_owned_coins, +}; + +use serviceHandlers::kittyHandler::kittyServicehandler::{ + create_kitty, + get_txn_and_inpututxolist_for_list_kitty_for_sale, + list_kitty_for_sale, + get_txn_and_inpututxolist_for_delist_kitty_from_sale, + delist_kitty_from_sale, + get_txn_and_inpututxolist_for_kitty_name_update, + update_kitty_name, + get_txn_and_inpututxolist_for_td_kitty_name_update, + update_td_kitty_name, + get_txn_and_inpututxolist_for_td_kitty_price_update, + update_td_kitty_price, + get_kitty_by_dna, + get_td_kitty_by_dna, + get_all_kitty_list, + get_all_td_kitty_list, + get_owned_kitty_list, + get_owned_td_kitty_list, + get_txn_and_inpututxolist_for_breed_kitty, + breed_kitty, + get_txn_and_inpututxolist_for_buy_kitty, + buy_kitty, + + + /*delist_kitty_from_sale, + buy_kitty, + breed_kitty,*/ +}; + +use serviceHandlers::blockHandler::blockServicehandler::{ get_block}; + +/// The default RPC endpoint for the wallet to connect to +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; + +use axum::{routing::{get, post, put}, Router}; +use std::net::SocketAddr; +use tower_http::cors::{Any, CorsLayer}; + + +#[tokio::main] +async fn main() { + let cors = CorsLayer::new().allow_origin(Any); + + let app = Router::new() + .route("/get-block", get(get_block)) + .route("/mint-coins", post(mint_coins)) + .route("/create-kitty", post(create_kitty)) + .route("/get-txn-and-inpututxolist-for-listkitty-forsale", get(get_txn_and_inpututxolist_for_list_kitty_for_sale)) + .route("/listkitty-for-sale", post(list_kitty_for_sale)) + .route("/get-txn-and-inpututxolist-for-delist-kitty-from-sale", get(get_txn_and_inpututxolist_for_delist_kitty_from_sale)) + .route("/delist-kitty-from-sale", post(delist_kitty_from_sale)) + .route("/get-txn-and-inpututxolist-for-kitty-name-update", get(get_txn_and_inpututxolist_for_kitty_name_update)) + .route("/update-kitty-name", post(update_kitty_name)) + .route("/get-txn-and-inpututxolist-for-td-kitty-name-update", get(get_txn_and_inpututxolist_for_td_kitty_name_update)) + .route("/update-td-kitty-name", post(update_td_kitty_name)) + .route("/get-txn-and-inpututxolist-for-td-kitty-price-update", get(get_txn_and_inpututxolist_for_td_kitty_price_update)) + .route("/get-txn-and-inpututxolist-for-breed-kitty", get(get_txn_and_inpututxolist_for_breed_kitty)) + .route("/breed-kitty", post(breed_kitty)) + .route("/get-txn-and-inpututxolist-for-buy-kitty", get(get_txn_and_inpututxolist_for_buy_kitty)) + .route("/buy-kitty", post(buy_kitty)) + .route("/update-td-kitty-price", post(update_td_kitty_price)) + .route("/get-kitty-by-dna", get(get_kitty_by_dna)) + .route("/get-tradable-kitty-by-dna", get(get_td_kitty_by_dna)) + .route("/get-all-kitty-list", get(get_all_kitty_list)) + .route("/get-all-tradable-kitty-list", get(get_all_td_kitty_list)) + .route("/get-owned-kitty-list", get(get_owned_kitty_list)) + .route("/get-owned-tradable-kitty-list", get(get_owned_td_kitty_list)) + .route("/get-all-coins", get(get_all_coins)) + .route("/get-owned-coins", get(get_owned_coins)) + + + .route("/debug-generate-key", post(debug_generate_key)) + .route("/debug-get-keys", get(debug_get_keys)) + + //.route("/spend-coins", put(spend_coins)) + .layer(cors); + + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + println!("In the main"); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + + +async fn original_get_db() -> anyhow::Result { + + let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; + + // Read node's genesis block. + let node_genesis_hash = rpc::node_get_block_hash(0, &client) + .await? + .expect("node should be able to return some genesis hash"); + let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) + .await? + .expect("node should be able to return some genesis block"); + log::debug!("Node's Genesis block::{:?}", node_genesis_hash); + + // Open the local database + let data_path = temp_dir(); + let db_path = data_path.join("wallet_database"); + 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}"); + + // No filter at-all + let keystore_filter = |_v: &OuterVerifier| -> bool { + true + }; + + if !sled::Db::was_recovered(&db) { + println!("!sled::Db::was_recovered(&db) called "); + // This is a new instance, so we need to apply the genesis block to the database. + async { + sync::apply_block(&db, node_genesis_block, node_genesis_hash, &keystore_filter) + .await; + }; + } + + sync::synchronize(&db, &client, &keystore_filter).await?; + + println!( + "Wallet database synchronized with node to height {:?}", + sync::height(&db)?.expect("We just synced, so there is a height available") + ); + + if let Err(err) = db.flush() { + println!("Error flushing Sled database: {}", err); + } + + Ok(db) +} + + +async fn get_db() -> anyhow::Result { + let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; + let data_path = temp_dir(); + let db_path = data_path.join("wallet_database"); + let node_genesis_hash = rpc::node_get_block_hash(0, &client) + .await? + .expect("node should be able to return some genesis hash"); + let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) + .await? + .expect("node should be able to return some genesis block"); + println!("Node's Genesis block::{:?}", node_genesis_hash); + + let db = sync::open_db(db_path, node_genesis_hash, node_genesis_block.clone())?; + Ok(db) +} + + +async fn get_local_keystore() -> anyhow::Result { + let data_path = temp_dir(); + let keystore_path = data_path.join("keystore"); + println!("keystore_path: {:?}", keystore_path); + let keystore = sc_keystore::LocalKeystore::open(keystore_path.clone(), None)?; + keystore::insert_development_key_for_this_session(&keystore)?; + Ok(keystore) +} + +async fn sync_db bool>( + db: &Db, + client: &HttpClient, + filter: &F) -> anyhow::Result<()> { + + if !sled::Db::was_recovered(&db) { + let node_genesis_hash = rpc::node_get_block_hash(0, &client) + .await? + .expect("node should be able to return some genesis hash"); + let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) + .await? + .expect("node should be able to return some genesis block"); + + + println!(" in sync_db !sled::Db::was_recovered(&db)"); + async { + sync::apply_block(&db, node_genesis_block, node_genesis_hash, &filter) + .await; + }; + } + println!(" sync::synchronize will be called!!"); + sync::synchronize(&db, &client, &filter).await?; + + log::info!( + "Wallet database synchronized with node to height {:?}", + sync::height(&db)?.expect("We just synced, so there is a height available") + ); + Ok(()) +} + +async fn sync_and_get_db() -> anyhow::Result { + let db = get_db().await?; + let keystore = get_local_keystore().await?; + let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; + let keystore_filter = |v: &OuterVerifier| -> bool { + matches![v, + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) + if crate::keystore::has_key(&keystore, owner_pubkey) + ] || matches![v, OuterVerifier::UpForGrabs(UpForGrabs)] // used for timestamp + }; + sync_db(&db, &client, &keystore_filter).await?; + Ok(db) +} + +/// 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); + + let mut bytes: [u8; 32] = [0; 32]; + hex::decode_to_slice(s, &mut bytes as &mut [u8]) + .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; + Ok(H256::from(bytes)) +} + +use std::error::Error; +/// Parse an output ref from a string +pub(crate) fn convert_output_ref_from_string(s: &str) -> Result> { + let s = strip_0x_prefix(s); + let bytes = hex::decode(s)?; + + OutputRef::decode(&mut &bytes[..]) + .map_err(|_| "Failed to decode OutputRef from string".into()) +} + +fn output_ref_from_string(s: &str) -> Result { + let s = strip_0x_prefix(s); + let bytes = + hex::decode(s).map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; + + OutputRef::decode(&mut &bytes[..]) + .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation)) +} + +/// Takes a string and checks for a 0x prefix. Returns a string without a 0x prefix. +fn strip_0x_prefix(s: &str) -> &str { + if &s[..2] == "0x" { + &s[2..] + } else { + s + } +} + +/// Generate a plaform-specific temporary directory for the wallet +fn temp_dir() -> PathBuf { + // Since it is only used for testing purpose, we don't need a secure temp dir, just a unique one. + /* + std::env::temp_dir().join(format!( + "tuxedo-wallet-{}", + std::time::UNIX_EPOCH.elapsed().unwrap().as_millis(), + )) + */ + std::env::temp_dir().join(format!( + "tuxedo-wallet" + )) +} + +/// Generate the platform-specific default data path for the wallet +fn default_data_path() -> PathBuf { + // This uses the directories crate. + // https://docs.rs/directories/latest/directories/struct.ProjectDirs.html + + // Application developers may want to put actual qualifiers or organization here + let qualifier = ""; + let organization = ""; + let application = env!("CARGO_PKG_NAME"); + + directories::ProjectDirs::from(qualifier, organization, application) + .expect("app directories exist on all supported platforms; qed") + .data_dir() + .into() +} + +/// Utility to pretty print an outer verifier +pub fn pretty_print_verifier(v: &OuterVerifier) { + match v { + OuterVerifier::Sr25519Signature(sr25519_signature) => { + println! {"owned by {}", sr25519_signature.owner_pubkey} + } + OuterVerifier::UpForGrabs(_) => println!("that can be spent by anyone"), + OuterVerifier::ThresholdMultiSignature(multi_sig) => { + let string_sigs: Vec<_> = multi_sig + .signatories + .iter() + .map(|sig| format!("0x{}", hex::encode(sig))) + .collect(); + println!( + "Owned by {:?}, with a threshold of {} sigs necessary", + string_sigs, multi_sig.threshold + ); + } + } +} diff --git a/webservice-wallet-external-signing/src/money.rs b/webservice-wallet-external-signing/src/money.rs new file mode 100644 index 000000000..6e33ca6e3 --- /dev/null +++ b/webservice-wallet-external-signing/src/money.rs @@ -0,0 +1,340 @@ +//! Wallet features related to spending money and checking balances. + +use crate::{cli::MintCoinArgs, cli::SpendArgs,rpc::fetch_storage, sync}; + +use anyhow::anyhow; +use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; +use parity_scale_codec::Encode; +use runtime::{ + money::{Coin, MoneyConstraintChecker}, + OuterConstraintChecker, OuterVerifier, Transaction, +}; +use sc_keystore::LocalKeystore; +use sled::Db; +use sp_core::sr25519::Public; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use tuxedo_core::{ + types::{Input, Output, OutputRef}, + verifier::Sr25519Signature, +}; +use crate::original_get_db; + +/// Create and send a transaction that mints the coins on the network +pub async fn mint_coins(client: &HttpClient, args: MintCoinArgs) -> anyhow::Result<()> { + log::debug!("The args are:: {:?}", args); + + let transaction = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: vec![( + Coin::<0>::new(args.amount), + OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + ) + .into()], + checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Mint), + }; + + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let _spawn_response: Result = client.request("author_submitExtrinsic", params).await; + + log::info!( + "Node's response to mint-coin transaction: {:?}", + _spawn_response + ); + + let minted_coin_ref = OutputRef { + tx_hash: ::hash_of(&transaction.encode()), + index: 0, + }; + let output = &transaction.outputs[0]; + let amount = output.payload.extract::>()?.0; + print!( + "Minted {:?} worth {amount}. ", + hex::encode(minted_coin_ref.encode()) + ); + crate::pretty_print_verifier(&output.verifier); + + Ok(()) +} +use sp_core::H256; +struct RecipientOutput { + pub recipient:H256, + pub output_amount:Vec +} +fn extract_recipient_list_from_args(args: SpendArgs,) -> Vec { + let mut recipient_list:Vec = Vec::new(); + for i in args.recipients { + let rec_pient = RecipientOutput { + recipient:i.0, + output_amount:i.1, + }; + recipient_list.push(rec_pient); + } + recipient_list +} +/// Create and send a transaction that spends coins on the network +pub async fn spend_coins( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: SpendArgs, +) -> anyhow::Result<()> { + + log::info!("In the spend_coins_to_multiple_recipient The args are:: {:?}", args); + let mut transaction = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: Vec::new(), + checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend), + }; + + let recipient_list:Vec = extract_recipient_list_from_args(args.clone()); + + let mut total_output_amount = 0; + for recipient in &recipient_list { + for amount in &recipient.output_amount { + let output = Output { + payload: Coin::<0>::new(*amount).into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: recipient.recipient, + }), + }; + total_output_amount += amount; + transaction.outputs.push(output); + } + } + + // The total input set will consist of any manually chosen inputs + // plus any automatically chosen to make the input amount high enough + let mut total_input_amount = 0; + let mut all_input_refs = args.input; + for output_ref in &all_input_refs { + let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( + "user-specified output ref not found in local database" + ))?; + total_input_amount += amount; + } + //TODO filtering on a specific sender + + // If the supplied inputs are not valuable enough to cover the output amount + // we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.) + if total_input_amount < total_output_amount { + match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { + Some(more_inputs) => { + all_input_refs.extend(more_inputs); + } + None => Err(anyhow!( + "Not enough value in database to construct transaction" + ))?, + } + } + + // Make sure each input decodes and is still present in the node's storage, + // and then push to transaction. + for output_ref in &all_input_refs { + get_coin_from_storage(output_ref, client).await?; + transaction.inputs.push(Input { + output_ref: output_ref.clone(), + redeemer: vec![], // We will sign the total transaction so this should be empty + }); + } + + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + + // insert the proof + input.redeemer = redeemer; + } + + // Send the transaction + let genesis_spend_hex = hex::encode(transaction.encode()); + let params = rpc_params![genesis_spend_hex]; + let genesis_spend_response: Result = + client.request("author_submitExtrinsic", params).await; + log::info!( + "Node's response to spend transaction: {:?}", + genesis_spend_response + ); + + // Print new output refs for user to check later + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_coin_ref = OutputRef { + tx_hash, + index: i as u32, + }; + let amount = output.payload.extract::>()?.0; + + print!( + "Created {:?} worth {amount}. ", + hex::encode(new_coin_ref.encode()) + ); + crate::pretty_print_verifier(&output.verifier); + } + + Ok(()) +} +/* +/// Create and send a transaction that spends coins on the network +pub async fn spend_coins1( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: SpendArgs, +) -> anyhow::Result<()> { + log::info!("The args are:: {:?}", args); + + // Construct a template Transaction to push coins into later + let mut transaction = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: Vec::new(), + checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend), + }; + + // Construct each output and then push to the transactions + let mut total_output_amount = 0; + for amount in &args.output_amount { + let output = Output { + payload: Coin::<0>::new(*amount).into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.recipient, + }), + }; + total_output_amount += amount; + transaction.outputs.push(output); + } + + // The total input set will consist of any manually chosen inputs + // plus any automatically chosen to make the input amount high enough + let mut total_input_amount = 0; + let mut all_input_refs = args.input; + for output_ref in &all_input_refs { + let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( + "user-specified output ref not found in local database" + ))?; + total_input_amount += amount; + } + //TODO filtering on a specific sender + + // If the supplied inputs are not valuable enough to cover the output amount + // we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.) + if total_input_amount < total_output_amount { + match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { + Some(more_inputs) => { + all_input_refs.extend(more_inputs); + } + None => Err(anyhow!( + "Not enough value in database to construct transaction" + ))?, + } + } + + // Make sure each input decodes and is still present in the node's storage, + // and then push to transaction. + for output_ref in &all_input_refs { + get_coin_from_storage(output_ref, client).await?; + transaction.inputs.push(Input { + output_ref: output_ref.clone(), + redeemer: vec![], // We will sign the total transaction so this should be empty + }); + } + + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + + // insert the proof + input.redeemer = redeemer; + } + + // Send the transaction + let genesis_spend_hex = hex::encode(transaction.encode()); + let params = rpc_params![genesis_spend_hex]; + let genesis_spend_response: Result = + client.request("author_submitExtrinsic", params).await; + log::info!( + "Node's response to spend transaction: {:?}", + genesis_spend_response + ); + + // Print new output refs for user to check later + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_coin_ref = OutputRef { + tx_hash, + index: i as u32, + }; + let amount = output.payload.extract::>()?.0; + + print!( + "Created {:?} worth {amount}. ", + hex::encode(new_coin_ref.encode()) + ); + crate::pretty_print_verifier(&output.verifier); + } + + Ok(()) +} +*/ + +/// Given an output ref, fetch the details about this coin from the node's +/// storage. +pub async fn get_coin_from_storage( + output_ref: &OutputRef, + client: &HttpClient, +) -> anyhow::Result<(Coin<0>, OuterVerifier)> { + let utxo = fetch_storage::(output_ref, client).await?; + let coin_in_storage: Coin<0> = utxo.payload.extract()?; + + Ok((coin_in_storage, utxo.verifier)) +} + +/// Apply a transaction to the local database, storing the new coins. +pub(crate) fn apply_transaction( + db: &Db, + tx_hash: ::Output, + index: u32, + output: &Output, +) -> anyhow::Result<()> { + let amount = output.payload.extract::>()?.0; + let output_ref = OutputRef { tx_hash, index }; + match output.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + // Add it to the global unspent_outputs table + crate::sync::add_unspent_output(db, &output_ref, &owner_pubkey, &amount) + } + _ => Err(anyhow!("{:?}", ())), + } +} diff --git a/webservice-wallet-external-signing/src/output_filter.rs b/webservice-wallet-external-signing/src/output_filter.rs new file mode 100644 index 000000000..555fc5a55 --- /dev/null +++ b/webservice-wallet-external-signing/src/output_filter.rs @@ -0,0 +1,137 @@ +use runtime::{OuterVerifier, Output}; +use sp_core::H256; +use tuxedo_core::types::OutputRef; + +pub type OutputInfo = (Output, OutputRef); + +type TxHash = H256; +/// The Filter type which is the closure signature used by functions to filter UTXOS +pub type Filter = Box Result, ()>>; + +pub trait OutputFilter { + /// The Filter type which is the closure signature used by functions to filter UTXOS + type Filter; + /// Builds a filter to be passed to wallet sync functions to sync the chosen outputs + /// to the users DB. + fn build_filter(verifier: OuterVerifier) -> Self::Filter; +} + +pub struct Sr25519SignatureFilter; +impl OutputFilter for Sr25519SignatureFilter { + // Todo Add filter error + type Filter = Result; + + fn build_filter(verifier: OuterVerifier) -> Self::Filter { + Ok(Box::new(move |outputs, tx_hash| { + let filtered_outputs = outputs + .iter() + .enumerate() + .map(|(i, output)| { + ( + output.clone(), + OutputRef { + tx_hash: *tx_hash, + index: i as u32, + }, + ) + }) + .filter(|(output, _)| output.verifier == verifier) + .collect::>(); + Ok(filtered_outputs) + })) + } +} + +mod tests { + use super::*; + + #[cfg(test)] + use tuxedo_core::{dynamic_typing::DynamicallyTypedData, verifier::*}; + + pub struct TestSr25519SignatureFilter; + impl OutputFilter for TestSr25519SignatureFilter { + type Filter = Result; + + fn build_filter(_verifier: OuterVerifier) -> Self::Filter { + Ok(Box::new(move |_outputs, _tx_hash| { + println!("printed something"); + Ok(vec![]) + })) + } + } + + #[test] + fn filter_prints() { + let verifier = OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: H256::zero(), + }); + let output = Output { + verifier: verifier.clone(), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }; + + let my_filter = + TestSr25519SignatureFilter::build_filter(verifier).expect("Can build print filter"); + let _ = my_filter(&[output], &H256::zero()); + } + + #[test] + fn filter_sr25519_signature_works() { + let verifier = OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: H256::zero(), + }); + + let outputs_to_filter = vec![ + Output { + verifier: verifier.clone(), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }, + Output { + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: H256::from_slice(b"asdfasdfasdfasdfasdfasdfasdfasdf"), + }), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }, + Output { + verifier: OuterVerifier::ThresholdMultiSignature(ThresholdMultiSignature { + threshold: 1, + signatories: vec![H256::zero()], + }), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }, + ]; + + let expected_filtered_output_infos = vec![( + Output { + verifier: verifier.clone(), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )]; + + let my_filter = Sr25519SignatureFilter::build_filter(verifier) + .expect("Can build Sr25519Signature filter"); + let filtered_output_infos = my_filter(&outputs_to_filter, &H256::zero()) + .expect("Can filter the outputs by verifier correctly"); + + assert_eq!(filtered_output_infos, expected_filtered_output_infos); + } +} diff --git a/webservice-wallet-external-signing/src/rpc.rs b/webservice-wallet-external-signing/src/rpc.rs new file mode 100644 index 000000000..0d5466e6d --- /dev/null +++ b/webservice-wallet-external-signing/src/rpc.rs @@ -0,0 +1,61 @@ +//! 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> { + let params = rpc_params![Some(height)]; + let rpc_response: Option = client.request("chain_getBlockHash", params).await?; + let maybe_hash = rpc_response.map(|s| crate::h256_from_string(&s).unwrap()); + Ok(maybe_hash) +} + +/// Typed helper to get the node's full block at a particular hash +pub async fn node_get_block(hash: H256, client: &HttpClient) -> anyhow::Result> { + let s = hex::encode(hash.0); + let params = rpc_params![s]; + + let maybe_rpc_response: Option = + client.request("chain_getBlock", params).await?; + let rpc_response = maybe_rpc_response.unwrap(); + + let json_opaque_block = rpc_response.get("block").cloned().unwrap(); + let opaque_block: OpaqueBlock = serde_json::from_value(json_opaque_block).unwrap(); + + // I need a structured block, not an opaque one. To achieve that, I'll + // scale encode it, then once again decode it. + // Feels kind of like a hack, but I honestly don't know what else to do. + // I don't see any way to get the bytes out of an OpaqueExtrinsic. + let scale_bytes = opaque_block.encode(); + + let structured_block = Block::decode(&mut &scale_bytes[..]).unwrap(); + + Ok(Some(structured_block)) +} + +/// 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) +} diff --git a/webservice-wallet-external-signing/src/serviceHandlers/blockHandler/blockServicehandler.rs b/webservice-wallet-external-signing/src/serviceHandlers/blockHandler/blockServicehandler.rs new file mode 100644 index 000000000..a643c225a --- /dev/null +++ b/webservice-wallet-external-signing/src/serviceHandlers/blockHandler/blockServicehandler.rs @@ -0,0 +1,68 @@ +use serde::{Serialize}; + +use jsonrpsee::http_client::HttpClientBuilder; + + + + + + +use crate::rpc; + + + +/// The default RPC endpoint for the wallet to connect to +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; + +use axum::{Json,http::HeaderMap}; + + + +use runtime::{Block}; +use anyhow::bail; + +#[derive(Debug, Serialize)] +pub struct BlockResponse { + pub message: String, +} + +pub async fn get_block(headers: HeaderMap) -> Json { + let block_number_header = headers.get("Block-Number").unwrap_or_else(|| { + panic!("Block-Number header is missing"); + }); + let block_number = block_number_header.to_str().unwrap_or_else(|_| { + panic!("Failed to parse Block-Number header"); + }); + + // Convert the block number to the appropriate type if needed + let block_number: u128 = block_number.parse().unwrap_or_else(|_| { + panic!("Failed to parse block number as u128"); + }); + + match get_blocks(block_number).await { + Ok(Some(node_block)) => Json(BlockResponse { + message: format!("block found {:?}",node_block), + }), + + Ok(None) => Json(BlockResponse { + message: format!("Node's block not found"), + }), + Err(err) => Json(BlockResponse { + message: format!("Error getting the block: {:?}", err), + }), + } +} + +async fn get_blocks(number: u128) -> anyhow::Result> { + let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; + let node_block_hash = rpc::node_get_block_hash(number.try_into().unwrap(), &client) + .await? + .expect("node should be able to return some genesis hash"); + println!("Get blocks node_block_hash {:?} ",node_block_hash); + let maybe_block = rpc::node_get_block(node_block_hash, &client).await?; + println!("BlockData {:?} ",maybe_block.clone().unwrap()); + match maybe_block { + Some(block) => Ok(Some(block)), + None => bail!("Block not found for hash: {:?}", node_block_hash), + } +} \ No newline at end of file diff --git a/webservice-wallet-external-signing/src/serviceHandlers/keyHandler/keyServicehandler.rs b/webservice-wallet-external-signing/src/serviceHandlers/keyHandler/keyServicehandler.rs new file mode 100644 index 000000000..13493dca9 --- /dev/null +++ b/webservice-wallet-external-signing/src/serviceHandlers/keyHandler/keyServicehandler.rs @@ -0,0 +1,63 @@ +use serde::{Deserialize, Serialize}; + + +use crate::keystore; + + + +use axum::{Json}; + +#[derive(Debug, Deserialize)] +pub struct GenerateKeyRequest { + pub password: Option, +} + +#[derive(Debug, Serialize)] +pub struct GenerateKeyResponse { + pub message: String, + pub public_key: Option, + pub phrase: Option, +} + +pub async fn debug_generate_key(body: Json) -> Json { + match keystore::generate_key(body.password.clone()).await { + Ok((public_key, phrase)) => Json(GenerateKeyResponse { + message: format!("Keys generated successfully"), + public_key: Some(public_key), + phrase: Some(phrase), + }), + Err(err) => Json(GenerateKeyResponse { + message: format!("Error generating keys: {:?}", err), + public_key: None, + phrase: None, + }), + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// get keys +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#[derive(Debug, Serialize)] +pub struct GetKeyResponse { + pub message: String, + pub keys: Option>, +} + +pub async fn debug_get_keys() -> Json { + match keystore::get_keys().await { + Ok(keys_iter) => { + // Lets collect keys into a vector of strings + let keys: Vec = keys_iter.map(|key| hex::encode(key)).collect(); + + Json(GetKeyResponse { + message: format!("Keys retrieved successfully"), + keys: Some(keys), + }) + } + Err(err) => Json(GetKeyResponse { + message: format!("Error retrieving keys: {:?}", err), + keys: None, + }), + } +} diff --git a/webservice-wallet-external-signing/src/serviceHandlers/kittyHandler/kittyServicehandler.rs b/webservice-wallet-external-signing/src/serviceHandlers/kittyHandler/kittyServicehandler.rs new file mode 100644 index 000000000..40838a204 --- /dev/null +++ b/webservice-wallet-external-signing/src/serviceHandlers/kittyHandler/kittyServicehandler.rs @@ -0,0 +1,1146 @@ +use serde::{Deserialize, Serialize}; + + +use jsonrpsee::http_client::HttpClientBuilder; + + + +use crate::kitty; +use sp_core::H256; + +use crate::cli::{CreateKittyArgs, + DelistKittyFromSaleArgs, UpdateKittyNameArgs, UpdateKittyPriceArgs, + BuyKittyArgs, BreedKittyArgs}; + +/// The default RPC endpoint for the wallet to connect to +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; +use crate::get_local_keystore; +use crate::sync_and_get_db; +use crate::original_get_db; +use crate::convert_output_ref_from_string; + +use axum::{Json,http::HeaderMap}; + +use std::convert::Infallible; + + + +use runtime::{ + kitties::{ + KittyData, + }, + money::{Coin}, + tradable_kitties::{TradableKittyData}, + OuterVerifier, Transaction, +}; +use tuxedo_core::types::OutputRef; +use tuxedo_core::types::Output; + +#[derive(Debug, Deserialize)] +pub struct CreateKittyRequest { + pub name: String, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct CreateKittyResponse { + pub message: String, + pub kitty:Option + // Add any additional fields as needed +} + +pub async fn create_kitty(body: Json) -> Result, Infallible> { + println!("create_kitties called "); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + //let db = sync_and_get_db().await.expect("Error"); + let db = original_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(CreateKittyResponse { + message: format!("Error creating HTTP client: {:?}", err), + kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + + match kitty::create_kitty(&db, &client, CreateKittyArgs { + kitty_name: body.name.to_string(), + owner: public_key_h256, + }).await { + Ok(Some(created_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = CreateKittyResponse { + message: format!("Kitty created successfully"), + kitty: Some(created_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(CreateKittyResponse { + message: format!("Kitty creation failed: No data returned"), + kitty:None, + })), + Err(err) => Ok(Json(CreateKittyResponse { + message: format!("Error creating kitty: {:?}", err), + kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// Get kitty by DNA +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetKittyByDnaResponse { + pub message: String, + pub kitty:Option, +} + +pub async fn get_kitty_by_dna(headers: HeaderMap) -> Json { + println!("Headers map = {:?}",headers); + let dna_header = headers + .get("kitty-dna") + .expect("Kitty DNA header is missing") + .to_str() + .expect("Failed to parse Kitty DNA header"); + let db = original_get_db().await.expect("Error"); + let mut found_kitty: Option<(KittyData, OutputRef)> = None; + + if let Ok(Some((kitty_info, out_ref))) = + crate::sync::get_kitty_from_local_db_based_on_dna(&db,dna_header) + { + found_kitty = Some((kitty_info, out_ref)); + } + + let response = match found_kitty { + Some((kitty_info, _)) => GetKittyByDnaResponse { + message: format!("Success: Found Kitty with DNA {:?}", dna_header), + kitty: Some(kitty_info), + }, + None => GetKittyByDnaResponse { + message: format!("Error: Can't find Kitty with DNA {:?}", dna_header), + kitty: None, + }, + }; + + Json(response) +} + + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetTdKittyByDnaResponse { + pub message: String, + pub td_kitty:Option, +} + +pub async fn get_td_kitty_by_dna(headers: HeaderMap) -> Json { + println!("Headers map = in td kitty {:?}",headers); + let dna_header = headers + .get("td-kitty-dna") + .expect("Td-Kitty DNA header is missing") + .to_str() + .expect("Failed to parse Td-Kitty DNA header"); + let db = original_get_db().await.expect("Error"); + let mut found_td_kitty: Option<(TradableKittyData, OutputRef)> = None; + + if let Ok(Some((td_kitty_info, out_ref))) = + crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,dna_header) + { + found_td_kitty = Some((td_kitty_info, out_ref)); + } + + let response = match found_td_kitty { + Some((kitty_info, _)) => GetTdKittyByDnaResponse { + message: format!("Success: Found Tradable Kitty with DNA {:?}", dna_header), + td_kitty: Some(kitty_info), + }, + None => GetTdKittyByDnaResponse { + message: format!("Error: Can't find Tradable Kitty with DNA {:?}", dna_header), + td_kitty: None, + }, + }; + + Json(response) +} + +//////////////////////////////////////////////////////////////////// +// Get all kitty List +//////////////////////////////////////////////////////////////////// +#[derive(Debug, Serialize, Deserialize)] +pub struct GetAllKittiesResponse { + pub message: String, + pub kitty_list:Option>, +} + +pub async fn get_all_kitty_list() -> Json { + let db = original_get_db().await.expect("Error"); + + match crate::sync::get_all_kitties_from_local_db(&db) { + Ok(all_kitties) => { + let kitty_list: Vec = all_kitties.map(|(_, kitty)| kitty).collect(); + + if !kitty_list.is_empty() { + return Json(GetAllKittiesResponse { + message: format!("Success: Found Kitties"), + kitty_list: Some(kitty_list), + }); + } + }, + Err(_) => { + return Json(GetAllKittiesResponse { + message: format!("Error: Can't find Kitties"), + kitty_list: None, + }); + } + } + + Json(GetAllKittiesResponse { + message: format!("Error: Can't find Kitties"), + kitty_list: None, + }) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetAllTdKittiesResponse { + pub message: String, + pub td_kitty_list:Option>, +} + +pub async fn get_all_td_kitty_list() -> Json { + let db = original_get_db().await.expect("Error"); + + match crate::sync::get_all_tradable_kitties_from_local_db(&db) { + Ok(owned_kitties) => { + let tradable_kitty_list: Vec = owned_kitties.map(|(_, kitty)| kitty).collect(); + + if !tradable_kitty_list.is_empty() { + return Json(GetAllTdKittiesResponse { + message: format!("Success: Found TradableKitties"), + td_kitty_list: Some(tradable_kitty_list), + }); + } + }, + Err(_) => { + return Json(GetAllTdKittiesResponse { + message: format!("Error: Can't find TradableKitties"), + td_kitty_list: None, + }); + } + } + + Json(GetAllTdKittiesResponse { + message: format!("Error: Can't find Kitties"), + td_kitty_list: None, + }) +} +//////////////////////////////////////////////////////////////////// +// Get owned kitties +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetOwnedKittiesResponse { + pub message: String, + pub kitty_list:Option>, +} +use std::str::FromStr; +pub async fn get_owned_kitty_list(headers: HeaderMap) -> Json { + let public_key_header = headers.get("owner_public_key").expect("public_key_header is missing"); + + let public_key_h256 = H256::from_str(public_key_header.to_str().expect("Failed to convert to H256")); + + let db = original_get_db().await.expect("Error"); + + match crate::sync::get_owned_kitties_from_local_db(&db,&public_key_h256.unwrap()) { + Ok(owned_kitties) => { + let kitty_list: Vec = owned_kitties.map(|(_, kitty,_)| kitty).collect(); + + if !kitty_list.is_empty() { + return Json(GetOwnedKittiesResponse { + message: format!("Success: Found Kitties"), + kitty_list: Some(kitty_list), + }); + } + }, + Err(_) => { + return Json(GetOwnedKittiesResponse { + message: format!("Error: Can't find Kitties"), + kitty_list: None, + }); + } + } + + Json(GetOwnedKittiesResponse { + message: format!("Error: Can't find Kitties"), + kitty_list: None, + }) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetOwnedTdKittiesResponse { + pub message: String, + pub td_kitty_list:Option>, +} + +pub async fn get_owned_td_kitty_list(headers: HeaderMap) -> Json { + let public_key_header = headers.get("owner_public_key").expect("public_key_header is missing"); + let public_key_h256 = H256::from_str(public_key_header.to_str().expect("Failed to convert to H256")); + let db = original_get_db().await.expect("Error"); + + match crate::sync::get_owned_tradable_kitties_from_local_db(&db,&public_key_h256.unwrap()) { + Ok(owned_kitties) => { + let tradable_kitty_list: Vec = owned_kitties.map(|(_, kitty, _)| kitty).collect(); + + if !tradable_kitty_list.is_empty() { + return Json(GetOwnedTdKittiesResponse { + message: format!("Success: Found TradableKitties"), + td_kitty_list: Some(tradable_kitty_list), + }); + } + }, + Err(_) => { + return Json(GetOwnedTdKittiesResponse { + message: format!("Error: Can't find TradableKitties"), + td_kitty_list: None, + }); + } + } + + Json(GetOwnedTdKittiesResponse { + message: format!("Error: Can't find td Kitties"), + td_kitty_list: None, + }) +} + +//////////////////////////////////////////////////////////////////// +// Common structures and functions +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetTxnAndUtxoListForList { + pub message: String, + pub transaction: Option, + pub input_utxo_list:Option>>, +} + +#[derive(Debug, Deserialize)] +pub struct SignedTxnRequest { + pub signed_transaction: Transaction, +} + +async fn create_response( + txn: Option, + message: String, +) -> Json { + match txn { + Some(txn) => { + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Json(GetTxnAndUtxoListForList { + message: format!("Error creating HTTP client: {:?}", err), + transaction: None, + input_utxo_list: None, + }); + } + }; + let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; + Json(GetTxnAndUtxoListForList { + message, + transaction: Some(txn), + input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), + }) + }, + None => Json(GetTxnAndUtxoListForList { + message, + transaction: None, + input_utxo_list: None, + }), + } +} + + +//////////////////////////////////////////////////////////////////// +// List kitty for Sale +//////////////////////////////////////////////////////////////////// + + + +pub async fn get_txn_and_inpututxolist_for_list_kitty_for_sale(headers: HeaderMap) -> Json { + println!("Headers map = {:?}",headers); + + let dna_header = headers + .get("kitty-dna") + .expect("Kitty DNA header is missing") + .to_str() + .expect("Failed to parse Kitty DNA header"); + + let price_header = headers + .get("kitty-price") + .expect("Kitty price is missing"); + + let price_number: u128 = price_header + .to_str() + .expect("Failed to parse priceheader") + .parse() + .expect("ailed to parse price number as u128"); + + + let public_key_header = headers + .get("owner_public_key") + .expect("public_key_header is missing"); + + let public_key_h256 = H256::from_str(public_key_header + .to_str() + .expect("Failed to convert to H256")); + + let db = original_get_db().await.expect("Error"); + + match kitty::create_txn_for_list_kitty(&db, + dna_header, + price_number, + public_key_h256.unwrap(), + ).await { + Ok(txn) => create_response( + txn, + "List kitty for Sale txn created successfully".to_string(), + ).await, + Err(err) => create_response( + None, + format!("Error!! List kitty for sale txn creation: {:?}", err), + ).await, + } +} + +#[derive(Debug, Serialize)] +pub struct ListKittyForSaleResponse { + pub message: String, + pub td_kitty:Option + // Add any additional fields as needed +} + +pub async fn list_kitty_for_sale (body: Json) -> Result, Infallible> { + println!("List kitties for sale is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(ListKittyForSaleResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + match kitty::list_kitty_for_sale(&body.signed_transaction, + &client).await { + Ok(Some(listed_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = ListKittyForSaleResponse { + message: format!("Kitty listed for sale successfully"), + td_kitty: Some(listed_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(ListKittyForSaleResponse { + message: format!("Kitty listing forsale failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(ListKittyForSaleResponse { + message: format!("Error listing forsale: {:?}", err), + td_kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// De-list kitty from Sale +//////////////////////////////////////////////////////////////////// + + +pub async fn get_txn_and_inpututxolist_for_delist_kitty_from_sale(headers: HeaderMap) -> Json { + // create_tx_for_list_kitty + println!("Headers map = {:?}",headers); + let dna_header = headers + .get("kitty-dna") + .expect("Kitty DNA header is missing") + .to_str() + .expect("Failed to parse Kitty DNA header"); + + let public_key_header = headers + .get("owner_public_key") + .expect("public_key_header is missing"); + + let public_key_h256 = H256::from_str(public_key_header + .to_str() + .expect("Failed to convert to H256")); + + let db = original_get_db().await.expect("Error"); + + match kitty::create_txn_for_delist_kitty(&db, + dna_header, + public_key_h256.unwrap(), + ).await { + Ok(txn) => create_response( + txn, + "List kitty for Sale txn created successfully".to_string(), + ).await, + Err(err) => create_response( + None, + format!("Error!! List kitty for sale txn creation: {:?}", err), + ).await, + } +} + +#[derive(Debug, Serialize)] +pub struct DelistKittyFromSaleResponse { + pub message: String, + pub kitty:Option +} +pub async fn delist_kitty_from_sale(body: Json) -> Result, Infallible> { + println!("List kitties for sale is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(DelistKittyFromSaleResponse { + message: format!("Error creating HTTP client: {:?}", err), + kitty:None, + })); + } + }; + + match kitty::delist_kitty_from_sale(&body.signed_transaction, + &client).await { + Ok(Some(delisted_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = DelistKittyFromSaleResponse { + message: format!("Kitty delisted from sale successfully"), + kitty: Some(delisted_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(DelistKittyFromSaleResponse { + message: format!("Kitty delisting from sale failed: No data returned"), + kitty:None, + })), + Err(err) => Ok(Json(DelistKittyFromSaleResponse { + message: format!("Error delisting from sale: {:?}", err), + kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// Update kitty name +//////////////////////////////////////////////////////////////////// + +pub async fn get_txn_and_inpututxolist_for_kitty_name_update(headers: HeaderMap) -> Json { + println!("Headers map = {:?}",headers); + let dna_header = headers + .get("kitty-dna") + .expect("Kitty DNA header is missing") + .to_str() + .expect("Failed to parse Kitty DNA header"); + + let new_name_header = headers + .get("kitty-new-name") + .expect("Kitty name is missing"); + + let public_key_header = headers + .get("owner_public_key") + .expect("public_key_header is missing"); + + let public_key_h256 = H256::from_str(public_key_header + .to_str() + .expect("Failed to convert to H256")); + + let db = original_get_db().await.expect("Error"); + + match kitty::create_txn_for_kitty_name_update(&db, + dna_header, + new_name_header.to_str().expect("Failed to parse name header").to_string(), + public_key_h256.unwrap(), + ).await { + Ok(txn) => create_response( + txn, + "Kitty name update txn created successfully".to_string(), + ).await, + Err(err) => create_response( + None, + format!("Error!! Kitty name update txn creation: {:?}", err), + ).await, + } +} + +#[derive(Debug, Serialize)] +pub struct UpdateKittyNameResponse { + pub message: String, + pub kitty:Option +} +pub async fn update_kitty_name(body: Json) -> Result, Infallible> { + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(UpdateKittyNameResponse { + message: format!("Error creating HTTP client: {:?}", err), + kitty:None, + })); + } + }; + + match kitty::update_kitty_name(&body.signed_transaction, + &client).await { + Ok(Some(updated_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = UpdateKittyNameResponse { + message: format!("Kitty name updated successfully"), + kitty: Some(updated_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(UpdateKittyNameResponse { + message: format!("Kitty name update failed: No data returned"), + kitty:None, + })), + Err(err) => Ok(Json(UpdateKittyNameResponse { + message: format!("Error!! Kitty name update: {:?}", err), + kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// Update tradable kitty name +//////////////////////////////////////////////////////////////////// + +pub async fn get_txn_and_inpututxolist_for_td_kitty_name_update(headers: HeaderMap) -> Json { + println!("Headers map = {:?}",headers); + let dna_header = headers + .get("kitty-dna") + .expect("Kitty DNA header is missing") + .to_str() + .expect("Failed to parse Kitty DNA header"); + let db = original_get_db().await.expect("Error"); + + let new_name_header = headers + .get("kitty-new-name") + .expect("Kitty name is missing"); + + let public_key_header = headers + .get("owner_public_key") + .expect("public_key_header is missing"); + + let public_key_h256 = H256::from_str(public_key_header + .to_str() + .expect("Failed to convert to H256")); + + match kitty::create_txn_for_td_kitty_name_update(&db, + dna_header, + new_name_header.to_str().expect("Failed to parse name header").to_string(), + public_key_h256.unwrap(), + ).await { + Ok(txn) => create_response( + txn, + "Td Kitty name update txn created successfully".to_string(), + ).await, + Err(err) => create_response( + None, + format!("Error!! Td-Kitty name update txn creation: {:?}", err), + ).await, + } +} + +#[derive(Debug, Serialize)] +pub struct UpdateTddKittyNameResponse { + pub message: String, + pub td_kitty:Option +} +pub async fn update_td_kitty_name(body: Json) -> Result, Infallible> { + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(UpdateTddKittyNameResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + match kitty::update_td_kitty_name(&body.signed_transaction, + &client).await { + Ok(Some(updated_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = UpdateTddKittyNameResponse { + message: format!("Td-Kitty name updated successfully"), + td_kitty: Some(updated_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(UpdateTddKittyNameResponse { + message: format!("Td-Kitty name update failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(UpdateTddKittyNameResponse { + message: format!("Error!! Td-Kitty name update: {:?}", err), + td_kitty:None, + })), + } +} + + +//////////////////////////////////////////////////////////////////// +// Update td-kitty price +//////////////////////////////////////////////////////////////////// + +pub async fn get_txn_and_inpututxolist_for_td_kitty_price_update(headers: HeaderMap) -> Json { + println!("Headers map = {:?}",headers); + let dna_header = headers + .get("kitty-dna") + .expect("Kitty DNA header is missing") + .to_str() + .expect("Failed to parse Kitty DNA header"); + + let price_header = headers + .get("kitty-price") + .expect("Kitty price is missing"); + + // Convert the block number to the appropriate type if needed + let price_number: u128 = price_header + .to_str() + .expect("Failed to parse priceheader to str") + .parse().expect("Failed to parse priceheader to u128"); + + let db = original_get_db().await.expect("Error"); + + let public_key_header = headers + .get("owner_public_key") + .expect("public_key_header is missing"); + + let public_key_h256 = H256::from_str(public_key_header + .to_str() + .expect("Failed to convert to H256")); + + match kitty::create_txn_for_td_kitty_price_update( + &db, + dna_header, + price_number, + public_key_h256.unwrap(), + ).await { + Ok(Some(txn)) => { + // Convert created_kitty to JSON and include it in the response + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Json(GetTxnAndUtxoListForList { + message: format!("Error creating HTTP client: {:?}", err), + transaction:None, + input_utxo_list:None + }); + } + }; + let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; + + let response = GetTxnAndUtxoListForList { + message: format!("Kitty name update txn created successfully"), + transaction: Some(txn), + input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), + }; + Json(response) + }, + Ok(None) => Json(GetTxnAndUtxoListForList { + message: format!("Kitty name update txn creation failed: No input returned"), + transaction:None, + input_utxo_list:None + }), + Err(err) => Json(GetTxnAndUtxoListForList { + message: format!("Error!! Kitty name update txn creation: {:?}", err), + transaction:None, + input_utxo_list:None + }), + } +} + +#[derive(Debug, Serialize)] +pub struct UpdateTdKittyPriceResponse { + pub message: String, + pub td_kitty:Option +} + +pub async fn update_td_kitty_price(body: Json) -> Result, Infallible> { + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(UpdateTdKittyPriceResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + match kitty::update_td_kitty_price(&body.signed_transaction, + &client).await { + Ok(Some(updated_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = UpdateTdKittyPriceResponse { + message: format!("Kitty price updated successfully"), + td_kitty: Some(updated_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(UpdateTdKittyPriceResponse { + message: format!("Kitty price update failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(UpdateTdKittyPriceResponse { + message: format!("Error in kitty price update: {:?}", err), + td_kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// Breed kitty +//////////////////////////////////////////////////////////////////// + +pub async fn get_txn_and_inpututxolist_for_breed_kitty(headers: HeaderMap) -> Json { + println!("Headers map = {:?}",headers); + let mom_dna = headers + .get("mom-dna") + .expect("MOM DNA header is missing") + .to_str() + .expect("Failed to parse MOM DNA header"); + + let dad_dna = headers + .get("dad-dna") + .expect("Dad DNA header is missing") + .to_str() + .expect("Failed to parse Dad DNA header"); + + let child_kitty_name = headers + .get("child-kitty-name") + .expect("Child Kitty name is missing"); + + let db = original_get_db().await.expect("Error"); + + let public_key_header = headers + .get("owner_public_key") + .expect("public_key_header is missing"); + + let public_key_h256 = H256::from_str(public_key_header + .to_str() + .expect("Failed to convert to H256")); + + match kitty::create_txn_for_breed_kitty( + &db, + mom_dna, + dad_dna, + child_kitty_name.to_str().expect("Failed to parse name header").to_string(), + public_key_h256.unwrap(), + ).await { + Ok(Some(txn)) => { + // Convert created_kitty to JSON and include it in the response + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Json(GetTxnAndUtxoListForList { + message: format!("Error creating HTTP client: {:?}", err), + transaction:None, + input_utxo_list:None + }); + } + }; + let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; + + let response = GetTxnAndUtxoListForList { + message: format!("Kitty name update txn created successfully"), + transaction: Some(txn), + input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), + }; + Json(response) + }, + Ok(None) => Json(GetTxnAndUtxoListForList { + message: format!("Kitty name update txn creation failed: No input returned"), + transaction:None, + input_utxo_list:None + }), + Err(err) => Json(GetTxnAndUtxoListForList { + message: format!("Error!! Kitty name update txn creation: {:?}", err), + transaction:None, + input_utxo_list:None + }), + } +} + +#[derive(Debug, Serialize)] +pub struct BreedKittyResponse { + pub message: String, + pub mom_kitty:Option, + pub dad_kitty:Option, + pub child_kitty:Option, +} + +pub async fn breed_kitty(body: Json) -> Result, Infallible> { + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(BreedKittyResponse { + message: format!("Error creating HTTP client: {:?}", err), + mom_kitty: None, + dad_kitty: None, + child_kitty: None, + })); + } + }; + + match kitty::breed_kitty(&body.signed_transaction, + &client).await { + Ok(Some(kitty_family)) => { + // Convert created_kitty to JSON and include it in the response + let response = BreedKittyResponse { + message: format!("Kitty breeding done successfully"), + mom_kitty: Some(kitty_family[0].clone()), + dad_kitty: Some(kitty_family[1].clone()), + child_kitty: Some(kitty_family[2].clone()), + + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(BreedKittyResponse { + message: format!("Kitty breeding failed: No data returned"), + mom_kitty: None, + dad_kitty: None, + child_kitty: None, + })), + Err(err) => Ok(Json(BreedKittyResponse { + message: format!("Error in kitty breed: {:?}", err), + mom_kitty: None, + dad_kitty: None, + child_kitty: None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// Buy kitty +//////////////////////////////////////////////////////////////////// + +pub async fn get_txn_and_inpututxolist_for_buy_kitty(headers: HeaderMap) -> Json { + println!("Headers map = {:?}",headers); + + let input_coins: Vec = headers + .get_all("input-coins") + .iter() + // Convert each coin string to an OutputRef, filter out errors + .filter_map(|header| { + let coin_str = header.to_str().unwrap_or_default(); + match convert_output_ref_from_string(coin_str) { + Ok(output_ref) => Some(output_ref), + Err(err) => { + // Print error message and skip this coin + eprintln!("Error converting input coin: {}", err); + None + } + } + }) + .collect(); + println!("Input coins: {:?}", input_coins); + let output_amount: Vec = headers + .get("output_amount") + .map_or_else(|| Vec::new(), |header| { + header + .to_str() + .unwrap_or_default() + .split(',') + .filter_map(|amount_str| amount_str.parse().ok()) + .collect() + }); + // Use the converted coins Vec as needed + println!("output_amount: {:?}", output_amount); + + let kitty_dna = headers + .get("kitty-dna") + .expect("Kitty DNA header is missing") + .to_str() + .expect("Failed to parse Kitty DNA header"); + + + let db = original_get_db().await.expect("Error"); + + let buyer_public_key = headers + .get("buyer_public_key") + .expect("buyer_public_key is missing"); + + let buyer_public_key_h256 = H256::from_str(buyer_public_key + .to_str() + .expect("Failed to convert buyer_public_keyto H256")); + + let seller_public_key = headers + .get("seller_public_key") + .expect("seller_public_key is missing"); + + let seller_public_key_h256 = H256::from_str(seller_public_key + .to_str() + .expect("Failed to convert seller_public_key to H256")); + + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Json(GetTxnAndUtxoListForList { + message: format!("Error creating HTTP client: {:?}", err), + transaction:None, + input_utxo_list:None + }); + } + }; + + match kitty::create_txn_for_buy_kitty( + &db, + input_coins, + &kitty_dna, + buyer_public_key_h256.unwrap(), + seller_public_key_h256.unwrap(), + &output_amount, + &client, + ).await { + Ok(Some(txn)) => { + // Convert created_kitty to JSON and include it in the response + let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; + + let response = GetTxnAndUtxoListForList { + message: format!("Kitty name update txn created successfully"), + transaction: Some(txn), + input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), + }; + Json(response) + }, + Ok(None) => Json(GetTxnAndUtxoListForList { + message: format!("Kitty name update txn creation failed: No input returned"), + transaction:None, + input_utxo_list:None + }), + Err(err) => Json(GetTxnAndUtxoListForList { + message: format!("Error!! Kitty name update txn creation: {:?}", err), + transaction:None, + input_utxo_list:None + }), + } +} + +#[derive(Debug, Serialize)] +pub struct BuyTdKittyResponse { + pub message: String, + pub td_kitty:Option +} + + +pub async fn buy_kitty(body: Json) -> Result, Infallible> { + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(BuyTdKittyResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + match kitty::buy_kitty(&body.signed_transaction, + &client).await { + Ok(Some(traded_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = BuyTdKittyResponse { + message: format!("Kitty traded successfully"), + td_kitty: Some(traded_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(BuyTdKittyResponse { + message: format!("Kitty trade failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(BuyTdKittyResponse { + message: format!("Error in trading: {:?}", err), + td_kitty:None, + })), + } +} + + + +/* +pub async fn breed_kitty(body: Json) -> Result, Infallible> { + println!("update_td_kitty_price is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let db = sync_and_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(BreedKittyResponse { + message: format!("Error creating HTTP client: {:?}", err), + mom_kitty:None, + dad_kitty:None, + child_kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256_of_owner = H256::from_slice(&public_key_bytes); + + let ks = get_local_keystore().await.expect("Error"); + + match kitty::breed_kitty(&db, &client, &ks,BreedKittyArgs { + mom_name: body.mom_name.clone(), + dad_name: body.dad_name.clone(), + owner: public_key_h256_of_owner, + }).await { + Ok(Some(new_family)) => { + // Convert created_kitty to JSON and include it in the response + let response = BreedKittyResponse { + message: format!("breeding successfully"), + mom_kitty:Some(new_family[0].clone()), + dad_kitty:Some(new_family[1].clone()), + child_kitty:Some(new_family[2].clone()), + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(BreedKittyResponse { + message: format!("Error in breeding failed: No data returned"), + mom_kitty:None, + dad_kitty:None, + child_kitty:None, + })), + Err(err) => Ok(Json(BreedKittyResponse { + message: format!("Error in breeding : {:?}", err), + mom_kitty:None, + dad_kitty:None, + child_kitty:None, + })), + } +} +*/ \ No newline at end of file diff --git a/webservice-wallet-external-signing/src/serviceHandlers/moneyHandler/moneyServicehandler.rs b/webservice-wallet-external-signing/src/serviceHandlers/moneyHandler/moneyServicehandler.rs new file mode 100644 index 000000000..2b42a45cc --- /dev/null +++ b/webservice-wallet-external-signing/src/serviceHandlers/moneyHandler/moneyServicehandler.rs @@ -0,0 +1,124 @@ +use serde::{Deserialize, Serialize}; + +use jsonrpsee::http_client::HttpClientBuilder; +use crate::money; +use sp_core::H256; + +use crate::cli::MintCoinArgs; +use crate::original_get_db; + +/// The default RPC endpoint for the wallet to connect to +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; + + +use axum::{Json,http::HeaderMap}; + + + +#[derive(Debug, Deserialize)] +pub struct MintCoinsRequest { + pub amount: u128, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct MintCoinsResponse { + pub message: String, + + // Add any additional fields as needed +} + +pub async fn mint_coins(body: Json) -> Json { + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Json(MintCoinsResponse { + message: format!("Error creating HTTP client: {:?}", err), + }); + } + }; + + // Convert the hexadecimal string to bytes + //let public_key_bytes = hex::decode(SHAWN_PUB_KEY).expect("Invalid hexadecimal string"); + let public_key_bytes = hex::decode(body.owner_public_key.as_str()).expect("Invalid hexadecimal string"); + + // Convert the bytes to H256 + let public_key_h256 = H256::from_slice(&public_key_bytes); + // Call the mint_coins function from your CLI wallet module + match money::mint_coins(&client, MintCoinArgs { + amount: body.amount, + owner: public_key_h256, + }).await { + Ok(()) => Json(MintCoinsResponse { + message: format!("Coins minted successfully"), + }), + Err(err) => Json(MintCoinsResponse { + message: format!("Error minting coins: {:?}", err), + }), + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetCoinsResponse { + pub message: String, + pub coins:Option>, +} + +pub async fn get_all_coins() -> Json { + let db = original_get_db().await.expect("Error"); + + match crate::sync::get_all_coins(&db) { + Ok(all_coins) => { + + if !all_coins.is_empty() { + return Json(GetCoinsResponse { + message: format!("Success: Found Coins"), + coins: Some(all_coins), + }); + } + }, + Err(_) => { + return Json(GetCoinsResponse { + message: format!("Error: Can't find coins"), + coins: None, + }); + } + } + + Json(GetCoinsResponse { + message: format!("Error: Can't find coins"), + coins: None, + }) +} + +use std::str::FromStr; +pub async fn get_owned_coins(headers: HeaderMap) -> Json { + let public_key_header = headers.get("owner_public_key").expect("public_key_header is missing"); + let public_key_h256 = H256::from_str(public_key_header.to_str().expect("Failed to convert to H256")); + + let db = original_get_db().await.expect("Error"); + + match crate::sync::get_owned_coins(&db,&public_key_h256.unwrap()) { + Ok(all_coins) => { + + if !all_coins.is_empty() { + return Json(GetCoinsResponse { + message: format!("Success: Found Coins"), + coins: Some(all_coins), + }); + } + }, + Err(_) => { + return Json(GetCoinsResponse { + message: format!("Error: Can't find coins"), + coins: None, + }); + } + } + + Json(GetCoinsResponse { + message: format!("Error: Can't find coins"), + coins: None, + }) +} diff --git a/webservice-wallet-external-signing/src/sync.rs b/webservice-wallet-external-signing/src/sync.rs new file mode 100644 index 000000000..2bae5a886 --- /dev/null +++ b/webservice-wallet-external-signing/src/sync.rs @@ -0,0 +1,1199 @@ +//! This module is responsible for maintaining the wallet's local database of blocks +//! and owned UTXOs to the canonical database reported by the node. +//! +//! It is backed by a sled database +//! +//! ## Schema +//! +//! There are 4 tables in the database +//! BlockHashes block_number:u32 => block_hash:H256 +//! Blocks block_hash:H256 => block:Block +//! UnspentOutputs output_ref => (owner_pubkey, amount) +//! SpentOutputs output_ref => (owner_pubkey, amount) + +use std::path::PathBuf; + +use crate::rpc; +use anyhow::anyhow; +use parity_scale_codec::{Decode, Encode}; +use sled::Db; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use tuxedo_core::{ + dynamic_typing::UtxoData, + types::{Input, OutputRef}, +}; + + + +use jsonrpsee::http_client::HttpClient; +use runtime::kitties::KittyDNA; +use runtime::kitties::KittyData; + +use runtime::{ + money::Coin, timestamp::Timestamp, tradable_kitties::TradableKittyData, Block, OuterVerifier, + Transaction, +}; + +/*Todo: Do we need all the data of kitty here +use runtime::{ + kitties::{KittyData, Parent,KittyHelpers,MomKittyStatus,DadKittyStatus, + KittyDNA,FreeKittyConstraintChecker} +}; +*/ + +/// The identifier for the blocks tree in the db. +const BLOCKS: &str = "blocks"; + +/// The identifier for the block_hashes tree in the db. +const BLOCK_HASHES: &str = "block_hashes"; + +/// The identifier for the unspent tree in the db. +const UNSPENT: &str = "unspent"; + +/// The identifier for the spent tree in the db. +const SPENT: &str = "spent"; + +/// The identifier for the owned kitties in the db. +const FRESH_KITTY: &str = "fresh_kitty"; + +/// The identifier for the owned kitties in the db. +const USED_KITTY: &str = "used_kitty"; + +/// The identifier for the owned kitties in the db. +const FRESH_TRADABLE_KITTY: &str = "fresh_tradable_kitty"; + +/// The identifier for the owned kitties in the db. +const USED_TRADABLE_KITTY: &str = "used_tradable_kitty"; + +/// 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 +/// If an empty database is opened, it is initialized with the expected genesis hash and genesis block +pub(crate) fn open_db( + db_path: PathBuf, + expected_genesis_hash: H256, + expected_genesis_block: Block, +) -> anyhow::Result { + //TODO figure out why this assertion fails. + //assert_eq!(BlakeTwo256::hash_of(&expected_genesis_block.encode()), expected_genesis_hash, "expected block hash does not match expected block"); + + let db = sled::open(db_path)?; + + // Open the tables we'll need + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + let wallet_blocks_tree = db.open_tree("blocks")?; + + // If the database is already populated, just make sure it is for the same genesis block + if height(&db)?.is_some() { + // There are database blocks, so do a quick precheck to make sure they use the same genesis block. + let wallet_genesis_ivec = wallet_block_hashes_tree + .get(0.encode())? + .expect("We know there are some blocks, so there should be a 0th block."); + let wallet_genesis_hash = H256::decode(&mut &wallet_genesis_ivec[..])?; + log::debug!("Found existing database."); + if expected_genesis_hash != wallet_genesis_hash { + log::error!("Wallet's genesis does not match expected. Aborting database opening."); + return Err(anyhow!("Node reports a different genesis block than wallet. Wallet: {wallet_genesis_hash:?}. Expected: {expected_genesis_hash:?}. Aborting all operations")); + } + return Ok(db); + } + + // If there are no local blocks yet, initialize the tables + log::info!( + "Initializing fresh sync from genesis {:?}", + expected_genesis_hash + ); + + // Update both tables + wallet_block_hashes_tree.insert(0u32.encode(), expected_genesis_hash.encode())?; + wallet_blocks_tree.insert( + expected_genesis_hash.encode(), + expected_genesis_block.encode(), + )?; + + Ok(db) +} + +/// Synchronize the local database to the database of the running node. +/// The wallet entirely trusts the data the node feeds it. In the bigger +/// picture, that means run your own (light) node. +pub(crate) async fn synchronize bool>( + db: &Db, + client: &HttpClient, + filter: &F, +) -> anyhow::Result<()> { + //log::info!("Synchronizing wallet with node."); + println!("Synchronizing wallet with node."); + + // Start the algorithm at the height that the wallet currently thinks is best. + // Fetch the block hash at that height from both the wallet's local db and the node + let mut height: u32 = height(db)?.ok_or(anyhow!("tried to sync an uninitialized database"))?; + let mut wallet_hash = get_block_hash(db, height)? + .expect("Local database should have a block hash at the height reported as best"); + let mut node_hash: Option = rpc::node_get_block_hash(height, client).await?; + + // There may have been a re-org since the last time the node synced. So we loop backwards from the + // best height the wallet knows about checking whether the wallet knows the same block as the node. + // If not, we roll this block back on the wallet's local db, and then check the next ancestor. + // When the wallet and the node agree on the best block, the wallet can re-sync following the node. + // In the best case, where there is no re-org, this loop will execute zero times. + while Some(wallet_hash) != node_hash { + log::info!("Divergence at height {height}. Node reports block: {node_hash:?}. Reverting wallet block: {wallet_hash:?}."); + + unapply_highest_block(db).await?; + + // Update for the next iteration + height -= 1; + wallet_hash = get_block_hash(db, height)? + .expect("Local database should have a block hash at the height reported as best"); + node_hash = rpc::node_get_block_hash(height, client).await?; + } + + // Orphaned blocks (if any) have been discarded at this point. + // So we prepare our variables for forward syncing. + log::debug!("Resyncing from common ancestor {node_hash:?} - {wallet_hash:?}"); + height += 1; + node_hash = rpc::node_get_block_hash(height, client).await?; + + // Now that we have checked for reorgs and rolled back any orphan blocks, we can go ahead and sync forward. + while let Some(hash) = node_hash { + log::debug!("Forward syncing height {height}, hash {hash:?}"); + + // Fetch the entire block in order to apply its transactions + let block = rpc::node_get_block(hash, client) + .await? + .expect("Node should be able to return a block whose hash it already returned"); + + // Apply the new block + apply_block(db, block, hash, filter).await?; + + height += 1; + + node_hash = rpc::node_get_block_hash(height, client).await?; + } + + log::debug!("Done with forward sync up to {}", height - 1); + println!("Done with forward sync up to {}", height - 1); + if let Err(err) = db.flush() { + println!("Error flushing Sled database: {}", err); + } + Ok(()) +} + +/// Gets the owner and amount associated with an output ref from the unspent table +/// +/// Some if the output ref exists, None if it doesn't +pub(crate) fn get_unspent(db: &Db, output_ref: &OutputRef) -> anyhow::Result> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + let Some(ivec) = wallet_unspent_tree.get(output_ref.encode())? else { + return Ok(None); + }; + + Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) +} + +/// Picks an arbitrary set of unspent outputs from the database for spending. +/// The set's token values must add up to at least the specified target value. +/// +/// The return value is None if the total value of the database is less than the target +/// It is Some(Vec![...]) when it is possible +pub(crate) fn get_arbitrary_unspent_set( + db: &Db, + target: u128, +) -> anyhow::Result>> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + + let mut total = 0u128; + let mut keepers = Vec::new(); + + let mut unspent_iter = wallet_unspent_tree.iter(); + while total < target { + let Some(pair) = unspent_iter.next() else { + return Ok(None); + }; + + let (output_ref_ivec, owner_amount_ivec) = pair?; + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..])?; + println!( + "in Sync::get_arbitrary_unspent_set output_ref = {:?}", + output_ref + ); + let (_owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + total += amount; + keepers.push(output_ref); + } + + Ok(Some(keepers)) +} + +/// Gets the block hash from the local database given a block height. Similar the Node's RPC. +/// +/// Some if the block exists, None if the block does not exist. +pub(crate) fn get_block_hash(db: &Db, height: u32) -> anyhow::Result> { + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + let Some(ivec) = wallet_block_hashes_tree.get(height.encode())? else { + return Ok(None); + }; + + let hash = H256::decode(&mut &ivec[..])?; + + Ok(Some(hash)) +} + +// This is part of what I expect to be a useful public interface. For now it is not used. +#[allow(dead_code)] +/// Gets the block from the local database given a block hash. Similar to the Node's RPC. +pub(crate) fn get_block(db: &Db, hash: H256) -> anyhow::Result> { + let wallet_blocks_tree = db.open_tree(BLOCKS)?; + let Some(ivec) = wallet_blocks_tree.get(hash.encode())? else { + return Ok(None); + }; + + let block = Block::decode(&mut &ivec[..])?; + + Ok(Some(block)) +} + +/// Apply a block to the local database +pub(crate) async fn apply_block bool>( + db: &Db, + b: Block, + block_hash: H256, + filter: &F, +) -> anyhow::Result<()> { + //log::info!("Applying Block {:?}, Block_Hash {:?}", b, block_hash); + //println!("Applying Block {:?}, Block_Hash {:?}", b, block_hash); + // Write the hash to the block_hashes table + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + wallet_block_hashes_tree.insert(b.header.number.encode(), block_hash.encode())?; + + // Write the block to the blocks table + let wallet_blocks_tree = db.open_tree(BLOCKS)?; + wallet_blocks_tree.insert(block_hash.encode(), b.encode())?; + + // Iterate through each transaction + for tx in b.extrinsics { + apply_transaction(db, tx, filter).await?; + } + if let Err(err) = db.flush() { + println!("Error flushing Sled database: {}", err); + } + + Ok(()) +} + +/// Apply a single transaction to the local database +/// The owner-specific tables are mappings from output_refs to coin amounts +async fn apply_transaction bool>( + db: &Db, + tx: Transaction, + filter: &F, +) -> anyhow::Result<()> { + let tx_hash = BlakeTwo256::hash_of(&tx.encode()); + log::debug!("syncing transaction {tx_hash:?}"); + + // Insert all new outputs + for (index, output) in tx + .outputs + .iter() + .filter(|o| filter(&o.verifier)) + .enumerate() + { + match output.payload.type_id { + Coin::<0>::TYPE_ID => { + crate::money::apply_transaction(db, tx_hash, index as u32, output)?; + } + + // I dont want to store the time stamp for now + Timestamp::TYPE_ID => { + crate::timestamp::apply_transaction(db, output)?; + } + + KittyData::TYPE_ID => { + crate::kitty::apply_transaction(db, tx_hash, index as u32, output)?; + } + TradableKittyData::TYPE_ID => { + crate::kitty::apply_td_transaction(db, tx_hash, index as u32, output)?; + } + + _ => continue, + } + } + + log::debug!("about to spend all inputs"); + // Spend all the inputs + for Input { output_ref, .. } in tx.inputs { + spend_output(db, &output_ref)?; + mark_as_used_kitties(db, &output_ref)?; + mark_as_used_tradable_kitties(db, &output_ref)?; + } + + if let Err(err) = db.flush() { + println!("Error flushing Sled database: {}", err); + } + + Ok(()) +} + +/// Add a new output to the database updating all tables. +pub(crate) fn add_unspent_output( + db: &Db, + output_ref: &OutputRef, + owner_pubkey: &H256, + amount: &u128, +) -> anyhow::Result<()> { + let unspent_tree = db.open_tree(UNSPENT)?; + unspent_tree.insert(output_ref.encode(), (owner_pubkey, amount).encode())?; + + Ok(()) +} + +/// Remove an output from the database updating all tables. +fn remove_unspent_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let unspent_tree = db.open_tree(UNSPENT)?; + + unspent_tree.remove(output_ref.encode())?; + + Ok(()) +} + +/// Mark an existing output as spent. This does not purge all record of the output from the db. +/// It just moves the record from the unspent table to the spent table +fn spend_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let unspent_tree = db.open_tree(UNSPENT)?; + let spent_tree = db.open_tree(SPENT)?; + + let Some(ivec) = unspent_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + let (owner, amount) = <(H256, u128)>::decode(&mut &ivec[..])?; + spent_tree.insert(output_ref.encode(), (owner, amount).encode())?; + + Ok(()) +} + +/// Mark an output that was previously spent back as unspent. +fn unspend_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let unspent_tree = db.open_tree(UNSPENT)?; + let spent_tree = db.open_tree(SPENT)?; + + let Some(ivec) = spent_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + let (owner, amount) = <(H256, u128)>::decode(&mut &ivec[..])?; + unspent_tree.insert(output_ref.encode(), (owner, amount).encode())?; + + Ok(()) +} + +/// Run a transaction backwards against a database. Mark all of the Inputs +/// as unspent, and drop all of the outputs. +fn unapply_transaction(db: &Db, tx: &Transaction) -> anyhow::Result<()> { + // Loop through the inputs moving each from spent to unspent + for Input { output_ref, .. } in &tx.inputs { + unspend_output(db, output_ref)?; + } + + // Loop through the outputs pruning them from unspent and dropping all record + let tx_hash = BlakeTwo256::hash_of(&tx.encode()); + + for i in 0..tx.outputs.len() { + let output_ref = OutputRef { + tx_hash, + index: i as u32, + }; + remove_unspent_output(db, &output_ref)?; + } + + Ok(()) +} + +/// Unapply the best block that the wallet currently knows about +pub(crate) async fn unapply_highest_block(db: &Db) -> anyhow::Result { + let wallet_blocks_tree = db.open_tree(BLOCKS)?; + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + + // Find the best height + let height = height(db)?.ok_or(anyhow!("Cannot unapply block from uninitialized database"))?; + + // Take the hash from the block_hashes tables + let Some(ivec) = wallet_block_hashes_tree.remove(height.encode())? else { + return Err(anyhow!( + "No block hash found at height reported as best. DB is inconsistent." + )); + }; + let hash = H256::decode(&mut &ivec[..])?; + + // Take the block from the blocks table + let Some(ivec) = wallet_blocks_tree.remove(hash.encode())? else { + return Err(anyhow!( + "Block was not present in db but block hash was. DB is corrupted." + )); + }; + + let block = Block::decode(&mut &ivec[..])?; + + // Loop through the transactions in reverse order calling unapply + for tx in block.extrinsics.iter().rev() { + unapply_transaction(db, tx)?; + } + + Ok(block) +} + +/// Get the block height that the wallet is currently synced to +/// +/// None means the db is not yet initialized with a genesis block +pub(crate) fn height(db: &Db) -> anyhow::Result> { + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + let num_blocks = wallet_block_hashes_tree.len(); + + Ok(if num_blocks == 0 { + None + } else { + Some(num_blocks as u32 - 1) + }) +} + +// This is part of what I expect to be a useful public interface. For now it is not used. +#[allow(dead_code)] +/// Debugging use. Print out the entire block_hashes tree. +pub(crate) fn print_block_hashes_tree(db: &Db) -> anyhow::Result<()> { + for height in 0..height(db)?.unwrap() { + let hash = get_block_hash(db, height)?; + println!("height: {height}, hash: {hash:?}"); + } + + Ok(()) +} + +/// Debugging use. Print the entire unspent outputs tree. +pub(crate) fn print_unspent_tree(db: &Db) -> anyhow::Result<()> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + for x in wallet_unspent_tree.iter() { + let (output_ref_ivec, owner_amount_ivec) = x?; + let output_ref = hex::encode(output_ref_ivec); + let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + println!("{output_ref}: owner {owner_pubkey:?}, amount {amount}"); + } + + Ok(()) +} + +/// Iterate the entire unspent set summing the values of the coins +/// on a per-address basis. +pub(crate) fn get_balances(db: &Db) -> anyhow::Result> { + let mut balances = std::collections::HashMap::::new(); + + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + + for raw_data in wallet_unspent_tree.iter() { + let (_output_ref_ivec, owner_amount_ivec) = raw_data?; + let (owner, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + balances + .entry(owner) + .and_modify(|old| *old += amount) + .or_insert(amount); + } + + Ok(balances.into_iter()) +} + +// Kitty related functions +/// Add kitties to the database updating all tables. +pub fn add_fresh_kitty_to_db( + db: &Db, + output_ref: &OutputRef, + owner_pubkey: &H256, + kitty: &KittyData, +) -> anyhow::Result<()> { + let kitty_owned_tree = db.open_tree(FRESH_KITTY)?; + println!("add_fresh_kitty_to_db {:?}",kitty); + kitty_owned_tree.insert(output_ref.encode(), (owner_pubkey, kitty).encode())?; + + Ok(()) +} + +pub fn add_fresh_tradable_kitty_to_db( + db: &Db, + output_ref: &OutputRef, + owner_pubkey: &H256, + kitty: &TradableKittyData, +) -> anyhow::Result<()> { + let tradable_kitty_owned_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + println!("add_fresh_tradable_kitty_to_db {:?}",kitty); + tradable_kitty_owned_tree.insert(output_ref.encode(), (owner_pubkey, kitty).encode())?; + + Ok(()) +} + +/// Remove an output from the database updating all tables. +fn remove_used_kitty_from_db(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let kitty_owned_tree = db.open_tree(FRESH_KITTY)?; + println!("remove_used_kitty_from_db {:?}",output_ref); + kitty_owned_tree.remove(output_ref.encode())?; + + Ok(()) +} + +/// Remove an output from the database updating all tables. +fn remove_used_tradable_kitty_from_db(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let tradable_kitty_owned_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + println!("remove_used_tradable_kitty_from_dbb {:?}",output_ref); + tradable_kitty_owned_tree.remove(output_ref.encode())?; + + Ok(()) +} + +/// Mark an existing output as spent. This does not purge all record of the output from the db. +/// It just moves the record from the unspent table to the spent table +fn mark_as_used_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let fresh_kitty_tree = db.open_tree(FRESH_KITTY)?; + let used_kitty_tree = db.open_tree(USED_KITTY)?; + + let Some(ivec) = fresh_kitty_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; + used_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; + println!("mark_as_used_kitties is called "); + Ok(()) +} + +/// Mark an existing output as spent. This does not purge all record of the output from the db. +/// It just moves the record from the unspent table to the spent table +fn mark_as_used_tradable_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let fresh_tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let used_tradable_kitty_tree = db.open_tree(USED_TRADABLE_KITTY)?; + + let Some(ivec) = fresh_tradable_kitty_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; + used_tradable_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; + println!("mark_as_used_tradable_kitties is called "); + Ok(()) +} + +/// Mark an output that was previously spent back as unspent. +fn unmark_as_used_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let fresh_kitty_tree = db.open_tree(FRESH_KITTY)?; + let used_kitty_tree = db.open_tree(USED_KITTY)?; + + let Some(ivec) = used_kitty_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; + fresh_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; + + Ok(()) +} + +/// Mark an output that was previously spent back as unspent. +fn unmark_as_used_tradable_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let fresh_Tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let used_Tradable_kitty_tree = db.open_tree(USED_TRADABLE_KITTY)?; + + let Some(ivec) = used_Tradable_kitty_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; + fresh_Tradable_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; + + Ok(()) +} + +/// Iterate the entire owned kitty +/// on a per-address basis. +pub(crate) fn get_all_kitties_from_local_db<'a>( + db: &'a Db, +) -> anyhow::Result + 'a> { + get_all_kitties_and_td_kitties_from_local_db(db, FRESH_KITTY) +} + +/// Iterate the entire owned tradable kitty +/// on a per-address basis. +pub(crate) fn get_all_tradable_kitties_from_local_db<'a>( + db: &'a Db, +) -> anyhow::Result + 'a> { + get_all_kitties_and_td_kitties_from_local_db(db, FRESH_TRADABLE_KITTY) +} + +pub(crate) fn get_kitty_from_local_db_based_on_name( + db: &Db, + name: String, +) -> anyhow::Result> { + get_data_from_local_db_based_on_name(db, FRESH_KITTY, name, |kitty: &KittyData| &kitty.name) +} + +pub(crate) fn get_tradable_kitty_from_local_db_based_on_name( + db: &Db, + name: String, +) -> anyhow::Result> { + get_data_from_local_db_based_on_name( + db, + FRESH_TRADABLE_KITTY, + name, + |kitty: &TradableKittyData| &kitty.kitty_basic_data.name, + ) +} + +pub(crate) fn get_kitty_from_local_db_based_on_dna( + db: &Db, + dna: &str, +) -> anyhow::Result> { + get_data_from_local_db_based_on_dna(db, FRESH_KITTY, dna, |kitty: &KittyData| kitty.dna.clone()) +} + +pub(crate) fn get_tradable_kitty_from_local_db_based_on_dna( + db: &Db, + dna: &str, +) -> anyhow::Result> { + get_data_from_local_db_based_on_dna( + db, + FRESH_TRADABLE_KITTY, + dna, + |kitty: &TradableKittyData| kitty.kitty_basic_data.dna.clone(), + ) +} + +/// Iterate the entire owned kitty +/// on a per-address basis. +pub(crate) fn get_owned_kitties_from_local_db<'a>( + db: &'a Db, + owner_pub_key: &'a H256, +) -> anyhow::Result + 'a> { + get_any_owned_kitties_from_local_db(db, FRESH_KITTY, &owner_pub_key) +} + +/// Iterate the entire owned tradable kitty +/// on a per-address basis. +pub(crate) fn get_owned_tradable_kitties_from_local_db<'a>( + db: &'a Db, + owner_pub_key: &'a H256, +) -> anyhow::Result + 'a> { + get_any_owned_kitties_from_local_db(db, FRESH_TRADABLE_KITTY, &owner_pub_key) +} + +pub(crate) fn get_all_coins(db: &Db) -> anyhow::Result> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + let mut result = Vec::new(); + + for x in wallet_unspent_tree.iter() { + let (output_ref_ivec, owner_amount_ivec) = x?; + let output_ref = hex::encode(output_ref_ivec); + let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + result.push((output_ref, owner_pubkey, amount)); + } + + Ok(result) +} + +pub(crate) fn get_owned_coins(db: &Db, owner_pub_key: &H256) -> anyhow::Result> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + let mut result = Vec::new(); + + for x in wallet_unspent_tree.iter() { + let (output_ref_ivec, owner_amount_ivec) = x?; + let output_ref = hex::encode(output_ref_ivec); + let (coin_owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + // Check if the coin's owner public key matches the provided owner_pub_key + if &coin_owner_pubkey == owner_pub_key { + result.push((output_ref, coin_owner_pubkey, amount)); + } + } + + Ok(result) +} + +pub(crate) fn is_kitty_name_duplicate( + db: &Db, + name: String, + owner_pubkey: &H256, +) -> anyhow::Result> { + is_name_duplicate(db, name, owner_pubkey, FRESH_KITTY, |kitty: &KittyData| { + &kitty.name + }) +} + +pub(crate) fn is_td_kitty_name_duplicate( + db: &Db, + name: String, + owner_pubkey: &H256, +) -> anyhow::Result> { + is_name_duplicate( + db, + name, + owner_pubkey, + FRESH_TRADABLE_KITTY, + |kitty: &TradableKittyData| &kitty.kitty_basic_data.name, + ) +} + +/// Gets the owner and amount associated with an output ref from the unspent table +/// +/// Some if the output ref exists, None if it doesn't +pub(crate) fn get_kitty_fromlocaldb( + db: &Db, + output_ref: &OutputRef, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + let Some(ivec) = wallet_owned_kitty_tree.get(output_ref.encode())? else { + return Ok(None); + }; + + Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) +} + +/// Gets the owner and amount associated with an output ref from the unspent table +/// +/// Some if the output ref exists, None if it doesn't +pub(crate) fn get_tradable_kitty_fromlocaldb( + db: &Db, + output_ref: &OutputRef, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let Some(ivec) = wallet_owned_kitty_tree.get(output_ref.encode())? else { + return Ok(None); + }; + + Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) +} + +////////////////////////// +// Private Functions +////////////////////////// + +fn get_all_kitties_and_td_kitties_from_local_db<'a, T>( + db: &'a Db, + tree_name: &'a str, +) -> anyhow::Result + 'a> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_tradable_kitty_tree = db.open_tree(tree_name)?; + + Ok(wallet_owned_tradable_kitty_tree + .iter() + .filter_map(|raw_data| { + let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + Some((owner, kitty)) + })) +} + +fn get_data_from_local_db_based_on_dna( + db: &Db, + tree_name: &str, + dna: &str, + dna_extractor: impl Fn(&T) -> KittyDNA, +) -> anyhow::Result> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_kitty_tree = db.open_tree(tree_name)?; + + let dna_bytes = hex::decode(dna).expect("Invalid hexadecimal string"); + let kitty_dna = KittyDNA(H256::from_slice(&dna_bytes)); + + let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree + .iter() + .filter_map(|raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + println!("Owner = {:?} Dna : {:?} -> output_ref {:?}", owner, kitty_dna, output_ref.clone()); + + if dna_extractor(&kitty) == kitty_dna { + println!(" Name : {:?} matched", kitty_dna); + Some((Some(kitty), output_ref)) + } else { + println!(" Name : {:?} NOTmatched", kitty_dna); + None + } + }) + .next() + .unwrap_or(( + None, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )); // Use unwrap_or to handle the Option + + println!("output_ref = {:?}", output_ref); + println!("kitty dna {:?} found_status = {:?}", kitty_dna,found_kitty); + + Ok(found_kitty.map(|kitty| (kitty, output_ref))) +} + +fn get_data_from_local_db_based_on_name( + db: &Db, + tree_name: &str, + name: String, + name_extractor: impl Fn(&T) -> &[u8; 4], +) -> anyhow::Result> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_kitty_tree = db.open_tree(tree_name)?; + + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree + .iter() + .filter_map(|raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + println!("Owner = {:?} Name : {:?} -> output_ref {:?}", owner, name, output_ref.clone()); + + if name_extractor(&kitty) == kitty_name { + println!(" Name : {:?} matched", name); + Some((Some(kitty), output_ref)) + } else { + println!(" Name : {:?} NOTmatched", name); + None + } + }) + .next() + .unwrap_or(( + None, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )); // Use unwrap_or to handle the Option + + println!("output_ref = {:?}", output_ref); + println!("kitty Name {} found_status = {:?}", name,found_kitty); + + Ok(found_kitty.map(|kitty| (kitty, output_ref))) +} + +fn get_any_owned_kitties_from_local_db<'a, T>( + db: &'a Db, + tree_name: &'a str, + owner_pubkey: &'a H256, +) -> anyhow::Result + 'a> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_kitty_tree = db.open_tree(tree_name)?; + + Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + if owner == *owner_pubkey { + Some((owner, kitty, output_ref)) + } else { + None + } + })) +} + +fn is_name_duplicate( + db: &Db, + name: String, + owner_pubkey: &H256, + tree_name: &str, + name_extractor: impl Fn(&T) -> &[u8; 4], +) -> anyhow::Result> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_kitty_tree = db.open_tree(tree_name)?; + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let found_kitty: Option = wallet_owned_kitty_tree + .iter() + .filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + println!("Name : {:?}", name); + + if *name_extractor(&kitty) == kitty_name[..] && owner == *owner_pubkey { + Some(Some(kitty)) + } else { + None + } + }) + .next() + .unwrap_or(None); // Use unwrap_or to handle the Option + + println!("found_kitty = {:?}", found_kitty); + let is_kitty_found = match found_kitty { + Some(k) => Some(true), + None => Some(false), + }; + Ok(is_kitty_found) +} + +fn string_to_h256(s: &str) -> Result { + let bytes = hex::decode(s)?; + // Assuming H256 is a fixed-size array with 32 bytes + let mut h256 = [0u8; 32]; + h256.copy_from_slice(&bytes); + Ok(h256.into()) +} + +/* +pub(crate) fn is_kitty_name_duplicate1( + db: &Db, + owner_pubkey: &H256, + name: String, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let found_kitty: (Option) = wallet_owned_kitty_tree + .iter() + .filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + println!("Name : {:?}", name); + + if kitty.name == &kitty_name[..] && owner == *owner_pubkey { + Some(Some(kitty)) + } else { + None + } + }) + .next() + .unwrap_or(None); // Use unwrap_or to handle the Option + + println!("found_kitty = {:?}", found_kitty); + let is_kitty_found = match found_kitty { + Some(k) => Some(true), + None => Some(false), + }; + Ok(is_kitty_found) +} + +pub(crate) fn is_tradable_kitty_name_duplicate1( + db: &Db, + owner_pubkey: &H256, + name: String, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let found_kitty: (Option) = wallet_owned_kitty_tree + .iter() + .filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + println!("Name : {:?}", name); + + if kitty.kitty_basic_data.name == &kitty_name[..] && owner == *owner_pubkey { + Some(Some(kitty)) + } else { + None + } + }) + .next() + .unwrap_or(None); // Use unwrap_or to handle the Option + + println!("found_kitty = {:?}", found_kitty); + let is_kitty_found = match found_kitty { + Some(k) => Some(true), + None => Some(false), + }; + Ok(is_kitty_found) +} + +/// Debugging use. Print the entire unspent outputs tree. +pub(crate) fn print_owned_kitties(db: &Db) -> anyhow::Result<()> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + for x in wallet_unspent_tree.iter() { + let (output_ref_ivec, owner_amount_ivec) = x?; + let output_ref = hex::encode(output_ref_ivec); + let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + println!("{output_ref}: owner {owner_pubkey:?}, amount {amount}"); + } + + Ok(()) +} + +pub(crate) fn get_all_kitties_from_local_db1( + db: &Db, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + + Ok(wallet_owned_kitty_tree.iter().filter_map(|raw_data| { + let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + Some((owner, kitty)) + })) +} + +/// Iterate the entire owned kitty +/// on a per-address basis. +pub(crate) fn get_all_tradable_kitties_from_local_db1( + db: &Db, +) -> anyhow::Result> { + let wallet_owned_tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + + Ok(wallet_owned_tradable_kitty_tree.iter().filter_map(|raw_data| { + let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + Some((owner, kitty)) + })) +} + +pub(crate) fn get_owned_kitties_from_local_db<'a>( + db: &'a Db, + args: &'a ShowOwnedKittyArgs, +) -> anyhow::Result + 'a> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + + Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref_str = hex::encode(output_ref_ivec.clone()); + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + if owner == args.owner { + Some((owner, kitty, output_ref)) + } else { + None + } + })) +} + + +pub(crate) fn get_owned_tradable_kitties_from_local_db<'a>( + db: &'a Db, + args: &'a ShowOwnedKittyArgs, +) -> anyhow::Result + 'a> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + + Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref_str = hex::encode(output_ref_ivec.clone()); + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + if owner == args.owner { + Some((owner, kitty, output_ref)) + } else { + None + } + })) +} + + +pub(crate) fn get_kitty_from_local_db_based_on_name_bk( + db: &Db, + name: String, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let (found_kitty, output_ref) = wallet_owned_kitty_tree + .iter() + .filter_map(|raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + println!("Name : {:?} -> output_ref {:?}", name, output_ref.clone()); + + if kitty.name == &kitty_name[..] { + Some((Some(kitty), output_ref)) + } else { + None + } + }) + .next() + .unwrap_or(( + None, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )); // Use unwrap_or to handle the Option + + println!("output_ref = {:?}", output_ref); + println!("found_kitty = {:?}", found_kitty); + + Ok(found_kitty.map(|kitty| (kitty, output_ref))) +} + + +pub(crate) fn get_tradable_kitty_from_local_db_based_on_name( + db: &Db, + name: String, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree + .iter() + .filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref_str = hex::encode(output_ref_ivec.clone()); + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + println!("Name : {:?} -> output_ref {:?}", name, output_ref.clone()); + + if kitty.kitty_basic_data.name == &kitty_name[..] { + Some((Some(kitty), output_ref)) + } else { + None + } + }) + .next() + .unwrap_or(( + None, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )); // Use unwrap_or to handle the Option + + println!("output_ref = {:?}", output_ref); + println!("found_kitty = {:?}", found_kitty); + + Ok(found_kitty.map(|kitty| (kitty, output_ref))) +} + +*/ diff --git a/webservice-wallet-external-signing/src/timestamp.rs b/webservice-wallet-external-signing/src/timestamp.rs new file mode 100644 index 000000000..d5da075a0 --- /dev/null +++ b/webservice-wallet-external-signing/src/timestamp.rs @@ -0,0 +1,27 @@ +//! Wallet features related to on-chain timestamps. + +use anyhow::anyhow; +use parity_scale_codec::{Decode, Encode}; +use runtime::{timestamp::Timestamp, OuterVerifier}; +use sled::Db; +use tuxedo_core::types::Output; + +/// The identifier for the current timestamp in the db. +const TIMESTAMP: &str = "timestamp"; + +pub(crate) fn apply_transaction(db: &Db, output: &Output) -> anyhow::Result<()> { + let timestamp = output.payload.extract::()?.time; + let timestamp_tree = db.open_tree(TIMESTAMP)?; + timestamp_tree.insert([0], timestamp.encode())?; + Ok(()) +} + +/// Apply a transaction to the local database, storing the new timestamp. +pub(crate) fn get_timestamp(db: &Db) -> anyhow::Result { + let timestamp_tree = db.open_tree(TIMESTAMP)?; + let timestamp = timestamp_tree + .get([0])? + .ok_or_else(|| anyhow!("Could not find timestamp in database."))?; + u64::decode(&mut ×tamp[..]) + .map_err(|_| anyhow!("Could not decode timestamp from database.")) +} diff --git a/webservice-wallet-with-inbuilt-key-store/Cargo.toml b/webservice-wallet-with-inbuilt-key-store/Cargo.toml new file mode 100644 index 000000000..68ba3558f --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/Cargo.toml @@ -0,0 +1,36 @@ +[package] +description = "A simple example / template wallet built for the tuxedo template runtime" +edition = "2021" +license = "Apache-2.0" +name = "tuxedo-template-web-service-wallet" +repository = "https://github.com/Off-Narrative-Labs/Tuxedo" +version = "1.0.0-dev" + +[dependencies] +runtime = { package = "tuxedo-template-runtime", path = "../tuxedo-template-runtime" } +tuxedo-core = { path = "../tuxedo-core" } + +anyhow = { workspace = true } +clap = { features = [ "derive" ], workspace = true } +directories = { workspace = true } +env_logger = { workspace = true } +futures = { workspace = true } +hex = { workspace = true } +hex-literal = { workspace = true } +jsonrpsee = { features = [ "http-client" ], workspace = true } +log = { workspace = true } +parity-scale-codec = { workspace = true } +serde_json = { workspace = true } +sled = { workspace = true } +tokio = { features = [ "full" ], workspace = true } + +rand = "0.8" + +sc-keystore = { workspace = true } +sp-core = { workspace = true } +sp-keystore = { workspace = true } +sp-runtime = { workspace = true } + +axum = "0.5.16" +serde = { version = "1.0", features = ["derive"] } +tower-http = { version = "0.3.4", features = ["cors"] } diff --git a/webservice-wallet-with-inbuilt-key-store/Dockerfile b/webservice-wallet-with-inbuilt-key-store/Dockerfile new file mode 100644 index 000000000..b685c5129 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/Dockerfile @@ -0,0 +1,35 @@ +# This is a multi-stage docker file. See https://docs.docker.com/build/building/multi-stage/ +# for details about this pattern. + + + +# For the build stage, we use an image provided by Parity +FROM docker.io/paritytech/ci-linux:production as builder +WORKDIR /wallet +#TODO The Workdir and Copy command is different here than in the node... +COPY . . +RUN cargo build --locked --release -p tuxedo-template-wallet + + +# For the second stage, we use a minimal Ubuntu image +FROM docker.io/library/ubuntu:20.04 +LABEL description="Tuxedo Templet Wallet" + +COPY --from=builder /wallet/target/release/tuxedo-template-wallet /usr/local/bin + +RUN useradd -m -u 1000 -U -s /bin/sh -d /node-dev node-dev && \ + mkdir -p /wallet-data /node-dev/.local/share && \ + chown -R node-dev:node-dev /wallet-data && \ + # Make the wallet data directory available outside the container. + ln -s /wallet-data /node-dev/.local/share/tuxedo-template-wallet && \ + # unclutter and minimize the attack surface + rm -rf /usr/bin /usr/sbin && \ + # check if executable works in this container + /usr/local/bin/tuxedo-template-wallet --version + +USER node-dev + +EXPOSE 9944 +VOLUME ["/wallet-data"] + +ENTRYPOINT ["/usr/local/bin/tuxedo-template-wallet"] \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/README.md b/webservice-wallet-with-inbuilt-key-store/README.md new file mode 100644 index 000000000..8b272ccb1 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/README.md @@ -0,0 +1,299 @@ +# Tuxedo Template Wallet + +A cli wallet for the Tuxedo Node Template + +## Overview + +This wallet works with the Tuxedo Node Template and Tuxedo Template Runtime which is also contained in this repository. + +Like many UTXO wallets, this one synchronizes a local-to-the-wallet database of UTXOs that exist on the current best chain. +The wallet does not sync the entire blockchain state. +Rather, it syncs a subset of the state that it considers "relevant". +Currently, the wallet syncs any UTXOs that contain tokens owned by a key in the wallet's keystore. +However, the wallet is designed so that this notion of "relevance" is generalizeable. +This design allows developers building chains with Tuxedo to extend the wallet for their own needs. +However, because this is a text- based wallet, it is likely not well-suited for end users of popular dapps. + +## CLI Documentation + +The node application has a thorough help page that you can access on the CLI. It also has help pages for all subcommands. Please explore and read these docs thoroughly. + +```sh +# Show the wallet's main help page +$ tuxedo-template-wallet --help + +A simple example / template wallet built for the tuxedo template runtime + +Usage: tuxedo-template-wallet [OPTIONS] + +Commands: + +... + +# Show the help for a subcommand +$ tuxedo-template-wallet verify-coin --help +Verify that a particular coin exists. + +Show its value and owner from both chain storage and the local database. + +Usage: tuxedo-template-wallet verify-coin + +Arguments: + + A hex-encoded output reference + +Options: + -h, --help + Print help (see a summary with '-h') +``` + +## Guided Tour + +This guided tour shows off some of the most common and important wallet features. It can serve as a quickstart, but is not a substitute for reading the help pages mentioned above. (Seriously, please rtfm). + +To follow this walkthrough, you should already have a fresh tuxedo template dev node running as described in the [main readme](../README.md). For example, `node-template --dev`. + +### Syncing up an Initial Wallet + +The wallet is not a long-running process. +The wallet starts up, syncs with the latest chain state, performs the action invoked, and exits. + +Let's begin by just starting a new wallet and letting it sync. + +```sh +$ tuxedo-template-wallet + +[2023-04-11T17:44:40Z INFO tuxedo_template_wallet::sync] Initializing fresh sync from genesis 0x12aba3510dc0918aec178a32927f145d22d62afe63392713cb65b85570206327 +[2023-04-11T17:44:40Z INFO tuxedo_template_wallet] Number of blocks in the db: 0 +[2023-04-11T17:44:40Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 20 +[2023-04-11T17:44:40Z INFO tuxedo_template_wallet] No Wallet Command invoked. Exiting. +``` + +The logs indicate that a fresh database was created and had no blocks in it. Then, by communicating with the node, the wallet was able to sync 20 blocks. Finally it tells us that we didn't ask the wallet to tell us any specific information or send any transactions, so it just exits. + +Let's run the same command again and see that the wallet persists state. + +```sh +$ tuxedo-template-wallet + +[2023-04-11T17:46:17Z INFO tuxedo_template_wallet] Number of blocks in the db: 20 +[2023-04-11T17:46:17Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 52 +[2023-04-11T17:46:17Z INFO tuxedo_template_wallet] No Wallet Command invoked. Exiting. +``` + +This time, it is not a fresh database. In fact it starts from block 20, where it left off previously, and syncs up to block 52. Again, we didn't tell the wallet any specific action to take, so it just exits. + +We can also tell the wallet to skip the initial sync if we want to for any reason. +```sh +$ tuxedo-template-wallet --no-sync + +[2023-04-11T17:47:48Z INFO tuxedo_template_wallet] Number of blocks in the db: 52 +[2023-04-11T17:47:48Z WARN tuxedo_template_wallet] Skipping sync with node. Using previously synced information. +[2023-04-11T17:47:48Z INFO tuxedo_template_wallet] No Wallet Command invoked. Exiting. +``` + +Now that we understand that the wallet syncs up with the node each time it starts, let's explore our first wallet command. Like most wallets, it will tell you how many tokens you own. + +```sh +$ tuxedo-template-wallet show-balance + +[2023-04-11T18:07:52Z INFO tuxedo_template_wallet] Number of blocks in the db: 52 +[2023-04-11T18:07:52Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 55 +Balance Summary +0xd2bf…df67: 100 +-------------------- +total : 100 +``` +The wallet begins by syncing the blockchain state, as usual. +Then it shows us that it knows about this `0xd2bf...` account. +This is the test account, or the "SHAWN" account. +The wallet already contains these keys so you can start learning quickly. +And it seems this account has some money. +Let's look further. + +### Exploring the Genesis Coin + +The chain begins with a single coin in storage. +We can confirm that the node and the wallet are familiar with the genesis coin using the `verify-coin` subcommand. + +```sh +$ tuxedo-template-wallet verify-coin 000000000000000000000000000000000000000000000000000000000000000000000000 + +[2023-04-11T17:50:04Z INFO tuxedo_template_wallet] Number of blocks in the db: 55 +[2023-04-11T17:50:04Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 80 +Details of coin 000000000000000000000000000000000000000000000000000000000000000000000000: +Found in storage. Value: 100, owned by 0xd2bf…df67 +Found in local db. Value: 100, owned by 0xd2bf…df67 +``` + +After syncing, it tells us the status of the coin that we are asking about. +That number with all the `0`s is called an `OutputRef` and it is a unique way to refer to a utxo. +The wallet tells us that the coin is found in the chain's storage and in the wallet's own local db. +Both sources agree that the coin exists, is worth 100, and is owned by Shawn. + +Let's "split" this coin by creating a transaction that spends it and creates two new coins worth 40 and 50, burning the remaining 10. + +```sh +$ tuxedo-template-wallet spend-coins \ + --output-amount 40 \ + --output-amount 50 + +[2023-04-11T17:58:00Z INFO tuxedo_template_wallet] Number of blocks in the db: 80 +[2023-04-11T17:58:00Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 87 +[2023-04-11T17:58:00Z INFO tuxedo_template_wallet] Node's response to spend transaction: Ok("0xad0de5922a27fab1a3ce116868ada789677c80a0e70018bd32464b2e737d3546") + +Created "9b3b0d17ad5f7784e840c40089d4d0aa0de990c5c620d49a0729c3a45afa35bf00000000" worth 40. owned by 0xd2bf…df67 +Created "9b3b0d17ad5f7784e840c40089d4d0aa0de990c5c620d49a0729c3a45afa35bf01000000" worth 50. owned by 0xd2bf…df67 +``` + +Our command told the wallet to create a transaction that spends some coins (in this case the genesis coin) and creates two new coins with the given amounts, burning the remaining 10. +It also tells us the `OutputRef`s of the new coins created. + +A balance check reveals that our balance has decreased by the 10 burnt tokes as expected. + +```sh +$ tuxedo-template-wallet show-balance + +[2023-04-11T18:52:26Z INFO tuxedo_template_wallet] Number of blocks in the db: 87 +[2023-04-11T18:52:26Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 95 + +Balance Summary +0xd2bf…df67: 90 +-------------------- +total : 90 + +``` + +In this case we didn't specify a recipient of the new outputs, so the same default address was used. Next let's explore using some other keys. + +### Using Your Own Keys + +Of course we can use other keys than the example Shawn key. +The wallet supports generating our own keys, or inserting pre-existing keys. +To follow this guide as closely as possible, you should insert the same key we generated. + +```sh +# Generate a new key +$ tuxedo-template-wallet generate-key + + Generated public key is f41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b (5HamRMAa...) + Generated Phrase is decide city tattoo arrest jeans split main sad slam blame crack farm + +# Or, to continue on with demo, insert the same generated key +$ tuxedo-template-wallet insert-key "decide city tattoo arrest jeans split main sad slam blame crack farm" + + The generated public key is f41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b (5HamRMAa...) +``` + +With our new keys in the keystore, let's send some coins from Shawn to our own key. + +```sh +$ tuxedo-template-wallet spend-coins \ + --recipient f41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b \ + --output-amount 20 \ + --output-amount 10 + +[2023-04-11T18:53:46Z INFO tuxedo_template_wallet] Number of blocks in the db: 95 +[2023-04-11T18:53:46Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 99 +[2023-04-11T18:53:46Z INFO tuxedo_template_wallet::money] Node's response to spend transaction: Ok("0x7b8466f6c418637958f8090304dbdd7f115c27abf787b8f034a41d522bdf2baf") + +Created "90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d00000000" worth 20. owned by 0xf41a…e06b +Created "90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d01000000" worth 10. owned by 0xf41a…e06b +``` + +This command will consume one of the existing coins, and create two new ones owned by our key. +Our new coins will be worth 20 and 10 tokens. +Let's check the balance summary to confirm. + +```sh +$ tuxedo-template-wallet show-balance + +[2023-04-11T18:54:42Z INFO tuxedo_template_wallet] Number of blocks in the db: 99 +[2023-04-11T18:54:42Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 101 + +Balance Summary +0xd2bf…df67: 50 +0xf41a…e06b: 30 +-------------------- +total : 80 +``` +It is possible to create new coins using the wallet. Let's explore how to do it. + +### Minting coins + +We can optionally pass the amount and public key of the owner as arguments to mint_coins. +If optional arguments are not passed below are the default values: +Amount is `100` and Public key of owner is Shawn key. + +```sh +$ tuxedo-template-wallet mint-coins \ + --owner 0xdeba7f5d5088cda3e32ccaf479056dd934d87fa8129987ca6db57c122bd73341 \ + --amount 200 \ + +[2024-01-18T14:22:19Z INFO tuxedo_template_wallet] Number of blocks in the db: 6 +[2024-01-18T14:22:19Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 14 +[2024-01-18T14:22:19Z INFO tuxedo_template_wallet::money] Node's response to mint-coin transaction: Ok("0xaff830b7755fee67c288afe18dfa6eabffe06286005b0fd6cb8e57b246c08df6") +Created "f76373909591d85f796c36ed4b265e46efabdf5b5c493b94246d590823cc42a500000000" worth 200. owned by 0xdeba…3341 +``` +It is possible to verify a newly minted coin exists in both chain storage and the local database using verify-coin command. + +### Manually Selecting Inputs + +So far, we have let the wallet select which inputs to spend on our behalf. +This is typically fine, but some users like to select specific inputs for their transactions. +The wallet supports this. +But before we can spend specific inputs, let's learn how to print the complete list of unspent outputs. + +```sh +$ tuxedo-template-wallet show-all-outputs + +[2023-04-11T18:55:23Z INFO tuxedo_template_wallet] Number of blocks in the db: 101 +[2023-04-11T18:55:23Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 104 + +###### Unspent outputs ########### +90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d00000000: owner 0xf41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b, amount 20 +90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d01000000: owner 0xf41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b, amount 10 +9b3b0d17ad5f7784e840c40089d4d0aa0de990c5c620d49a0729c3a45afa35bf01000000: owner 0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67, amount 50 +``` + +Now that we know precisely which outputs exist in our chain, we can choose to spend a specific one. +Let's consume our 20 token input and send 15 of its coins to Shawn, burning the remaining 5. +Because we are sending to Shawn, and Shawn is the default recipient, we could leave off the `--recipient` flag, but I'll choose to include it anyway. + +```sh +# The input value has to be copied from your own `show-all-outputs` results +$ tuxedo-template-wallet spend-coins \ + --input 90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d00000000 \ + --recipient 0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 \ + --output-amount 15 + +[2023-04-11T18:57:20Z INFO tuxedo_template_wallet] Number of blocks in the db: 94 +[2023-04-11T18:57:20Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 133 +[2023-04-11T18:57:20Z INFO tuxedo_template_wallet::money] Node's response to spend transaction: Ok("0x80018b868d1e29be5cb758e15618091da8185cd7256ae3338df4605732fcfe9f") + +Created "4788fd9d517af94c2cfac80cb23fa6a63c41784b6fab01efd5d33b907af2550500000000" worth 15. owned by 0xd2bf…df67 +``` + +You should confirm for yourself that both the balance summary and the complete list of UTXOs look as you expect. + +### Multi Owner + +The final wallet feature that we will demonstrate is its ability to construct transactions with inputs coming from multiple different owners. + +Here we will create a transaction with a single output worth 70 units owned by some address that we'll call Jose, and we'll let the wallet select the inputs itself. +This will require inputs from both Shawn and us, and the wallet is able to handle this. + +```sh +$ tuxedo-template-wallet spend-coins \ + --recipient 0x066ae8f6f5c3f04e7fc163555d6ef62f6f8878435a931ba7eaf02424a16afe62 \ + --output-amount 70 + +[2023-04-11T18:59:18Z INFO tuxedo_template_wallet] Number of blocks in the db: 146 +[2023-04-11T18:59:18Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 173 +[2023-04-11T18:59:19Z INFO tuxedo_template_wallet::money] Node's response to spend transaction: Ok("0x04efb1c55f4efacbe41d00d3c5fe554470328a37150df6053bd48088e73a023c") + +Created "d0f722019e05863769e64ac6d33ad3ebeb359ce0469e93a9856bfcc236c4bad700000000" worth 70. owned by 0x066a…fe62 +``` + +Now we check the balance summary and find it is empty. +That is because Jose's keys are not in the keystore, so the wallet does not track his tokens. diff --git a/webservice-wallet-with-inbuilt-key-store/src/TradableKitties.rs b/webservice-wallet-with-inbuilt-key-store/src/TradableKitties.rs new file mode 100644 index 000000000..6c3867bd2 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/TradableKitties.rs @@ -0,0 +1,443 @@ +use crate::money::get_coin_from_storage; +use crate::rpc::fetch_storage; +use crate::sync; + +//use crate::cli::BreedArgs; +use tuxedo_core::{ + types::{Input, Output, OutputRef}, + verifier::Sr25519Signature, +}; + +use crate::kitty; +use crate::kitty::Gender; +use anyhow::anyhow; +use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; +use parity_scale_codec::Encode; +use rand::distributions::Alphanumeric; +use rand::Rng; +use sc_keystore::LocalKeystore; +use sled::Db; +use sp_core::sr25519::Public; +use sp_runtime::traits::{BlakeTwo256, Hash}; +//use crate::kitty::get_kitty_name; + +use runtime::{ + kitties::{ + DadKittyStatus, FreeKittyConstraintChecker, KittyDNA, KittyData, KittyHelpers, + MomKittyStatus, Parent, + }, + money::{Coin, MoneyConstraintChecker}, + tradable_kitties::TradableKittyConstraintChecker, + tradable_kitties::TradableKittyData, + OuterVerifier, Transaction, +}; + +use crate::cli::BuyKittyArgs; + +fn generate_random_string(length: usize) -> String { + let rng = rand::thread_rng(); + let random_string: String = rng + .sample_iter(&Alphanumeric) + .take(length) + .map(char::from) + .collect(); + random_string +} +/* +pub async fn set_kitty_property( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: KittyPropertyArgs, +) -> anyhow::Result<()> { + log::info!("The set_kitty_property are:: {:?}", args); + + let kitty_to_be_updated = + crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, args.current_name.clone()); + let Some((kitty_info, kitty_out_ref)) = kitty_to_be_updated.unwrap() else { + println!("No kitty with name : {}",args.current_name.clone() ); + return Err(anyhow!("No kitty with name {}",args.current_name.clone())); + }; + let kitty_ref = Input { + output_ref: kitty_out_ref, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + let inputs: Vec = vec![kitty_ref]; + // inputs.push(kitty_ref); + + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(args.new_name.clone().as_bytes()); + &array + }; + + // Create the KittyData + let mut output_kitty = kitty_info.clone(); + output_kitty.kitty_basic_data.name = *kitty_name; + output_kitty.price = Some(args.price); + output_kitty.is_available_for_sale = args.is_available_for_sale; + + let output = Output { + payload: output_kitty.into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::UpdateProperties.into(), + }; + + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + + // insert the proof + input.redeemer = redeemer; + } + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + println!("Node's response to spawn transaction: {:?}", spawn_response); + + // Print new output refs for user to check later + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_kitty_ref = OutputRef { + tx_hash, + index: i as u32, + }; + let new_kitty = output.payload.extract::()?.kitty_basic_data.dna.0; + + print!( + "Created {:?} worth {:?}. ", + hex::encode(new_kitty_ref.encode()), + new_kitty + ); + crate::pretty_print_verifier(&output.verifier); + } + + Ok(()) +} + + +pub async fn breed_kitty( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: BreedKittyArgs, +) -> anyhow::Result<()> { + log::info!("The Breed kittyArgs are:: {:?}", args); + + let kitty_mom = crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, args.mom_name.clone()); + let Some((kitty_mom_info, out_ref_mom)) = kitty_mom.unwrap() else { + return Err(anyhow!("No kitty with name {}",args.mom_name)); // Todo this needs to error + }; + + let kitty_dad = crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, args.dad_name.clone()); + let Some((kitty_dad_info, out_ref_dad)) = kitty_dad.unwrap() else { + return Err(anyhow!("No kitty with name {}",args.dad_name)); // Todo this needs to error + }; + log::info!("kitty_mom_ref:: {:?}", out_ref_mom); + log::info!("kitty_dad_ref:: {:?}", out_ref_dad); + + let mom_ref = Input { + output_ref: out_ref_mom, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + + let dad_ref = Input { + output_ref: out_ref_dad, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + + let mut inputs: Vec = vec![]; + inputs.push(mom_ref); + inputs.push(dad_ref); + + let mut new_mom: TradableKittyData = kitty_mom_info; + new_mom.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::RearinToGo); + if new_mom.kitty_basic_data.num_breedings >= 2 { + new_mom.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); + } + new_mom.kitty_basic_data.num_breedings += 1; + new_mom.kitty_basic_data.free_breedings -= 1; + + // Create the Output mom + let output_mom = Output { + payload: new_mom.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let mut new_dad:TradableKittyData = kitty_dad_info; + new_dad.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::RearinToGo); + if new_dad.kitty_basic_data.num_breedings >= 2 { + new_dad.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::Tired); + } + + new_dad.kitty_basic_data.num_breedings += 1; + new_dad.kitty_basic_data.free_breedings -= 1; + // Create the Output dada + let output_dad = Output { + payload: new_dad.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let child_gender = match gen_random_gender() { + Gender::Male => Parent::dad(), + Gender::Female => Parent::mom(), + }; + let child_kitty = KittyData { + parent: child_gender, + free_breedings: 2, + name: *b"tomy", + dna: KittyDNA(BlakeTwo256::hash_of(&( + new_mom.kitty_basic_data.dna.clone(), + new_dad.kitty_basic_data.dna.clone(), + new_mom.kitty_basic_data.num_breedings, + new_dad.kitty_basic_data.num_breedings, + ))), + num_breedings: 0, + }; + + let child = TradableKittyData { + kitty_basic_data: child_kitty, + price: None, + is_available_for_sale: false, + }; + println!("New mom Dna = {:?}", new_mom.kitty_basic_data.dna); + println!("New Dad Dna = {:?}", new_dad.kitty_basic_data.dna); + println!("Child Dna = {:?}", child.kitty_basic_data.dna); + // Create the Output child + let output_child = Output { + payload: child.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let new_family = Box::new(vec![output_mom, output_dad, output_child]); + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: (&[ + new_family[0].clone(), + new_family[1].clone(), + new_family[2].clone(), + ]) + .to_vec(), + checker: TradableKittyConstraintChecker::Breed.into(), + }; + + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + + // insert the proof + input.redeemer = redeemer; + } + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + + // Send the transaction to the node + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + println!("Node's response to spawn transaction: {:?}", spawn_response); + + // Print new output refs for user to check later + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_kitty_ref = OutputRef { + tx_hash, + index: i as u32, + }; + let new_kitty = output.payload.extract::()?.kitty_basic_data.dna.0; + + print!( + "Created {:?} Tradable Kitty {:?}. ", + hex::encode(new_kitty_ref.encode()), + new_kitty + ); + crate::pretty_print_verifier(&output.verifier); + } + + Ok(()) +} + +pub async fn buy_kitty( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: BuyKittyArgs, +) -> anyhow::Result<()> { + log::info!("The Buy kittyArgs are:: {:?}", args); + + let kitty_to_be_bought = + crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, args.kitty_name); + + let Some((kitty_info, kitty_out_ref)) = kitty_to_be_bought.unwrap() else { + return Err(anyhow!( + "Not enough value in database to construct transaction" + ))?; + }; + let kitty_ref = Input { + output_ref: kitty_out_ref, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + let mut inputs: Vec = vec![]; + inputs.push(kitty_ref); + + // Create the KittyData + let mut output_kitty = kitty_info.clone(); + + let output = Output { + payload: output_kitty.into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::Buy.into() + }; + + // Construct each output and then push to the transactions for Money + let mut total_output_amount = 0; + for amount in &args.output_amount { + let output = Output { + payload: Coin::<0>::new(*amount).into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.seller, + }), + }; + total_output_amount += amount; + transaction.outputs.push(output); + if total_output_amount >= kitty_info.price.unwrap().into() { + break; + } + } + + let mut total_input_amount = 0; + let mut all_input_refs = args.input; + for output_ref in &all_input_refs { + let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( + "user-specified output ref not found in local database" + ))?; + total_input_amount += amount; + } + + if total_input_amount < total_output_amount { + match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { + Some(more_inputs) => { + all_input_refs.extend(more_inputs); + } + None => Err(anyhow!( + "Not enough value in database to construct transaction" + ))?, + } + } + + // Make sure each input decodes and is still present in the node's storage, + // and then push to transaction. + for output_ref in &all_input_refs { + get_coin_from_storage(output_ref, client).await?; + transaction.inputs.push(Input { + output_ref: output_ref.clone(), + redeemer: vec![], // We will sign the total transaction so this should be empty + }); + } + + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + + let public = Public::from_h256(owner_pubkey); + + log::info!("owner_pubkey:: {:?}", owner_pubkey); + crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + + // insert the proof + input.redeemer = redeemer; + } + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + println!("Node's response to spawn transaction: {:?}", spawn_response); + + // Print new output refs for user to check later + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_kitty_ref = OutputRef { + tx_hash, + index: i as u32, + }; + let new_kitty = output.payload.extract::()?.kitty_basic_data.dna.0; + + print!( + "Created {:?} worth {:?}. ", + hex::encode(new_kitty_ref.encode()), + new_kitty + ); + crate::pretty_print_verifier(&output.verifier); + } + + Ok(()) +} +*/ diff --git a/webservice-wallet-with-inbuilt-key-store/src/amoeba.rs b/webservice-wallet-with-inbuilt-key-store/src/amoeba.rs new file mode 100644 index 000000000..4ed66b282 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/amoeba.rs @@ -0,0 +1,127 @@ +//! Toy off-chain process to create an amoeba and perform mitosis on it + +use crate::rpc::fetch_storage; + +use std::{thread::sleep, time::Duration}; + +use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; +use parity_scale_codec::Encode; +use runtime::{ + amoeba::{AmoebaCreation, AmoebaDetails, AmoebaMitosis}, + OuterVerifier, Transaction, +}; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use tuxedo_core::{ + types::{Input, Output, OutputRef}, + verifier::UpForGrabs, +}; + +pub async fn amoeba_demo(client: &HttpClient) -> anyhow::Result<()> { + // Construct a simple amoeba spawning transaction (no signature required) + let eve = AmoebaDetails { + generation: 0, + four_bytes: *b"eve_", + }; + let spawn_tx = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: vec![Output { + payload: eve.into(), + verifier: UpForGrabs.into(), + }], + checker: AmoebaCreation.into(), + }; + + // Calculate the OutputRef which also serves as the storage location + let eve_ref = OutputRef { + tx_hash: ::hash_of(&spawn_tx.encode()), + index: 0, + }; + + // Send the transaction + let spawn_hex = hex::encode(spawn_tx.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + println!("Node's response to spawn transaction: {:?}", spawn_response); + + // Wait a few seconds to make sure a block has been authored. + sleep(Duration::from_secs(3)); + + // Check that the amoeba is in storage and print its details + let eve_from_storage: AmoebaDetails = fetch_storage::(&eve_ref, client) + .await? + .payload + .extract()?; + println!("Eve Amoeba retrieved from storage: {:?}", eve_from_storage); + + // Create a mitosis transaction on the Eve amoeba + let cain = AmoebaDetails { + generation: 1, + four_bytes: *b"cain", + }; + let able = AmoebaDetails { + generation: 1, + four_bytes: *b"able", + }; + let mitosis_tx = Transaction { + inputs: vec![Input { + output_ref: eve_ref, + redeemer: Vec::new(), + }], + peeks: Vec::new(), + outputs: vec![ + Output { + payload: cain.into(), + verifier: UpForGrabs.into(), + }, + Output { + payload: able.into(), + verifier: UpForGrabs.into(), + }, + ], + checker: AmoebaMitosis.into(), + }; + + // Calculate the two OutputRefs for the daughters + let cain_ref = OutputRef { + tx_hash: ::hash_of(&mitosis_tx.encode()), + index: 0, + }; + let able_ref = OutputRef { + tx_hash: ::hash_of(&mitosis_tx.encode()), + index: 1, + }; + + // Send the mitosis transaction + let mitosis_hex = hex::encode(mitosis_tx.encode()); + let params = rpc_params![mitosis_hex]; + let mitosis_response: Result = + client.request("author_submitExtrinsic", params).await; + println!( + "Node's response to mitosis transaction: {:?}", + mitosis_response + ); + + // Wait a few seconds to make sure a block has been authored. + sleep(Duration::from_secs(3)); + + // Check that the daughters are in storage and print their details + let cain_from_storage: AmoebaDetails = fetch_storage::(&cain_ref, client) + .await? + .payload + .extract()?; + println!( + "Cain Amoeba retrieved from storage: {:?}", + cain_from_storage + ); + let able_from_storage: AmoebaDetails = fetch_storage::(&able_ref, client) + .await? + .payload + .extract()?; + println!( + "Able Amoeba retrieved from storage: {:?}", + able_from_storage + ); + + Ok(()) +} diff --git a/webservice-wallet-with-inbuilt-key-store/src/cli.rs b/webservice-wallet-with-inbuilt-key-store/src/cli.rs new file mode 100644 index 000000000..c5dbd3a48 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/cli.rs @@ -0,0 +1,388 @@ +//! Tuxedo Template Wallet's Command Line Interface. +//! +//! Built with clap's derive macros. + +use std::path::PathBuf; + +use clap::{ArgAction::Append, Args, Parser, Subcommand}; +use sp_core::H256; +use tuxedo_core::types::OutputRef; + +use crate::{h256_from_string, keystore::SHAWN_PUB_KEY, output_ref_from_string, DEFAULT_ENDPOINT}; + +/// The default number of coins to be minted. +pub const DEFAULT_MINT_VALUE: &str = "100"; + +/// Default recipient is SHAWN_KEY and output amount is 0 +pub const DEFAULT_RECIPIENT: &str = "d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 0"; + +/// The default name of the kitty to be created. Show be 4 character long +pub const DEFAULT_KITTY_NAME: &str = " "; + +/// The default gender of the kitty to be created. +pub const DEFAULT_KITTY_GENDER: &str = "female"; +use crate::keystore; + + +fn parse_recipient_coins(s: &str) -> Result<(H256, Vec), &'static str> { + println!("In parse_recipient_coins"); + let parts: Vec<&str> = s.split_whitespace().collect(); + if parts.len() >= 2 { + let mut recipient = h256_from_string(parts[0]); + let coins = parts[1..].iter().filter_map(|&c| c.parse().ok()).collect(); + match recipient { + Ok(r) => { + println!("Recipient: {}", r); + return Ok((r, coins)); + }, + _ => {}, + }; + } + println!("Sending the error value "); + Err("Invalid input format") +} + + + + +/// The wallet's main CLI struct +#[derive(Debug, Parser)] +#[command(about, version)] +pub struct Cli { + #[arg(long, short, default_value_t = DEFAULT_ENDPOINT.to_string())] + /// RPC endpoint of the node that this wallet will connect to. + pub endpoint: String, + + #[arg(long, short)] + /// Path where the wallet data is stored. Default value is platform specific. + pub path: Option, + + #[arg(long, verbatim_doc_comment)] + /// Skip the initial sync that the wallet typically performs with the node. + /// The wallet will use the latest data it had previously synced. + pub no_sync: bool, + + #[arg(long)] + /// A temporary directory will be created to store the configuration and will be deleted at the end of the process. + /// path will be ignored if this is set. + pub tmp: bool, + + #[arg(long, verbatim_doc_comment)] + /// Specify a development wallet instance, using a temporary directory (like --tmp). + /// The keystore will contain the development key Shawn. + pub dev: bool, + + #[command(subcommand)] + pub command: Option, +} + +/// The tasks supported by the wallet +#[derive(Debug, Subcommand)] +pub enum Command { + /// Print the block based on block height. + /// get the block hash ad print the block. + getBlock { + /// Input the blockheight to be retrived. + block_height: Option, // fixme + }, + + /* + /// Demonstrate creating an amoeba and performing mitosis on it. + AmoebaDemo, + */ + /// Mint coins , optionally amount and publicKey of owner can be passed + /// if amount is not passed , 100 coins are minted + /// If publickKey of owner is not passed , then by default SHAWN_PUB_KEY is used. + #[command(verbatim_doc_comment)] + MintCoins(MintCoinArgs), + + /// Verify that a particular coin exists. + /// Show its value and owner from both chain storage and the local database. + #[command(verbatim_doc_comment)] + VerifyCoin { + /// A hex-encoded output reference + #[arg(value_parser = output_ref_from_string)] + output_ref: OutputRef, + }, + + //Some(Command::MintCoins { amount }) => money::mint_coins(&db, &client, &keystore,amount).await, + /// Spend some coins. + /// For now, all outputs in a single transaction go to the same recipient. + // FixMe: #62 + #[command(verbatim_doc_comment)] + SpendCoins(SpendArgs), + + /// Insert a private key into the keystore to later use when signing transactions. + InsertKey { + /// Seed phrase of the key to insert. + seed: String, + // /// Height from which the blockchain should be scanned to sync outputs + // /// belonging to this address. If non is provided, no re-syncing will + // /// happen and this key will be treated like a new key. + // sync_height: Option, + }, + + /// Generate a private key using either some or no password and insert into the keystore. + GenerateKey { + /// Initialize a public/private key pair with a password + password: Option, + }, + + /// Show public information about all the keys in the keystore. + ShowKeys, + + /// Remove a specific key from the keystore. + /// WARNING! This will permanently delete the private key information. + /// Make sure your keys are backed up somewhere safe. + #[command(verbatim_doc_comment)] + RemoveKey { + /// The public key to remove + #[arg(value_parser = h256_from_string)] + pub_key: H256, + }, + + /// For each key tracked by the wallet, shows the sum of all UTXO values owned by that key. + /// This sum is sometimes known as the "balance". + #[command(verbatim_doc_comment)] + ShowBalance, + + /// Show the complete list of UTXOs known to the wallet. + ShowAllOutputs, + + /// Show the latest on-chain timestamp. + ShowTimestamp, + + /// Create Kitty without mom and dad. + CreateKitty(CreateKittyArgs), + + /// Verify that a particular kitty exists. + /// Show its details and owner from both chain storage and the local database. + + #[command(verbatim_doc_comment)] + VerifyKitty { + /// A hex-encoded output reference + #[arg(value_parser = output_ref_from_string)] + output_ref: OutputRef, + }, + + /// Breed Kitties. + #[command(verbatim_doc_comment)] + BreedKitty(BreedKittyArgs), + + /// List Kitty for sale. + ListKittyForSale(ListKittyForSaleArgs), + + /// Delist Kitty from sale. + DelistKittyFromSale(DelistKittyFromSaleArgs), + + /// Update kitty name. + UpdateKittyName(UpdateKittyNameArgs), + + /// Update kitty price. Applicable only to tradable kitties + UpdateKittyPrice(UpdateKittyPriceArgs), + + /// Buy Kitty. + #[command(verbatim_doc_comment)] + BuyKitty(BuyKittyArgs), + + /// Show all kitties key tracked by the wallet. + #[command(verbatim_doc_comment)] + ShowAllKitties, + + /// ShowOwnedKitties. + /// Shows the kitties owned by owner. + #[command(verbatim_doc_comment)] + ShowOwnedKitties(ShowOwnedKittyArgs), +} + +#[derive(Debug, Args)] +pub struct MintCoinArgs { + /// Pass the amount to be minted. + #[arg(long, short, verbatim_doc_comment, action = Append,default_value = DEFAULT_MINT_VALUE)] + pub amount: u128, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +/* +// Old implementation +#[derive(Debug, Args)] +pub struct SpendArgs { + /// An input to be consumed by this transaction. This argument may be specified multiple times. + /// They must all be coins. + #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] + pub input: Vec, + + // /// All inputs to the transaction will be from this same sender. + // /// When not specified, inputs from any owner are chosen indiscriminantly + // #[arg(long, short, value_parser = h256_from_string)] + // sender: Option, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the recipient. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub recipient: H256, + + // The `action = Append` allows us to accept the same value multiple times. + /// An output amount. For the transaction to be valid, the outputs must add up to less than the sum of the inputs. + /// The wallet will not enforce this and will gladly send an invalid which will then be rejected by the node. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub output_amount: Vec, +} +*/ + +#[derive(Debug, Args,Clone)] +pub struct SpendArgs { + /// An input to be consumed by this transaction. This argument may be specified multiple times. + /// They must all be coins. + #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] + pub input: Vec, + + /// Variable number of recipients and their associated coins. + /// For example, "--recipients 0x1234 1 2 --recipients 0x5678 3 4 6" + // #[arg(long, short, verbatim_doc_comment, value_parser = parse_recipient_coins, action = Append)] + // pub recipients: Vec<(H256, Vec)>, + #[arg(long, short, verbatim_doc_comment, value_parser = parse_recipient_coins, action = Append, + default_value = DEFAULT_RECIPIENT)] + pub recipients: Vec<(H256, Vec)>, +} + +#[derive(Debug, Args)] +pub struct CreateKittyArgs { + + /// Pass the name of the kitty to be minted. + /// If kitty name is not passed ,name is choosen randomly from predefine name vector. + #[arg(long, short, action = Append, default_value = DEFAULT_KITTY_NAME)] + pub kitty_name: String, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct ShowOwnedKittyArgs { + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct BreedKittyArgs { + /// Name of Mom to be used for breeding . + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub mom_name: String, + + /// Name of Dad to be used for breeding . + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub dad_name: String, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct UpdateKittyNameArgs { + /// Current name of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub current_name: String, + + /// New name of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub new_name: String, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct UpdateKittyPriceArgs { + /// Current name of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub current_name: String, + + /// Price of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub price: u128, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct BuyKittyArgs { + /// An input to be consumed by this transaction. This argument may be specified multiple times. + /// They must all be coins. + #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] + pub input: Vec, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the seller of tradable kitty. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub seller: H256, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner of tradable kitty. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, + + /// Name of kitty to be bought. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub kitty_name: String, + + // The `action = Append` allows us to accept the same value multiple times. + /// An output amount. For the transaction to be valid, the outputs must add up to less than the sum of the inputs. + /// The wallet will not enforce this and will gladly send an invalid which will then be rejected by the node. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub output_amount: Vec, +} + +#[derive(Debug, Args)] +pub struct ListKittyForSaleArgs { + /// Pass the name of the kitty to be listed for sale. + #[arg(long, short, verbatim_doc_comment, action = Append, default_value = DEFAULT_KITTY_NAME)] + pub name: String, + + /// Price of Kitty. + #[arg(long, short, verbatim_doc_comment, action = Append)] + pub price: u128, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} + +#[derive(Debug, Args)] +pub struct DelistKittyFromSaleArgs { + /// Pass the name of the kitty to be delisted or removed from the sale . + #[arg(long, short, verbatim_doc_comment, action = Append, default_value = DEFAULT_KITTY_NAME)] + pub name: String, + + // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html + // shows how to specify a custom parsing function + /// Hex encoded address (sr25519 pubkey) of the owner. + #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] + pub owner: H256, +} diff --git a/webservice-wallet-with-inbuilt-key-store/src/keystore.rs b/webservice-wallet-with-inbuilt-key-store/src/keystore.rs new file mode 100644 index 000000000..34e6bb8d9 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/keystore.rs @@ -0,0 +1,138 @@ +//! Wallet's local keystore. +//! +//! This is a thin wrapper around sc-cli for use in tuxedo wallets + +use anyhow::anyhow; +use parity_scale_codec::Encode; +use sc_keystore::LocalKeystore; +use sp_core::{ + crypto::Pair as PairT, + sr25519::{Pair, Public}, + H256, +}; +use sp_keystore::Keystore; +use sp_runtime::KeyTypeId; +use std::path::Path; +use crate::get_local_keystore; +/// A KeyTypeId to use in the keystore for Tuxedo transactions. We'll use this everywhere +/// until it becomes clear that there is a reason to use multiple of them +const KEY_TYPE: KeyTypeId = KeyTypeId(*b"_tux"); + +/// A default seed phrase for signing inputs when none is provided +/// Corresponds to the default pubkey. +pub const SHAWN_PHRASE: &str = + "news slush supreme milk chapter athlete soap sausage put clutch what kitten"; + +/// The public key corresponding to the default seed above. +pub const SHAWN_PUB_KEY: &str = "d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"; + +/// Insert the example "Shawn" key into the keystore for the current session only. +pub fn insert_development_key_for_this_session(keystore: &LocalKeystore) -> anyhow::Result<()> { + keystore.sr25519_generate_new(KEY_TYPE, Some(SHAWN_PHRASE))?; + Ok(()) +} + +/// Sign a given message with the private key that corresponds to the given public key. +/// +/// Returns an error if the keystore itself errors, or does not contain the requested key. +pub fn sign_with( + keystore: &LocalKeystore, + public: &Public, + message: &[u8], +) -> anyhow::Result> { + + let sig = keystore + .sr25519_sign(KEY_TYPE, public, message)? + .ok_or(anyhow!("Key doesn't exist in keystore"))?; + + Ok(sig.encode()) +} + +/// Insert the private key associated with the given seed into the keystore for later use. +pub fn insert_key(keystore: &LocalKeystore, seed: &str) -> anyhow::Result<()> { + // We need to provide a public key to the keystore manually, so let's calculate it. + let public_key = Pair::from_phrase(seed, None)?.0.public(); + println!("The generated public key is {:?}", public_key); + keystore + .insert(KEY_TYPE, seed, public_key.as_ref()) + .map_err(|()| anyhow!("Error inserting key"))?; + Ok(()) +} + +/// Generate a new key from system entropy and insert it into the keystore, optionally +/// protected by a password. +/// +/// TODO there is no password support when using keys later when signing. +/* +pub fn generate_key(keystore: &LocalKeystore, password: Option) -> anyhow::Result<()> { + + let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); + println!("Generated public key is {:?}", pair.public()); + println!("Generated Phrase is {}", phrase); + keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; + Ok(()) +} + + +pub async fn generate_key(password: Option) -> anyhow::Result<(String, String)> { + let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); + let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); + println!("Generated public key is {:?}", pair.public()); + println!("Generated Phrase is {}", phrase); + keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; + insert_key(&keystore,&phrase.as_str()); + + get_keys(&keystore)?.for_each(|pubkey| { + println!("key: 0x{}", hex::encode(pubkey)); + }); + + Ok((pair.public().to_string(), phrase)) +} + +pub fn get_keys(keystore: &LocalKeystore) -> anyhow::Result>> { + Ok(keystore.keys(KEY_TYPE)?.into_iter()) +} +*/ +pub async fn generate_key(password: Option) -> anyhow::Result<(String, String)> { + let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); + let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); + println!("Generated public key is {:?}", pair.public()); + println!("Generated Phrase is {}", phrase); + + // Insert the generated key pair into the keystore + + keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; + insert_key(&keystore,&phrase.as_str()); + get_keys().await?.for_each(|pubkey| { + println!("key: 0x{}", hex::encode(pubkey)); + }); + + let public_key_hex = hex::encode(pair.public()); + + Ok((public_key_hex.to_string(), phrase)) +} + + +/// Check whether a specific key is in the keystore +pub fn has_key(keystore: &LocalKeystore, pubkey: &H256) -> bool { + keystore.has_keys(&[(pubkey.encode(), KEY_TYPE)]) +} + +pub async fn get_keys() -> anyhow::Result>> { + let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); + + Ok(keystore.keys(KEY_TYPE)?.into_iter()) +} + + +/// Caution. Removes key from keystore. Call with care. +pub fn remove_key(keystore_path: &Path, pub_key: &H256) -> anyhow::Result<()> { + // The keystore doesn't provide an API for removing keys, so we + // remove them from the filesystem directly + let filename = format!("{}{}", hex::encode(KEY_TYPE.0), hex::encode(pub_key.0)); + let path = keystore_path.join(filename); + + std::fs::remove_file(path)?; + + Ok(()) +} diff --git a/webservice-wallet-with-inbuilt-key-store/src/kitty.rs b/webservice-wallet-with-inbuilt-key-store/src/kitty.rs new file mode 100644 index 000000000..b07b38888 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/kitty.rs @@ -0,0 +1,1045 @@ +use crate::money::get_coin_from_storage; +use crate::rpc::fetch_storage; +use crate::sync; + +//use crate::cli::BreedArgs; +use tuxedo_core::{ + dynamic_typing::UtxoData, + types::{Input, Output, OutputRef}, + verifier::Sr25519Signature, +}; + +use anyhow::anyhow; +use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; +use parity_scale_codec::Encode; +use rand::distributions::Alphanumeric; +use rand::Rng; +use sc_keystore::LocalKeystore; +use sled::Db; +use sp_core::sr25519::Public; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use crate::get_local_keystore; + +use runtime::{ + kitties::{ + DadKittyStatus, FreeKittyConstraintChecker, KittyDNA, KittyData, KittyHelpers, + MomKittyStatus, Parent, + }, + money::{Coin, MoneyConstraintChecker}, + tradable_kitties::{TradableKittyConstraintChecker, TradableKittyData}, + OuterVerifier, Transaction, +}; + +use crate::cli::DEFAULT_KITTY_NAME; + +use crate::cli::{ + BreedKittyArgs, BuyKittyArgs, CreateKittyArgs, DelistKittyFromSaleArgs, ListKittyForSaleArgs, + UpdateKittyNameArgs, UpdateKittyPriceArgs, +}; +use parity_scale_codec::Decode; + +static MALE_KITTY_NAMES: [&str; 50] = [ + "Whis", "Purr", "Feli", "Leo", "Maxi", "Osca", "Simb", "Char", "Milo", "Tige", "Jasp", + "Smokey", "Oliv", "Loki", "Boot", "Gizm", "Rock", "Budd", "Shad", "Zeus", "Tedd", "Samm", + "Rust", "Tom", "Casp", "Blue", "Coop", "Coco", "Mitt", "Bent", "Geor", "Luck", "Rome", "Moch", + "Muff", "Ches", "Whis", "Appl", "Hunt", "Toby", "Finn", "Frod", "Sale", "Kobe", "Dext", "Jinx", + "Mick", "Pump", "Thor", "Sunn", +]; + +// Female kitty names +static FEMALE_KITTY_NAMES: [&str; 50] = [ + "Luna", "Mist", "Cleo", "Bell", "Lucy", "Nala", "Zoe", "Dais", "Lily", "Mia", "Chlo", "Stel", + "Coco", "Will", "Ruby", "Grac", "Sash", "Moll", "Lola", "Zara", "Mist", "Ange", "Rosi", "Soph", + "Zeld", "Layl", "Ambe", "Prin", "Cind", "Moch", "Zara", "Dais", "Cinn", "Oliv", "Peac", "Pixi", + "Harl", "Mimi", "Pipe", "Whis", "Cher", "Fion", "Kiki", "Suga", "Peac", "Ange", "Mapl", "Zigg", + "Lily", "Nova", +]; + +pub fn generate_random_string(length: usize) -> String { + let rng = rand::thread_rng(); + let random_string: String = rng + .sample_iter(&Alphanumeric) + .take(length) + .map(char::from) + .collect(); + random_string +} + +#[derive(Encode, Decode, Debug, Clone, PartialEq)] +pub enum Gender { + Male, + Female, +} + +fn create_tx_input_based_on_td_kittyName( + db: &Db, + name: String, +) -> anyhow::Result<(TradableKittyData, Input)> { + let tradable_kitty = + crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, name.clone()); + let Some((tradable_kitty_info, out_ref)) = tradable_kitty.unwrap() else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let input = Input { + output_ref: out_ref, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + Ok((tradable_kitty_info, input)) +} + +fn create_tx_input_based_on_kitty_name<'a>( + db: &Db, + name: String, +) -> anyhow::Result<(KittyData, Input)> { + //let kitty = crate::sync::get_kitty_from_local_db_based_on_name(&db, name.clone()); + //let name_extractor_kitty_data = move |kitty: &'a KittyData| -> &'a [u8; 4] { &kitty.name }; + + let kitty = crate::sync::get_kitty_from_local_db_based_on_name(&db, name.clone()); + let Some((kitty_info, out_ref)) = kitty.unwrap() else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let input = Input { + output_ref: out_ref, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + Ok((kitty_info, input)) +} + +fn create_tx_input_based_on_td_kitty_name<'a>( + db: &Db, + name: String, +) -> anyhow::Result<(TradableKittyData, Input)> { + let kitty = crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, name.clone()); + let Some((kitty_info, out_ref)) = kitty.unwrap() else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let input = Input { + output_ref: out_ref, + redeemer: vec![], // We will sign the total transaction so this should be empty + }; + Ok((kitty_info, input)) +} + +fn print_new_output(transaction: &Transaction) -> anyhow::Result<()> { + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_ref = OutputRef { + tx_hash, + index: i as u32, + }; + match output.payload.type_id { + KittyData::TYPE_ID => { + let new_kitty = output.payload.extract::()?.dna.0; + print!( + "Created {:?} basic Kitty {:?}. ", + hex::encode(new_ref.encode()), + new_kitty + ); + } + TradableKittyData::TYPE_ID => { + let new_kitty = output + .payload + .extract::()? + .kitty_basic_data + .dna + .0; + print!( + "Created {:?} TradableKitty {:?}. ", + hex::encode(new_ref.encode()), + new_kitty + ); + } + Coin::<0>::TYPE_ID => { + let amount = output.payload.extract::>()?.0; + print!( + "Created {:?} worth {amount}. ", + hex::encode(new_ref.encode()) + ); + } + + _ => continue, + } + crate::pretty_print_verifier(&output.verifier); + } + Ok(()) +} + +async fn send_signed_tx( + transaction: &Transaction, + client: &HttpClient, +) -> anyhow::Result<()> { + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + + println!("Node's response to spawn transaction: {:?}", spawn_response); + match spawn_response { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), + } +} + +async fn send_tx( + transaction: &mut Transaction, + client: &HttpClient, + local_keystore: Option<&LocalKeystore>, +) -> anyhow::Result<()> { + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + + let _ = match local_keystore { + Some(ks) => { + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(ks, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + // insert the proof + input.redeemer = redeemer; + } + } + None => {} + }; + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + + println!("Node's response to spawn transaction: {:?}", spawn_response); + match spawn_response { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), + } +} + + + +fn gen_random_gender() -> Gender { + // Create a local random number generator + let mut rng = rand::thread_rng(); + + // Generate a random number between 0 and 1 + let random_number = rng.gen_range(0..=1); + + // We Use the random number to determine the gender + match random_number { + 0 => Gender::Male, + _ => Gender::Female, + } +} + +fn get_random_kitty_name_from_pre_defined_vec(g: Gender, name_slice: &mut [u8; 4]) { + // Create a local random number generator + let mut rng = rand::thread_rng(); + + // Generate a random number between 0 and 1 + let random_number = rng.gen_range(0..=50); + + // We Use the random number to determine the gender + + let name = match g { + Gender::Male => MALE_KITTY_NAMES[random_number], + Gender::Female => FEMALE_KITTY_NAMES[random_number], + }; + //name.to_string() + name_slice.copy_from_slice(name.clone().as_bytes()); +} + +fn validate_kitty_name_from_db( + db: &Db, + owner_pubkey: &H256, + name: String, + name_slice: &mut [u8; 4], +) -> anyhow::Result<()> { + if name.len() != 4 { + return Err(anyhow!( + "Please input a name of length 4 characters. Current length: {}", + name.len() + )); + } + + match crate::sync::is_kitty_name_duplicate(&db, name.clone(), &owner_pubkey) { + Ok(Some(true)) => { + println!("Kitty name is duplicate , select another name"); + return Err(anyhow!( + "Please input a non-duplicate name of length 4 characters" + )); + } + _ => {} + }; + name_slice.copy_from_slice(name.clone().as_bytes()); + + return Ok(()); +} + +fn create_new_family( + new_mom: &mut KittyData, + new_dad: &mut KittyData, + new_child: &mut KittyData, +) -> anyhow::Result<()> { + new_mom.parent = Parent::Mom(MomKittyStatus::RearinToGo); + if new_mom.num_breedings >= 2 { + new_mom.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); + } + new_mom.num_breedings = new_mom.num_breedings.checked_add(1).expect("REASON"); + new_mom.free_breedings = new_mom.free_breedings.checked_sub(1).expect("REASON"); + + new_dad.parent = Parent::Dad(DadKittyStatus::RearinToGo); + if new_dad.num_breedings >= 2 { + new_dad.parent = Parent::Dad(DadKittyStatus::Tired); + } + + new_dad.num_breedings = new_dad.num_breedings.checked_add(1).expect("REASON"); + new_dad.free_breedings = new_dad.free_breedings.checked_sub(1).expect("REASON"); + + let child_gender = match gen_random_gender() { + Gender::Male => Parent::dad(), + Gender::Female => Parent::mom(), + }; + + let child = KittyData { + parent: child_gender, + free_breedings: 2, + name: *b"tomy", // Name of child kitty need to be generated in better way + dna: KittyDNA(BlakeTwo256::hash_of(&( + new_mom.dna.clone(), + new_dad.dna.clone(), + new_mom.num_breedings, + new_dad.num_breedings, + ))), + num_breedings: 0, + // price: None, + // is_available_for_sale: false, + }; + *new_child = child; + Ok(()) +} + +pub async fn create_kitty( + db: &Db, + client: &HttpClient, + args: CreateKittyArgs, +) -> anyhow::Result> { + let mut kitty_name = [0; 4]; + + let g = gen_random_gender(); + let gender = match g { + Gender::Male => Parent::dad(), + Gender::Female => Parent::mom(), + }; + + if args.kitty_name.clone() == DEFAULT_KITTY_NAME.to_string() { + get_random_kitty_name_from_pre_defined_vec(g, &mut kitty_name); + } else { + validate_kitty_name_from_db(&db, &args.owner, args.kitty_name.clone(), &mut kitty_name)?; + } + // Generate a random string of length 5 + let random_string = generate_random_string(5) + args.kitty_name.as_str(); + let dna_preimage: &[u8] = random_string.as_bytes(); + + let child_kitty = KittyData { + parent: gender, + dna: KittyDNA(BlakeTwo256::hash(dna_preimage)), + name: kitty_name, // Default value for now, as the provided code does not specify a value + ..Default::default() + }; + + // Create the Output + let output = Output { + payload: child_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + // Create the Transaction + let mut transaction = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: vec![output], + checker: FreeKittyConstraintChecker::Create.into(), + }; + + send_tx(&mut transaction, &client, None).await?; + print_new_output(&transaction)?; + Ok(Some(child_kitty)) +} + +pub async fn list_kitty_for_sale( + signed_transaction: &Transaction, + db: &Db, + client: &HttpClient, +) -> anyhow::Result> { + let tradable_kitty: TradableKittyData = signed_transaction.outputs[0].payload + .extract::() + .map_err(|_| anyhow!("Invalid output, Expected TradableKittyData"))?; + log::info!("The list_kitty_for_sale args : {:?}", signed_transaction); + send_signed_tx(&signed_transaction, &client).await?; + print_new_output(&signed_transaction)?; + Ok(Some(tradable_kitty)) +} + +pub async fn delist_kitty_from_sale( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: DelistKittyFromSaleArgs, +) -> anyhow::Result> { + log::info!("The list_kitty_for_sale args : {:?}", args); + let Ok((td_kitty_info, input)) = create_tx_input_based_on_td_kittyName(db, args.name.clone()) + else { + return Err(anyhow!("No kitty with name {} in localdb", args.name)); + }; + + let inputs: Vec = vec![input]; + let basic_kitty = td_kitty_info.kitty_basic_data; + + // Create the Output + let output = Output { + payload: basic_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::DelistKittiesFromSale.into(), + }; + + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + Ok(Some(basic_kitty)) +} + +pub async fn breed_kitty( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: BreedKittyArgs, +) -> anyhow::Result>> { + log::info!("The Breed kittyArgs are:: {:?}", args); + + let Ok((mom_kitty_info, mom_ref)) = + create_tx_input_based_on_kitty_name(db, args.mom_name.clone()) + else { + return Err(anyhow!("No kitty with name {} in localdb", args.mom_name)); + }; + + let Ok((dad_kitty_info, dad_ref)) = + create_tx_input_based_on_kitty_name(db, args.dad_name.clone()) + else { + return Err(anyhow!("No kitty with name {} in localdb", args.dad_name)); + }; + + let inputs: Vec = vec![mom_ref, dad_ref]; + + let mut new_mom: KittyData = mom_kitty_info; + + let mut new_dad = dad_kitty_info; + + let mut child: KittyData = Default::default(); + + create_new_family(&mut new_mom, &mut new_dad, &mut child)?; + // Create the Output mom + println!("New mom Dna = {:?}", new_mom.dna); + println!("New Dad Dna = {:?}", new_dad.dna); + println!("Child Dna = {:?}", child.dna); + + let output_mom = Output { + payload: new_mom.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + // Create the Output dada + let output_dad = Output { + payload: new_dad.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let output_child = Output { + payload: child.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let new_family = Box::new(vec![output_mom, output_dad, output_child]); + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: (&[ + new_family[0].clone(), + new_family[1].clone(), + new_family[2].clone(), + ]) + .to_vec(), + checker: FreeKittyConstraintChecker::Breed.into(), + }; + + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + Ok(vec![ + new_mom.clone(), + new_dad.clone(), + child.clone(), + ].into()) +} + +pub async fn buy_kitty( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: BuyKittyArgs, +) -> anyhow::Result> { + log::info!("The Buy kittyArgs are:: {:?}", args); + + let Ok((kitty_info, kitty_ref)) = + create_tx_input_based_on_td_kittyName(db, args.kitty_name.clone()) + else { + return Err(anyhow!("No kitty with name {} in localdb", args.kitty_name)); + }; + + let inputs: Vec = vec![kitty_ref]; + // Create the KittyData + let mut output_kitty = kitty_info.clone(); + + let output = Output { + payload: output_kitty.into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::Buy.into(), + }; + + // Construct each output and then push to the transactions for Money + let mut total_output_amount = 0; + for amount in &args.output_amount { + let output = Output { + payload: Coin::<0>::new(*amount).into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.seller, + }), + }; + total_output_amount += amount; + transaction.outputs.push(output); + if total_output_amount >= kitty_info.price.into() { + break; + } + } + + let mut total_input_amount = 0; + let mut all_input_refs = args.input; + for output_ref in &all_input_refs { + let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( + "user-specified output ref not found in local database" + ))?; + total_input_amount += amount; + } + + if total_input_amount < total_output_amount { + match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { + Some(more_inputs) => { + all_input_refs.extend(more_inputs); + } + None => Err(anyhow!( + "Not enough value in database to construct transaction" + ))?, + } + } + + // Make sure each input decodes and is still present in the node's storage, + // and then push to transaction. + for output_ref in &all_input_refs { + get_coin_from_storage(output_ref, client).await?; + transaction.inputs.push(Input { + output_ref: output_ref.clone(), + redeemer: vec![], // We will sign the total transaction so this should be empty + }); + } + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + + Ok(Some(kitty_info)) +} + +pub async fn update_td_kitty_name( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: UpdateKittyNameArgs, +) -> anyhow::Result> { + log::info!("The set_kitty_property are:: {:?}", args); + + let Ok((td_kitty_info, td_kitty_ref)) = create_tx_input_based_on_td_kitty_name(db, args.current_name.clone()) else { + return Err(anyhow!("No kitty with name {} in localdb",args.current_name)); + }; + + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(args.new_name.as_bytes()); + &array + }; + + let inputs: Vec = vec![td_kitty_ref.clone()]; + let mut updated_kitty: TradableKittyData = td_kitty_info; + updated_kitty.kitty_basic_data.name = *kitty_name; + let output = Output { + payload: updated_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::UpdateKittiesName.into(), + }; + + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + Ok(Some(updated_kitty)) +} + +pub async fn update_kitty_name( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: UpdateKittyNameArgs, +) -> anyhow::Result> { + log::info!("The set_kitty_property are:: {:?}", args); + + let Ok((input_kitty_info, input_kitty_ref)) = create_tx_input_based_on_kitty_name(db,args.current_name.clone()) else { + return Err(anyhow!("No kitty with name {} in localdb",args.current_name)); + }; + + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(args.new_name.as_bytes()); + &array + }; + + let inputs: Vec = vec![input_kitty_ref]; + + let mut updated_kitty: KittyData = input_kitty_info.clone(); + updated_kitty.name = *kitty_name; + let output = Output { + payload: updated_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: FreeKittyConstraintChecker::UpdateKittiesName.into(), + }; + + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + Ok(Some(updated_kitty)) +} + +pub async fn update_kitty_price( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: UpdateKittyPriceArgs, +) -> anyhow::Result> { + log::info!("The set_kitty_property are:: {:?}", args); + let Ok((input_kitty_info, input_kitty_ref)) = + create_tx_input_based_on_td_kitty_name(db, args.current_name.clone()) + else { + return Err(anyhow!( + "No kitty with name {} in localdb", + args.current_name + )); + }; + + let inputs: Vec = vec![input_kitty_ref]; + let mut updated_kitty: TradableKittyData = input_kitty_info; + updated_kitty.price = args.price; + let output = Output { + payload: updated_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::UpdateKittiesPrice.into(), + }; + + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + Ok(Some(updated_kitty)) +} + +/// Apply a transaction to the local database, storing the new coins. +pub(crate) fn apply_transaction( + db: &Db, + tx_hash: ::Output, + index: u32, + output: &Output, +) -> anyhow::Result<()> { + let kitty_detail: KittyData = output.payload.extract()?; + + let output_ref = OutputRef { tx_hash, index }; + println!("in kitty:apply_transaction output_ref = {:?}", output_ref); + match output.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + // Add it to the global unspent_outputs table + crate::sync::add_fresh_kitty_to_db(db, &output_ref, &owner_pubkey, &kitty_detail) + } + _ => Err(anyhow!("{:?}", ())), + } +} + +pub(crate) fn convert_kitty_name_string(kitty: &KittyData) -> Option { + if let Ok(kitty_name) = std::str::from_utf8(&kitty.name) { + return Some(kitty_name.to_string()); + } else { + println!("Invalid UTF-8 data in the Kittyname"); + } + None +} + +/// Given an output ref, fetch the details about this coin from the node's +/// storage. +// Need to revisit this for tradableKitty +pub async fn get_kitty_from_storage( + output_ref: &OutputRef, + client: &HttpClient, +) -> anyhow::Result<(KittyData, OuterVerifier)> { + let utxo = fetch_storage::(output_ref, client).await?; + + let kitty_in_storage: KittyData = utxo.payload.extract()?; + + Ok((kitty_in_storage, utxo.verifier)) +} + +/// Apply a transaction to the local database, storing the new coins. +pub(crate) fn apply_td_transaction( + db: &Db, + tx_hash: ::Output, + index: u32, + output: &Output, +) -> anyhow::Result<()> { + let tradable_kitty_detail: TradableKittyData = output.payload.extract()?; + + let output_ref = OutputRef { tx_hash, index }; + println!( + "in Tradable kitty:apply_transaction output_ref = {:?}", + output_ref + ); + match output.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + // Add it to the global unspent_outputs table + crate::sync::add_fresh_tradable_kitty_to_db( + db, + &output_ref, + &owner_pubkey, + &tradable_kitty_detail, + ) + } + _ => Err(anyhow!("{:?}", ())), + } +} + +pub(crate) fn convert_td_kitty_name_string(tradable_kitty: &TradableKittyData) -> Option { + if let Ok(kitty_name) = std::str::from_utf8(&tradable_kitty.kitty_basic_data.name) { + return Some(kitty_name.to_string()); + } else { + println!("Invalid UTF-8 data in the Kittyname"); + } + None +} + +/// Given an output ref, fetch the details about this coin from the node's +/// storage. +pub async fn get_td_kitty_from_storage( + output_ref: &OutputRef, + client: &HttpClient, +) -> anyhow::Result<(TradableKittyData, OuterVerifier)> { + let utxo = fetch_storage::(output_ref, client).await?; + let kitty_in_storage: TradableKittyData = utxo.payload.extract()?; + + Ok((kitty_in_storage, utxo.verifier)) +} + +////////////////////////////////////////////////////////////////// +// Below is for web server handling +////////////////////////////////////////////////////////////////// + + +async fn send_tx_with_signed_inputs( + transaction: &mut Transaction, + client: &HttpClient, + local_keystore: Option<&LocalKeystore>, + signed_inputs: Vec, +) -> anyhow::Result<()> { + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + transaction.inputs = signed_inputs; + + let _ = match local_keystore { + Some(ks) => { + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(ks, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + // insert the proof + input.redeemer = redeemer; + } + } + None => {} + }; + + // Encode the transaction + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let spawn_response: Result = client.request("author_submitExtrinsic", params).await; + + println!("Node's response to spawn transaction: {:?}", spawn_response); + match spawn_response { + Ok(_) => Ok(()), + Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), + } +} + +pub async fn create_txn_for_list_kitty( + db: &Db, + name: String, + price:u128, + publick_key:H256, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, name.clone()) else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let inputs: Vec = vec![input]; + + let tradable_kitty = TradableKittyData { + kitty_basic_data: kitty_info, + price: price, + }; + + // Create the Output + let output = Output { + payload: tradable_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: publick_key, + }), + }; + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::ListKittiesForSale.into(), + }; + + Ok(Some(transaction)) +} + +pub async fn create_inpututxo_list( + transaction: &mut Transaction, + client: &HttpClient, +) -> anyhow::Result>>> { + let mut utxo_list: Vec> = Vec::new(); + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + utxo_list.push(utxo); + } + Ok(Some(utxo_list)) +} + +// Below function will not be used in real usage , it is just for test purpose + +pub async fn create_signed_txn_for_list_kitty( + db: &Db, + name: String, + price:u128, + publick_key:H256, + client: &HttpClient, +) -> anyhow::Result> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, name.clone()) else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let inputs: Vec = vec![input]; + + let tradable_kitty = TradableKittyData { + kitty_basic_data: kitty_info, + price: price, + }; + + // Create the Output + let output = Output { + payload: tradable_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: publick_key, + }), + }; + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::ListKittiesForSale.into(), + }; + + let stripped_encoded_transaction = transaction.clone().encode(); + let local_keystore = get_local_keystore().await.expect("Key store error"); + + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(&local_keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + // insert the proof + input.redeemer = redeemer; + } + + Ok(Some(transaction)) +} + + +/* +pub async fn create_inputs_for_list_kitty( + db: &Db, + name: String, + publick_key:H256, +) -> anyhow::Result>> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, name.clone()) else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let inputs: Vec = vec![input]; + + Ok(Some(inputs)) +} +pub async fn list_kitty_for_sale_based_on_inputs( + db: &Db, + name: String, + price: u128, + inputs: Vec, + public_key:H256, +) -> anyhow::Result>> { + // Need to filter based on name and publick key. + // Ideally need to filter based on DNA. + let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, name.clone()) else { + return Err(anyhow!("No kitty with name {} in localdb", name)); + }; + + let inputs: Vec = vec![input]; + + + Ok(Some(inputs)) +} +pub async fn list_kitty_for_sale( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: ListKittyForSaleArgs, +) -> anyhow::Result> { + log::info!("The list_kitty_for_sale args : {:?}", args); + + let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, args.name.clone()) else { + return Err(anyhow!("No kitty with name {} in localdb", args.name)); + }; + + let inputs: Vec = vec![input]; + + let tradable_kitty = TradableKittyData { + kitty_basic_data: kitty_info, + price: args.price, + }; + + // Create the Output + let output = Output { + payload: tradable_kitty.clone().into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + }; + + // Create the Transaction + let mut transaction = Transaction { + inputs: inputs, + peeks: Vec::new(), + outputs: vec![output], + checker: TradableKittyConstraintChecker::ListKittiesForSale.into(), + }; + send_tx(&mut transaction, &client, Some(&keystore)).await?; + print_new_output(&transaction)?; + Ok(Some(tradable_kitty)) +} +*/ \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/main.rs b/webservice-wallet-with-inbuilt-key-store/src/main.rs new file mode 100644 index 000000000..e245b62f5 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/main.rs @@ -0,0 +1,311 @@ +//! A simple CLI wallet. For now it is a toy just to start testing things out. + +use clap::Parser; +use jsonrpsee::http_client::HttpClientBuilder; +use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; +use parity_scale_codec::{Decode, Encode}; +use runtime::OuterVerifier; +use std::path::PathBuf; +use sled::Db; +//use crate::kitty::{create_kitty,list_kitty_for_sale}; +use tuxedo_core::{types::OutputRef, verifier::*}; +use sp_core::H256; +use sc_keystore::LocalKeystore; + +//mod amoeba; +mod TradableKitties; +mod cli; +mod req_resp; +mod keystore; +mod kitty; +mod money; +mod output_filter; +mod rpc; +mod sync; +mod timestamp; + +use cli::{Cli, Command}; +use crate::cli::MintCoinArgs; +use crate::cli::CreateKittyArgs; + +//use moneyServicehandler::{MintCoinsRequest, MintCoinsResponse}; +mod serviceHandlers { + + pub mod blockHandler { + pub mod blockServicehandler; + } + + pub mod moneyHandler { + pub mod moneyServicehandler; + } + + pub mod kittyHandler { + pub mod kittyServicehandler; + } + + pub mod keyHandler { + pub mod keyServicehandler; + } +} + +use serviceHandlers::keyHandler::keyServicehandler::{ + GenerateKeyRequest, GenerateKeyResponse, generate_key, + GetKeyResponse, get_keys, +}; + +use serviceHandlers::moneyHandler::moneyServicehandler::{MintCoinsRequest, MintCoinsResponse, mint_coins}; + +use serviceHandlers::kittyHandler::kittyServicehandler::{ + CreateKittyRequest, CreateKittyResponse, create_kitty, + GetTxnAndUtxoListForListKittyForSaleResponse, get_txn_and_inpututxolist_for_list_kitty_for_sale, + debug_get_signed_txn_for_list_kitty_for_sale,// added just for debug + ListKittyForSaleRequest, ListKittyForSaleResponse, list_kitty_for_sale, + DelistKittyFromSaleRequest, DelistKittyFromSaleResponse, delist_kitty_from_sale, + UpdateKittyNameRequest, UpdateKittyNameResponse, update_kitty_name, + UpdateTdKittyNameRequest, UpdateTdKittyNameResponse, update_td_kitty_name, + UpdateTdKittyPriceRequest, UpdateTdKittyPriceResponse, update_td_kitty_price, + BuyTdKittyRequest, BuyTdKittyResponse, buy_kitty, + BreedKittyRequest, BreedKittyResponse, breed_kitty, +}; + +use serviceHandlers::blockHandler::blockServicehandler::{ BlockResponse, get_block}; + +/// The default RPC endpoint for the wallet to connect to +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; +use crate::{ keystore::SHAWN_PUB_KEY}; + + +use axum::{http::StatusCode, response::IntoResponse, routing::{get, post, put},Json, Router}; +use axum::{response::Html,}; +use std::net::SocketAddr; +use tower_http::cors::{Any, CorsLayer}; +use runtime::{opaque::Block as OpaqueBlock, Block}; +use anyhow::bail; +use serde::{Deserialize, Serialize}; + + +#[tokio::main] +async fn main() { + let cors = CorsLayer::new().allow_origin(Any); + + let app = Router::new() + .route("/get-block", get(get_block)) + .route("/mint-coins", post(mint_coins)) + .route("/create-kitty", post(create_kitty)) + .route("/get-txn-and-inpututxolist-for-listkitty-forsale", get(get_txn_and_inpututxolist_for_list_kitty_for_sale)) + .route("/debug-get-signed-for-listkitty", get(debug_get_signed_txn_for_list_kitty_for_sale)) + .route("/listkitty-for-sale", post(list_kitty_for_sale)) + //.route("/spend-coins", put(spend_coins)) + .layer(cors); + + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + println!("In the main"); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + + +async fn original_get_db() -> anyhow::Result { + + let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); + + let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; + + // Read node's genesis block. + let node_genesis_hash = rpc::node_get_block_hash(0, &client) + .await? + .expect("node should be able to return some genesis hash"); + let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) + .await? + .expect("node should be able to return some genesis block"); + log::debug!("Node's Genesis block::{:?}", node_genesis_hash); + + // Open the local database + let data_path = temp_dir(); + let db_path = data_path.join("wallet_database"); + 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}"); + + // No filter at-all + let keystore_filter = |_v: &OuterVerifier| -> bool { + true + }; + + if !sled::Db::was_recovered(&db) { + println!("!sled::Db::was_recovered(&db) called "); + // This is a new instance, so we need to apply the genesis block to the database. + async { + sync::apply_block(&db, node_genesis_block, node_genesis_hash, &keystore_filter) + .await; + }; + } + + sync::synchronize(&db, &client, &keystore_filter).await?; + + log::info!( + "Wallet database synchronized with node to height {:?}", + sync::height(&db)?.expect("We just synced, so there is a height available") + ); + + if let Err(err) = db.flush() { + println!("Error flushing Sled database: {}", err); + } + + Ok(db) +} + + +async fn get_db() -> anyhow::Result { + let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; + let data_path = temp_dir(); + let db_path = data_path.join("wallet_database"); + let node_genesis_hash = rpc::node_get_block_hash(0, &client) + .await? + .expect("node should be able to return some genesis hash"); + let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) + .await? + .expect("node should be able to return some genesis block"); + println!("Node's Genesis block::{:?}", node_genesis_hash); + + let db = sync::open_db(db_path, node_genesis_hash, node_genesis_block.clone())?; + Ok(db) +} + + +async fn get_local_keystore() -> anyhow::Result { + let data_path = temp_dir(); + let keystore_path = data_path.join("keystore"); + println!("keystore_path: {:?}", keystore_path); + let keystore = sc_keystore::LocalKeystore::open(keystore_path.clone(), None)?; + keystore::insert_development_key_for_this_session(&keystore)?; + Ok(keystore) +} + +async fn sync_db bool>( + db: &Db, + client: &HttpClient, + filter: &F) -> anyhow::Result<()> { + + if !sled::Db::was_recovered(&db) { + let node_genesis_hash = rpc::node_get_block_hash(0, &client) + .await? + .expect("node should be able to return some genesis hash"); + let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) + .await? + .expect("node should be able to return some genesis block"); + + + println!(" in sync_db !sled::Db::was_recovered(&db)"); + async { + sync::apply_block(&db, node_genesis_block, node_genesis_hash, &filter) + .await; + }; + } + println!(" sync::synchronize will be called!!"); + sync::synchronize(&db, &client, &filter).await?; + + log::info!( + "Wallet database synchronized with node to height {:?}", + sync::height(&db)?.expect("We just synced, so there is a height available") + ); + Ok(()) +} + +async fn sync_and_get_db() -> anyhow::Result { + let db = get_db().await?; + let keystore = get_local_keystore().await?; + let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; + let keystore_filter = |v: &OuterVerifier| -> bool { + matches![v, + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) + if crate::keystore::has_key(&keystore, owner_pubkey) + ] || matches![v, OuterVerifier::UpForGrabs(UpForGrabs)] // used for timestamp + }; + sync_db(&db, &client, &keystore_filter).await?; + Ok(db) +} + +/// 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); + + let mut bytes: [u8; 32] = [0; 32]; + hex::decode_to_slice(s, &mut bytes as &mut [u8]) + .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; + Ok(H256::from(bytes)) +} + +/// Parse an output ref from a string +fn output_ref_from_string(s: &str) -> Result { + let s = strip_0x_prefix(s); + let bytes = + hex::decode(s).map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; + + OutputRef::decode(&mut &bytes[..]) + .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation)) +} + +/// Takes a string and checks for a 0x prefix. Returns a string without a 0x prefix. +fn strip_0x_prefix(s: &str) -> &str { + if &s[..2] == "0x" { + &s[2..] + } else { + s + } +} + +/// Generate a plaform-specific temporary directory for the wallet +fn temp_dir() -> PathBuf { + // Since it is only used for testing purpose, we don't need a secure temp dir, just a unique one. + /* + std::env::temp_dir().join(format!( + "tuxedo-wallet-{}", + std::time::UNIX_EPOCH.elapsed().unwrap().as_millis(), + )) + */ + std::env::temp_dir().join(format!( + "tuxedo-wallet" + )) +} + +/// Generate the platform-specific default data path for the wallet +fn default_data_path() -> PathBuf { + // This uses the directories crate. + // https://docs.rs/directories/latest/directories/struct.ProjectDirs.html + + // Application developers may want to put actual qualifiers or organization here + let qualifier = ""; + let organization = ""; + let application = env!("CARGO_PKG_NAME"); + + directories::ProjectDirs::from(qualifier, organization, application) + .expect("app directories exist on all supported platforms; qed") + .data_dir() + .into() +} + +/// Utility to pretty print an outer verifier +pub fn pretty_print_verifier(v: &OuterVerifier) { + match v { + OuterVerifier::Sr25519Signature(sr25519_signature) => { + println! {"owned by {}", sr25519_signature.owner_pubkey} + } + OuterVerifier::UpForGrabs(_) => println!("that can be spent by anyone"), + OuterVerifier::ThresholdMultiSignature(multi_sig) => { + let string_sigs: Vec<_> = multi_sig + .signatories + .iter() + .map(|sig| format!("0x{}", hex::encode(sig))) + .collect(); + println!( + "Owned by {:?}, with a threshold of {} sigs necessary", + string_sigs, multi_sig.threshold + ); + } + } +} diff --git a/webservice-wallet-with-inbuilt-key-store/src/money.rs b/webservice-wallet-with-inbuilt-key-store/src/money.rs new file mode 100644 index 000000000..3f8931713 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/money.rs @@ -0,0 +1,339 @@ +//! Wallet features related to spending money and checking balances. + +use crate::{cli::MintCoinArgs, cli::SpendArgs,rpc::fetch_storage, sync}; + +use anyhow::anyhow; +use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; +use parity_scale_codec::Encode; +use runtime::{ + money::{Coin, MoneyConstraintChecker}, + OuterConstraintChecker, OuterVerifier, Transaction, +}; +use sc_keystore::LocalKeystore; +use sled::Db; +use sp_core::sr25519::Public; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use tuxedo_core::{ + types::{Input, Output, OutputRef}, + verifier::Sr25519Signature, +}; + +/// Create and send a transaction that mints the coins on the network +pub async fn mint_coins(client: &HttpClient, args: MintCoinArgs) -> anyhow::Result<()> { + log::debug!("The args are:: {:?}", args); + + let transaction = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: vec![( + Coin::<0>::new(args.amount), + OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.owner, + }), + ) + .into()], + checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Mint), + }; + + let spawn_hex = hex::encode(transaction.encode()); + let params = rpc_params![spawn_hex]; + let _spawn_response: Result = client.request("author_submitExtrinsic", params).await; + + log::info!( + "Node's response to mint-coin transaction: {:?}", + _spawn_response + ); + + let minted_coin_ref = OutputRef { + tx_hash: ::hash_of(&transaction.encode()), + index: 0, + }; + let output = &transaction.outputs[0]; + let amount = output.payload.extract::>()?.0; + print!( + "Minted {:?} worth {amount}. ", + hex::encode(minted_coin_ref.encode()) + ); + crate::pretty_print_verifier(&output.verifier); + + Ok(()) +} +use sp_core::H256; +struct recipient_output { + pub recipient:H256, + pub output_amount:Vec +} +fn extract_recipient_list_from_args(args: SpendArgs,) -> Vec { + let mut recipient_list:Vec = Vec::new(); + for i in args.recipients { + let rec_pient = recipient_output { + recipient:i.0, + output_amount:i.1, + }; + recipient_list.push(rec_pient); + } + recipient_list +} +/// Create and send a transaction that spends coins on the network +pub async fn spend_coins( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: SpendArgs, +) -> anyhow::Result<()> { + + log::info!("In the spend_coins_to_multiple_recipient The args are:: {:?}", args); + let mut transaction = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: Vec::new(), + checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend), + }; + + let mut recipient_list:Vec = extract_recipient_list_from_args(args.clone()); + + let mut total_output_amount = 0; + for recipient in &recipient_list { + for amount in &recipient.output_amount { + let output = Output { + payload: Coin::<0>::new(*amount).into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: recipient.recipient, + }), + }; + total_output_amount += amount; + transaction.outputs.push(output); + } + } + + // The total input set will consist of any manually chosen inputs + // plus any automatically chosen to make the input amount high enough + let mut total_input_amount = 0; + let mut all_input_refs = args.input; + for output_ref in &all_input_refs { + let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( + "user-specified output ref not found in local database" + ))?; + total_input_amount += amount; + } + //TODO filtering on a specific sender + + // If the supplied inputs are not valuable enough to cover the output amount + // we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.) + if total_input_amount < total_output_amount { + match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { + Some(more_inputs) => { + all_input_refs.extend(more_inputs); + } + None => Err(anyhow!( + "Not enough value in database to construct transaction" + ))?, + } + } + + // Make sure each input decodes and is still present in the node's storage, + // and then push to transaction. + for output_ref in &all_input_refs { + get_coin_from_storage(output_ref, client).await?; + transaction.inputs.push(Input { + output_ref: output_ref.clone(), + redeemer: vec![], // We will sign the total transaction so this should be empty + }); + } + + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + + // insert the proof + input.redeemer = redeemer; + } + + // Send the transaction + let genesis_spend_hex = hex::encode(transaction.encode()); + let params = rpc_params![genesis_spend_hex]; + let genesis_spend_response: Result = + client.request("author_submitExtrinsic", params).await; + log::info!( + "Node's response to spend transaction: {:?}", + genesis_spend_response + ); + + // Print new output refs for user to check later + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_coin_ref = OutputRef { + tx_hash, + index: i as u32, + }; + let amount = output.payload.extract::>()?.0; + + print!( + "Created {:?} worth {amount}. ", + hex::encode(new_coin_ref.encode()) + ); + crate::pretty_print_verifier(&output.verifier); + } + + Ok(()) +} +/* +/// Create and send a transaction that spends coins on the network +pub async fn spend_coins1( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: SpendArgs, +) -> anyhow::Result<()> { + log::info!("The args are:: {:?}", args); + + // Construct a template Transaction to push coins into later + let mut transaction = Transaction { + inputs: Vec::new(), + peeks: Vec::new(), + outputs: Vec::new(), + checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend), + }; + + // Construct each output and then push to the transactions + let mut total_output_amount = 0; + for amount in &args.output_amount { + let output = Output { + payload: Coin::<0>::new(*amount).into(), + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: args.recipient, + }), + }; + total_output_amount += amount; + transaction.outputs.push(output); + } + + // The total input set will consist of any manually chosen inputs + // plus any automatically chosen to make the input amount high enough + let mut total_input_amount = 0; + let mut all_input_refs = args.input; + for output_ref in &all_input_refs { + let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( + "user-specified output ref not found in local database" + ))?; + total_input_amount += amount; + } + //TODO filtering on a specific sender + + // If the supplied inputs are not valuable enough to cover the output amount + // we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.) + if total_input_amount < total_output_amount { + match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { + Some(more_inputs) => { + all_input_refs.extend(more_inputs); + } + None => Err(anyhow!( + "Not enough value in database to construct transaction" + ))?, + } + } + + // Make sure each input decodes and is still present in the node's storage, + // and then push to transaction. + for output_ref in &all_input_refs { + get_coin_from_storage(output_ref, client).await?; + transaction.inputs.push(Input { + output_ref: output_ref.clone(), + redeemer: vec![], // We will sign the total transaction so this should be empty + }); + } + + // Keep a copy of the stripped encoded transaction for signing purposes + let stripped_encoded_transaction = transaction.clone().encode(); + + // Iterate back through the inputs, signing, and putting the signatures in place. + for input in &mut transaction.inputs { + // Fetch the output from storage + let utxo = fetch_storage::(&input.output_ref, client).await?; + + // Construct the proof that it can be consumed + let redeemer = match utxo.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + let public = Public::from_h256(owner_pubkey); + crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? + } + OuterVerifier::UpForGrabs(_) => Vec::new(), + OuterVerifier::ThresholdMultiSignature(_) => todo!(), + }; + + // insert the proof + input.redeemer = redeemer; + } + + // Send the transaction + let genesis_spend_hex = hex::encode(transaction.encode()); + let params = rpc_params![genesis_spend_hex]; + let genesis_spend_response: Result = + client.request("author_submitExtrinsic", params).await; + log::info!( + "Node's response to spend transaction: {:?}", + genesis_spend_response + ); + + // Print new output refs for user to check later + let tx_hash = ::hash_of(&transaction.encode()); + for (i, output) in transaction.outputs.iter().enumerate() { + let new_coin_ref = OutputRef { + tx_hash, + index: i as u32, + }; + let amount = output.payload.extract::>()?.0; + + print!( + "Created {:?} worth {amount}. ", + hex::encode(new_coin_ref.encode()) + ); + crate::pretty_print_verifier(&output.verifier); + } + + Ok(()) +} +*/ + +/// Given an output ref, fetch the details about this coin from the node's +/// storage. +pub async fn get_coin_from_storage( + output_ref: &OutputRef, + client: &HttpClient, +) -> anyhow::Result<(Coin<0>, OuterVerifier)> { + let utxo = fetch_storage::(output_ref, client).await?; + let coin_in_storage: Coin<0> = utxo.payload.extract()?; + + Ok((coin_in_storage, utxo.verifier)) +} + +/// Apply a transaction to the local database, storing the new coins. +pub(crate) fn apply_transaction( + db: &Db, + tx_hash: ::Output, + index: u32, + output: &Output, +) -> anyhow::Result<()> { + let amount = output.payload.extract::>()?.0; + let output_ref = OutputRef { tx_hash, index }; + match output.verifier { + OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { + // Add it to the global unspent_outputs table + crate::sync::add_unspent_output(db, &output_ref, &owner_pubkey, &amount) + } + _ => Err(anyhow!("{:?}", ())), + } +} diff --git a/webservice-wallet-with-inbuilt-key-store/src/output_filter.rs b/webservice-wallet-with-inbuilt-key-store/src/output_filter.rs new file mode 100644 index 000000000..555fc5a55 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/output_filter.rs @@ -0,0 +1,137 @@ +use runtime::{OuterVerifier, Output}; +use sp_core::H256; +use tuxedo_core::types::OutputRef; + +pub type OutputInfo = (Output, OutputRef); + +type TxHash = H256; +/// The Filter type which is the closure signature used by functions to filter UTXOS +pub type Filter = Box Result, ()>>; + +pub trait OutputFilter { + /// The Filter type which is the closure signature used by functions to filter UTXOS + type Filter; + /// Builds a filter to be passed to wallet sync functions to sync the chosen outputs + /// to the users DB. + fn build_filter(verifier: OuterVerifier) -> Self::Filter; +} + +pub struct Sr25519SignatureFilter; +impl OutputFilter for Sr25519SignatureFilter { + // Todo Add filter error + type Filter = Result; + + fn build_filter(verifier: OuterVerifier) -> Self::Filter { + Ok(Box::new(move |outputs, tx_hash| { + let filtered_outputs = outputs + .iter() + .enumerate() + .map(|(i, output)| { + ( + output.clone(), + OutputRef { + tx_hash: *tx_hash, + index: i as u32, + }, + ) + }) + .filter(|(output, _)| output.verifier == verifier) + .collect::>(); + Ok(filtered_outputs) + })) + } +} + +mod tests { + use super::*; + + #[cfg(test)] + use tuxedo_core::{dynamic_typing::DynamicallyTypedData, verifier::*}; + + pub struct TestSr25519SignatureFilter; + impl OutputFilter for TestSr25519SignatureFilter { + type Filter = Result; + + fn build_filter(_verifier: OuterVerifier) -> Self::Filter { + Ok(Box::new(move |_outputs, _tx_hash| { + println!("printed something"); + Ok(vec![]) + })) + } + } + + #[test] + fn filter_prints() { + let verifier = OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: H256::zero(), + }); + let output = Output { + verifier: verifier.clone(), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }; + + let my_filter = + TestSr25519SignatureFilter::build_filter(verifier).expect("Can build print filter"); + let _ = my_filter(&[output], &H256::zero()); + } + + #[test] + fn filter_sr25519_signature_works() { + let verifier = OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: H256::zero(), + }); + + let outputs_to_filter = vec![ + Output { + verifier: verifier.clone(), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }, + Output { + verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { + owner_pubkey: H256::from_slice(b"asdfasdfasdfasdfasdfasdfasdfasdf"), + }), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }, + Output { + verifier: OuterVerifier::ThresholdMultiSignature(ThresholdMultiSignature { + threshold: 1, + signatories: vec![H256::zero()], + }), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }, + ]; + + let expected_filtered_output_infos = vec![( + Output { + verifier: verifier.clone(), + payload: DynamicallyTypedData { + data: vec![], + type_id: *b"1234", + }, + }, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )]; + + let my_filter = Sr25519SignatureFilter::build_filter(verifier) + .expect("Can build Sr25519Signature filter"); + let filtered_output_infos = my_filter(&outputs_to_filter, &H256::zero()) + .expect("Can filter the outputs by verifier correctly"); + + assert_eq!(filtered_output_infos, expected_filtered_output_infos); + } +} diff --git a/webservice-wallet-with-inbuilt-key-store/src/req_resp.rs b/webservice-wallet-with-inbuilt-key-store/src/req_resp.rs new file mode 100644 index 000000000..1f31acaf0 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/req_resp.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + + +#[derive(Debug, Deserialize)] +pub struct MintCoinsRequest { + pub amount: u32, +} + +#[derive(Debug, Serialize)] +pub struct MintCoinsResponse { + pub message: String, + // Add any additional fields as needed +} + +#[derive(Debug, Deserialize)] +pub struct CreateKittyRequest { + pub name: String, +} + +#[derive(Debug, Serialize)] +pub struct CreateKittyResponse { + pub message: String, + // Add any additional fields as needed +} \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/rpc.rs b/webservice-wallet-with-inbuilt-key-store/src/rpc.rs new file mode 100644 index 000000000..0d5466e6d --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/rpc.rs @@ -0,0 +1,61 @@ +//! 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> { + let params = rpc_params![Some(height)]; + let rpc_response: Option = client.request("chain_getBlockHash", params).await?; + let maybe_hash = rpc_response.map(|s| crate::h256_from_string(&s).unwrap()); + Ok(maybe_hash) +} + +/// Typed helper to get the node's full block at a particular hash +pub async fn node_get_block(hash: H256, client: &HttpClient) -> anyhow::Result> { + let s = hex::encode(hash.0); + let params = rpc_params![s]; + + let maybe_rpc_response: Option = + client.request("chain_getBlock", params).await?; + let rpc_response = maybe_rpc_response.unwrap(); + + let json_opaque_block = rpc_response.get("block").cloned().unwrap(); + let opaque_block: OpaqueBlock = serde_json::from_value(json_opaque_block).unwrap(); + + // I need a structured block, not an opaque one. To achieve that, I'll + // scale encode it, then once again decode it. + // Feels kind of like a hack, but I honestly don't know what else to do. + // I don't see any way to get the bytes out of an OpaqueExtrinsic. + let scale_bytes = opaque_block.encode(); + + let structured_block = Block::decode(&mut &scale_bytes[..]).unwrap(); + + Ok(Some(structured_block)) +} + +/// 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) +} diff --git a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/blockHandler/blockServicehandler.rs b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/blockHandler/blockServicehandler.rs new file mode 100644 index 000000000..1b1a1b91b --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/blockHandler/blockServicehandler.rs @@ -0,0 +1,74 @@ +use serde::{Deserialize, Serialize}; + +use jsonrpsee::http_client::HttpClientBuilder; +use parity_scale_codec::{Decode, Encode}; +use runtime::OuterVerifier; +use std::path::PathBuf; +use sled::Db; +use crate::money; +use sp_core::H256; +use crate::rpc; + +use crate::cli::MintCoinArgs; +use crate::cli::CreateKittyArgs; + +/// The default RPC endpoint for the wallet to connect to +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; +use crate::{ keystore::SHAWN_PUB_KEY}; + + +use axum::{http::StatusCode, response::IntoResponse, routing::{get, post},Json, Router,http::HeaderMap}; + +use std::net::SocketAddr; +use tower_http::cors::{Any, CorsLayer}; +use runtime::{opaque::Block as OpaqueBlock, Block}; +use anyhow::bail; + +#[derive(Debug, Serialize)] +pub struct BlockResponse { + pub message: String, +} + +pub async fn get_block(headers: HeaderMap) -> Json { + let block_number_header = headers.get("Block-Number").unwrap_or_else(|| { + panic!("Block-Number header is missing"); + }); + let block_number = block_number_header.to_str().unwrap_or_else(|_| { + panic!("Failed to parse Block-Number header"); + }); + + // Convert the block number to the appropriate type if needed + let block_number: u128 = block_number.parse().unwrap_or_else(|_| { + panic!("Failed to parse block number as u128"); + }); + + match get_blocks(block_number).await { + Ok(Some(node_block)) => Json(BlockResponse { + message: format!("block found {:?}",node_block), + }), + + Ok(None) => Json(BlockResponse { + message: format!("Node's block not found"), + }), + Err(err) => Json(BlockResponse { + message: format!("Error getting the block: {:?}", err), + }), + Err(_) => Json(BlockResponse { + message: format!("Unknown Error getting the block: "), + }), + } +} + +async fn get_blocks(number: u128) -> anyhow::Result> { + let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; + let node_block_hash = rpc::node_get_block_hash(number.try_into().unwrap(), &client) + .await? + .expect("node should be able to return some genesis hash"); + println!("Get blocks node_block_hash {:?} ",node_block_hash); + let maybe_block = rpc::node_get_block(node_block_hash, &client).await?; + println!("BlockData {:?} ",maybe_block.clone().unwrap()); + match maybe_block { + Some(block) => Ok(Some(block)), + None => bail!("Block not found for hash: {:?}", node_block_hash), + } +} \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/keyHandler/keyServicehandler.rs b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/keyHandler/keyServicehandler.rs new file mode 100644 index 000000000..7e9dd02d2 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/keyHandler/keyServicehandler.rs @@ -0,0 +1,69 @@ +use serde::{Deserialize, Serialize}; + +use sp_core::H256; +use crate::keystore; + +use crate::{ keystore::SHAWN_PUB_KEY}; + +use axum::{http::StatusCode, response::IntoResponse, routing::{get, post},Json, Router}; +use axum::{response::Html,}; +use std::net::SocketAddr; +use tower_http::cors::{Any, CorsLayer}; +use runtime::{opaque::Block as OpaqueBlock, Block}; +use anyhow::bail; + + +#[derive(Debug, Deserialize)] +pub struct GenerateKeyRequest { + pub password: Option, +} + +#[derive(Debug, Serialize)] +pub struct GenerateKeyResponse { + pub message: String, + pub public_key: Option, + pub phrase: Option, +} + +pub async fn generate_key(body: Json) -> Json { + match keystore::generate_key(body.password.clone()).await { + Ok((public_key, phrase)) => Json(GenerateKeyResponse { + message: format!("Keys generated successfully"), + public_key: Some(public_key), + phrase: Some(phrase), + }), + Err(err) => Json(GenerateKeyResponse { + message: format!("Error generating keys: {:?}", err), + public_key: None, + phrase: None, + }), + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// get keys +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#[derive(Debug, Serialize)] +pub struct GetKeyResponse { + pub message: String, + pub keys: Option>, +} + +pub async fn get_keys() -> Json { + match keystore::get_keys().await { + Ok(keys_iter) => { + // Lets collect keys into a vector of strings + let keys: Vec = keys_iter.map(|key| hex::encode(key)).collect(); + + Json(GetKeyResponse { + message: format!("Keys retrieved successfully"), + keys: Some(keys), + }) + } + Err(err) => Json(GetKeyResponse { + message: format!("Error retrieving keys: {:?}", err), + keys: None, + }), + } +} diff --git a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/kittyHandler/kittyServicehandler.rs b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/kittyHandler/kittyServicehandler.rs new file mode 100644 index 000000000..8ef1b9cd5 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/kittyHandler/kittyServicehandler.rs @@ -0,0 +1,841 @@ +use serde::{Deserialize, Serialize}; + + +use jsonrpsee::http_client::HttpClientBuilder; +use parity_scale_codec::{Decode, Encode}; +use std::path::PathBuf; +use sled::Db; +use crate::kitty; +use sp_core::H256; + +use crate::cli::{CreateKittyArgs, ListKittyForSaleArgs, + DelistKittyFromSaleArgs, UpdateKittyNameArgs, UpdateKittyPriceArgs, + BuyKittyArgs, BreedKittyArgs}; + +/// The default RPC endpoint for the wallet to connect to +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; +use crate::{ keystore::SHAWN_PUB_KEY}; + +use crate::get_db; +use crate::get_local_keystore; +use crate::sync_and_get_db; +use crate::original_get_db; + + + +use axum::{http::StatusCode, response::IntoResponse, routing::{get, post, put, patch},Json, Router,http::HeaderMap}; + +use std::convert::Infallible; +use axum::{response::Html,}; +use std::net::SocketAddr; +use tower_http::cors::{Any, CorsLayer}; +use runtime::{opaque::Block as OpaqueBlock, Block}; +use anyhow::bail; +//use parity_scale_codec::Input; +use tuxedo_core::types::Input; + + +use runtime::{ + kitties::{ + DadKittyStatus, FreeKittyConstraintChecker, KittyDNA, KittyData, KittyHelpers, + MomKittyStatus, Parent, + }, + money::{Coin, MoneyConstraintChecker}, + tradable_kitties::{TradableKittyConstraintChecker, TradableKittyData}, + OuterVerifier, Transaction, +}; +use tuxedo_core::types::OutputRef; +use tuxedo_core::types::Output; + +#[derive(Debug, Deserialize)] +pub struct CreateKittyRequest { + pub name: String, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct CreateKittyResponse { + pub message: String, + pub kitty:Option + // Add any additional fields as needed +} + +pub async fn create_kitty(body: Json) -> Result, Infallible> { + println!("create_kitties called "); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + //let db = sync_and_get_db().await.expect("Error"); + let db = original_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(CreateKittyResponse { + message: format!("Error creating HTTP client: {:?}", err), + kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + + match kitty::create_kitty(&db, &client, CreateKittyArgs { + kitty_name: body.name.to_string(), + owner: public_key_h256, + }).await { + Ok(Some(created_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = CreateKittyResponse { + message: format!("Kitty created successfully"), + kitty: Some(created_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(CreateKittyResponse { + message: format!("Kitty creation failed: No data returned"), + kitty:None, + })), + Err(err) => Ok(Json(CreateKittyResponse { + message: format!("Error creating kitty: {:?}", err), + kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// List kitty for Sale +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Serialize, Deserialize)] +pub struct GetTxnAndUtxoListForListKittyForSaleResponse { + pub message: String, + pub transaction: Option, + pub input_utxo_list:Option>>, +} + +pub async fn get_txn_and_inpututxolist_for_list_kitty_for_sale(headers: HeaderMap) -> Json { + // create_tx_for_list_kitty + println!("Headers map = {:?}",headers); + let name_header = headers.get("kitty-name").unwrap_or_else(|| { + panic!("Kitty name is missing"); + }); + + let name_str = name_header.to_str().unwrap_or_else(|_| { + panic!("Failed to parse name header"); + }); + + // ------------------------------- + let price_header = headers.get("kitty-price").unwrap_or_else(|| { + panic!("Kitty price is missing"); + }); + let price_str = price_header.to_str().unwrap_or_else(|_| { + panic!("Failed to parse priceheader"); + }); + + // Convert the block number to the appropriate type if needed + let price_number: u128 = price_str.parse().unwrap_or_else(|_| { + panic!("Failed to parse price number as u128"); + }); + + // ------------------------------- + + let publick_key_header = headers.get("owner_public_key").unwrap_or_else(|| { + panic!("publick_key_header is missing"); + }); + + let publick_key_str = publick_key_header.to_str().unwrap_or_else(|_| { + panic!("publick_key_header to parse"); + }); + + let public_key_bytes = hex::decode(publick_key_str.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + //let db = sync_and_get_db().await.expect("Error"); + let db = original_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Json(GetTxnAndUtxoListForListKittyForSaleResponse { + message: format!("Error creating HTTP client: {:?}", err), + transaction:None, + input_utxo_list:None + }); + } + }; + + match kitty::create_txn_for_list_kitty(&db, + name_str.to_string(), + price_number, + public_key_h256, + ).await { + Ok(Some(txn)) => { + // Convert created_kitty to JSON and include it in the response + let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; + + let response = GetTxnAndUtxoListForListKittyForSaleResponse { + message: format!("Kitty listed for sale successfully"), + transaction: Some(txn), + input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), + }; + Json(response) + }, + Ok(None) => Json(GetTxnAndUtxoListForListKittyForSaleResponse { + message: format!("Kitty listing forsale failed: No input returned"), + transaction:None, + input_utxo_list:None + }), + Err(err) => Json(GetTxnAndUtxoListForListKittyForSaleResponse { + message: format!("Error!! listing forsale: {:?}", err), + transaction:None, + input_utxo_list:None + }), + } +} + +pub async fn debug_get_signed_txn_for_list_kitty_for_sale(headers: HeaderMap) -> Json { + // create_tx_for_list_kitty + println!("Headers map = {:?}",headers); + let name_header = headers.get("kitty-name").unwrap_or_else(|| { + panic!("Kitty name is missing"); + }); + + let name_str = name_header.to_str().unwrap_or_else(|_| { + panic!("Failed to parse name header"); + }); + + // ------------------------------- + let price_header = headers.get("kitty-price").unwrap_or_else(|| { + panic!("Kitty price is missing"); + }); + let price_str = price_header.to_str().unwrap_or_else(|_| { + panic!("Failed to parse priceheader"); + }); + + // Convert the block number to the appropriate type if needed + let price_number: u128 = price_str.parse().unwrap_or_else(|_| { + panic!("Failed to parse price number as u128"); + }); + + // ------------------------------- + + let publick_key_header = headers.get("owner_public_key").unwrap_or_else(|| { + panic!("publick_key_header is missing"); + }); + + let publick_key_str = publick_key_header.to_str().unwrap_or_else(|_| { + panic!("publick_key_header to parse"); + }); + + let public_key_bytes = hex::decode(publick_key_str.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + //let db = sync_and_get_db().await.expect("Error"); + let db = original_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Json(GetTxnAndUtxoListForListKittyForSaleResponse { + message: format!("Error creating HTTP client: {:?}", err), + transaction:None, + input_utxo_list:None + }); + } + }; + match kitty::create_signed_txn_for_list_kitty(&db, + name_str.to_string(), + price_number, + public_key_h256, + &client, + ).await { + Ok(Some(txn)) => { + // Convert created_kitty to JSON and include it in the response + let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; + + let response = GetTxnAndUtxoListForListKittyForSaleResponse { + message: format!("Kitty listed for sale successfully"), + transaction: Some(txn), + input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), + }; + Json(response) + }, + Ok(None) => Json(GetTxnAndUtxoListForListKittyForSaleResponse { + message: format!("Kitty listing forsale failed: No input returned"), + transaction:None, + input_utxo_list:None + }), + Err(err) => Json(GetTxnAndUtxoListForListKittyForSaleResponse { + message: format!("Error!! listing forsale: {:?}", err), + transaction:None, + input_utxo_list:None + }), + } +} + +#[derive(Debug, Deserialize)] +pub struct ListKittyForSaleRequest { + pub signed_transaction: Transaction, +} + +#[derive(Debug, Serialize)] +pub struct ListKittyForSaleResponse { + pub message: String, + pub td_kitty:Option + // Add any additional fields as needed +} + +pub async fn list_kitty_for_sale (body: Json) -> Result, Infallible> { + println!("List kitties for sale is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + //let db = sync_and_get_db().await.expect("Error"); + let db = original_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(ListKittyForSaleResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + match kitty::list_kitty_for_sale(&body.signed_transaction, + &db, &client).await { + Ok(Some(listed_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = ListKittyForSaleResponse { + message: format!("Kitty listed for sale successfully"), + td_kitty: Some(listed_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(ListKittyForSaleResponse { + message: format!("Kitty listing forsale failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(ListKittyForSaleResponse { + message: format!("Error listing forsale: {:?}", err), + td_kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// De-list kitty from Sale +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Deserialize)] +pub struct DelistKittyFromSaleRequest { + pub name: String, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct DelistKittyFromSaleResponse { + pub message: String, + pub kitty:Option +} +pub async fn delist_kitty_from_sale (body: Json) -> Result, Infallible> { + println!("delist_kitty_from_sale is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + //let db = sync_and_get_db().await.expect("Error"); + let db = original_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(DelistKittyFromSaleResponse { + message: format!("Error creating HTTP client: {:?}", err), + kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + let ks = get_local_keystore().await.expect("Error"); + + match kitty::delist_kitty_from_sale(&db, &client, &ks,DelistKittyFromSaleArgs { + name: body.name.to_string(), + owner: public_key_h256, + }).await { + Ok(Some(delisted_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = DelistKittyFromSaleResponse { + message: format!("Kitty listed for sale successfully"), + kitty: Some(delisted_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(DelistKittyFromSaleResponse { + message: format!("Kitty listing forsale failed: No data returned"), + kitty:None, + })), + Err(err) => Ok(Json(DelistKittyFromSaleResponse { + message: format!("Error listing forsale: {:?}", err), + kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// Update kitty name +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Deserialize)] +pub struct UpdateKittyNameRequest { + pub current_name: String, + pub new_name:String, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct UpdateKittyNameResponse { + pub message: String, + pub kitty:Option +} +pub async fn update_kitty_name(body: Json) -> Result, Infallible> { + println!("update_kitty_name is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let db = sync_and_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(UpdateKittyNameResponse { + message: format!("Error creating HTTP client: {:?}", err), + kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + let ks = get_local_keystore().await.expect("Error"); + + match kitty::update_kitty_name(&db, &client, &ks,UpdateKittyNameArgs { + current_name: body.current_name.to_string(), + new_name: body.new_name.to_string(), + owner: public_key_h256, + }).await { + Ok(Some(updated_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = UpdateKittyNameResponse { + message: format!("Kitty listed for sale successfully"), + kitty: Some(updated_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(UpdateKittyNameResponse { + message: format!("Kitty listing forsale failed: No data returned"), + kitty:None, + })), + Err(err) => Ok(Json(UpdateKittyNameResponse { + message: format!("Error listing forsale: {:?}", err), + kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// Update tradable kitty name +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Deserialize)] +pub struct UpdateTdKittyNameRequest { + pub current_name: String, + pub new_name:String, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct UpdateTdKittyNameResponse { + pub message: String, + pub td_kitty:Option +} +pub async fn update_td_kitty_name(body: Json) -> Result, Infallible> { + println!("update_td_kitty_name is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let db = sync_and_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(UpdateTdKittyNameResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + let ks = get_local_keystore().await.expect("Error"); + + match kitty::update_td_kitty_name(&db, &client, &ks,UpdateKittyNameArgs { + current_name: body.current_name.to_string(), + new_name: body.new_name.to_string(), + owner: public_key_h256, + }).await { + Ok(Some(updated_td_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = UpdateTdKittyNameResponse { + message: format!("Kitty listed for sale successfully"), + td_kitty: Some(updated_td_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(UpdateTdKittyNameResponse { + message: format!("Kitty listing forsale failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(UpdateTdKittyNameResponse { + message: format!("Error listing forsale: {:?}", err), + td_kitty:None, + })), + } +} + +//////////////////////////////////////////////////////////////////// +// Update tradable kitty price +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Deserialize)] +pub struct UpdateTdKittyPriceRequest { + pub current_name: String, + pub price:u128, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct UpdateTdKittyPriceResponse { + pub message: String, + pub td_kitty:Option +} + +pub async fn update_td_kitty_price(body: Json) -> Result, Infallible> { + println!("update_td_kitty_price is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let db = sync_and_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(UpdateTdKittyPriceResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + let ks = get_local_keystore().await.expect("Error"); + + match kitty::update_kitty_price(&db, &client, &ks,UpdateKittyPriceArgs { + current_name: body.current_name.to_string(), + price: body.price, + owner: public_key_h256, + }).await { + Ok(Some(updated_td_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = UpdateTdKittyPriceResponse { + message: format!("Kitty listed for sale successfully"), + td_kitty: Some(updated_td_kitty), + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(UpdateTdKittyPriceResponse { + message: format!("Kitty listing forsale failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(UpdateTdKittyPriceResponse { + message: format!("Error listing forsale: {:?}", err), + td_kitty:None, + })), + } +} + + +//////////////////////////////////////////////////////////////////// +// Buy kitty +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Deserialize)] +pub struct BuyTdKittyRequest { + pub input_coins: Vec, + pub kitty_name: String, + pub owner_public_key:String, + pub seller_public_key:String, + pub output_amount: Vec, +} + +#[derive(Debug, Serialize)] +pub struct BuyTdKittyResponse { + pub message: String, + pub td_kitty:Option +} + +pub async fn buy_kitty(body: Json) -> Result, Infallible> { + println!("update_td_kitty_price is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let db = sync_and_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(BuyTdKittyResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256_of_owner = H256::from_slice(&public_key_bytes); + + let public_key_bytes = hex::decode(body.seller_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256_of_seller = H256::from_slice(&public_key_bytes); + + let ks = get_local_keystore().await.expect("Error"); + + match kitty::buy_kitty(&db, &client, &ks,BuyKittyArgs { + input: body.input_coins.clone(), + kitty_name: body.kitty_name.clone(), + seller: public_key_h256_of_seller, + owner: public_key_h256_of_owner, + output_amount: body.output_amount.clone(), + }).await { + Ok(Some(updated_td_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = BuyTdKittyResponse { + message: format!("Kitty listed for sale successfully"), + td_kitty: Some(updated_td_kitty), + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(BuyTdKittyResponse { + message: format!("Kitty listing forsale failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(BuyTdKittyResponse { + message: format!("Error listing forsale: {:?}", err), + td_kitty:None, + })), + } +} + + +//////////////////////////////////////////////////////////////////// +// Breed kitty +//////////////////////////////////////////////////////////////////// + +#[derive(Debug, Deserialize)] +pub struct BreedKittyRequest { + pub mom_name: String, + pub dad_name: String, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct BreedKittyResponse { + pub message: String, + pub mom_kitty:Option, + pub dad_kitty:Option, + pub child_kitty:Option, +} + +pub async fn breed_kitty(body: Json) -> Result, Infallible> { + println!("update_td_kitty_price is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let db = sync_and_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(BreedKittyResponse { + message: format!("Error creating HTTP client: {:?}", err), + mom_kitty:None, + dad_kitty:None, + child_kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256_of_owner = H256::from_slice(&public_key_bytes); + + let ks = get_local_keystore().await.expect("Error"); + + match kitty::breed_kitty(&db, &client, &ks,BreedKittyArgs { + mom_name: body.mom_name.clone(), + dad_name: body.dad_name.clone(), + owner: public_key_h256_of_owner, + }).await { + Ok(Some(new_family)) => { + // Convert created_kitty to JSON and include it in the response + let response = BreedKittyResponse { + message: format!("breeding successfully"), + mom_kitty:Some(new_family[0].clone()), + dad_kitty:Some(new_family[1].clone()), + child_kitty:Some(new_family[2].clone()), + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(BreedKittyResponse { + message: format!("Error in breeding failed: No data returned"), + mom_kitty:None, + dad_kitty:None, + child_kitty:None, + })), + Err(err) => Ok(Json(BreedKittyResponse { + message: format!("Error in breeding : {:?}", err), + mom_kitty:None, + dad_kitty:None, + child_kitty:None, + })), + } +} + +/* + +#[derive(Debug, Deserialize)] +pub struct ListKittyForSaleRequest { + pub name: String, + pub price: u128, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct ListKittyForSaleResponse { + pub message: String, + pub td_kitty:Option + // Add any additional fields as needed +} + +pub async fn list_kitty_for_sale (body: Json) -> Result, Infallible> { + println!("List kitties for sale is called {:?}",body); + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + //let db = sync_and_get_db().await.expect("Error"); + let db = original_get_db().await.expect("Error"); + + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Ok(Json(ListKittyForSaleResponse { + message: format!("Error creating HTTP client: {:?}", err), + td_kitty:None, + })); + } + }; + + // Convert the hexadecimal string to bytes + let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + let ks = get_local_keystore().await.expect("Error"); + + match kitty::list_kitty_for_sale(&db, &client, &ks,ListKittyForSaleArgs { + name: body.name.to_string(), + price: body.price, + owner: public_key_h256, + }).await { + Ok(Some(listed_kitty)) => { + // Convert created_kitty to JSON and include it in the response + let response = ListKittyForSaleResponse { + message: format!("Kitty listed for sale successfully"), + td_kitty: Some(listed_kitty), // Include the created kitty in the response + }; + Ok(Json(response)) + }, + Ok(None) => Ok(Json(ListKittyForSaleResponse { + message: format!("Kitty listing forsale failed: No data returned"), + td_kitty:None, + })), + Err(err) => Ok(Json(ListKittyForSaleResponse { + message: format!("Error listing forsale: {:?}", err), + td_kitty:None, + })), + } +} + +/////////////////////////////////// +#[derive(Debug, Serialize, Deserialize)] +pub struct GetTxnForListKittyForSaleResponse { + pub message: String, + pub list_kitty_inputs:Option> +} + +pub async fn get_inputs_for_list_kitty_for_sale(headers: HeaderMap) -> Json { + // create_tx_for_list_kitty + let name_header = headers.get("kitty-name").unwrap_or_else(|| { + panic!("Kitty name is missing"); + }); + + let name_str = name_header.to_str().unwrap_or_else(|_| { + panic!("Failed to parse name header"); + }); + + // ------------------------------- + let price_header = headers.get("kitty-price").unwrap_or_else(|| { + panic!("Kitty price is missing"); + }); + let price_str = price_header.to_str().unwrap_or_else(|_| { + panic!("Failed to parse priceheader"); + }); + + // Convert the block number to the appropriate type if needed + let price_number: u128 = price_str.parse().unwrap_or_else(|_| { + panic!("Failed to parse price number as u128"); + }); + + // ------------------------------- + + let publick_key_header = headers.get("kitty-name").unwrap_or_else(|| { + panic!("publick_key_header is missing"); + }); + + let publick_key_str = publick_key_header.to_str().unwrap_or_else(|_| { + panic!("publick_key_header to parse"); + }); + + let public_key_bytes = hex::decode(publick_key_str.clone()).expect("Invalid hexadecimal string"); + let public_key_h256 = H256::from_slice(&public_key_bytes); + + let db = original_get_db().await.expect("Error"); + + match kitty::create_inputs_for_list_kitty(&db, + name_str.to_string(), + // price_number, + public_key_h256, + ).await { + Ok(Some(inputs)) => { + // Convert created_kitty to JSON and include it in the response + let response = GetTxnForListKittyForSaleResponse { + message: format!("Kitty listed for sale successfully"), + list_kitty_inputs: Some(inputs), // Include the created kitty in the response + }; + Json(response) + }, + Ok(None) => Json(GetTxnForListKittyForSaleResponse { + message: format!("Kitty listing forsale failed: No input returned"), + list_kitty_inputs:None, + }), + Err(err) => Json(GetTxnForListKittyForSaleResponse { + message: format!("Error listing forsale: {:?}", err), + list_kitty_inputs:None, + }), + } +} + +*/ \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/moneyHandler/moneyServicehandler.rs b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/moneyHandler/moneyServicehandler.rs new file mode 100644 index 000000000..b46680b9a --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/moneyHandler/moneyServicehandler.rs @@ -0,0 +1,68 @@ +use serde::{Deserialize, Serialize}; + +use jsonrpsee::http_client::HttpClientBuilder; +use parity_scale_codec::{Decode, Encode}; +use runtime::OuterVerifier; +use std::path::PathBuf; +use sled::Db; +use crate::money; +use sp_core::H256; + +use crate::cli::MintCoinArgs; + +/// The default RPC endpoint for the wallet to connect to +const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; +use crate::{ keystore::SHAWN_PUB_KEY}; + + +use axum::{http::StatusCode, response::IntoResponse, routing::{get, post},Json, Router}; +use axum::{response::Html,}; +use std::net::SocketAddr; +use tower_http::cors::{Any, CorsLayer}; +use runtime::{opaque::Block as OpaqueBlock, Block}; +use anyhow::bail; + + +#[derive(Debug, Deserialize)] +pub struct MintCoinsRequest { + pub amount: u128, + pub owner_public_key:String, +} + +#[derive(Debug, Serialize)] +pub struct MintCoinsResponse { + pub message: String, + + // Add any additional fields as needed +} + +pub async fn mint_coins(body: Json) -> Json { + let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); + let client = match client_result { + Ok(client) => client, + Err(err) => { + return Json(MintCoinsResponse { + message: format!("Error creating HTTP client: {:?}", err), + }); + } + }; + + // Convert the hexadecimal string to bytes + //let public_key_bytes = hex::decode(SHAWN_PUB_KEY).expect("Invalid hexadecimal string"); + let public_key_bytes = hex::decode(body.owner_public_key.as_str()).expect("Invalid hexadecimal string"); + + // Convert the bytes to H256 + let public_key_h256 = H256::from_slice(&public_key_bytes); + // Call the mint_coins function from your CLI wallet module + match money::mint_coins(&client, MintCoinArgs { + amount: body.amount, + owner: public_key_h256, + }).await { + Ok(()) => Json(MintCoinsResponse { + message: format!("Coins minted successfully"), + }), + Err(err) => Json(MintCoinsResponse { + message: format!("Error minting coins: {:?}", err), + }), + } +} \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/sync.rs b/webservice-wallet-with-inbuilt-key-store/src/sync.rs new file mode 100644 index 000000000..bb35b8ab8 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/sync.rs @@ -0,0 +1,1096 @@ +//! This module is responsible for maintaining the wallet's local database of blocks +//! and owned UTXOs to the canonical database reported by the node. +//! +//! It is backed by a sled database +//! +//! ## Schema +//! +//! There are 4 tables in the database +//! BlockHashes block_number:u32 => block_hash:H256 +//! Blocks block_hash:H256 => block:Block +//! UnspentOutputs output_ref => (owner_pubkey, amount) +//! SpentOutputs output_ref => (owner_pubkey, amount) + +use std::path::PathBuf; + +use crate::rpc; +use anyhow::anyhow; +use parity_scale_codec::{Decode, Encode}; +use sled::Db; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, Hash, Zero}; +use tuxedo_core::{ + dynamic_typing::UtxoData, + types::{Input, OutputRef}, +}; + +use crate::cli::ShowOwnedKittyArgs; +use anyhow::Error; +use jsonrpsee::http_client::HttpClient; +use runtime::kitties::KittyDNA; +use runtime::kitties::KittyData; + +use runtime::{ + money::Coin, timestamp::Timestamp, tradable_kitties::TradableKittyData, Block, OuterVerifier, + Transaction, +}; + +/*Todo: Do we need all the data of kitty here +use runtime::{ + kitties::{KittyData, Parent,KittyHelpers,MomKittyStatus,DadKittyStatus, + KittyDNA,FreeKittyConstraintChecker} +}; +*/ + +/// The identifier for the blocks tree in the db. +const BLOCKS: &str = "blocks"; + +/// The identifier for the block_hashes tree in the db. +const BLOCK_HASHES: &str = "block_hashes"; + +/// The identifier for the unspent tree in the db. +const UNSPENT: &str = "unspent"; + +/// The identifier for the spent tree in the db. +const SPENT: &str = "spent"; + +/// The identifier for the owned kitties in the db. +const FRESH_KITTY: &str = "fresh_kitty"; + +/// The identifier for the owned kitties in the db. +const USED_KITTY: &str = "used_kitty"; + +/// The identifier for the owned kitties in the db. +const FRESH_TRADABLE_KITTY: &str = "fresh_tradable_kitty"; + +/// The identifier for the owned kitties in the db. +const USED_TRADABLE_KITTY: &str = "used_tradable_kitty"; + +/// 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 +/// If an empty database is opened, it is initialized with the expected genesis hash and genesis block +pub(crate) fn open_db( + db_path: PathBuf, + expected_genesis_hash: H256, + expected_genesis_block: Block, +) -> anyhow::Result { + //TODO figure out why this assertion fails. + //assert_eq!(BlakeTwo256::hash_of(&expected_genesis_block.encode()), expected_genesis_hash, "expected block hash does not match expected block"); + + let db = sled::open(db_path)?; + + // Open the tables we'll need + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + let wallet_blocks_tree = db.open_tree("blocks")?; + + // If the database is already populated, just make sure it is for the same genesis block + if height(&db)?.is_some() { + // There are database blocks, so do a quick precheck to make sure they use the same genesis block. + let wallet_genesis_ivec = wallet_block_hashes_tree + .get(0.encode())? + .expect("We know there are some blocks, so there should be a 0th block."); + let wallet_genesis_hash = H256::decode(&mut &wallet_genesis_ivec[..])?; + log::debug!("Found existing database."); + if expected_genesis_hash != wallet_genesis_hash { + log::error!("Wallet's genesis does not match expected. Aborting database opening."); + return Err(anyhow!("Node reports a different genesis block than wallet. Wallet: {wallet_genesis_hash:?}. Expected: {expected_genesis_hash:?}. Aborting all operations")); + } + return Ok(db); + } + + // If there are no local blocks yet, initialize the tables + log::info!( + "Initializing fresh sync from genesis {:?}", + expected_genesis_hash + ); + + // Update both tables + wallet_block_hashes_tree.insert(0u32.encode(), expected_genesis_hash.encode())?; + wallet_blocks_tree.insert( + expected_genesis_hash.encode(), + expected_genesis_block.encode(), + )?; + + Ok(db) +} + +/// Synchronize the local database to the database of the running node. +/// The wallet entirely trusts the data the node feeds it. In the bigger +/// picture, that means run your own (light) node. +pub(crate) async fn synchronize bool>( + db: &Db, + client: &HttpClient, + filter: &F, +) -> anyhow::Result<()> { + //log::info!("Synchronizing wallet with node."); + println!("Synchronizing wallet with node."); + + // Start the algorithm at the height that the wallet currently thinks is best. + // Fetch the block hash at that height from both the wallet's local db and the node + let mut height: u32 = height(db)?.ok_or(anyhow!("tried to sync an uninitialized database"))?; + let mut wallet_hash = get_block_hash(db, height)? + .expect("Local database should have a block hash at the height reported as best"); + let mut node_hash: Option = rpc::node_get_block_hash(height, client).await?; + + // There may have been a re-org since the last time the node synced. So we loop backwards from the + // best height the wallet knows about checking whether the wallet knows the same block as the node. + // If not, we roll this block back on the wallet's local db, and then check the next ancestor. + // When the wallet and the node agree on the best block, the wallet can re-sync following the node. + // In the best case, where there is no re-org, this loop will execute zero times. + while Some(wallet_hash) != node_hash { + log::info!("Divergence at height {height}. Node reports block: {node_hash:?}. Reverting wallet block: {wallet_hash:?}."); + + unapply_highest_block(db).await?; + + // Update for the next iteration + height -= 1; + wallet_hash = get_block_hash(db, height)? + .expect("Local database should have a block hash at the height reported as best"); + node_hash = rpc::node_get_block_hash(height, client).await?; + } + + // Orphaned blocks (if any) have been discarded at this point. + // So we prepare our variables for forward syncing. + log::debug!("Resyncing from common ancestor {node_hash:?} - {wallet_hash:?}"); + height += 1; + node_hash = rpc::node_get_block_hash(height, client).await?; + + // Now that we have checked for reorgs and rolled back any orphan blocks, we can go ahead and sync forward. + while let Some(hash) = node_hash { + log::debug!("Forward syncing height {height}, hash {hash:?}"); + + // Fetch the entire block in order to apply its transactions + let block = rpc::node_get_block(hash, client) + .await? + .expect("Node should be able to return a block whose hash it already returned"); + + // Apply the new block + apply_block(db, block, hash, filter).await?; + + height += 1; + + node_hash = rpc::node_get_block_hash(height, client).await?; + } + + log::debug!("Done with forward sync up to {}", height - 1); + println!("Done with forward sync up to {}", height - 1); + if let Err(err) = db.flush() { + println!("Error flushing Sled database: {}", err); + } + Ok(()) +} + +/// Gets the owner and amount associated with an output ref from the unspent table +/// +/// Some if the output ref exists, None if it doesn't +pub(crate) fn get_unspent(db: &Db, output_ref: &OutputRef) -> anyhow::Result> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + let Some(ivec) = wallet_unspent_tree.get(output_ref.encode())? else { + return Ok(None); + }; + + Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) +} + +/// Picks an arbitrary set of unspent outputs from the database for spending. +/// The set's token values must add up to at least the specified target value. +/// +/// The return value is None if the total value of the database is less than the target +/// It is Some(Vec![...]) when it is possible +pub(crate) fn get_arbitrary_unspent_set( + db: &Db, + target: u128, +) -> anyhow::Result>> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + + let mut total = 0u128; + let mut keepers = Vec::new(); + + let mut unspent_iter = wallet_unspent_tree.iter(); + while total < target { + let Some(pair) = unspent_iter.next() else { + return Ok(None); + }; + + let (output_ref_ivec, owner_amount_ivec) = pair?; + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..])?; + println!( + "in Sync::get_arbitrary_unspent_set output_ref = {:?}", + output_ref + ); + let (_owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + total += amount; + keepers.push(output_ref); + } + + Ok(Some(keepers)) +} + +/// Gets the block hash from the local database given a block height. Similar the Node's RPC. +/// +/// Some if the block exists, None if the block does not exist. +pub(crate) fn get_block_hash(db: &Db, height: u32) -> anyhow::Result> { + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + let Some(ivec) = wallet_block_hashes_tree.get(height.encode())? else { + return Ok(None); + }; + + let hash = H256::decode(&mut &ivec[..])?; + + Ok(Some(hash)) +} + +// This is part of what I expect to be a useful public interface. For now it is not used. +#[allow(dead_code)] +/// Gets the block from the local database given a block hash. Similar to the Node's RPC. +pub(crate) fn get_block(db: &Db, hash: H256) -> anyhow::Result> { + let wallet_blocks_tree = db.open_tree(BLOCKS)?; + let Some(ivec) = wallet_blocks_tree.get(hash.encode())? else { + return Ok(None); + }; + + let block = Block::decode(&mut &ivec[..])?; + + Ok(Some(block)) +} + +/// Apply a block to the local database +pub(crate) async fn apply_block bool>( + db: &Db, + b: Block, + block_hash: H256, + filter: &F, +) -> anyhow::Result<()> { + //log::info!("Applying Block {:?}, Block_Hash {:?}", b, block_hash); + //println!("Applying Block {:?}, Block_Hash {:?}", b, block_hash); + // Write the hash to the block_hashes table + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + wallet_block_hashes_tree.insert(b.header.number.encode(), block_hash.encode())?; + + // Write the block to the blocks table + let wallet_blocks_tree = db.open_tree(BLOCKS)?; + wallet_blocks_tree.insert(block_hash.encode(), b.encode())?; + + // Iterate through each transaction + for tx in b.extrinsics { + apply_transaction(db, tx, filter).await?; + } + if let Err(err) = db.flush() { + println!("Error flushing Sled database: {}", err); + } + + Ok(()) +} + +/// Apply a single transaction to the local database +/// The owner-specific tables are mappings from output_refs to coin amounts +async fn apply_transaction bool>( + db: &Db, + tx: Transaction, + filter: &F, +) -> anyhow::Result<()> { + let tx_hash = BlakeTwo256::hash_of(&tx.encode()); + log::debug!("syncing transaction {tx_hash:?}"); + + // Insert all new outputs + for (index, output) in tx + .outputs + .iter() + .filter(|o| filter(&o.verifier)) + .enumerate() + { + match output.payload.type_id { + Coin::<0>::TYPE_ID => { + crate::money::apply_transaction(db, tx_hash, index as u32, output)?; + } + Timestamp::TYPE_ID => { + crate::timestamp::apply_transaction(db, output)?; + } + KittyData::TYPE_ID => { + crate::kitty::apply_transaction(db, tx_hash, index as u32, output)?; + } + TradableKittyData::TYPE_ID => { + crate::kitty::apply_td_transaction(db, tx_hash, index as u32, output)?; + } + + _ => continue, + } + } + + log::debug!("about to spend all inputs"); + // Spend all the inputs + for Input { output_ref, .. } in tx.inputs { + spend_output(db, &output_ref)?; + mark_as_used_kitties(db, &output_ref)?; + mark_as_used_tradable_kitties(db, &output_ref)?; + } + + if let Err(err) = db.flush() { + println!("Error flushing Sled database: {}", err); + } + + Ok(()) +} + +/// Add a new output to the database updating all tables. +pub(crate) fn add_unspent_output( + db: &Db, + output_ref: &OutputRef, + owner_pubkey: &H256, + amount: &u128, +) -> anyhow::Result<()> { + let unspent_tree = db.open_tree(UNSPENT)?; + unspent_tree.insert(output_ref.encode(), (owner_pubkey, amount).encode())?; + + Ok(()) +} + +/// Remove an output from the database updating all tables. +fn remove_unspent_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let unspent_tree = db.open_tree(UNSPENT)?; + + unspent_tree.remove(output_ref.encode())?; + + Ok(()) +} + +/// Mark an existing output as spent. This does not purge all record of the output from the db. +/// It just moves the record from the unspent table to the spent table +fn spend_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let unspent_tree = db.open_tree(UNSPENT)?; + let spent_tree = db.open_tree(SPENT)?; + + let Some(ivec) = unspent_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + let (owner, amount) = <(H256, u128)>::decode(&mut &ivec[..])?; + spent_tree.insert(output_ref.encode(), (owner, amount).encode())?; + + Ok(()) +} + +/// Mark an output that was previously spent back as unspent. +fn unspend_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let unspent_tree = db.open_tree(UNSPENT)?; + let spent_tree = db.open_tree(SPENT)?; + + let Some(ivec) = spent_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + let (owner, amount) = <(H256, u128)>::decode(&mut &ivec[..])?; + unspent_tree.insert(output_ref.encode(), (owner, amount).encode())?; + + Ok(()) +} + +/// Run a transaction backwards against a database. Mark all of the Inputs +/// as unspent, and drop all of the outputs. +fn unapply_transaction(db: &Db, tx: &Transaction) -> anyhow::Result<()> { + // Loop through the inputs moving each from spent to unspent + for Input { output_ref, .. } in &tx.inputs { + unspend_output(db, output_ref)?; + } + + // Loop through the outputs pruning them from unspent and dropping all record + let tx_hash = BlakeTwo256::hash_of(&tx.encode()); + + for i in 0..tx.outputs.len() { + let output_ref = OutputRef { + tx_hash, + index: i as u32, + }; + remove_unspent_output(db, &output_ref)?; + } + + Ok(()) +} + +/// Unapply the best block that the wallet currently knows about +pub(crate) async fn unapply_highest_block(db: &Db) -> anyhow::Result { + let wallet_blocks_tree = db.open_tree(BLOCKS)?; + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + + // Find the best height + let height = height(db)?.ok_or(anyhow!("Cannot unapply block from uninitialized database"))?; + + // Take the hash from the block_hashes tables + let Some(ivec) = wallet_block_hashes_tree.remove(height.encode())? else { + return Err(anyhow!( + "No block hash found at height reported as best. DB is inconsistent." + )); + }; + let hash = H256::decode(&mut &ivec[..])?; + + // Take the block from the blocks table + let Some(ivec) = wallet_blocks_tree.remove(hash.encode())? else { + return Err(anyhow!( + "Block was not present in db but block hash was. DB is corrupted." + )); + }; + + let block = Block::decode(&mut &ivec[..])?; + + // Loop through the transactions in reverse order calling unapply + for tx in block.extrinsics.iter().rev() { + unapply_transaction(db, tx)?; + } + + Ok(block) +} + +/// Get the block height that the wallet is currently synced to +/// +/// None means the db is not yet initialized with a genesis block +pub(crate) fn height(db: &Db) -> anyhow::Result> { + let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; + let num_blocks = wallet_block_hashes_tree.len(); + + Ok(if num_blocks == 0 { + None + } else { + Some(num_blocks as u32 - 1) + }) +} + +// This is part of what I expect to be a useful public interface. For now it is not used. +#[allow(dead_code)] +/// Debugging use. Print out the entire block_hashes tree. +pub(crate) fn print_block_hashes_tree(db: &Db) -> anyhow::Result<()> { + for height in 0..height(db)?.unwrap() { + let hash = get_block_hash(db, height)?; + println!("height: {height}, hash: {hash:?}"); + } + + Ok(()) +} + +/// Debugging use. Print the entire unspent outputs tree. +pub(crate) fn print_unspent_tree(db: &Db) -> anyhow::Result<()> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + for x in wallet_unspent_tree.iter() { + let (output_ref_ivec, owner_amount_ivec) = x?; + let output_ref = hex::encode(output_ref_ivec); + let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + println!("{output_ref}: owner {owner_pubkey:?}, amount {amount}"); + } + + Ok(()) +} + +/// Iterate the entire unspent set summing the values of the coins +/// on a per-address basis. +pub(crate) fn get_balances(db: &Db) -> anyhow::Result> { + let mut balances = std::collections::HashMap::::new(); + + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + + for raw_data in wallet_unspent_tree.iter() { + let (_output_ref_ivec, owner_amount_ivec) = raw_data?; + let (owner, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + balances + .entry(owner) + .and_modify(|old| *old += amount) + .or_insert(amount); + } + + Ok(balances.into_iter()) +} + +// Kitty related functions +/// Add kitties to the database updating all tables. +pub fn add_fresh_kitty_to_db( + db: &Db, + output_ref: &OutputRef, + owner_pubkey: &H256, + kitty: &KittyData, +) -> anyhow::Result<()> { + let kitty_owned_tree = db.open_tree(FRESH_KITTY)?; + kitty_owned_tree.insert(output_ref.encode(), (owner_pubkey, kitty).encode())?; + + Ok(()) +} + +pub fn add_fresh_tradable_kitty_to_db( + db: &Db, + output_ref: &OutputRef, + owner_pubkey: &H256, + kitty: &TradableKittyData, +) -> anyhow::Result<()> { + let tradable_kitty_owned_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + tradable_kitty_owned_tree.insert(output_ref.encode(), (owner_pubkey, kitty).encode())?; + + Ok(()) +} + +/// Remove an output from the database updating all tables. +fn remove_used_kitty_from_db(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let kitty_owned_tree = db.open_tree(FRESH_KITTY)?; + kitty_owned_tree.remove(output_ref.encode())?; + + Ok(()) +} + +/// Remove an output from the database updating all tables. +fn remove_used_tradable_kitty_from_db(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let tradable_kitty_owned_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + tradable_kitty_owned_tree.remove(output_ref.encode())?; + + Ok(()) +} + +/// Mark an existing output as spent. This does not purge all record of the output from the db. +/// It just moves the record from the unspent table to the spent table +fn mark_as_used_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let fresh_kitty_tree = db.open_tree(FRESH_KITTY)?; + let used_kitty_tree = db.open_tree(USED_KITTY)?; + + let Some(ivec) = fresh_kitty_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; + used_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; + + Ok(()) +} + +/// Mark an existing output as spent. This does not purge all record of the output from the db. +/// It just moves the record from the unspent table to the spent table +fn mark_as_used_tradable_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let fresh_tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let used_tradable_kitty_tree = db.open_tree(USED_TRADABLE_KITTY)?; + + let Some(ivec) = fresh_tradable_kitty_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; + used_tradable_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; + + Ok(()) +} + +/// Mark an output that was previously spent back as unspent. +fn unmark_as_used_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let fresh_kitty_tree = db.open_tree(FRESH_KITTY)?; + let used_kitty_tree = db.open_tree(USED_KITTY)?; + + let Some(ivec) = used_kitty_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; + fresh_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; + + Ok(()) +} + +/// Mark an output that was previously spent back as unspent. +fn unmark_as_used_tradable_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { + let fresh_Tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let used_Tradable_kitty_tree = db.open_tree(USED_TRADABLE_KITTY)?; + + let Some(ivec) = used_Tradable_kitty_tree.remove(output_ref.encode())? else { + return Ok(()); + }; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; + fresh_Tradable_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; + + Ok(()) +} + +/// Iterate the entire owned kitty +/// on a per-address basis. +pub(crate) fn get_all_kitties_from_local_db<'a>( + db: &'a Db, +) -> anyhow::Result + 'a> { + get_all_kitties_and_td_kitties_from_local_db(db, FRESH_KITTY) +} + +/// Iterate the entire owned tradable kitty +/// on a per-address basis. +pub(crate) fn get_all_tradable_kitties_from_local_db<'a>( + db: &'a Db, +) -> anyhow::Result + 'a> { + get_all_kitties_and_td_kitties_from_local_db(db, FRESH_TRADABLE_KITTY) +} + +pub(crate) fn get_kitty_from_local_db_based_on_name( + db: &Db, + name: String, +) -> anyhow::Result> { + get_data_from_local_db_based_on_name(db, FRESH_KITTY, name, |kitty: &KittyData| &kitty.name) +} + +pub(crate) fn get_tradable_kitty_from_local_db_based_on_name( + db: &Db, + name: String, +) -> anyhow::Result> { + get_data_from_local_db_based_on_name( + db, + FRESH_TRADABLE_KITTY, + name, + |kitty: &TradableKittyData| &kitty.kitty_basic_data.name, + ) +} + +/// Iterate the entire owned kitty +/// on a per-address basis. +pub(crate) fn get_owned_kitties_from_local_db<'a>( + db: &'a Db, + args: &'a ShowOwnedKittyArgs, +) -> anyhow::Result + 'a> { + get_any_owned_kitties_from_local_db(db, FRESH_KITTY, &args.owner) +} + +/// Iterate the entire owned tradable kitty +/// on a per-address basis. +pub(crate) fn get_owned_tradable_kitties_from_local_db<'a>( + db: &'a Db, + args: &'a ShowOwnedKittyArgs, +) -> anyhow::Result + 'a> { + get_any_owned_kitties_from_local_db(db, FRESH_TRADABLE_KITTY, &args.owner) +} + +pub(crate) fn is_kitty_name_duplicate( + db: &Db, + name: String, + owner_pubkey: &H256, +) -> anyhow::Result> { + is_name_duplicate(db, name, owner_pubkey, FRESH_KITTY, |kitty: &KittyData| { + &kitty.name + }) +} + +pub(crate) fn is_td_kitty_name_duplicate( + db: &Db, + name: String, + owner_pubkey: &H256, +) -> anyhow::Result> { + is_name_duplicate( + db, + name, + owner_pubkey, + FRESH_TRADABLE_KITTY, + |kitty: &TradableKittyData| &kitty.kitty_basic_data.name, + ) +} + +/// Gets the owner and amount associated with an output ref from the unspent table +/// +/// Some if the output ref exists, None if it doesn't +pub(crate) fn get_kitty_fromlocaldb( + db: &Db, + output_ref: &OutputRef, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + let Some(ivec) = wallet_owned_kitty_tree.get(output_ref.encode())? else { + return Ok(None); + }; + + Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) +} + +/// Gets the owner and amount associated with an output ref from the unspent table +/// +/// Some if the output ref exists, None if it doesn't +pub(crate) fn get_tradable_kitty_fromlocaldb( + db: &Db, + output_ref: &OutputRef, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let Some(ivec) = wallet_owned_kitty_tree.get(output_ref.encode())? else { + return Ok(None); + }; + + Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) +} + +////////////////////////// +// Private Functions +////////////////////////// + +fn get_all_kitties_and_td_kitties_from_local_db<'a, T>( + db: &'a Db, + tree_name: &'a str, +) -> anyhow::Result + 'a> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_tradable_kitty_tree = db.open_tree(tree_name)?; + + Ok(wallet_owned_tradable_kitty_tree + .iter() + .filter_map(|raw_data| { + let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + Some((owner, kitty)) + })) +} + +fn get_data_from_local_db_based_on_name( + db: &Db, + tree_name: &str, + name: String, + name_extractor: impl Fn(&T) -> &[u8; 4], +) -> anyhow::Result> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_kitty_tree = db.open_tree(tree_name)?; + + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree + .iter() + .filter_map(|raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + println!("Owner = {:?} Name : {:?} -> output_ref {:?}", owner, name, output_ref.clone()); + + if name_extractor(&kitty) == kitty_name { + println!(" Name : {:?} matched", name); + Some((Some(kitty), output_ref)) + } else { + println!(" Name : {:?} NOTmatched", name); + None + } + }) + .next() + .unwrap_or(( + None, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )); // Use unwrap_or to handle the Option + + println!("output_ref = {:?}", output_ref); + println!("kitty Name {} found_status = {:?}", name,found_kitty); + + Ok(found_kitty.map(|kitty| (kitty, output_ref))) +} + +fn get_any_owned_kitties_from_local_db<'a, T>( + db: &'a Db, + tree_name: &'a str, + owner_pubkey: &'a H256, +) -> anyhow::Result + 'a> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_kitty_tree = db.open_tree(tree_name)?; + + Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref_str = hex::encode(output_ref_ivec.clone()); + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + if owner == *owner_pubkey { + Some((owner, kitty, output_ref)) + } else { + None + } + })) +} + +fn is_name_duplicate( + db: &Db, + name: String, + owner_pubkey: &H256, + tree_name: &str, + name_extractor: impl Fn(&T) -> &[u8; 4], +) -> anyhow::Result> +where + T: Decode + Clone + std::fmt::Debug, +{ + let wallet_owned_kitty_tree = db.open_tree(tree_name)?; + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let found_kitty: (Option) = wallet_owned_kitty_tree + .iter() + .filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + println!("Name : {:?}", name); + + if *name_extractor(&kitty) == kitty_name[..] && owner == *owner_pubkey { + Some(Some(kitty)) + } else { + None + } + }) + .next() + .unwrap_or(None); // Use unwrap_or to handle the Option + + println!("found_kitty = {:?}", found_kitty); + let is_kitty_found = match found_kitty { + Some(k) => Some(true), + None => Some(false), + }; + Ok(is_kitty_found) +} + +fn string_to_h256(s: &str) -> Result { + let bytes = hex::decode(s)?; + // Assuming H256 is a fixed-size array with 32 bytes + let mut h256 = [0u8; 32]; + h256.copy_from_slice(&bytes); + Ok(h256.into()) +} + +/* +pub(crate) fn is_kitty_name_duplicate1( + db: &Db, + owner_pubkey: &H256, + name: String, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let found_kitty: (Option) = wallet_owned_kitty_tree + .iter() + .filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + println!("Name : {:?}", name); + + if kitty.name == &kitty_name[..] && owner == *owner_pubkey { + Some(Some(kitty)) + } else { + None + } + }) + .next() + .unwrap_or(None); // Use unwrap_or to handle the Option + + println!("found_kitty = {:?}", found_kitty); + let is_kitty_found = match found_kitty { + Some(k) => Some(true), + None => Some(false), + }; + Ok(is_kitty_found) +} + +pub(crate) fn is_tradable_kitty_name_duplicate1( + db: &Db, + owner_pubkey: &H256, + name: String, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let found_kitty: (Option) = wallet_owned_kitty_tree + .iter() + .filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + println!("Name : {:?}", name); + + if kitty.kitty_basic_data.name == &kitty_name[..] && owner == *owner_pubkey { + Some(Some(kitty)) + } else { + None + } + }) + .next() + .unwrap_or(None); // Use unwrap_or to handle the Option + + println!("found_kitty = {:?}", found_kitty); + let is_kitty_found = match found_kitty { + Some(k) => Some(true), + None => Some(false), + }; + Ok(is_kitty_found) +} + +/// Debugging use. Print the entire unspent outputs tree. +pub(crate) fn print_owned_kitties(db: &Db) -> anyhow::Result<()> { + let wallet_unspent_tree = db.open_tree(UNSPENT)?; + for x in wallet_unspent_tree.iter() { + let (output_ref_ivec, owner_amount_ivec) = x?; + let output_ref = hex::encode(output_ref_ivec); + let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; + + println!("{output_ref}: owner {owner_pubkey:?}, amount {amount}"); + } + + Ok(()) +} + +pub(crate) fn get_all_kitties_from_local_db1( + db: &Db, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + + Ok(wallet_owned_kitty_tree.iter().filter_map(|raw_data| { + let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + Some((owner, kitty)) + })) +} + +/// Iterate the entire owned kitty +/// on a per-address basis. +pub(crate) fn get_all_tradable_kitties_from_local_db1( + db: &Db, +) -> anyhow::Result> { + let wallet_owned_tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + + Ok(wallet_owned_tradable_kitty_tree.iter().filter_map(|raw_data| { + let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + + Some((owner, kitty)) + })) +} + +pub(crate) fn get_owned_kitties_from_local_db<'a>( + db: &'a Db, + args: &'a ShowOwnedKittyArgs, +) -> anyhow::Result + 'a> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + + Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref_str = hex::encode(output_ref_ivec.clone()); + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + if owner == args.owner { + Some((owner, kitty, output_ref)) + } else { + None + } + })) +} + + +pub(crate) fn get_owned_tradable_kitties_from_local_db<'a>( + db: &'a Db, + args: &'a ShowOwnedKittyArgs, +) -> anyhow::Result + 'a> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + + Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref_str = hex::encode(output_ref_ivec.clone()); + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + if owner == args.owner { + Some((owner, kitty, output_ref)) + } else { + None + } + })) +} + + +pub(crate) fn get_kitty_from_local_db_based_on_name_bk( + db: &Db, + name: String, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; + + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let (found_kitty, output_ref) = wallet_owned_kitty_tree + .iter() + .filter_map(|raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + println!("Name : {:?} -> output_ref {:?}", name, output_ref.clone()); + + if kitty.name == &kitty_name[..] { + Some((Some(kitty), output_ref)) + } else { + None + } + }) + .next() + .unwrap_or(( + None, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )); // Use unwrap_or to handle the Option + + println!("output_ref = {:?}", output_ref); + println!("found_kitty = {:?}", found_kitty); + + Ok(found_kitty.map(|kitty| (kitty, output_ref))) +} + + +pub(crate) fn get_tradable_kitty_from_local_db_based_on_name( + db: &Db, + name: String, +) -> anyhow::Result> { + let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; + let mut array = [0; 4]; + let kitty_name: &[u8; 4] = { + array.copy_from_slice(name.as_bytes()); + &array + }; + + let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree + .iter() + .filter_map(move |raw_data| { + let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; + let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; + let output_ref_str = hex::encode(output_ref_ivec.clone()); + let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; + println!("Name : {:?} -> output_ref {:?}", name, output_ref.clone()); + + if kitty.kitty_basic_data.name == &kitty_name[..] { + Some((Some(kitty), output_ref)) + } else { + None + } + }) + .next() + .unwrap_or(( + None, + OutputRef { + tx_hash: H256::zero(), + index: 0, + }, + )); // Use unwrap_or to handle the Option + + println!("output_ref = {:?}", output_ref); + println!("found_kitty = {:?}", found_kitty); + + Ok(found_kitty.map(|kitty| (kitty, output_ref))) +} + +*/ diff --git a/webservice-wallet-with-inbuilt-key-store/src/timestamp.rs b/webservice-wallet-with-inbuilt-key-store/src/timestamp.rs new file mode 100644 index 000000000..d5da075a0 --- /dev/null +++ b/webservice-wallet-with-inbuilt-key-store/src/timestamp.rs @@ -0,0 +1,27 @@ +//! Wallet features related to on-chain timestamps. + +use anyhow::anyhow; +use parity_scale_codec::{Decode, Encode}; +use runtime::{timestamp::Timestamp, OuterVerifier}; +use sled::Db; +use tuxedo_core::types::Output; + +/// The identifier for the current timestamp in the db. +const TIMESTAMP: &str = "timestamp"; + +pub(crate) fn apply_transaction(db: &Db, output: &Output) -> anyhow::Result<()> { + let timestamp = output.payload.extract::()?.time; + let timestamp_tree = db.open_tree(TIMESTAMP)?; + timestamp_tree.insert([0], timestamp.encode())?; + Ok(()) +} + +/// Apply a transaction to the local database, storing the new timestamp. +pub(crate) fn get_timestamp(db: &Db) -> anyhow::Result { + let timestamp_tree = db.open_tree(TIMESTAMP)?; + let timestamp = timestamp_tree + .get([0])? + .ok_or_else(|| anyhow!("Could not find timestamp in database."))?; + u64::decode(&mut ×tamp[..]) + .map_err(|_| anyhow!("Could not decode timestamp from database.")) +} From 9e2c908d67d3cefcd0db081a5b6f42f6fbf45405 Mon Sep 17 00:00:00 2001 From: Amit Nadiger Date: Mon, 1 Apr 2024 21:43:03 +0530 Subject: [PATCH 14/14] Revert "web service implementation" This reverts commit 9c4f9030b7910c77ed08a5466fdca3ac487bc577. --- Cargo.toml | 2 - tuxedo-core/src/executive.rs | 11 +- webservice-wallet-external-signing/Cargo.toml | 36 - webservice-wallet-external-signing/Dockerfile | 35 - webservice-wallet-external-signing/README.md | 418 ------ .../src/amoeba.rs | 127 -- webservice-wallet-external-signing/src/cli.rs | 388 ------ .../src/keystore.rs | 138 -- .../src/kitty.rs | 1235 ----------------- .../src/main.rs | 350 ----- .../src/money.rs | 340 ----- .../src/output_filter.rs | 137 -- webservice-wallet-external-signing/src/rpc.rs | 61 - .../blockHandler/blockServicehandler.rs | 68 - .../keyHandler/keyServicehandler.rs | 63 - .../kittyHandler/kittyServicehandler.rs | 1146 --------------- .../moneyHandler/moneyServicehandler.rs | 124 -- .../src/sync.rs | 1199 ---------------- .../src/timestamp.rs | 27 - .../Cargo.toml | 36 - .../Dockerfile | 35 - .../README.md | 299 ---- .../src/TradableKitties.rs | 443 ------ .../src/amoeba.rs | 127 -- .../src/cli.rs | 388 ------ .../src/keystore.rs | 138 -- .../src/kitty.rs | 1045 -------------- .../src/main.rs | 311 ----- .../src/money.rs | 339 ----- .../src/output_filter.rs | 137 -- .../src/req_resp.rs | 24 - .../src/rpc.rs | 61 - .../blockHandler/blockServicehandler.rs | 74 - .../keyHandler/keyServicehandler.rs | 69 - .../kittyHandler/kittyServicehandler.rs | 841 ----------- .../moneyHandler/moneyServicehandler.rs | 68 - .../src/sync.rs | 1096 --------------- .../src/timestamp.rs | 27 - 38 files changed, 3 insertions(+), 11460 deletions(-) delete mode 100644 webservice-wallet-external-signing/Cargo.toml delete mode 100644 webservice-wallet-external-signing/Dockerfile delete mode 100644 webservice-wallet-external-signing/README.md delete mode 100644 webservice-wallet-external-signing/src/amoeba.rs delete mode 100644 webservice-wallet-external-signing/src/cli.rs delete mode 100644 webservice-wallet-external-signing/src/keystore.rs delete mode 100644 webservice-wallet-external-signing/src/kitty.rs delete mode 100644 webservice-wallet-external-signing/src/main.rs delete mode 100644 webservice-wallet-external-signing/src/money.rs delete mode 100644 webservice-wallet-external-signing/src/output_filter.rs delete mode 100644 webservice-wallet-external-signing/src/rpc.rs delete mode 100644 webservice-wallet-external-signing/src/serviceHandlers/blockHandler/blockServicehandler.rs delete mode 100644 webservice-wallet-external-signing/src/serviceHandlers/keyHandler/keyServicehandler.rs delete mode 100644 webservice-wallet-external-signing/src/serviceHandlers/kittyHandler/kittyServicehandler.rs delete mode 100644 webservice-wallet-external-signing/src/serviceHandlers/moneyHandler/moneyServicehandler.rs delete mode 100644 webservice-wallet-external-signing/src/sync.rs delete mode 100644 webservice-wallet-external-signing/src/timestamp.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/Cargo.toml delete mode 100644 webservice-wallet-with-inbuilt-key-store/Dockerfile delete mode 100644 webservice-wallet-with-inbuilt-key-store/README.md delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/TradableKitties.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/amoeba.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/cli.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/keystore.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/kitty.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/main.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/money.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/output_filter.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/req_resp.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/rpc.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/blockHandler/blockServicehandler.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/keyHandler/keyServicehandler.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/kittyHandler/kittyServicehandler.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/moneyHandler/moneyServicehandler.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/sync.rs delete mode 100644 webservice-wallet-with-inbuilt-key-store/src/timestamp.rs diff --git a/Cargo.toml b/Cargo.toml index 7e5116435..524a68b43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,6 @@ members = [ "tuxedo-parachain-core/register_validate_block", "tuxedo-parachain-core", "wallet", - "webservice-wallet-external-signing", - #"webservice-wallet-with-inbuilt-key-store", "wardrobe/amoeba", "wardrobe/money", "wardrobe/parachain", diff --git a/tuxedo-core/src/executive.rs b/tuxedo-core/src/executive.rs index d4c07ff95..aaec674c2 100644 --- a/tuxedo-core/src/executive.rs +++ b/tuxedo-core/src/executive.rs @@ -45,7 +45,7 @@ impl>, V: Verifier, C: ConstraintChecker pub fn validate_tuxedo_transaction( transaction: &Transaction, ) -> Result> { - log::info!( + debug!( target: LOG_TARGET, "validating tuxedo transaction", ); @@ -54,11 +54,6 @@ impl>, V: Verifier, C: ConstraintChecker // Duplicate peeks are allowed, although they are inefficient and wallets should not create such transactions { let input_set: BTreeSet<_> = transaction.inputs.iter().map(|o| o.encode()).collect(); - log::info!( - target: LOG_TARGET, - "input_set.len() {} and transaction.inputs.len() {}",input_set.len(), - transaction.inputs.len() - ); ensure!( input_set.len() == transaction.inputs.len(), UtxoError::DuplicateInput @@ -112,7 +107,7 @@ impl>, V: Verifier, C: ConstraintChecker index: index as u32, }; - log::info!( + debug!( target: LOG_TARGET, "Checking for pre-existing output {:?}", output_ref ); @@ -138,7 +133,7 @@ impl>, V: Verifier, C: ConstraintChecker // If any of the inputs are missing, we cannot make any more progress // If they are all present, we may proceed to call the constraint checker if !missing_inputs.is_empty() { - log::info!( + debug!( target: LOG_TARGET, "Transaction is valid but still has missing inputs. Returning early.", ); diff --git a/webservice-wallet-external-signing/Cargo.toml b/webservice-wallet-external-signing/Cargo.toml deleted file mode 100644 index 68ba3558f..000000000 --- a/webservice-wallet-external-signing/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -description = "A simple example / template wallet built for the tuxedo template runtime" -edition = "2021" -license = "Apache-2.0" -name = "tuxedo-template-web-service-wallet" -repository = "https://github.com/Off-Narrative-Labs/Tuxedo" -version = "1.0.0-dev" - -[dependencies] -runtime = { package = "tuxedo-template-runtime", path = "../tuxedo-template-runtime" } -tuxedo-core = { path = "../tuxedo-core" } - -anyhow = { workspace = true } -clap = { features = [ "derive" ], workspace = true } -directories = { workspace = true } -env_logger = { workspace = true } -futures = { workspace = true } -hex = { workspace = true } -hex-literal = { workspace = true } -jsonrpsee = { features = [ "http-client" ], workspace = true } -log = { workspace = true } -parity-scale-codec = { workspace = true } -serde_json = { workspace = true } -sled = { workspace = true } -tokio = { features = [ "full" ], workspace = true } - -rand = "0.8" - -sc-keystore = { workspace = true } -sp-core = { workspace = true } -sp-keystore = { workspace = true } -sp-runtime = { workspace = true } - -axum = "0.5.16" -serde = { version = "1.0", features = ["derive"] } -tower-http = { version = "0.3.4", features = ["cors"] } diff --git a/webservice-wallet-external-signing/Dockerfile b/webservice-wallet-external-signing/Dockerfile deleted file mode 100644 index b685c5129..000000000 --- a/webservice-wallet-external-signing/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -# This is a multi-stage docker file. See https://docs.docker.com/build/building/multi-stage/ -# for details about this pattern. - - - -# For the build stage, we use an image provided by Parity -FROM docker.io/paritytech/ci-linux:production as builder -WORKDIR /wallet -#TODO The Workdir and Copy command is different here than in the node... -COPY . . -RUN cargo build --locked --release -p tuxedo-template-wallet - - -# For the second stage, we use a minimal Ubuntu image -FROM docker.io/library/ubuntu:20.04 -LABEL description="Tuxedo Templet Wallet" - -COPY --from=builder /wallet/target/release/tuxedo-template-wallet /usr/local/bin - -RUN useradd -m -u 1000 -U -s /bin/sh -d /node-dev node-dev && \ - mkdir -p /wallet-data /node-dev/.local/share && \ - chown -R node-dev:node-dev /wallet-data && \ - # Make the wallet data directory available outside the container. - ln -s /wallet-data /node-dev/.local/share/tuxedo-template-wallet && \ - # unclutter and minimize the attack surface - rm -rf /usr/bin /usr/sbin && \ - # check if executable works in this container - /usr/local/bin/tuxedo-template-wallet --version - -USER node-dev - -EXPOSE 9944 -VOLUME ["/wallet-data"] - -ENTRYPOINT ["/usr/local/bin/tuxedo-template-wallet"] \ No newline at end of file diff --git a/webservice-wallet-external-signing/README.md b/webservice-wallet-external-signing/README.md deleted file mode 100644 index 03c8a7621..000000000 --- a/webservice-wallet-external-signing/README.md +++ /dev/null @@ -1,418 +0,0 @@ -# Tuxedo web service functionality - -A REST API for communicating with Tuxedo node template. - -## Overview - -This is a service built on Axum to support the decentralized application (DApp) built on the Tuxedo Blockchain that allows users to create, trade, breed, and manage virtual cats known as "Kitties". This README provides an overview of the available operations and REST APIs for interacting with the Cryptokitties platform. - -Like many UTXO wallets, this web service synchronizes a local-to-the-wallet database of UTXOs that exist on the current best chain.Let's call this as Indexer from now on. -The Indexer does not sync the entire blockchain state. -Rather, it syncs a subset of the state that it considers "relevant". -Currently, the Indexer syncs all relevant UTXOs i.e. Coins, KittyData, TradableKittyData, Timestamps. -However, the Indexer is designed so that this notion of "relevance" is generalizable. -This design allows developers building chains with Tuxedo to extend the Indexer for their own needs. -However, because this is a rest API-based web service, it is likely to be used by DApps which will leverage the REST API to achieve results. - -The overall idea behind the web service architecture: https://github.com/mlabs-haskell/TuxedoDapp/issues/35 - -Links : -**Sequence dig for API flow:** https://github.com/mlabs-haskell/TuxedoDapp/issues/35#issuecomment-2020211287 - -**Algorithm to create the redeemer:** https://github.com/mlabs-haskell/TuxedoDapp/issues/35#issuecomment-2015171702 - -**The overall procedure required from DApp**: https://github.com/mlabs-haskell/TuxedoDapp/issues/35#issuecomment-2011277263 - -**Difference between signed transaction and unsigned transaction example:** https://github.com/mlabs-haskell/TuxedoDapp/issues/35#issuecomment-2020399526 - -## REST Documentation - -Webservice can be run by using - -```sh -$ cargo run -``` - -## Guided tour for REST APIS usage - -This guided tour shows REST apis usage and curl command used to hit the endpoints : - -### Minting coins - -Rest apis for minting coins - -**end point:**: mint-coins - -**Amount to mint:** 6000 - -**Public_key of owner:** d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -```sh -$ curl -X POST -H "Content-Type: application/json" -d '{"amount": 6000,"owner_public_key":"d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}' http://localhost:3000/mint-coins - -``` - -### Get all coins - -Rest apis for getting all the coins stored in the web service. Basically web service stores all the coin UTXO which are synced from the genesis block to the current height. - -**end point:**: get-all-coins - -```sh -$ curl -X GET -H "Content-Type: application/json" http://localhost:3000/get-all-coins - -``` - -### Get all owned coins - -Rest API for getting all the coins owned by a particular user or public key in the web service. Web service stores all the coin utxos which are synced from the genesis block to the current height. Webservice will filter the coin UTXO filtered by the supplied public jey. - -**end point:**:get-owned-coins - -**Public_key of owner:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-owned-coins - -``` - -### Create kitty: - -Rest API for creating the kitty - -**end point:**:create-kitty - -**Name of kitty to be created:**:amit - -**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -**Returns:** Created kitty. - - -```sh -$ curl -X POST -H "Content-Type: application/json" -d '{"name": "amit","owner_public_key":"d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}' http://localhost:3000/create-kitty - -``` - -### Get all kitties: - -Rest API forgetting all the kitties stored in the local db. It returns all the kitties irrespective of onwer. - -**end point:**:get-all-kitty-list - -**Returns:** All basic kitties irrespective of owner. - -```sh -$ curl -X GET -H "Content-Type: application/json" http://localhost:3000/get-all-kitty-list - -``` - -### Get owned kitties: - -Rest API forgetting all the owned kitties by any particular owner i.e. public key stored in the local db. - -**end point:**:get-owned-kitty-list - -**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example : d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -**Returns:** All the kitties owned by the user i.e public key. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "owner_public_key: 563b6da067f38dc194cbe41ce0b840a985dcbef92b1e5b0a6e04f35544ddfd16" http://localhost:3000/get-owned-kitty-list - -``` -### Get kitty details by DNA : - -Rest API for getting all the details of the kitty by DNA. - -**end point:**:get-kitty-by-dna - -**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de - -**Returns:** The kitty whose DNA matches, else None. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de" http://localhost:3000/get-kitty-by-dna - -``` -## From now on all the below APIS will have two API Calls in Sequence for one operation: - -**1. Get Transaction and Input UTXO List:** - Retrieves the transaction and input list required for generating the Redeemer by the web DApp. This call is not routed to the blockchain but is handled entirely by the web service. - - **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service :** - Sends the signed transaction to the blockchain via web service for verification and validation using the verifier and constraint checker, respectively. - - -### List kitty for sale : -Rest API used for listing a Kitty for sale, converting it into a TradableKitty with an associated price. - -**1. Get Transaction and Input UTXO List for list kitty for sale:** - -**end point:**:get-txn-and-inpututxolist-for-listkitty-forsale - -**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de - -**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example : d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -**kitty-price:** Price of the kitty - -**Returns:** Transaction for listing a kitty for sale without redeemer. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 394bd079207af3e0b1a9b1eb1dc40d5d5694bd1fd904d56b96d6fad0039b1f7c" -H "kitty-price: 100" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-listkitty-forsale - -``` - **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** - - **end point:**:listkitty-for-sale - -**signed_transaction:**: Send the signed transaction. i.e all inputs should have redeemer to prove the ownership of spending or usage. - -**Returns:** Tradable kitty . - - ```sh -$ curl -X POST \ - -H "Content-Type: application/json" \ - -d '{ - "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0x0367d974927186bdeb3f1f1c111352711d9e1106a68bde6e4cfd0e64722e4f3a","index":0},"redeemer":[198, 69, 78, 148, 249, 1, 63, 2, 217, 105, 106, 87, 179, 252, 24, 66, 129, 190, 253, 17, 31, 87, 71, 231, 100, 31, 9, 81, 93, 141, 7, 81, 155, 0, 27, 38, 87, 16, 30, 55, 164, 220, 174, 37, 207, 163, 82, 216, 155, 195, 166, 253, 67, 95, 47, 240, 74, 20, 108, 160, 185, 71, 199, 129]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"TradableKitty":"ListKittiesForSale"}},"input_utxo_list":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}]}' \ - http://localhost:3000/listkitty-for-sale - -``` - -### Tradable kitty name update : -Rest API is used for updating the name of tradable kitty. - -**1. Get Transaction and Input UTXO List for name update of tradable kitty:** - -**end point:**:get-txn-and-inpututxolist-for-td-kitty-name-update - -**DNA of tradable kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de - -**Public_key of owner of tradable kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -**kitty-new-name:** New name of the kitty - -**Returns:** Transaction with tradable kitty name update without redeemer. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 394bd079207af3e0b1a9b1eb1dc40d5d5694bd1fd904d56b96d6fad0039b1f7c" -H "kitty-new-name: jbbl" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-td-kitty-name-update - -``` - **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** - - **end point:**:update-td-kitty-name - -**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. - - **Returns:** Tradable kitty with updated name. - - ```sh -$ curl -X POST \ - -H "Content-Type: application/json" \ - -d '{ - "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0xb696b071fdbdca1adcec9149d21a167a04d851693e97b70900ac7547e23c0d0e","index":0},"redeemer":[232, 135, 109, 225, 49, 100, 3, 154, 233, 14, 37, 46, 219, 87, 87, 126, 194, 46, 21, 194, 58, 138, 235, 176, 121, 59, 164, 20, 98, 31, 165, 109, 121, 81, 63, 97, 243, 214, 105, 123, 163, 143, 8, 179, 52, 18, 168, 140, 193, 238, 120, 215, 59, 174, 231, 168, 22, 92, 124, 114, 78, 51, 15, 129]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,98,98,108,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"TradableKitty":"UpdateKittiesName"}},"input_utxo_list":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}]}' \ - http://localhost:3000/update-td-kitty-name - -``` -### Tradable kitty price update : -Rest API is used for updating the price of tradable kitty. - -**1. Get Transaction and Input UTXO List for price update of tradable kitty:** - -**end point:**:get-txn-and-inpututxolist-for-td-kitty-name-update - -**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de - -**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -**kitty-new-name:** New name of the kitty - -**Returns:** Transaction with tradable kitty price update without redeemer. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 394bd079207af3e0b1a9b1eb1dc40d5d5694bd1fd904d56b96d6fad0039b1f7c" -H "kitty-new-name: jbbl" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-td-kitty-name-update - -``` - **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** - - **end point:**:update-td-kitty-name - -**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. - - **Returns:** Tradable kitty with updated price. - - ```sh -$ curl -X POST \ - -H "Content-Type: application/json" \ - -d '{ - "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0xb696b071fdbdca1adcec9149d21a167a04d851693e97b70900ac7547e23c0d0e","index":0},"redeemer":[232, 135, 109, 225, 49, 100, 3, 154, 233, 14, 37, 46, 219, 87, 87, 126, 194, 46, 21, 194, 58, 138, 235, 176, 121, 59, 164, 20, 98, 31, 165, 109, 121, 81, 63, 97, 243, 214, 105, 123, 163, 143, 8, 179, 52, 18, 168, 140, 193, 238, 120, 215, 59, 174, 231, 168, 22, 92, 124, 114, 78, 51, 15, 129]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,98,98,108,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"TradableKitty":"UpdateKittiesName"}},"input_utxo_list":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}]}' \ - http://localhost:3000/update-td-kitty-name -``` - -### De-List kitty from sale : -Rest API is used for removing a tradable Kitty from the sale, converting it back to a Basic Kitty without an associated price. - -**1. Get Transaction and Input UTXO List for delist-kitty-from-sale:** - -**end point:**:get-txn-and-inpututxolist-for-delist-kitty-from-sale - -**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de - -**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - - **Returns:** Transaction with a delisted kitty without redeemer.. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna:95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-delist-kitty-from-sale - -``` - **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** - - **end point:**:update-td-kitty-name - -**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. - - **Returns:** Basic kitty. - - ```sh -$ curl -X POST \ - -H "Content-Type: application/json" \ - -d '{ - "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0xe680ce989ddaa35c7ed9f3ec1f48ff956457e00a9f4635bd97f2e682cf7e300a","index":0},"redeemer":[74, 200, 62, 251, 42, 74, 130, 155, 97, 200, 209, 13, 99, 178, 179, 5, 181, 124, 177, 221, 67, 131, 151, 81, 188, 224, 7, 56, 253, 244, 36, 76, 23, 177, 67, 218, 177, 229, 88, 178, 78, 42, 182, 143, 133, 172, 75, 96, 169, 132, 83, 203, 16, 210, 96, 190, 19, 118, 84, 78, 40, 56, 236, 128]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,98,98,108],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"TradableKitty":"DelistKittiesFromSale"}},"input_utxo_list":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,98,98,108,231,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}]}' \ - http://localhost:3000/delist-kitty-from-sale - -``` - -### kitty name update : -Rest API is used for updating the name of basic kitty. - -**1. Get Transaction and Input UTXO List for name update of kitty:** - -**end point:**:get-txn-and-inpututxolist-for-kitty-name-update - -**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de - -**Public_key of owner of kitty:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -**kitty-new-name:** New name of the kitty - -**Returns:** Transaction with kitty name update without redeemer. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de" -H "kitty-new-name: jram" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-kitty-name-update - -``` - **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** - - **end point:**:update-kitty-name - -**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. - -**Returns:** Kitty with an updated name. - - ```sh -$ curl -X POST \ - -H "Content-Type: application/json" \ - -d '{ - "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0x9492d8c80fb5a8cf2720c0072d00c91c821502894fa4482a9c99fc027bf22daf","index":0},"redeemer":[132, 84, 163, 3, 64, 12, 74, 150, 176, 70, 223, 124, 252, 222, 23, 187, 141, 55, 207, 97, 55, 172, 128, 201, 147, 148, 8, 228, 108, 113, 36, 24, 10, 118, 178, 195, 8, 124, 127, 238, 172, 23, 127, 249, 203, 109, 196, 101, 76, 64, 162, 102, 184, 93, 63, 187, 193, 247, 129, 94, 44, 84, 200, 141]}],"peeks":[],"outputs":[{"payload":{"data":[1,0,2,0,0,0,0,0,0,0,57,75,208,121,32,122,243,224,177,169,177,235,29,196,13,93,86,148,189,31,217,4,213,107,150,214,250,208,3,155,31,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,114,97,109],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"FreeKitty":"UpdateKittiesName"}}}' \ - http://localhost:3000/update-kitty-name - -``` - -### Breed kitty : -Rest API is used for breeding a new Kitty from two parent Kitties, creating a child DNA based on both - -**1. Get Transaction and Input UTXO List for breed kitty:** - -**end point:**:get-txn-and-inpututxolist-for-breed-kitty - -**DNA of mom kitty:** Input the DNA of kitty. Note it should start without 0X. Example e9243fb13a45a51d221cfca21a1a197aa35a1f0723cae3497fda971c825cb1d6 - -**DNA of dad kitty:** Input the DNA of kitty. Note it should start without 0X. Example 9741b6456f4b82bb243adfe5e887de9ce3a70e01d7ab39c0f9f565b24a2b059b - -**Public_key of the owner of kitties:** Public key of owner: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -**"child-kitty-name** Name of child kitty - -**Returns:** Transaction with breeding info such as mom, dad, child i.e. new family without a redeemer. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "mom-dna: e9243fb13a45a51d221cfca21a1a197aa35a1f0723cae3497fda971c825cb1d6" -H "dad-dna: 9741b6456f4b82bb243adfe5e887de9ce3a70e01d7ab39c0f9f565b24a2b059b" -H "child-kitty-name: jram" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-txn-and-inpututxolist-for-breed-kitty - -``` - **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** - - **end point:**:breed-kitty - -**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. - -**Returns:** New family. I.e Mom kitty, Dad kitty and Child kitty. The mom and dad will have breeding status updated EX: From raringToGo to Tired or hadRecentBirth. - - ```sh -$ curl -X POST \ - -H "Content-Type: application/json" \ - -d '{ - "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0x8f83929cfc36c5ea445787421278f0688a2e7b482e71bd75d5ac7f36028c575b","index":0},"redeemer":[238, 126, 35, 95, 5, 149, 96, 160, 143, 172, 139, 56, 130, 116, 141, 93, 52, 181, 62, 9, 81, 32, 56, 199, 30, 48, 28, 186, 247, 72, 180, 125, 163, 197, 198, 5, 254, 86, 113, 164, 20, 112, 49, 37, 217, 91, 175, 248, 183, 126, 250, 169, 118, 165, 213, 242, 27, 47, 249, 32, 158, 89, 232, 141]},{"output_ref":{"tx_hash":"0x6bb11e2df46081e9252787342116b0b32be9d3302ca1dac535df85642ba46242","index":0},"redeemer":[112, 18, 73, 37, 101, 45, 254, 161, 83, 84, 12, 135, 125, 65, 6, 235, 200, 84, 16, 109, 12, 247, 240, 52, 116, 11, 46, 109, 86, 241, 69, 26, 223, 154, 215, 190, 247, 110, 248, 75, 246, 71, 126, 223, 23, 180, 233, 209, 98, 9, 178, 82, 46, 52, 110, 251, 52, 223, 232, 182, 82, 226, 5, 143]}],"peeks":[],"outputs":[{"payload":{"data":[0,1,1,0,0,0,0,0,0,0,233,36,63,177,58,69,165,29,34,28,252,162,26,26,25,122,163,90,31,7,35,202,227,73,127,218,151,28,130,92,177,214,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}},{"payload":{"data":[1,1,1,0,0,0,0,0,0,0,151,65,182,69,111,75,130,187,36,58,223,229,232,135,222,156,227,167,14,1,215,171,57,192,249,245,101,178,74,43,5,155,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,116,105],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}},{"payload":{"data":[0,0,2,0,0,0,0,0,0,0,191,64,163,127,195,246,227,90,81,218,5,243,219,78,156,51,82,162,4,192,66,249,180,130,64,229,219,239,136,216,243,153,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,106,114,97,109],"type_id":[75,105,116,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}}],"checker":{"FreeKitty":"Breed"}}}' \ - http://localhost:3000/breed-kitty - -``` -**The output message of breed looks like the below :** - ```sh -$O/P message : -{"message":"Kitty breeding done successfully","mom_kitty":{"parent":{"Mom":"HadBirthRecently"},"free_breedings":1,"dna":"0xe9243fb13a45a51d221cfca21a1a197aa35a1f0723cae3497fda971c825cb1d6","num_breedings":1,"name":[97,109,105,116]},"dad_kitty":{"parent":{"Dad":"Tired"},"free_breedings":1,"dna":"0x9741b6456f4b82bb243adfe5e887de9ce3a70e01d7ab39c0f9f565b24a2b059b","num_breedings":1,"name":[97,109,116,105]},"child_kitty":{"parent":{"Mom":"RearinToGo"},"free_breedings":2,"dna":"0xbf40a37fc3f6e35a51da05f3db4e9c3352a204c042f9b48240e5dbef88d8f399","num_breedings":0,"name":[106,114,97,109]}} - -``` - -### Buy tradable kitty : -Rest API that allows buying a Tradable Kitty from a seller using cryptocurrency i.e money/coin - -**1. Get Transaction and Input UTXO List for buying tradable kitty:** - -**end point:**:get-txn-and-inpututxolist-for-buy-kitty - -**DNA of kitty:** Input the DNA of kitty. Note it should start without 0X. Example 95b951b609a4434b19eb4435dc4fe3eb6f0102ff3448922d933e6edf6b14f6de - -**input-coins:** Reference of input coins owned by the buyer to be used for buying. We can input multiple input coins. EX: 4d732d8b0d0995151617c5c3beb600dc07a9e1be9fc8e95d9c792be42d65911000000000 - -**output_amount:** The amount to be paid for transaction which should be >= price of kitty. - -**buyer_public_key:** Public key of buyer i.e owner of coins used for buying: Note it should start without 0X. Example: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 - -**seller_public_key:** Public key of seller i.e owner of kitty to be sold: Note it should start without 0X. Example: fab33c8c12f8df78fa515faa2fcc4bbf7829804a4d187984e13253660a9c1223 - -**Returns:** Transaction containing coins and kitty used in trading along with public keys of owner without redeemer. - -```sh -$ curl -X GET -H "Content-Type: application/json" -H "kitty-dna: bc147303f7d0a361ac22a50bf2ca2ec513d926a327ed678827c90d6512feadd6" -H "input-coins: 4d732d8b0d0995151617c5c3beb600dc07a9e1be9fc8e95d9c792be42d65911000000000" -H "output_amount: 200" -H "buyer_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" -H "seller_public_key: fab33c8c12f8df78fa515faa2fcc4bbf7829804a4d187984e13253660a9c1223"http://localhost:3000/get-txn-and-inpututxolist-for-buy-kitty - -``` - **2. Perform Actual Operation i.e send the signed transaction to the blockchain via web service:** - - **end point:**:/buy_kitty - -**signed_transaction:**: Send the signed transaction. i.e all inputs should have a redeemer to prove the ownership of spending or usage. - - **Returns:** Traded kitty with success or fail message. - - ```sh -$ curl -X POST \ - -H "Content-Type: application/json" \ - -d '{ - "signed_transaction": {"inputs":[{"output_ref":{"tx_hash":"0x9bffe2abf274e0008f3f34af60cd083e909f884f2064e10f25ca46166306ae81","index":0},"redeemer":[134, 152, 55, 235, 162, 163, 255, 144, 247, 94, 237, 234, 127, 220, 149, 66, 226, 223, 43, 116, 16, 156, 165, 251, 221, 234, 13, 136, 132, 189, 187, 27, 206, 197, 48, 23, 188, 43, 41, 94, 103, 242, 174, 100, 249, 158, 206, 55, 88, 199, 103, 246, 227, 126, 138, 252, 205, 7, 132, 3, 112, 239, 52, 129]},{"output_ref":{"tx_hash":"0x4d732d8b0d0995151617c5c3beb600dc07a9e1be9fc8e95d9c792be42d659110","index":0},"redeemer":[166, 2, 32, 88, 200, 30, 54, 252, 155, 169, 122, 237, 29, 44, 33, 22, 102, 77, 71, 128, 35, 214, 84, 147, 193, 59, 45, 110, 69, 52, 25, 75, 5, 248, 227, 232, 110, 165, 177, 178, 218, 240, 235, 61, 25, 248, 242, 132, 106, 115, 62, 88, 57, 238, 39, 150, 202, 64, 237, 111, 147, 210, 215, 131]}],"peeks":[],"outputs":[{"payload":{"data":[0,0,2,0,0,0,0,0,0,0,188,20,115,3,247,208,163,97,172,34,165,11,242,202,46,197,19,217,38,163,39,237,103,136,39,201,13,101,18,254,173,214,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,109,105,116,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[116,100,107,116]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"}}},{"payload":{"data":[200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"type_id":[99,111,105,0]},"verifier":{"Sr25519Signature":{"owner_pubkey":"0xfab33c8c12f8df78fa515faa2fcc4bbf7829804a4d187984e13253660a9c1223"}}}],"checker":{"TradableKitty":"Buy"}}}' \ - http://localhost:3000/buy_kitty -``` - -After the transaction is success.We can verify the kitty is transferred to buyer and coins are transferred to the seller using the below rest APIS : - - ```sh -$ curl -X GET -H "Content-Type: application/json" -H "owner_public_key: d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67" http://localhost:3000/get-owned-kitty-list - - -curl -X GET -H "Content-Type: application/json" -H "owner_public_key: fab33c8c12f8df78fa515faa2fcc4bbf7829804a4d187984e13253660a9c1223" http://localhost:3000/get-owned-kitty-list - -``` - -My test results of buy kitty: https://github.com/mlabs-haskell/TuxedoDapp/issues/27#issuecomment-2029302071 - -Please also see the below link for how to achieve the buy transaction which involves of signing from both buyer and seller in the same transaction : https://github.com/Off-Narrative-Labs/Tuxedo/issues/169 diff --git a/webservice-wallet-external-signing/src/amoeba.rs b/webservice-wallet-external-signing/src/amoeba.rs deleted file mode 100644 index 4ed66b282..000000000 --- a/webservice-wallet-external-signing/src/amoeba.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! Toy off-chain process to create an amoeba and perform mitosis on it - -use crate::rpc::fetch_storage; - -use std::{thread::sleep, time::Duration}; - -use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; -use parity_scale_codec::Encode; -use runtime::{ - amoeba::{AmoebaCreation, AmoebaDetails, AmoebaMitosis}, - OuterVerifier, Transaction, -}; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use tuxedo_core::{ - types::{Input, Output, OutputRef}, - verifier::UpForGrabs, -}; - -pub async fn amoeba_demo(client: &HttpClient) -> anyhow::Result<()> { - // Construct a simple amoeba spawning transaction (no signature required) - let eve = AmoebaDetails { - generation: 0, - four_bytes: *b"eve_", - }; - let spawn_tx = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: vec![Output { - payload: eve.into(), - verifier: UpForGrabs.into(), - }], - checker: AmoebaCreation.into(), - }; - - // Calculate the OutputRef which also serves as the storage location - let eve_ref = OutputRef { - tx_hash: ::hash_of(&spawn_tx.encode()), - index: 0, - }; - - // Send the transaction - let spawn_hex = hex::encode(spawn_tx.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - println!("Node's response to spawn transaction: {:?}", spawn_response); - - // Wait a few seconds to make sure a block has been authored. - sleep(Duration::from_secs(3)); - - // Check that the amoeba is in storage and print its details - let eve_from_storage: AmoebaDetails = fetch_storage::(&eve_ref, client) - .await? - .payload - .extract()?; - println!("Eve Amoeba retrieved from storage: {:?}", eve_from_storage); - - // Create a mitosis transaction on the Eve amoeba - let cain = AmoebaDetails { - generation: 1, - four_bytes: *b"cain", - }; - let able = AmoebaDetails { - generation: 1, - four_bytes: *b"able", - }; - let mitosis_tx = Transaction { - inputs: vec![Input { - output_ref: eve_ref, - redeemer: Vec::new(), - }], - peeks: Vec::new(), - outputs: vec![ - Output { - payload: cain.into(), - verifier: UpForGrabs.into(), - }, - Output { - payload: able.into(), - verifier: UpForGrabs.into(), - }, - ], - checker: AmoebaMitosis.into(), - }; - - // Calculate the two OutputRefs for the daughters - let cain_ref = OutputRef { - tx_hash: ::hash_of(&mitosis_tx.encode()), - index: 0, - }; - let able_ref = OutputRef { - tx_hash: ::hash_of(&mitosis_tx.encode()), - index: 1, - }; - - // Send the mitosis transaction - let mitosis_hex = hex::encode(mitosis_tx.encode()); - let params = rpc_params![mitosis_hex]; - let mitosis_response: Result = - client.request("author_submitExtrinsic", params).await; - println!( - "Node's response to mitosis transaction: {:?}", - mitosis_response - ); - - // Wait a few seconds to make sure a block has been authored. - sleep(Duration::from_secs(3)); - - // Check that the daughters are in storage and print their details - let cain_from_storage: AmoebaDetails = fetch_storage::(&cain_ref, client) - .await? - .payload - .extract()?; - println!( - "Cain Amoeba retrieved from storage: {:?}", - cain_from_storage - ); - let able_from_storage: AmoebaDetails = fetch_storage::(&able_ref, client) - .await? - .payload - .extract()?; - println!( - "Able Amoeba retrieved from storage: {:?}", - able_from_storage - ); - - Ok(()) -} diff --git a/webservice-wallet-external-signing/src/cli.rs b/webservice-wallet-external-signing/src/cli.rs deleted file mode 100644 index 3d133421c..000000000 --- a/webservice-wallet-external-signing/src/cli.rs +++ /dev/null @@ -1,388 +0,0 @@ -//! Tuxedo Template Wallet's Command Line Interface. -//! -//! Built with clap's derive macros. - -use std::path::PathBuf; - -use clap::{ArgAction::Append, Args, Parser, Subcommand}; -use sp_core::H256; -use tuxedo_core::types::OutputRef; - -use crate::{h256_from_string, keystore::SHAWN_PUB_KEY, output_ref_from_string, DEFAULT_ENDPOINT}; - -/// The default number of coins to be minted. -pub const DEFAULT_MINT_VALUE: &str = "100"; - -/// Default recipient is SHAWN_KEY and output amount is 0 -pub const DEFAULT_RECIPIENT: &str = "d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 0"; - -/// The default name of the kitty to be created. Show be 4 character long -pub const DEFAULT_KITTY_NAME: &str = " "; - -/// The default gender of the kitty to be created. -pub const DEFAULT_KITTY_GENDER: &str = "female"; - - - -fn parse_recipient_coins(s: &str) -> Result<(H256, Vec), &'static str> { - println!("In parse_recipient_coins"); - let parts: Vec<&str> = s.split_whitespace().collect(); - if parts.len() >= 2 { - let recipient = h256_from_string(parts[0]); - let coins = parts[1..].iter().filter_map(|&c| c.parse().ok()).collect(); - match recipient { - Ok(r) => { - println!("Recipient: {}", r); - return Ok((r, coins)); - }, - _ => {}, - }; - } - println!("Sending the error value "); - Err("Invalid input format") -} - - - - -/// The wallet's main CLI struct -#[derive(Debug, Parser)] -#[command(about, version)] -pub struct Cli { - #[arg(long, short, default_value_t = DEFAULT_ENDPOINT.to_string())] - /// RPC endpoint of the node that this wallet will connect to. - pub endpoint: String, - - #[arg(long, short)] - /// Path where the wallet data is stored. Default value is platform specific. - pub path: Option, - - #[arg(long, verbatim_doc_comment)] - /// Skip the initial sync that the wallet typically performs with the node. - /// The wallet will use the latest data it had previously synced. - pub no_sync: bool, - - #[arg(long)] - /// A temporary directory will be created to store the configuration and will be deleted at the end of the process. - /// path will be ignored if this is set. - pub tmp: bool, - - #[arg(long, verbatim_doc_comment)] - /// Specify a development wallet instance, using a temporary directory (like --tmp). - /// The keystore will contain the development key Shawn. - pub dev: bool, - - #[command(subcommand)] - pub command: Option, -} - -/// The tasks supported by the wallet -#[derive(Debug, Subcommand)] -pub enum Command { - /// Print the block based on block height. - /// get the block hash ad print the block. - GetBlock { - /// Input the blockheight to be retrived. - block_height: Option, // fixme - }, - - /* - /// Demonstrate creating an amoeba and performing mitosis on it. - AmoebaDemo, - */ - /// Mint coins , optionally amount and publicKey of owner can be passed - /// if amount is not passed , 100 coins are minted - /// If publickKey of owner is not passed , then by default SHAWN_PUB_KEY is used. - #[command(verbatim_doc_comment)] - MintCoins(MintCoinArgs), - - /// Verify that a particular coin exists. - /// Show its value and owner from both chain storage and the local database. - #[command(verbatim_doc_comment)] - VerifyCoin { - /// A hex-encoded output reference - #[arg(value_parser = output_ref_from_string)] - output_ref: OutputRef, - }, - - //Some(Command::MintCoins { amount }) => money::mint_coins(&db, &client, &keystore,amount).await, - /// Spend some coins. - /// For now, all outputs in a single transaction go to the same recipient. - // FixMe: #62 - #[command(verbatim_doc_comment)] - SpendCoins(SpendArgs), - - /// Insert a private key into the keystore to later use when signing transactions. - InsertKey { - /// Seed phrase of the key to insert. - seed: String, - // /// Height from which the blockchain should be scanned to sync outputs - // /// belonging to this address. If non is provided, no re-syncing will - // /// happen and this key will be treated like a new key. - // sync_height: Option, - }, - - /// Generate a private key using either some or no password and insert into the keystore. - GenerateKey { - /// Initialize a public/private key pair with a password - password: Option, - }, - - /// Show public information about all the keys in the keystore. - ShowKeys, - - /// Remove a specific key from the keystore. - /// WARNING! This will permanently delete the private key information. - /// Make sure your keys are backed up somewhere safe. - #[command(verbatim_doc_comment)] - RemoveKey { - /// The public key to remove - #[arg(value_parser = h256_from_string)] - pub_key: H256, - }, - - /// For each key tracked by the wallet, shows the sum of all UTXO values owned by that key. - /// This sum is sometimes known as the "balance". - #[command(verbatim_doc_comment)] - ShowBalance, - - /// Show the complete list of UTXOs known to the wallet. - ShowAllOutputs, - - /// Show the latest on-chain timestamp. - ShowTimestamp, - - /// Create Kitty without mom and dad. - CreateKitty(CreateKittyArgs), - - /// Verify that a particular kitty exists. - /// Show its details and owner from both chain storage and the local database. - - #[command(verbatim_doc_comment)] - VerifyKitty { - /// A hex-encoded output reference - #[arg(value_parser = output_ref_from_string)] - output_ref: OutputRef, - }, - - /// Breed Kitties. - #[command(verbatim_doc_comment)] - BreedKitty(BreedKittyArgs), - - /// List Kitty for sale. - ListKittyForSale(ListKittyForSaleArgs), - - /// Delist Kitty from sale. - DelistKittyFromSale(DelistKittyFromSaleArgs), - - /// Update kitty name. - UpdateKittyName(UpdateKittyNameArgs), - - /// Update kitty price. Applicable only to tradable kitties - UpdateKittyPrice(UpdateKittyPriceArgs), - - /// Buy Kitty. - #[command(verbatim_doc_comment)] - BuyKitty(BuyKittyArgs), - - /// Show all kitties key tracked by the wallet. - #[command(verbatim_doc_comment)] - ShowAllKitties, - - /// ShowOwnedKitties. - /// Shows the kitties owned by owner. - #[command(verbatim_doc_comment)] - ShowOwnedKitties(ShowOwnedKittyArgs), -} - -#[derive(Debug, Args)] -pub struct MintCoinArgs { - /// Pass the amount to be minted. - #[arg(long, short, verbatim_doc_comment, action = Append,default_value = DEFAULT_MINT_VALUE)] - pub amount: u128, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -/* -// Old implementation -#[derive(Debug, Args)] -pub struct SpendArgs { - /// An input to be consumed by this transaction. This argument may be specified multiple times. - /// They must all be coins. - #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] - pub input: Vec, - - // /// All inputs to the transaction will be from this same sender. - // /// When not specified, inputs from any owner are chosen indiscriminantly - // #[arg(long, short, value_parser = h256_from_string)] - // sender: Option, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the recipient. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub recipient: H256, - - // The `action = Append` allows us to accept the same value multiple times. - /// An output amount. For the transaction to be valid, the outputs must add up to less than the sum of the inputs. - /// The wallet will not enforce this and will gladly send an invalid which will then be rejected by the node. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub output_amount: Vec, -} -*/ - -#[derive(Debug, Args,Clone)] -pub struct SpendArgs { - /// An input to be consumed by this transaction. This argument may be specified multiple times. - /// They must all be coins. - #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] - pub input: Vec, - - /// Variable number of recipients and their associated coins. - /// For example, "--recipients 0x1234 1 2 --recipients 0x5678 3 4 6" - // #[arg(long, short, verbatim_doc_comment, value_parser = parse_recipient_coins, action = Append)] - // pub recipients: Vec<(H256, Vec)>, - #[arg(long, short, verbatim_doc_comment, value_parser = parse_recipient_coins, action = Append, - default_value = DEFAULT_RECIPIENT)] - pub recipients: Vec<(H256, Vec)>, -} - -#[derive(Debug, Args)] -pub struct CreateKittyArgs { - - /// Pass the name of the kitty to be minted. - /// If kitty name is not passed ,name is choosen randomly from predefine name vector. - #[arg(long, short, action = Append, default_value = DEFAULT_KITTY_NAME)] - pub kitty_name: String, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct ShowOwnedKittyArgs { - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct BreedKittyArgs { - /// Name of Mom to be used for breeding . - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub mom_name: String, - - /// Name of Dad to be used for breeding . - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub dad_name: String, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct UpdateKittyNameArgs { - /// Current name of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub current_name: String, - - /// New name of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub new_name: String, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct UpdateKittyPriceArgs { - /// Current name of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub current_name: String, - - /// Price of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub price: u128, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct BuyKittyArgs { - /// An input to be consumed by this transaction. This argument may be specified multiple times. - /// They must all be coins. - #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] - pub input: Vec, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the seller of tradable kitty. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub seller: H256, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner of tradable kitty. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, - - /// Name of kitty to be bought. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub kitty_name: String, - - // The `action = Append` allows us to accept the same value multiple times. - /// An output amount. For the transaction to be valid, the outputs must add up to less than the sum of the inputs. - /// The wallet will not enforce this and will gladly send an invalid which will then be rejected by the node. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub output_amount: Vec, -} - -#[derive(Debug, Args)] -pub struct ListKittyForSaleArgs { - /// Pass the name of the kitty to be listed for sale. - #[arg(long, short, verbatim_doc_comment, action = Append, default_value = DEFAULT_KITTY_NAME)] - pub name: String, - - /// Price of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub price: u128, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct DelistKittyFromSaleArgs { - /// Pass the name of the kitty to be delisted or removed from the sale . - #[arg(long, short, verbatim_doc_comment, action = Append, default_value = DEFAULT_KITTY_NAME)] - pub name: String, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} diff --git a/webservice-wallet-external-signing/src/keystore.rs b/webservice-wallet-external-signing/src/keystore.rs deleted file mode 100644 index 34e6bb8d9..000000000 --- a/webservice-wallet-external-signing/src/keystore.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! Wallet's local keystore. -//! -//! This is a thin wrapper around sc-cli for use in tuxedo wallets - -use anyhow::anyhow; -use parity_scale_codec::Encode; -use sc_keystore::LocalKeystore; -use sp_core::{ - crypto::Pair as PairT, - sr25519::{Pair, Public}, - H256, -}; -use sp_keystore::Keystore; -use sp_runtime::KeyTypeId; -use std::path::Path; -use crate::get_local_keystore; -/// A KeyTypeId to use in the keystore for Tuxedo transactions. We'll use this everywhere -/// until it becomes clear that there is a reason to use multiple of them -const KEY_TYPE: KeyTypeId = KeyTypeId(*b"_tux"); - -/// A default seed phrase for signing inputs when none is provided -/// Corresponds to the default pubkey. -pub const SHAWN_PHRASE: &str = - "news slush supreme milk chapter athlete soap sausage put clutch what kitten"; - -/// The public key corresponding to the default seed above. -pub const SHAWN_PUB_KEY: &str = "d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"; - -/// Insert the example "Shawn" key into the keystore for the current session only. -pub fn insert_development_key_for_this_session(keystore: &LocalKeystore) -> anyhow::Result<()> { - keystore.sr25519_generate_new(KEY_TYPE, Some(SHAWN_PHRASE))?; - Ok(()) -} - -/// Sign a given message with the private key that corresponds to the given public key. -/// -/// Returns an error if the keystore itself errors, or does not contain the requested key. -pub fn sign_with( - keystore: &LocalKeystore, - public: &Public, - message: &[u8], -) -> anyhow::Result> { - - let sig = keystore - .sr25519_sign(KEY_TYPE, public, message)? - .ok_or(anyhow!("Key doesn't exist in keystore"))?; - - Ok(sig.encode()) -} - -/// Insert the private key associated with the given seed into the keystore for later use. -pub fn insert_key(keystore: &LocalKeystore, seed: &str) -> anyhow::Result<()> { - // We need to provide a public key to the keystore manually, so let's calculate it. - let public_key = Pair::from_phrase(seed, None)?.0.public(); - println!("The generated public key is {:?}", public_key); - keystore - .insert(KEY_TYPE, seed, public_key.as_ref()) - .map_err(|()| anyhow!("Error inserting key"))?; - Ok(()) -} - -/// Generate a new key from system entropy and insert it into the keystore, optionally -/// protected by a password. -/// -/// TODO there is no password support when using keys later when signing. -/* -pub fn generate_key(keystore: &LocalKeystore, password: Option) -> anyhow::Result<()> { - - let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); - println!("Generated public key is {:?}", pair.public()); - println!("Generated Phrase is {}", phrase); - keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; - Ok(()) -} - - -pub async fn generate_key(password: Option) -> anyhow::Result<(String, String)> { - let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); - let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); - println!("Generated public key is {:?}", pair.public()); - println!("Generated Phrase is {}", phrase); - keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; - insert_key(&keystore,&phrase.as_str()); - - get_keys(&keystore)?.for_each(|pubkey| { - println!("key: 0x{}", hex::encode(pubkey)); - }); - - Ok((pair.public().to_string(), phrase)) -} - -pub fn get_keys(keystore: &LocalKeystore) -> anyhow::Result>> { - Ok(keystore.keys(KEY_TYPE)?.into_iter()) -} -*/ -pub async fn generate_key(password: Option) -> anyhow::Result<(String, String)> { - let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); - let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); - println!("Generated public key is {:?}", pair.public()); - println!("Generated Phrase is {}", phrase); - - // Insert the generated key pair into the keystore - - keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; - insert_key(&keystore,&phrase.as_str()); - get_keys().await?.for_each(|pubkey| { - println!("key: 0x{}", hex::encode(pubkey)); - }); - - let public_key_hex = hex::encode(pair.public()); - - Ok((public_key_hex.to_string(), phrase)) -} - - -/// Check whether a specific key is in the keystore -pub fn has_key(keystore: &LocalKeystore, pubkey: &H256) -> bool { - keystore.has_keys(&[(pubkey.encode(), KEY_TYPE)]) -} - -pub async fn get_keys() -> anyhow::Result>> { - let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); - - Ok(keystore.keys(KEY_TYPE)?.into_iter()) -} - - -/// Caution. Removes key from keystore. Call with care. -pub fn remove_key(keystore_path: &Path, pub_key: &H256) -> anyhow::Result<()> { - // The keystore doesn't provide an API for removing keys, so we - // remove them from the filesystem directly - let filename = format!("{}{}", hex::encode(KEY_TYPE.0), hex::encode(pub_key.0)); - let path = keystore_path.join(filename); - - std::fs::remove_file(path)?; - - Ok(()) -} diff --git a/webservice-wallet-external-signing/src/kitty.rs b/webservice-wallet-external-signing/src/kitty.rs deleted file mode 100644 index f0f91dfca..000000000 --- a/webservice-wallet-external-signing/src/kitty.rs +++ /dev/null @@ -1,1235 +0,0 @@ -use crate::money::get_coin_from_storage; -use crate::rpc::fetch_storage; -use crate::sync; - -//use crate::cli::BreedArgs; -use tuxedo_core::{ - dynamic_typing::UtxoData, - types::{Input, Output, OutputRef}, - verifier::Sr25519Signature, -}; - -use anyhow::anyhow; -use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; -use parity_scale_codec::Encode; -use rand::distributions::Alphanumeric; -use rand::Rng; -use sc_keystore::LocalKeystore; -use sled::Db; -use sp_core::sr25519::Public; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use crate::get_local_keystore; - -use runtime::{ - kitties::{ - DadKittyStatus, FreeKittyConstraintChecker, KittyDNA, KittyData, - MomKittyStatus, Parent, - }, - money::{Coin}, - tradable_kitties::{TradableKittyConstraintChecker, TradableKittyData}, - OuterVerifier, Transaction, -}; - -use crate::cli::DEFAULT_KITTY_NAME; - -use crate::cli::{ - BreedKittyArgs, BuyKittyArgs, CreateKittyArgs, DelistKittyFromSaleArgs, - UpdateKittyNameArgs, UpdateKittyPriceArgs, -}; -use parity_scale_codec::Decode; - -static MALE_KITTY_NAMES: [&str; 50] = [ - "Whis", "Purr", "Feli", "Leo", "Maxi", "Osca", "Simb", "Char", "Milo", "Tige", "Jasp", - "Smokey", "Oliv", "Loki", "Boot", "Gizm", "Rock", "Budd", "Shad", "Zeus", "Tedd", "Samm", - "Rust", "Tom", "Casp", "Blue", "Coop", "Coco", "Mitt", "Bent", "Geor", "Luck", "Rome", "Moch", - "Muff", "Ches", "Whis", "Appl", "Hunt", "Toby", "Finn", "Frod", "Sale", "Kobe", "Dext", "Jinx", - "Mick", "Pump", "Thor", "Sunn", -]; - -// Female kitty names -static FEMALE_KITTY_NAMES: [&str; 50] = [ - "Luna", "Mist", "Cleo", "Bell", "Lucy", "Nala", "Zoe", "Dais", "Lily", "Mia", "Chlo", "Stel", - "Coco", "Will", "Ruby", "Grac", "Sash", "Moll", "Lola", "Zara", "Mist", "Ange", "Rosi", "Soph", - "Zeld", "Layl", "Ambe", "Prin", "Cind", "Moch", "Zara", "Dais", "Cinn", "Oliv", "Peac", "Pixi", - "Harl", "Mimi", "Pipe", "Whis", "Cher", "Fion", "Kiki", "Suga", "Peac", "Ange", "Mapl", "Zigg", - "Lily", "Nova", -]; - -pub fn generate_random_string(length: usize) -> String { - let rng = rand::thread_rng(); - let random_string: String = rng - .sample_iter(&Alphanumeric) - .take(length) - .map(char::from) - .collect(); - random_string -} - -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -pub enum Gender { - Male, - Female, -} - -fn create_tx_input_based_on_td_kittyName( - db: &Db, - name: String, -) -> anyhow::Result<(TradableKittyData, Input)> { - let tradable_kitty = - crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, name.clone()); - let Some((tradable_kitty_info, out_ref)) = tradable_kitty.unwrap() else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let input = Input { - output_ref: out_ref, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - Ok((tradable_kitty_info, input)) -} - -fn create_tx_input_based_on_kitty_name<'a>( - db: &Db, - name: String, -) -> anyhow::Result<(KittyData, Input)> { - //let kitty = crate::sync::get_kitty_from_local_db_based_on_name(&db, name.clone()); - //let name_extractor_kitty_data = move |kitty: &'a KittyData| -> &'a [u8; 4] { &kitty.name }; - - let kitty = crate::sync::get_kitty_from_local_db_based_on_name(&db, name.clone()); - let Some((kitty_info, out_ref)) = kitty.unwrap() else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let input = Input { - output_ref: out_ref, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - Ok((kitty_info, input)) -} - -fn create_tx_input_based_on_td_kitty_name<'a>( - db: &Db, - name: String, -) -> anyhow::Result<(TradableKittyData, Input)> { - let kitty = crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, name.clone()); - let Some((kitty_info, out_ref)) = kitty.unwrap() else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let input = Input { - output_ref: out_ref, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - Ok((kitty_info, input)) -} - -fn print_new_output(transaction: &Transaction) -> anyhow::Result<()> { - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_ref = OutputRef { - tx_hash, - index: i as u32, - }; - match output.payload.type_id { - KittyData::TYPE_ID => { - let new_kitty = output.payload.extract::()?.dna.0; - print!( - "Created {:?} basic Kitty {:?}. ", - hex::encode(new_ref.encode()), - new_kitty - ); - } - TradableKittyData::TYPE_ID => { - let new_kitty = output - .payload - .extract::()? - .kitty_basic_data - .dna - .0; - print!( - "Created {:?} TradableKitty {:?}. ", - hex::encode(new_ref.encode()), - new_kitty - ); - } - Coin::<0>::TYPE_ID => { - let amount = output.payload.extract::>()?.0; - print!( - "Created {:?} worth {amount}. ", - hex::encode(new_ref.encode()) - ); - } - - _ => continue, - } - crate::pretty_print_verifier(&output.verifier); - } - Ok(()) -} - -async fn send_signed_tx( - transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result<()> { - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - - println!("Node's response to spawn transaction: {:?}", spawn_response); - match spawn_response { - Ok(_) => Ok(()), - Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), - } -} - -async fn send_tx( - transaction: &mut Transaction, - client: &HttpClient, - local_keystore: Option<&LocalKeystore>, -) -> anyhow::Result<()> { - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - - let _ = match local_keystore { - Some(ks) => { - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(ks, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - // insert the proof - input.redeemer = redeemer; - } - } - None => {} - }; - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - - println!("Node's response to spawn transaction: {:?}", spawn_response); - match spawn_response { - Ok(_) => Ok(()), - Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), - } -} - - - -fn gen_random_gender() -> Gender { - // Create a local random number generator - let mut rng = rand::thread_rng(); - - // Generate a random number between 0 and 1 - let random_number = rng.gen_range(0..=1); - - // We Use the random number to determine the gender - match random_number { - 0 => Gender::Male, - _ => Gender::Female, - } -} - -fn get_random_kitty_name_from_pre_defined_vec(g: Gender, name_slice: &mut [u8; 4]) { - // Create a local random number generator - let mut rng = rand::thread_rng(); - - // Generate a random number between 0 and 1 - let random_number = rng.gen_range(0..=50); - - // We Use the random number to determine the gender - - let name = match g { - Gender::Male => MALE_KITTY_NAMES[random_number], - Gender::Female => FEMALE_KITTY_NAMES[random_number], - }; - //name.to_string() - name_slice.copy_from_slice(name.clone().as_bytes()); -} - -fn convert_name_string_tostr_slice( - name: String, - name_slice: &mut [u8; 4],) -> anyhow::Result<()> { - if name.len() != 4 { - return Err(anyhow!( - "Please input a name of length 4 characters. Current length: {}", - name.len() - )); - } - - name_slice.copy_from_slice(name.clone().as_bytes()); - return Ok(()); -} - -fn validate_kitty_name_from_db( - db: &Db, - owner_pubkey: &H256, - name: String, - name_slice: &mut [u8; 4], -) -> anyhow::Result<()> { - - - match crate::sync::is_kitty_name_duplicate(&db, name.clone(), &owner_pubkey) { - Ok(Some(true)) => { - println!("Kitty name is duplicate , select another name"); - return Err(anyhow!( - "Please input a non-duplicate name of length 4 characters" - )); - } - _ => {} - }; - convert_name_string_tostr_slice(name,name_slice)?; - - return Ok(()); -} - -fn create_new_family( - new_mom: &mut KittyData, - new_dad: &mut KittyData, - new_child: &mut KittyData, -) -> anyhow::Result<()> { - new_mom.parent = Parent::Mom(MomKittyStatus::RearinToGo); - if new_mom.num_breedings >= 0 { - new_mom.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); - } - new_mom.num_breedings = new_mom.num_breedings.checked_add(1).expect("REASON"); - new_mom.free_breedings = new_mom.free_breedings.checked_sub(1).expect("REASON"); - - new_dad.parent = Parent::Dad(DadKittyStatus::RearinToGo); - if new_dad.num_breedings >= 0 { - new_dad.parent = Parent::Dad(DadKittyStatus::Tired); - } - - new_dad.num_breedings = new_dad.num_breedings.checked_add(1).expect("REASON"); - new_dad.free_breedings = new_dad.free_breedings.checked_sub(1).expect("REASON"); - - let child_gender = match gen_random_gender() { - Gender::Male => Parent::dad(), - Gender::Female => Parent::mom(), - }; - - let child = KittyData { - parent: child_gender, - free_breedings: 2, - name: *b"tomy", // Name of child kitty need to be generated in better way - dna: KittyDNA(BlakeTwo256::hash_of(&( - new_mom.dna.clone(), - new_dad.dna.clone(), - new_mom.num_breedings, - new_dad.num_breedings, - ))), - num_breedings: 0, - // price: None, - // is_available_for_sale: false, - }; - *new_child = child; - Ok(()) -} - -pub async fn create_kitty( - db: &Db, - client: &HttpClient, - args: CreateKittyArgs, -) -> anyhow::Result> { - let mut kitty_name = [0; 4]; - - let g = gen_random_gender(); - let gender = match g { - Gender::Male => Parent::dad(), - Gender::Female => Parent::mom(), - }; - - convert_name_string_tostr_slice(args.kitty_name.clone(), &mut kitty_name)?; - // Generate a random string of length 5 - let random_string = generate_random_string(5) + args.kitty_name.as_str(); - let dna_preimage: &[u8] = random_string.as_bytes(); - - let child_kitty = KittyData { - parent: gender, - dna: KittyDNA(BlakeTwo256::hash(dna_preimage)), - name: kitty_name, // Default value for now, as the provided code does not specify a value - ..Default::default() - }; - - println!("DNA of created kitty is {:?}",child_kitty.dna); - - // Create the Output - let output = Output { - payload: child_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - // Create the Transaction - let mut transaction = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: vec![output], - checker: FreeKittyConstraintChecker::Create.into(), - }; - - send_tx(&mut transaction, &client, None).await?; - print_new_output(&transaction)?; - Ok(Some(child_kitty)) -} - -pub async fn send_txn_with_td_kitty_as_output( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result> { - let tradable_kitty: TradableKittyData = signed_transaction.outputs[0].payload - .extract::() - .map_err(|_| anyhow!("Invalid output, Expected TradableKittyData"))?; - - send_signed_tx(&signed_transaction, &client).await?; - print_new_output(&signed_transaction)?; - Ok(Some(tradable_kitty)) -} - -pub async fn send_txn_with_kitty_as_output( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result> { - let kitty: KittyData = signed_transaction.outputs[0].payload - .extract::() - .map_err(|_| anyhow!("Invalid output, Expected KittyData"))?; - - send_signed_tx(&signed_transaction, &client).await?; - print_new_output(&signed_transaction)?; - Ok(Some(kitty)) -} - -pub async fn list_kitty_for_sale( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result> { - send_txn_with_td_kitty_as_output(&signed_transaction,&client).await -} - -pub async fn update_kitty_name( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result> { - send_txn_with_kitty_as_output(&signed_transaction,&client).await -} - -pub async fn update_td_kitty_name( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result> { - send_txn_with_td_kitty_as_output(&signed_transaction,&client).await -} - -pub async fn update_td_kitty_price( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result> { - send_txn_with_td_kitty_as_output(&signed_transaction,&client).await -} - -pub async fn delist_kitty_from_sale( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result> { - send_txn_with_kitty_as_output(&signed_transaction,&client).await -} - - -pub async fn breed_kitty( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result>> { - let mom_kitty: KittyData = signed_transaction.outputs[0].payload - .extract::() - .map_err(|_| anyhow!("Invalid output, Expected KittyData"))?; - - let dad_kitty: KittyData = signed_transaction.outputs[1].payload - .extract::() - .map_err(|_| anyhow!("Invalid output, Expected KittyData"))?; - - let child_kitty: KittyData = signed_transaction.outputs[2].payload - .extract::() - .map_err(|_| anyhow!("Invalid output, Expected KittyData"))?; - - send_signed_tx(&signed_transaction, &client).await?; - print_new_output(&signed_transaction)?; - Ok(vec![ - mom_kitty.clone(), - dad_kitty.clone(), - child_kitty.clone(), - ].into()) -} - -pub async fn buy_kitty( - signed_transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result> { - let traded_kitty: TradableKittyData = signed_transaction.outputs[0].payload - .extract::() - .map_err(|_| anyhow!("Invalid output, Expected TradableKittyData"))?; - - - send_signed_tx(&signed_transaction, &client).await?; - print_new_output(&signed_transaction)?; - Ok(Some(traded_kitty)) -} - -pub async fn buy_kitty1( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: BuyKittyArgs, -) -> anyhow::Result> { - log::info!("The Buy kittyArgs are:: {:?}", args); - - let Ok((kitty_info, kitty_ref)) = - create_tx_input_based_on_td_kittyName(db, args.kitty_name.clone()) - else { - return Err(anyhow!("No kitty with name {} in localdb", args.kitty_name)); - }; - - let inputs: Vec = vec![kitty_ref]; - // Create the KittyData - let output_kitty = kitty_info.clone(); - - let output = Output { - payload: output_kitty.into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::Buy.into(), - }; - - // Construct each output and then push to the transactions for Money - let mut total_output_amount = 0; - for amount in &args.output_amount { - let output = Output { - payload: Coin::<0>::new(*amount).into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.seller, - }), - }; - total_output_amount += amount; - transaction.outputs.push(output); - if total_output_amount >= kitty_info.price.into() { - break; - } - } - - let mut total_input_amount = 0; - let mut all_input_refs = args.input; - for output_ref in &all_input_refs { - let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( - "user-specified output ref not found in local database" - ))?; - total_input_amount += amount; - } - - if total_input_amount < total_output_amount { - match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { - Some(more_inputs) => { - all_input_refs.extend(more_inputs); - } - None => Err(anyhow!( - "Not enough value in database to construct transaction" - ))?, - } - } - - // Make sure each input decodes and is still present in the node's storage, - // and then push to transaction. - for output_ref in &all_input_refs { - get_coin_from_storage(output_ref, client).await?; - transaction.inputs.push(Input { - output_ref: output_ref.clone(), - redeemer: vec![], // We will sign the total transaction so this should be empty - }); - } - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - - Ok(Some(kitty_info)) -} - - -pub async fn update_kitty_price( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: UpdateKittyPriceArgs, -) -> anyhow::Result> { - log::info!("The set_kitty_property are:: {:?}", args); - let Ok((input_kitty_info, input_kitty_ref)) = - create_tx_input_based_on_td_kitty_name(db, args.current_name.clone()) - else { - return Err(anyhow!( - "No kitty with name {} in localdb", - args.current_name - )); - }; - - let inputs: Vec = vec![input_kitty_ref]; - let mut updated_kitty: TradableKittyData = input_kitty_info; - updated_kitty.price = args.price; - let output = Output { - payload: updated_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::UpdateKittiesPrice.into(), - }; - - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - Ok(Some(updated_kitty)) -} - -/// Apply a transaction to the local database, storing the new coins. -pub(crate) fn apply_transaction( - db: &Db, - tx_hash: ::Output, - index: u32, - output: &Output, -) -> anyhow::Result<()> { - let kitty_detail: KittyData = output.payload.extract()?; - - let output_ref = OutputRef { tx_hash, index }; - println!("in kitty:apply_transaction output_ref = {:?}", output_ref); - match output.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - // Add it to the global unspent_outputs table - crate::sync::add_fresh_kitty_to_db(db, &output_ref, &owner_pubkey, &kitty_detail) - } - _ => Err(anyhow!("{:?}", ())), - } -} - -pub(crate) fn convert_kitty_name_string(kitty: &KittyData) -> Option { - if let Ok(kitty_name) = std::str::from_utf8(&kitty.name) { - return Some(kitty_name.to_string()); - } else { - println!("Invalid UTF-8 data in the Kittyname"); - } - None -} - -/// Given an output ref, fetch the details about this coin from the node's -/// storage. -// Need to revisit this for tradableKitty -pub async fn get_kitty_from_storage( - output_ref: &OutputRef, - client: &HttpClient, -) -> anyhow::Result<(KittyData, OuterVerifier)> { - let utxo = fetch_storage::(output_ref, client).await?; - - let kitty_in_storage: KittyData = utxo.payload.extract()?; - - Ok((kitty_in_storage, utxo.verifier)) -} - -/// Apply a transaction to the local database, storing the new coins. -pub(crate) fn apply_td_transaction( - db: &Db, - tx_hash: ::Output, - index: u32, - output: &Output, -) -> anyhow::Result<()> { - let tradable_kitty_detail: TradableKittyData = output.payload.extract()?; - - let output_ref = OutputRef { tx_hash, index }; - println!( - "in Tradable kitty:apply_transaction output_ref = {:?}", - output_ref - ); - match output.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - // Add it to the global unspent_outputs table - crate::sync::add_fresh_tradable_kitty_to_db( - db, - &output_ref, - &owner_pubkey, - &tradable_kitty_detail, - ) - } - _ => Err(anyhow!("{:?}", ())), - } -} - -pub(crate) fn convert_td_kitty_name_string(tradable_kitty: &TradableKittyData) -> Option { - if let Ok(kitty_name) = std::str::from_utf8(&tradable_kitty.kitty_basic_data.name) { - return Some(kitty_name.to_string()); - } else { - println!("Invalid UTF-8 data in the Kittyname"); - } - None -} - -/// Given an output ref, fetch the details about this coin from the node's -/// storage. -pub async fn get_td_kitty_from_storage( - output_ref: &OutputRef, - client: &HttpClient, -) -> anyhow::Result<(TradableKittyData, OuterVerifier)> { - let utxo = fetch_storage::(output_ref, client).await?; - let kitty_in_storage: TradableKittyData = utxo.payload.extract()?; - - Ok((kitty_in_storage, utxo.verifier)) -} - -////////////////////////////////////////////////////////////////// -// Below is for web server handling -////////////////////////////////////////////////////////////////// - - -async fn send_tx_with_signed_inputs( - transaction: &mut Transaction, - client: &HttpClient, - local_keystore: Option<&LocalKeystore>, - signed_inputs: Vec, -) -> anyhow::Result<()> { - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - transaction.inputs = signed_inputs; - - let _ = match local_keystore { - Some(ks) => { - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(ks, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - // insert the proof - input.redeemer = redeemer; - } - } - None => {} - }; - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - - println!("Node's response to spawn transaction: {:?}", spawn_response); - match spawn_response { - Ok(_) => Ok(()), - Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), - } -} - -pub async fn create_txn_for_list_kitty( - db: &Db, - dna: &str, - price:u128, - public_key:H256, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - - let mut found_kitty: Option<(KittyData, OutputRef)> = None; - - if let Ok(Some((kitty_info, out_ref))) = crate::sync::get_kitty_from_local_db_based_on_dna(&db,dna) { - found_kitty = Some((kitty_info, out_ref)); - } else { - return Err(anyhow!("No kitty with DNA {} in localdb", dna)); - } - - let input = Input { - output_ref: found_kitty.clone().unwrap().1, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - let inputs: Vec = vec![input]; - - let tradable_kitty = TradableKittyData { - kitty_basic_data: found_kitty.unwrap().0, - price: price, - }; - - // Create the Output - let output = Output { - payload: tradable_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: public_key, - }), - }; - - // Create the Transaction - let transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::ListKittiesForSale.into(), - }; - print_debug_signed_txn_with_local_ks(transaction.clone()).await?; - - Ok(Some(transaction)) -} - -pub async fn create_txn_for_delist_kitty( - db: &Db, - dna: &str, - public_key:H256, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - - let mut found_kitty: Option<(TradableKittyData, OutputRef)> = None; - - if let Ok(Some((td_kitty_info, out_ref))) = crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,dna) { - found_kitty = Some((td_kitty_info, out_ref)); - } else { - return Err(anyhow!("No kitty with DNA {} in localdb", dna)); - } - - let input = Input { - output_ref: found_kitty.clone().unwrap().1, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - let inputs: Vec = vec![input]; - let kitty = found_kitty.unwrap().0.kitty_basic_data; - - let output = Output { - payload: kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: public_key, - }), - }; - - // Create the Transaction - let transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::DelistKittiesFromSale.into(), - }; - print_debug_signed_txn_with_local_ks(transaction.clone()).await?; - Ok(Some(transaction)) -} - -pub async fn create_txn_for_kitty_name_update( - db: &Db, - dna: &str, - new_name: String, - public_key:H256, -) -> anyhow::Result> { - let mut found_kitty: Option<(KittyData, OutputRef)> = None; - - if let Ok(Some((kitty_info, out_ref))) = - crate::sync::get_kitty_from_local_db_based_on_dna(&db,dna) - { - found_kitty = Some((kitty_info, out_ref)); - } else { - return Err(anyhow!("No kitty with DNA {} in localdb", dna)); - } - - let input = Input { - output_ref: found_kitty.clone().unwrap().1, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - let inputs: Vec = vec![input]; - - let mut kitty_name = [0; 4]; - convert_name_string_tostr_slice(new_name,&mut kitty_name)?; - - // found_kitty.name = new_name - let mut kitty = found_kitty.clone().unwrap().0; - kitty.name = kitty_name;// Name updated - - - // Create the Output - let output = Output { - payload: kitty.into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: public_key, - }), - }; - - // Create the Transaction - let transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: FreeKittyConstraintChecker::UpdateKittiesName.into(), - }; - - print_debug_signed_txn_with_local_ks(transaction.clone()).await?; - Ok(Some(transaction)) -} - - -pub async fn create_txn_for_td_kitty_name_update( - db: &Db, - dna: &str, - new_name: String, - public_key:H256, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - let mut found_kitty: Option<(TradableKittyData, OutputRef)> = None; - - if let Ok(Some((kitty_info, out_ref))) = - crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,dna) - { - found_kitty = Some((kitty_info, out_ref)); - } else { - return Err(anyhow!("No kitty with DNA {} in localdb", dna)); - } - - let input = Input { - output_ref: found_kitty.clone().unwrap().1, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - - let inputs: Vec = vec![input]; - - let mut kitty_name = [0; 4]; - convert_name_string_tostr_slice(new_name,&mut kitty_name)?; - - let mut td_kitty = found_kitty.clone().unwrap().0; - td_kitty.kitty_basic_data.name = kitty_name;// Name updated - - // Create the Output - let output = Output { - payload: td_kitty.into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: public_key, - }), - }; - - // Create the Transaction - let transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::UpdateKittiesName.into(), - }; - - print_debug_signed_txn_with_local_ks(transaction.clone()).await?; - Ok(Some(transaction)) -} - - -pub async fn create_txn_for_td_kitty_price_update( - db: &Db, - dna: &str, - new_price: u128, - public_key:H256, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - let mut found_kitty: Option<(TradableKittyData, OutputRef)> = None; - - if let Ok(Some((kitty_info, out_ref))) = - crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,dna) - { - found_kitty = Some((kitty_info, out_ref)); - } else { - return Err(anyhow!("No kitty with DNA {} in localdb", dna)); - } - - let input = Input { - output_ref: found_kitty.clone().unwrap().1, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - - let inputs: Vec = vec![input]; - - let mut td_kitty = found_kitty.clone().unwrap().0; - td_kitty.price = new_price;// price updated - - // Create the Output - let output = Output { - payload: td_kitty.into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: public_key, - }), - }; - - // Create the Transaction - let transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::UpdateKittiesPrice.into(), - }; - print_debug_signed_txn_with_local_ks(transaction.clone()).await?; - Ok(Some(transaction)) -} - -pub async fn create_txn_for_breed_kitty( - db: &Db, - mom_dna: &str, - dad_dna: &str, - child_name: String, - owner_public_key:H256, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - let mut mom_kitty: Option<(KittyData, OutputRef)> = None; - - if let Ok(Some((kitty_info, out_ref))) = - crate::sync::get_kitty_from_local_db_based_on_dna(&db,mom_dna) - { - mom_kitty = Some((kitty_info, out_ref)); - } else { - return Err(anyhow!("No kitty with MOM DNA {} in localdb", mom_dna)); - } - - let mom_input = Input { - output_ref: mom_kitty.clone().unwrap().1, - redeemer: vec![], - }; - - let mut dad_kitty: Option<(KittyData, OutputRef)> = None; - - if let Ok(Some((kitty_info, out_ref))) = - crate::sync::get_kitty_from_local_db_based_on_dna(&db,dad_dna) - { - dad_kitty = Some((kitty_info, out_ref)); - } else { - return Err(anyhow!("No kitty with DAD DNA {} in localdb", dad_dna)); - } - - let dad_input = Input { - output_ref: dad_kitty.clone().unwrap().1, - redeemer: vec![], - }; - - let inputs: Vec = vec![mom_input,dad_input]; - - let mut child_kitty_name = [0; 4]; - convert_name_string_tostr_slice(child_name,&mut child_kitty_name)?; - - let mut new_mom: KittyData = mom_kitty.clone().unwrap().0; - - let mut new_dad = dad_kitty.clone().unwrap().0; - - let mut child_kitty: KittyData = Default::default(); - - create_new_family(&mut new_mom, &mut new_dad, &mut child_kitty)?; - // Create the Output mom - println!("New mom Dna = {:?}", new_mom.dna); - println!("New Dad Dna = {:?}", new_dad.dna); - println!("Child Dna = {:?}", child_kitty.dna); - child_kitty.name = child_kitty_name; - - let output_mom = Output { - payload: new_mom.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: owner_public_key, - }), - }; - - // Create the Output dada - let output_dad = Output { - payload: new_dad.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: owner_public_key, - }), - }; - - let output_child = Output { - payload: child_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: owner_public_key, - }), - }; - - let new_family = Box::new(vec![output_mom, output_dad, output_child]); - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: (&[ - new_family[0].clone(), - new_family[1].clone(), - new_family[2].clone(), - ]) - .to_vec(), - checker: FreeKittyConstraintChecker::Breed.into(), - }; - - print_debug_signed_txn_with_local_ks(transaction.clone()).await?; - Ok(Some(transaction)) -} - - - - -pub async fn create_txn_for_buy_kitty( - db: &Db, - input_coins:Vec, - kitty_dna: &str, - buyer_public_key:H256, - seller_public_key:H256, - output_amount: & Vec, - client: &HttpClient, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - let mut found_kitty: Option<(TradableKittyData, OutputRef)> = None; - - if let Ok(Some((kitty_info, out_ref))) = - crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,kitty_dna) { - found_kitty = Some((kitty_info, out_ref)); - } else { - return Err(anyhow!("No kitty with DNA {} in localdb", kitty_dna)); - } - - let input = Input { - output_ref: found_kitty.clone().unwrap().1, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - - let inputs: Vec = vec![input]; - - let mut output_kitty = found_kitty.clone().unwrap().0; - - let output = Output { - payload: output_kitty.into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: buyer_public_key, - }), - }; - - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::Buy.into(), - }; - - // Construct each output and then push to the transactions for Money - let mut total_output_amount = 0; - for amount in output_amount { - let output = Output { - payload: Coin::<0>::new(*amount).into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: seller_public_key, - }), - }; - total_output_amount += amount; - transaction.outputs.push(output); - if total_output_amount >= found_kitty.clone().unwrap().0.price.into() { - break; - } - } - - let mut total_input_amount = 0; - let mut all_input_refs = input_coins; - for output_ref in &all_input_refs { - let (_buyer_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( - "user-specified output ref not found in local database" - ))?; - total_input_amount += amount; - } - - if total_input_amount < total_output_amount { - match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { - Some(more_inputs) => { - all_input_refs.extend(more_inputs); - } - None => Err(anyhow!( - "Not enough value in database to construct transaction" - ))?, - } - } - - // Make sure each input decodes and is still present in the node's storage, - // and then push to transaction. - for output_ref in &all_input_refs { - get_coin_from_storage(output_ref, client).await?; - transaction.inputs.push(Input { - output_ref: output_ref.clone(), - redeemer: vec![], // We will sign the total transaction so this should be empty - }); - } - - print_debug_signed_txn_with_local_ks(transaction.clone()).await?; // this is just for debug purpose. - Ok(Some(transaction)) -} - - -pub async fn create_inpututxo_list( - transaction: &mut Transaction, - client: &HttpClient, -) -> anyhow::Result>>> { - let mut utxo_list: Vec> = Vec::new(); - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - utxo_list.push(utxo); - } - Ok(Some(utxo_list)) -} - - -// Below function will not be used in real usage , it is just for test purpose - -use crate::HttpClientBuilder; -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; -pub async fn print_debug_signed_txn_with_local_ks( - mut transaction:Transaction, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - - let stripped_encoded_transaction = transaction.clone().encode(); - let local_keystore = get_local_keystore().await.expect("Key store error"); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let client = client_result.unwrap(); - - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, &client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(&local_keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - // insert the proof - input.redeemer = redeemer; - } - println!("signed_transaction {:?}",transaction.clone()); - Ok(Some(transaction.clone())) -} \ No newline at end of file diff --git a/webservice-wallet-external-signing/src/main.rs b/webservice-wallet-external-signing/src/main.rs deleted file mode 100644 index 9a436de86..000000000 --- a/webservice-wallet-external-signing/src/main.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! A simple CLI wallet. For now it is a toy just to start testing things out. - - -use jsonrpsee::http_client::HttpClientBuilder; -use jsonrpsee::{http_client::HttpClient}; -use parity_scale_codec::{Decode}; -use runtime::OuterVerifier; -use std::path::PathBuf; -use sled::Db; -//use crate::kitty::{create_kitty,list_kitty_for_sale}; -use tuxedo_core::{types::OutputRef, verifier::*}; -use sp_core::H256; -use sc_keystore::LocalKeystore; - -//mod amoeba; -mod cli; -mod keystore; -mod kitty; -mod money; -mod output_filter; -mod rpc; -mod sync; -mod timestamp; - -//use moneyServicehandler::{MintCoinsRequest, MintCoinsResponse}; -mod serviceHandlers { - - pub mod blockHandler { - pub mod blockServicehandler; - } - - pub mod moneyHandler { - pub mod moneyServicehandler; - } - - pub mod kittyHandler { - pub mod kittyServicehandler; - } - - pub mod keyHandler { - pub mod keyServicehandler; - } -} - -use serviceHandlers::keyHandler::keyServicehandler::{ - debug_generate_key, - debug_get_keys, -}; - -use serviceHandlers::moneyHandler::moneyServicehandler::{ - mint_coins, - get_all_coins, - get_owned_coins, -}; - -use serviceHandlers::kittyHandler::kittyServicehandler::{ - create_kitty, - get_txn_and_inpututxolist_for_list_kitty_for_sale, - list_kitty_for_sale, - get_txn_and_inpututxolist_for_delist_kitty_from_sale, - delist_kitty_from_sale, - get_txn_and_inpututxolist_for_kitty_name_update, - update_kitty_name, - get_txn_and_inpututxolist_for_td_kitty_name_update, - update_td_kitty_name, - get_txn_and_inpututxolist_for_td_kitty_price_update, - update_td_kitty_price, - get_kitty_by_dna, - get_td_kitty_by_dna, - get_all_kitty_list, - get_all_td_kitty_list, - get_owned_kitty_list, - get_owned_td_kitty_list, - get_txn_and_inpututxolist_for_breed_kitty, - breed_kitty, - get_txn_and_inpututxolist_for_buy_kitty, - buy_kitty, - - - /*delist_kitty_from_sale, - buy_kitty, - breed_kitty,*/ -}; - -use serviceHandlers::blockHandler::blockServicehandler::{ get_block}; - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; - -use axum::{routing::{get, post, put}, Router}; -use std::net::SocketAddr; -use tower_http::cors::{Any, CorsLayer}; - - -#[tokio::main] -async fn main() { - let cors = CorsLayer::new().allow_origin(Any); - - let app = Router::new() - .route("/get-block", get(get_block)) - .route("/mint-coins", post(mint_coins)) - .route("/create-kitty", post(create_kitty)) - .route("/get-txn-and-inpututxolist-for-listkitty-forsale", get(get_txn_and_inpututxolist_for_list_kitty_for_sale)) - .route("/listkitty-for-sale", post(list_kitty_for_sale)) - .route("/get-txn-and-inpututxolist-for-delist-kitty-from-sale", get(get_txn_and_inpututxolist_for_delist_kitty_from_sale)) - .route("/delist-kitty-from-sale", post(delist_kitty_from_sale)) - .route("/get-txn-and-inpututxolist-for-kitty-name-update", get(get_txn_and_inpututxolist_for_kitty_name_update)) - .route("/update-kitty-name", post(update_kitty_name)) - .route("/get-txn-and-inpututxolist-for-td-kitty-name-update", get(get_txn_and_inpututxolist_for_td_kitty_name_update)) - .route("/update-td-kitty-name", post(update_td_kitty_name)) - .route("/get-txn-and-inpututxolist-for-td-kitty-price-update", get(get_txn_and_inpututxolist_for_td_kitty_price_update)) - .route("/get-txn-and-inpututxolist-for-breed-kitty", get(get_txn_and_inpututxolist_for_breed_kitty)) - .route("/breed-kitty", post(breed_kitty)) - .route("/get-txn-and-inpututxolist-for-buy-kitty", get(get_txn_and_inpututxolist_for_buy_kitty)) - .route("/buy-kitty", post(buy_kitty)) - .route("/update-td-kitty-price", post(update_td_kitty_price)) - .route("/get-kitty-by-dna", get(get_kitty_by_dna)) - .route("/get-tradable-kitty-by-dna", get(get_td_kitty_by_dna)) - .route("/get-all-kitty-list", get(get_all_kitty_list)) - .route("/get-all-tradable-kitty-list", get(get_all_td_kitty_list)) - .route("/get-owned-kitty-list", get(get_owned_kitty_list)) - .route("/get-owned-tradable-kitty-list", get(get_owned_td_kitty_list)) - .route("/get-all-coins", get(get_all_coins)) - .route("/get-owned-coins", get(get_owned_coins)) - - - .route("/debug-generate-key", post(debug_generate_key)) - .route("/debug-get-keys", get(debug_get_keys)) - - //.route("/spend-coins", put(spend_coins)) - .layer(cors); - - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - println!("In the main"); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await - .unwrap(); -} - - -async fn original_get_db() -> anyhow::Result { - - let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; - - // Read node's genesis block. - let node_genesis_hash = rpc::node_get_block_hash(0, &client) - .await? - .expect("node should be able to return some genesis hash"); - let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) - .await? - .expect("node should be able to return some genesis block"); - log::debug!("Node's Genesis block::{:?}", node_genesis_hash); - - // Open the local database - let data_path = temp_dir(); - let db_path = data_path.join("wallet_database"); - 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}"); - - // No filter at-all - let keystore_filter = |_v: &OuterVerifier| -> bool { - true - }; - - if !sled::Db::was_recovered(&db) { - println!("!sled::Db::was_recovered(&db) called "); - // This is a new instance, so we need to apply the genesis block to the database. - async { - sync::apply_block(&db, node_genesis_block, node_genesis_hash, &keystore_filter) - .await; - }; - } - - sync::synchronize(&db, &client, &keystore_filter).await?; - - println!( - "Wallet database synchronized with node to height {:?}", - sync::height(&db)?.expect("We just synced, so there is a height available") - ); - - if let Err(err) = db.flush() { - println!("Error flushing Sled database: {}", err); - } - - Ok(db) -} - - -async fn get_db() -> anyhow::Result { - let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; - let data_path = temp_dir(); - let db_path = data_path.join("wallet_database"); - let node_genesis_hash = rpc::node_get_block_hash(0, &client) - .await? - .expect("node should be able to return some genesis hash"); - let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) - .await? - .expect("node should be able to return some genesis block"); - println!("Node's Genesis block::{:?}", node_genesis_hash); - - let db = sync::open_db(db_path, node_genesis_hash, node_genesis_block.clone())?; - Ok(db) -} - - -async fn get_local_keystore() -> anyhow::Result { - let data_path = temp_dir(); - let keystore_path = data_path.join("keystore"); - println!("keystore_path: {:?}", keystore_path); - let keystore = sc_keystore::LocalKeystore::open(keystore_path.clone(), None)?; - keystore::insert_development_key_for_this_session(&keystore)?; - Ok(keystore) -} - -async fn sync_db bool>( - db: &Db, - client: &HttpClient, - filter: &F) -> anyhow::Result<()> { - - if !sled::Db::was_recovered(&db) { - let node_genesis_hash = rpc::node_get_block_hash(0, &client) - .await? - .expect("node should be able to return some genesis hash"); - let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) - .await? - .expect("node should be able to return some genesis block"); - - - println!(" in sync_db !sled::Db::was_recovered(&db)"); - async { - sync::apply_block(&db, node_genesis_block, node_genesis_hash, &filter) - .await; - }; - } - println!(" sync::synchronize will be called!!"); - sync::synchronize(&db, &client, &filter).await?; - - log::info!( - "Wallet database synchronized with node to height {:?}", - sync::height(&db)?.expect("We just synced, so there is a height available") - ); - Ok(()) -} - -async fn sync_and_get_db() -> anyhow::Result { - let db = get_db().await?; - let keystore = get_local_keystore().await?; - let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; - let keystore_filter = |v: &OuterVerifier| -> bool { - matches![v, - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) - if crate::keystore::has_key(&keystore, owner_pubkey) - ] || matches![v, OuterVerifier::UpForGrabs(UpForGrabs)] // used for timestamp - }; - sync_db(&db, &client, &keystore_filter).await?; - Ok(db) -} - -/// 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); - - let mut bytes: [u8; 32] = [0; 32]; - hex::decode_to_slice(s, &mut bytes as &mut [u8]) - .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; - Ok(H256::from(bytes)) -} - -use std::error::Error; -/// Parse an output ref from a string -pub(crate) fn convert_output_ref_from_string(s: &str) -> Result> { - let s = strip_0x_prefix(s); - let bytes = hex::decode(s)?; - - OutputRef::decode(&mut &bytes[..]) - .map_err(|_| "Failed to decode OutputRef from string".into()) -} - -fn output_ref_from_string(s: &str) -> Result { - let s = strip_0x_prefix(s); - let bytes = - hex::decode(s).map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; - - OutputRef::decode(&mut &bytes[..]) - .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation)) -} - -/// Takes a string and checks for a 0x prefix. Returns a string without a 0x prefix. -fn strip_0x_prefix(s: &str) -> &str { - if &s[..2] == "0x" { - &s[2..] - } else { - s - } -} - -/// Generate a plaform-specific temporary directory for the wallet -fn temp_dir() -> PathBuf { - // Since it is only used for testing purpose, we don't need a secure temp dir, just a unique one. - /* - std::env::temp_dir().join(format!( - "tuxedo-wallet-{}", - std::time::UNIX_EPOCH.elapsed().unwrap().as_millis(), - )) - */ - std::env::temp_dir().join(format!( - "tuxedo-wallet" - )) -} - -/// Generate the platform-specific default data path for the wallet -fn default_data_path() -> PathBuf { - // This uses the directories crate. - // https://docs.rs/directories/latest/directories/struct.ProjectDirs.html - - // Application developers may want to put actual qualifiers or organization here - let qualifier = ""; - let organization = ""; - let application = env!("CARGO_PKG_NAME"); - - directories::ProjectDirs::from(qualifier, organization, application) - .expect("app directories exist on all supported platforms; qed") - .data_dir() - .into() -} - -/// Utility to pretty print an outer verifier -pub fn pretty_print_verifier(v: &OuterVerifier) { - match v { - OuterVerifier::Sr25519Signature(sr25519_signature) => { - println! {"owned by {}", sr25519_signature.owner_pubkey} - } - OuterVerifier::UpForGrabs(_) => println!("that can be spent by anyone"), - OuterVerifier::ThresholdMultiSignature(multi_sig) => { - let string_sigs: Vec<_> = multi_sig - .signatories - .iter() - .map(|sig| format!("0x{}", hex::encode(sig))) - .collect(); - println!( - "Owned by {:?}, with a threshold of {} sigs necessary", - string_sigs, multi_sig.threshold - ); - } - } -} diff --git a/webservice-wallet-external-signing/src/money.rs b/webservice-wallet-external-signing/src/money.rs deleted file mode 100644 index 6e33ca6e3..000000000 --- a/webservice-wallet-external-signing/src/money.rs +++ /dev/null @@ -1,340 +0,0 @@ -//! Wallet features related to spending money and checking balances. - -use crate::{cli::MintCoinArgs, cli::SpendArgs,rpc::fetch_storage, sync}; - -use anyhow::anyhow; -use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; -use parity_scale_codec::Encode; -use runtime::{ - money::{Coin, MoneyConstraintChecker}, - OuterConstraintChecker, OuterVerifier, Transaction, -}; -use sc_keystore::LocalKeystore; -use sled::Db; -use sp_core::sr25519::Public; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use tuxedo_core::{ - types::{Input, Output, OutputRef}, - verifier::Sr25519Signature, -}; -use crate::original_get_db; - -/// Create and send a transaction that mints the coins on the network -pub async fn mint_coins(client: &HttpClient, args: MintCoinArgs) -> anyhow::Result<()> { - log::debug!("The args are:: {:?}", args); - - let transaction = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: vec![( - Coin::<0>::new(args.amount), - OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - ) - .into()], - checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Mint), - }; - - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let _spawn_response: Result = client.request("author_submitExtrinsic", params).await; - - log::info!( - "Node's response to mint-coin transaction: {:?}", - _spawn_response - ); - - let minted_coin_ref = OutputRef { - tx_hash: ::hash_of(&transaction.encode()), - index: 0, - }; - let output = &transaction.outputs[0]; - let amount = output.payload.extract::>()?.0; - print!( - "Minted {:?} worth {amount}. ", - hex::encode(minted_coin_ref.encode()) - ); - crate::pretty_print_verifier(&output.verifier); - - Ok(()) -} -use sp_core::H256; -struct RecipientOutput { - pub recipient:H256, - pub output_amount:Vec -} -fn extract_recipient_list_from_args(args: SpendArgs,) -> Vec { - let mut recipient_list:Vec = Vec::new(); - for i in args.recipients { - let rec_pient = RecipientOutput { - recipient:i.0, - output_amount:i.1, - }; - recipient_list.push(rec_pient); - } - recipient_list -} -/// Create and send a transaction that spends coins on the network -pub async fn spend_coins( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: SpendArgs, -) -> anyhow::Result<()> { - - log::info!("In the spend_coins_to_multiple_recipient The args are:: {:?}", args); - let mut transaction = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: Vec::new(), - checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend), - }; - - let recipient_list:Vec = extract_recipient_list_from_args(args.clone()); - - let mut total_output_amount = 0; - for recipient in &recipient_list { - for amount in &recipient.output_amount { - let output = Output { - payload: Coin::<0>::new(*amount).into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: recipient.recipient, - }), - }; - total_output_amount += amount; - transaction.outputs.push(output); - } - } - - // The total input set will consist of any manually chosen inputs - // plus any automatically chosen to make the input amount high enough - let mut total_input_amount = 0; - let mut all_input_refs = args.input; - for output_ref in &all_input_refs { - let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( - "user-specified output ref not found in local database" - ))?; - total_input_amount += amount; - } - //TODO filtering on a specific sender - - // If the supplied inputs are not valuable enough to cover the output amount - // we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.) - if total_input_amount < total_output_amount { - match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { - Some(more_inputs) => { - all_input_refs.extend(more_inputs); - } - None => Err(anyhow!( - "Not enough value in database to construct transaction" - ))?, - } - } - - // Make sure each input decodes and is still present in the node's storage, - // and then push to transaction. - for output_ref in &all_input_refs { - get_coin_from_storage(output_ref, client).await?; - transaction.inputs.push(Input { - output_ref: output_ref.clone(), - redeemer: vec![], // We will sign the total transaction so this should be empty - }); - } - - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - - // insert the proof - input.redeemer = redeemer; - } - - // Send the transaction - let genesis_spend_hex = hex::encode(transaction.encode()); - let params = rpc_params![genesis_spend_hex]; - let genesis_spend_response: Result = - client.request("author_submitExtrinsic", params).await; - log::info!( - "Node's response to spend transaction: {:?}", - genesis_spend_response - ); - - // Print new output refs for user to check later - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_coin_ref = OutputRef { - tx_hash, - index: i as u32, - }; - let amount = output.payload.extract::>()?.0; - - print!( - "Created {:?} worth {amount}. ", - hex::encode(new_coin_ref.encode()) - ); - crate::pretty_print_verifier(&output.verifier); - } - - Ok(()) -} -/* -/// Create and send a transaction that spends coins on the network -pub async fn spend_coins1( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: SpendArgs, -) -> anyhow::Result<()> { - log::info!("The args are:: {:?}", args); - - // Construct a template Transaction to push coins into later - let mut transaction = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: Vec::new(), - checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend), - }; - - // Construct each output and then push to the transactions - let mut total_output_amount = 0; - for amount in &args.output_amount { - let output = Output { - payload: Coin::<0>::new(*amount).into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.recipient, - }), - }; - total_output_amount += amount; - transaction.outputs.push(output); - } - - // The total input set will consist of any manually chosen inputs - // plus any automatically chosen to make the input amount high enough - let mut total_input_amount = 0; - let mut all_input_refs = args.input; - for output_ref in &all_input_refs { - let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( - "user-specified output ref not found in local database" - ))?; - total_input_amount += amount; - } - //TODO filtering on a specific sender - - // If the supplied inputs are not valuable enough to cover the output amount - // we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.) - if total_input_amount < total_output_amount { - match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { - Some(more_inputs) => { - all_input_refs.extend(more_inputs); - } - None => Err(anyhow!( - "Not enough value in database to construct transaction" - ))?, - } - } - - // Make sure each input decodes and is still present in the node's storage, - // and then push to transaction. - for output_ref in &all_input_refs { - get_coin_from_storage(output_ref, client).await?; - transaction.inputs.push(Input { - output_ref: output_ref.clone(), - redeemer: vec![], // We will sign the total transaction so this should be empty - }); - } - - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - - // insert the proof - input.redeemer = redeemer; - } - - // Send the transaction - let genesis_spend_hex = hex::encode(transaction.encode()); - let params = rpc_params![genesis_spend_hex]; - let genesis_spend_response: Result = - client.request("author_submitExtrinsic", params).await; - log::info!( - "Node's response to spend transaction: {:?}", - genesis_spend_response - ); - - // Print new output refs for user to check later - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_coin_ref = OutputRef { - tx_hash, - index: i as u32, - }; - let amount = output.payload.extract::>()?.0; - - print!( - "Created {:?} worth {amount}. ", - hex::encode(new_coin_ref.encode()) - ); - crate::pretty_print_verifier(&output.verifier); - } - - Ok(()) -} -*/ - -/// Given an output ref, fetch the details about this coin from the node's -/// storage. -pub async fn get_coin_from_storage( - output_ref: &OutputRef, - client: &HttpClient, -) -> anyhow::Result<(Coin<0>, OuterVerifier)> { - let utxo = fetch_storage::(output_ref, client).await?; - let coin_in_storage: Coin<0> = utxo.payload.extract()?; - - Ok((coin_in_storage, utxo.verifier)) -} - -/// Apply a transaction to the local database, storing the new coins. -pub(crate) fn apply_transaction( - db: &Db, - tx_hash: ::Output, - index: u32, - output: &Output, -) -> anyhow::Result<()> { - let amount = output.payload.extract::>()?.0; - let output_ref = OutputRef { tx_hash, index }; - match output.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - // Add it to the global unspent_outputs table - crate::sync::add_unspent_output(db, &output_ref, &owner_pubkey, &amount) - } - _ => Err(anyhow!("{:?}", ())), - } -} diff --git a/webservice-wallet-external-signing/src/output_filter.rs b/webservice-wallet-external-signing/src/output_filter.rs deleted file mode 100644 index 555fc5a55..000000000 --- a/webservice-wallet-external-signing/src/output_filter.rs +++ /dev/null @@ -1,137 +0,0 @@ -use runtime::{OuterVerifier, Output}; -use sp_core::H256; -use tuxedo_core::types::OutputRef; - -pub type OutputInfo = (Output, OutputRef); - -type TxHash = H256; -/// The Filter type which is the closure signature used by functions to filter UTXOS -pub type Filter = Box Result, ()>>; - -pub trait OutputFilter { - /// The Filter type which is the closure signature used by functions to filter UTXOS - type Filter; - /// Builds a filter to be passed to wallet sync functions to sync the chosen outputs - /// to the users DB. - fn build_filter(verifier: OuterVerifier) -> Self::Filter; -} - -pub struct Sr25519SignatureFilter; -impl OutputFilter for Sr25519SignatureFilter { - // Todo Add filter error - type Filter = Result; - - fn build_filter(verifier: OuterVerifier) -> Self::Filter { - Ok(Box::new(move |outputs, tx_hash| { - let filtered_outputs = outputs - .iter() - .enumerate() - .map(|(i, output)| { - ( - output.clone(), - OutputRef { - tx_hash: *tx_hash, - index: i as u32, - }, - ) - }) - .filter(|(output, _)| output.verifier == verifier) - .collect::>(); - Ok(filtered_outputs) - })) - } -} - -mod tests { - use super::*; - - #[cfg(test)] - use tuxedo_core::{dynamic_typing::DynamicallyTypedData, verifier::*}; - - pub struct TestSr25519SignatureFilter; - impl OutputFilter for TestSr25519SignatureFilter { - type Filter = Result; - - fn build_filter(_verifier: OuterVerifier) -> Self::Filter { - Ok(Box::new(move |_outputs, _tx_hash| { - println!("printed something"); - Ok(vec![]) - })) - } - } - - #[test] - fn filter_prints() { - let verifier = OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: H256::zero(), - }); - let output = Output { - verifier: verifier.clone(), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }; - - let my_filter = - TestSr25519SignatureFilter::build_filter(verifier).expect("Can build print filter"); - let _ = my_filter(&[output], &H256::zero()); - } - - #[test] - fn filter_sr25519_signature_works() { - let verifier = OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: H256::zero(), - }); - - let outputs_to_filter = vec![ - Output { - verifier: verifier.clone(), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }, - Output { - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: H256::from_slice(b"asdfasdfasdfasdfasdfasdfasdfasdf"), - }), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }, - Output { - verifier: OuterVerifier::ThresholdMultiSignature(ThresholdMultiSignature { - threshold: 1, - signatories: vec![H256::zero()], - }), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }, - ]; - - let expected_filtered_output_infos = vec![( - Output { - verifier: verifier.clone(), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )]; - - let my_filter = Sr25519SignatureFilter::build_filter(verifier) - .expect("Can build Sr25519Signature filter"); - let filtered_output_infos = my_filter(&outputs_to_filter, &H256::zero()) - .expect("Can filter the outputs by verifier correctly"); - - assert_eq!(filtered_output_infos, expected_filtered_output_infos); - } -} diff --git a/webservice-wallet-external-signing/src/rpc.rs b/webservice-wallet-external-signing/src/rpc.rs deleted file mode 100644 index 0d5466e6d..000000000 --- a/webservice-wallet-external-signing/src/rpc.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! 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> { - let params = rpc_params![Some(height)]; - let rpc_response: Option = client.request("chain_getBlockHash", params).await?; - let maybe_hash = rpc_response.map(|s| crate::h256_from_string(&s).unwrap()); - Ok(maybe_hash) -} - -/// Typed helper to get the node's full block at a particular hash -pub async fn node_get_block(hash: H256, client: &HttpClient) -> anyhow::Result> { - let s = hex::encode(hash.0); - let params = rpc_params![s]; - - let maybe_rpc_response: Option = - client.request("chain_getBlock", params).await?; - let rpc_response = maybe_rpc_response.unwrap(); - - let json_opaque_block = rpc_response.get("block").cloned().unwrap(); - let opaque_block: OpaqueBlock = serde_json::from_value(json_opaque_block).unwrap(); - - // I need a structured block, not an opaque one. To achieve that, I'll - // scale encode it, then once again decode it. - // Feels kind of like a hack, but I honestly don't know what else to do. - // I don't see any way to get the bytes out of an OpaqueExtrinsic. - let scale_bytes = opaque_block.encode(); - - let structured_block = Block::decode(&mut &scale_bytes[..]).unwrap(); - - Ok(Some(structured_block)) -} - -/// 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) -} diff --git a/webservice-wallet-external-signing/src/serviceHandlers/blockHandler/blockServicehandler.rs b/webservice-wallet-external-signing/src/serviceHandlers/blockHandler/blockServicehandler.rs deleted file mode 100644 index a643c225a..000000000 --- a/webservice-wallet-external-signing/src/serviceHandlers/blockHandler/blockServicehandler.rs +++ /dev/null @@ -1,68 +0,0 @@ -use serde::{Serialize}; - -use jsonrpsee::http_client::HttpClientBuilder; - - - - - - -use crate::rpc; - - - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; - -use axum::{Json,http::HeaderMap}; - - - -use runtime::{Block}; -use anyhow::bail; - -#[derive(Debug, Serialize)] -pub struct BlockResponse { - pub message: String, -} - -pub async fn get_block(headers: HeaderMap) -> Json { - let block_number_header = headers.get("Block-Number").unwrap_or_else(|| { - panic!("Block-Number header is missing"); - }); - let block_number = block_number_header.to_str().unwrap_or_else(|_| { - panic!("Failed to parse Block-Number header"); - }); - - // Convert the block number to the appropriate type if needed - let block_number: u128 = block_number.parse().unwrap_or_else(|_| { - panic!("Failed to parse block number as u128"); - }); - - match get_blocks(block_number).await { - Ok(Some(node_block)) => Json(BlockResponse { - message: format!("block found {:?}",node_block), - }), - - Ok(None) => Json(BlockResponse { - message: format!("Node's block not found"), - }), - Err(err) => Json(BlockResponse { - message: format!("Error getting the block: {:?}", err), - }), - } -} - -async fn get_blocks(number: u128) -> anyhow::Result> { - let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; - let node_block_hash = rpc::node_get_block_hash(number.try_into().unwrap(), &client) - .await? - .expect("node should be able to return some genesis hash"); - println!("Get blocks node_block_hash {:?} ",node_block_hash); - let maybe_block = rpc::node_get_block(node_block_hash, &client).await?; - println!("BlockData {:?} ",maybe_block.clone().unwrap()); - match maybe_block { - Some(block) => Ok(Some(block)), - None => bail!("Block not found for hash: {:?}", node_block_hash), - } -} \ No newline at end of file diff --git a/webservice-wallet-external-signing/src/serviceHandlers/keyHandler/keyServicehandler.rs b/webservice-wallet-external-signing/src/serviceHandlers/keyHandler/keyServicehandler.rs deleted file mode 100644 index 13493dca9..000000000 --- a/webservice-wallet-external-signing/src/serviceHandlers/keyHandler/keyServicehandler.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::{Deserialize, Serialize}; - - -use crate::keystore; - - - -use axum::{Json}; - -#[derive(Debug, Deserialize)] -pub struct GenerateKeyRequest { - pub password: Option, -} - -#[derive(Debug, Serialize)] -pub struct GenerateKeyResponse { - pub message: String, - pub public_key: Option, - pub phrase: Option, -} - -pub async fn debug_generate_key(body: Json) -> Json { - match keystore::generate_key(body.password.clone()).await { - Ok((public_key, phrase)) => Json(GenerateKeyResponse { - message: format!("Keys generated successfully"), - public_key: Some(public_key), - phrase: Some(phrase), - }), - Err(err) => Json(GenerateKeyResponse { - message: format!("Error generating keys: {:?}", err), - public_key: None, - phrase: None, - }), - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// get keys -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#[derive(Debug, Serialize)] -pub struct GetKeyResponse { - pub message: String, - pub keys: Option>, -} - -pub async fn debug_get_keys() -> Json { - match keystore::get_keys().await { - Ok(keys_iter) => { - // Lets collect keys into a vector of strings - let keys: Vec = keys_iter.map(|key| hex::encode(key)).collect(); - - Json(GetKeyResponse { - message: format!("Keys retrieved successfully"), - keys: Some(keys), - }) - } - Err(err) => Json(GetKeyResponse { - message: format!("Error retrieving keys: {:?}", err), - keys: None, - }), - } -} diff --git a/webservice-wallet-external-signing/src/serviceHandlers/kittyHandler/kittyServicehandler.rs b/webservice-wallet-external-signing/src/serviceHandlers/kittyHandler/kittyServicehandler.rs deleted file mode 100644 index 40838a204..000000000 --- a/webservice-wallet-external-signing/src/serviceHandlers/kittyHandler/kittyServicehandler.rs +++ /dev/null @@ -1,1146 +0,0 @@ -use serde::{Deserialize, Serialize}; - - -use jsonrpsee::http_client::HttpClientBuilder; - - - -use crate::kitty; -use sp_core::H256; - -use crate::cli::{CreateKittyArgs, - DelistKittyFromSaleArgs, UpdateKittyNameArgs, UpdateKittyPriceArgs, - BuyKittyArgs, BreedKittyArgs}; - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; -use crate::get_local_keystore; -use crate::sync_and_get_db; -use crate::original_get_db; -use crate::convert_output_ref_from_string; - -use axum::{Json,http::HeaderMap}; - -use std::convert::Infallible; - - - -use runtime::{ - kitties::{ - KittyData, - }, - money::{Coin}, - tradable_kitties::{TradableKittyData}, - OuterVerifier, Transaction, -}; -use tuxedo_core::types::OutputRef; -use tuxedo_core::types::Output; - -#[derive(Debug, Deserialize)] -pub struct CreateKittyRequest { - pub name: String, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct CreateKittyResponse { - pub message: String, - pub kitty:Option - // Add any additional fields as needed -} - -pub async fn create_kitty(body: Json) -> Result, Infallible> { - println!("create_kitties called "); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - //let db = sync_and_get_db().await.expect("Error"); - let db = original_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(CreateKittyResponse { - message: format!("Error creating HTTP client: {:?}", err), - kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - - match kitty::create_kitty(&db, &client, CreateKittyArgs { - kitty_name: body.name.to_string(), - owner: public_key_h256, - }).await { - Ok(Some(created_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = CreateKittyResponse { - message: format!("Kitty created successfully"), - kitty: Some(created_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(CreateKittyResponse { - message: format!("Kitty creation failed: No data returned"), - kitty:None, - })), - Err(err) => Ok(Json(CreateKittyResponse { - message: format!("Error creating kitty: {:?}", err), - kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// Get kitty by DNA -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetKittyByDnaResponse { - pub message: String, - pub kitty:Option, -} - -pub async fn get_kitty_by_dna(headers: HeaderMap) -> Json { - println!("Headers map = {:?}",headers); - let dna_header = headers - .get("kitty-dna") - .expect("Kitty DNA header is missing") - .to_str() - .expect("Failed to parse Kitty DNA header"); - let db = original_get_db().await.expect("Error"); - let mut found_kitty: Option<(KittyData, OutputRef)> = None; - - if let Ok(Some((kitty_info, out_ref))) = - crate::sync::get_kitty_from_local_db_based_on_dna(&db,dna_header) - { - found_kitty = Some((kitty_info, out_ref)); - } - - let response = match found_kitty { - Some((kitty_info, _)) => GetKittyByDnaResponse { - message: format!("Success: Found Kitty with DNA {:?}", dna_header), - kitty: Some(kitty_info), - }, - None => GetKittyByDnaResponse { - message: format!("Error: Can't find Kitty with DNA {:?}", dna_header), - kitty: None, - }, - }; - - Json(response) -} - - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetTdKittyByDnaResponse { - pub message: String, - pub td_kitty:Option, -} - -pub async fn get_td_kitty_by_dna(headers: HeaderMap) -> Json { - println!("Headers map = in td kitty {:?}",headers); - let dna_header = headers - .get("td-kitty-dna") - .expect("Td-Kitty DNA header is missing") - .to_str() - .expect("Failed to parse Td-Kitty DNA header"); - let db = original_get_db().await.expect("Error"); - let mut found_td_kitty: Option<(TradableKittyData, OutputRef)> = None; - - if let Ok(Some((td_kitty_info, out_ref))) = - crate::sync::get_tradable_kitty_from_local_db_based_on_dna(&db,dna_header) - { - found_td_kitty = Some((td_kitty_info, out_ref)); - } - - let response = match found_td_kitty { - Some((kitty_info, _)) => GetTdKittyByDnaResponse { - message: format!("Success: Found Tradable Kitty with DNA {:?}", dna_header), - td_kitty: Some(kitty_info), - }, - None => GetTdKittyByDnaResponse { - message: format!("Error: Can't find Tradable Kitty with DNA {:?}", dna_header), - td_kitty: None, - }, - }; - - Json(response) -} - -//////////////////////////////////////////////////////////////////// -// Get all kitty List -//////////////////////////////////////////////////////////////////// -#[derive(Debug, Serialize, Deserialize)] -pub struct GetAllKittiesResponse { - pub message: String, - pub kitty_list:Option>, -} - -pub async fn get_all_kitty_list() -> Json { - let db = original_get_db().await.expect("Error"); - - match crate::sync::get_all_kitties_from_local_db(&db) { - Ok(all_kitties) => { - let kitty_list: Vec = all_kitties.map(|(_, kitty)| kitty).collect(); - - if !kitty_list.is_empty() { - return Json(GetAllKittiesResponse { - message: format!("Success: Found Kitties"), - kitty_list: Some(kitty_list), - }); - } - }, - Err(_) => { - return Json(GetAllKittiesResponse { - message: format!("Error: Can't find Kitties"), - kitty_list: None, - }); - } - } - - Json(GetAllKittiesResponse { - message: format!("Error: Can't find Kitties"), - kitty_list: None, - }) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetAllTdKittiesResponse { - pub message: String, - pub td_kitty_list:Option>, -} - -pub async fn get_all_td_kitty_list() -> Json { - let db = original_get_db().await.expect("Error"); - - match crate::sync::get_all_tradable_kitties_from_local_db(&db) { - Ok(owned_kitties) => { - let tradable_kitty_list: Vec = owned_kitties.map(|(_, kitty)| kitty).collect(); - - if !tradable_kitty_list.is_empty() { - return Json(GetAllTdKittiesResponse { - message: format!("Success: Found TradableKitties"), - td_kitty_list: Some(tradable_kitty_list), - }); - } - }, - Err(_) => { - return Json(GetAllTdKittiesResponse { - message: format!("Error: Can't find TradableKitties"), - td_kitty_list: None, - }); - } - } - - Json(GetAllTdKittiesResponse { - message: format!("Error: Can't find Kitties"), - td_kitty_list: None, - }) -} -//////////////////////////////////////////////////////////////////// -// Get owned kitties -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetOwnedKittiesResponse { - pub message: String, - pub kitty_list:Option>, -} -use std::str::FromStr; -pub async fn get_owned_kitty_list(headers: HeaderMap) -> Json { - let public_key_header = headers.get("owner_public_key").expect("public_key_header is missing"); - - let public_key_h256 = H256::from_str(public_key_header.to_str().expect("Failed to convert to H256")); - - let db = original_get_db().await.expect("Error"); - - match crate::sync::get_owned_kitties_from_local_db(&db,&public_key_h256.unwrap()) { - Ok(owned_kitties) => { - let kitty_list: Vec = owned_kitties.map(|(_, kitty,_)| kitty).collect(); - - if !kitty_list.is_empty() { - return Json(GetOwnedKittiesResponse { - message: format!("Success: Found Kitties"), - kitty_list: Some(kitty_list), - }); - } - }, - Err(_) => { - return Json(GetOwnedKittiesResponse { - message: format!("Error: Can't find Kitties"), - kitty_list: None, - }); - } - } - - Json(GetOwnedKittiesResponse { - message: format!("Error: Can't find Kitties"), - kitty_list: None, - }) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetOwnedTdKittiesResponse { - pub message: String, - pub td_kitty_list:Option>, -} - -pub async fn get_owned_td_kitty_list(headers: HeaderMap) -> Json { - let public_key_header = headers.get("owner_public_key").expect("public_key_header is missing"); - let public_key_h256 = H256::from_str(public_key_header.to_str().expect("Failed to convert to H256")); - let db = original_get_db().await.expect("Error"); - - match crate::sync::get_owned_tradable_kitties_from_local_db(&db,&public_key_h256.unwrap()) { - Ok(owned_kitties) => { - let tradable_kitty_list: Vec = owned_kitties.map(|(_, kitty, _)| kitty).collect(); - - if !tradable_kitty_list.is_empty() { - return Json(GetOwnedTdKittiesResponse { - message: format!("Success: Found TradableKitties"), - td_kitty_list: Some(tradable_kitty_list), - }); - } - }, - Err(_) => { - return Json(GetOwnedTdKittiesResponse { - message: format!("Error: Can't find TradableKitties"), - td_kitty_list: None, - }); - } - } - - Json(GetOwnedTdKittiesResponse { - message: format!("Error: Can't find td Kitties"), - td_kitty_list: None, - }) -} - -//////////////////////////////////////////////////////////////////// -// Common structures and functions -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetTxnAndUtxoListForList { - pub message: String, - pub transaction: Option, - pub input_utxo_list:Option>>, -} - -#[derive(Debug, Deserialize)] -pub struct SignedTxnRequest { - pub signed_transaction: Transaction, -} - -async fn create_response( - txn: Option, - message: String, -) -> Json { - match txn { - Some(txn) => { - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Json(GetTxnAndUtxoListForList { - message: format!("Error creating HTTP client: {:?}", err), - transaction: None, - input_utxo_list: None, - }); - } - }; - let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; - Json(GetTxnAndUtxoListForList { - message, - transaction: Some(txn), - input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), - }) - }, - None => Json(GetTxnAndUtxoListForList { - message, - transaction: None, - input_utxo_list: None, - }), - } -} - - -//////////////////////////////////////////////////////////////////// -// List kitty for Sale -//////////////////////////////////////////////////////////////////// - - - -pub async fn get_txn_and_inpututxolist_for_list_kitty_for_sale(headers: HeaderMap) -> Json { - println!("Headers map = {:?}",headers); - - let dna_header = headers - .get("kitty-dna") - .expect("Kitty DNA header is missing") - .to_str() - .expect("Failed to parse Kitty DNA header"); - - let price_header = headers - .get("kitty-price") - .expect("Kitty price is missing"); - - let price_number: u128 = price_header - .to_str() - .expect("Failed to parse priceheader") - .parse() - .expect("ailed to parse price number as u128"); - - - let public_key_header = headers - .get("owner_public_key") - .expect("public_key_header is missing"); - - let public_key_h256 = H256::from_str(public_key_header - .to_str() - .expect("Failed to convert to H256")); - - let db = original_get_db().await.expect("Error"); - - match kitty::create_txn_for_list_kitty(&db, - dna_header, - price_number, - public_key_h256.unwrap(), - ).await { - Ok(txn) => create_response( - txn, - "List kitty for Sale txn created successfully".to_string(), - ).await, - Err(err) => create_response( - None, - format!("Error!! List kitty for sale txn creation: {:?}", err), - ).await, - } -} - -#[derive(Debug, Serialize)] -pub struct ListKittyForSaleResponse { - pub message: String, - pub td_kitty:Option - // Add any additional fields as needed -} - -pub async fn list_kitty_for_sale (body: Json) -> Result, Infallible> { - println!("List kitties for sale is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(ListKittyForSaleResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - match kitty::list_kitty_for_sale(&body.signed_transaction, - &client).await { - Ok(Some(listed_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = ListKittyForSaleResponse { - message: format!("Kitty listed for sale successfully"), - td_kitty: Some(listed_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(ListKittyForSaleResponse { - message: format!("Kitty listing forsale failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(ListKittyForSaleResponse { - message: format!("Error listing forsale: {:?}", err), - td_kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// De-list kitty from Sale -//////////////////////////////////////////////////////////////////// - - -pub async fn get_txn_and_inpututxolist_for_delist_kitty_from_sale(headers: HeaderMap) -> Json { - // create_tx_for_list_kitty - println!("Headers map = {:?}",headers); - let dna_header = headers - .get("kitty-dna") - .expect("Kitty DNA header is missing") - .to_str() - .expect("Failed to parse Kitty DNA header"); - - let public_key_header = headers - .get("owner_public_key") - .expect("public_key_header is missing"); - - let public_key_h256 = H256::from_str(public_key_header - .to_str() - .expect("Failed to convert to H256")); - - let db = original_get_db().await.expect("Error"); - - match kitty::create_txn_for_delist_kitty(&db, - dna_header, - public_key_h256.unwrap(), - ).await { - Ok(txn) => create_response( - txn, - "List kitty for Sale txn created successfully".to_string(), - ).await, - Err(err) => create_response( - None, - format!("Error!! List kitty for sale txn creation: {:?}", err), - ).await, - } -} - -#[derive(Debug, Serialize)] -pub struct DelistKittyFromSaleResponse { - pub message: String, - pub kitty:Option -} -pub async fn delist_kitty_from_sale(body: Json) -> Result, Infallible> { - println!("List kitties for sale is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(DelistKittyFromSaleResponse { - message: format!("Error creating HTTP client: {:?}", err), - kitty:None, - })); - } - }; - - match kitty::delist_kitty_from_sale(&body.signed_transaction, - &client).await { - Ok(Some(delisted_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = DelistKittyFromSaleResponse { - message: format!("Kitty delisted from sale successfully"), - kitty: Some(delisted_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(DelistKittyFromSaleResponse { - message: format!("Kitty delisting from sale failed: No data returned"), - kitty:None, - })), - Err(err) => Ok(Json(DelistKittyFromSaleResponse { - message: format!("Error delisting from sale: {:?}", err), - kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// Update kitty name -//////////////////////////////////////////////////////////////////// - -pub async fn get_txn_and_inpututxolist_for_kitty_name_update(headers: HeaderMap) -> Json { - println!("Headers map = {:?}",headers); - let dna_header = headers - .get("kitty-dna") - .expect("Kitty DNA header is missing") - .to_str() - .expect("Failed to parse Kitty DNA header"); - - let new_name_header = headers - .get("kitty-new-name") - .expect("Kitty name is missing"); - - let public_key_header = headers - .get("owner_public_key") - .expect("public_key_header is missing"); - - let public_key_h256 = H256::from_str(public_key_header - .to_str() - .expect("Failed to convert to H256")); - - let db = original_get_db().await.expect("Error"); - - match kitty::create_txn_for_kitty_name_update(&db, - dna_header, - new_name_header.to_str().expect("Failed to parse name header").to_string(), - public_key_h256.unwrap(), - ).await { - Ok(txn) => create_response( - txn, - "Kitty name update txn created successfully".to_string(), - ).await, - Err(err) => create_response( - None, - format!("Error!! Kitty name update txn creation: {:?}", err), - ).await, - } -} - -#[derive(Debug, Serialize)] -pub struct UpdateKittyNameResponse { - pub message: String, - pub kitty:Option -} -pub async fn update_kitty_name(body: Json) -> Result, Infallible> { - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(UpdateKittyNameResponse { - message: format!("Error creating HTTP client: {:?}", err), - kitty:None, - })); - } - }; - - match kitty::update_kitty_name(&body.signed_transaction, - &client).await { - Ok(Some(updated_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = UpdateKittyNameResponse { - message: format!("Kitty name updated successfully"), - kitty: Some(updated_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(UpdateKittyNameResponse { - message: format!("Kitty name update failed: No data returned"), - kitty:None, - })), - Err(err) => Ok(Json(UpdateKittyNameResponse { - message: format!("Error!! Kitty name update: {:?}", err), - kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// Update tradable kitty name -//////////////////////////////////////////////////////////////////// - -pub async fn get_txn_and_inpututxolist_for_td_kitty_name_update(headers: HeaderMap) -> Json { - println!("Headers map = {:?}",headers); - let dna_header = headers - .get("kitty-dna") - .expect("Kitty DNA header is missing") - .to_str() - .expect("Failed to parse Kitty DNA header"); - let db = original_get_db().await.expect("Error"); - - let new_name_header = headers - .get("kitty-new-name") - .expect("Kitty name is missing"); - - let public_key_header = headers - .get("owner_public_key") - .expect("public_key_header is missing"); - - let public_key_h256 = H256::from_str(public_key_header - .to_str() - .expect("Failed to convert to H256")); - - match kitty::create_txn_for_td_kitty_name_update(&db, - dna_header, - new_name_header.to_str().expect("Failed to parse name header").to_string(), - public_key_h256.unwrap(), - ).await { - Ok(txn) => create_response( - txn, - "Td Kitty name update txn created successfully".to_string(), - ).await, - Err(err) => create_response( - None, - format!("Error!! Td-Kitty name update txn creation: {:?}", err), - ).await, - } -} - -#[derive(Debug, Serialize)] -pub struct UpdateTddKittyNameResponse { - pub message: String, - pub td_kitty:Option -} -pub async fn update_td_kitty_name(body: Json) -> Result, Infallible> { - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(UpdateTddKittyNameResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - match kitty::update_td_kitty_name(&body.signed_transaction, - &client).await { - Ok(Some(updated_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = UpdateTddKittyNameResponse { - message: format!("Td-Kitty name updated successfully"), - td_kitty: Some(updated_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(UpdateTddKittyNameResponse { - message: format!("Td-Kitty name update failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(UpdateTddKittyNameResponse { - message: format!("Error!! Td-Kitty name update: {:?}", err), - td_kitty:None, - })), - } -} - - -//////////////////////////////////////////////////////////////////// -// Update td-kitty price -//////////////////////////////////////////////////////////////////// - -pub async fn get_txn_and_inpututxolist_for_td_kitty_price_update(headers: HeaderMap) -> Json { - println!("Headers map = {:?}",headers); - let dna_header = headers - .get("kitty-dna") - .expect("Kitty DNA header is missing") - .to_str() - .expect("Failed to parse Kitty DNA header"); - - let price_header = headers - .get("kitty-price") - .expect("Kitty price is missing"); - - // Convert the block number to the appropriate type if needed - let price_number: u128 = price_header - .to_str() - .expect("Failed to parse priceheader to str") - .parse().expect("Failed to parse priceheader to u128"); - - let db = original_get_db().await.expect("Error"); - - let public_key_header = headers - .get("owner_public_key") - .expect("public_key_header is missing"); - - let public_key_h256 = H256::from_str(public_key_header - .to_str() - .expect("Failed to convert to H256")); - - match kitty::create_txn_for_td_kitty_price_update( - &db, - dna_header, - price_number, - public_key_h256.unwrap(), - ).await { - Ok(Some(txn)) => { - // Convert created_kitty to JSON and include it in the response - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Json(GetTxnAndUtxoListForList { - message: format!("Error creating HTTP client: {:?}", err), - transaction:None, - input_utxo_list:None - }); - } - }; - let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; - - let response = GetTxnAndUtxoListForList { - message: format!("Kitty name update txn created successfully"), - transaction: Some(txn), - input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), - }; - Json(response) - }, - Ok(None) => Json(GetTxnAndUtxoListForList { - message: format!("Kitty name update txn creation failed: No input returned"), - transaction:None, - input_utxo_list:None - }), - Err(err) => Json(GetTxnAndUtxoListForList { - message: format!("Error!! Kitty name update txn creation: {:?}", err), - transaction:None, - input_utxo_list:None - }), - } -} - -#[derive(Debug, Serialize)] -pub struct UpdateTdKittyPriceResponse { - pub message: String, - pub td_kitty:Option -} - -pub async fn update_td_kitty_price(body: Json) -> Result, Infallible> { - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(UpdateTdKittyPriceResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - match kitty::update_td_kitty_price(&body.signed_transaction, - &client).await { - Ok(Some(updated_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = UpdateTdKittyPriceResponse { - message: format!("Kitty price updated successfully"), - td_kitty: Some(updated_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(UpdateTdKittyPriceResponse { - message: format!("Kitty price update failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(UpdateTdKittyPriceResponse { - message: format!("Error in kitty price update: {:?}", err), - td_kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// Breed kitty -//////////////////////////////////////////////////////////////////// - -pub async fn get_txn_and_inpututxolist_for_breed_kitty(headers: HeaderMap) -> Json { - println!("Headers map = {:?}",headers); - let mom_dna = headers - .get("mom-dna") - .expect("MOM DNA header is missing") - .to_str() - .expect("Failed to parse MOM DNA header"); - - let dad_dna = headers - .get("dad-dna") - .expect("Dad DNA header is missing") - .to_str() - .expect("Failed to parse Dad DNA header"); - - let child_kitty_name = headers - .get("child-kitty-name") - .expect("Child Kitty name is missing"); - - let db = original_get_db().await.expect("Error"); - - let public_key_header = headers - .get("owner_public_key") - .expect("public_key_header is missing"); - - let public_key_h256 = H256::from_str(public_key_header - .to_str() - .expect("Failed to convert to H256")); - - match kitty::create_txn_for_breed_kitty( - &db, - mom_dna, - dad_dna, - child_kitty_name.to_str().expect("Failed to parse name header").to_string(), - public_key_h256.unwrap(), - ).await { - Ok(Some(txn)) => { - // Convert created_kitty to JSON and include it in the response - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Json(GetTxnAndUtxoListForList { - message: format!("Error creating HTTP client: {:?}", err), - transaction:None, - input_utxo_list:None - }); - } - }; - let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; - - let response = GetTxnAndUtxoListForList { - message: format!("Kitty name update txn created successfully"), - transaction: Some(txn), - input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), - }; - Json(response) - }, - Ok(None) => Json(GetTxnAndUtxoListForList { - message: format!("Kitty name update txn creation failed: No input returned"), - transaction:None, - input_utxo_list:None - }), - Err(err) => Json(GetTxnAndUtxoListForList { - message: format!("Error!! Kitty name update txn creation: {:?}", err), - transaction:None, - input_utxo_list:None - }), - } -} - -#[derive(Debug, Serialize)] -pub struct BreedKittyResponse { - pub message: String, - pub mom_kitty:Option, - pub dad_kitty:Option, - pub child_kitty:Option, -} - -pub async fn breed_kitty(body: Json) -> Result, Infallible> { - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(BreedKittyResponse { - message: format!("Error creating HTTP client: {:?}", err), - mom_kitty: None, - dad_kitty: None, - child_kitty: None, - })); - } - }; - - match kitty::breed_kitty(&body.signed_transaction, - &client).await { - Ok(Some(kitty_family)) => { - // Convert created_kitty to JSON and include it in the response - let response = BreedKittyResponse { - message: format!("Kitty breeding done successfully"), - mom_kitty: Some(kitty_family[0].clone()), - dad_kitty: Some(kitty_family[1].clone()), - child_kitty: Some(kitty_family[2].clone()), - - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(BreedKittyResponse { - message: format!("Kitty breeding failed: No data returned"), - mom_kitty: None, - dad_kitty: None, - child_kitty: None, - })), - Err(err) => Ok(Json(BreedKittyResponse { - message: format!("Error in kitty breed: {:?}", err), - mom_kitty: None, - dad_kitty: None, - child_kitty: None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// Buy kitty -//////////////////////////////////////////////////////////////////// - -pub async fn get_txn_and_inpututxolist_for_buy_kitty(headers: HeaderMap) -> Json { - println!("Headers map = {:?}",headers); - - let input_coins: Vec = headers - .get_all("input-coins") - .iter() - // Convert each coin string to an OutputRef, filter out errors - .filter_map(|header| { - let coin_str = header.to_str().unwrap_or_default(); - match convert_output_ref_from_string(coin_str) { - Ok(output_ref) => Some(output_ref), - Err(err) => { - // Print error message and skip this coin - eprintln!("Error converting input coin: {}", err); - None - } - } - }) - .collect(); - println!("Input coins: {:?}", input_coins); - let output_amount: Vec = headers - .get("output_amount") - .map_or_else(|| Vec::new(), |header| { - header - .to_str() - .unwrap_or_default() - .split(',') - .filter_map(|amount_str| amount_str.parse().ok()) - .collect() - }); - // Use the converted coins Vec as needed - println!("output_amount: {:?}", output_amount); - - let kitty_dna = headers - .get("kitty-dna") - .expect("Kitty DNA header is missing") - .to_str() - .expect("Failed to parse Kitty DNA header"); - - - let db = original_get_db().await.expect("Error"); - - let buyer_public_key = headers - .get("buyer_public_key") - .expect("buyer_public_key is missing"); - - let buyer_public_key_h256 = H256::from_str(buyer_public_key - .to_str() - .expect("Failed to convert buyer_public_keyto H256")); - - let seller_public_key = headers - .get("seller_public_key") - .expect("seller_public_key is missing"); - - let seller_public_key_h256 = H256::from_str(seller_public_key - .to_str() - .expect("Failed to convert seller_public_key to H256")); - - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Json(GetTxnAndUtxoListForList { - message: format!("Error creating HTTP client: {:?}", err), - transaction:None, - input_utxo_list:None - }); - } - }; - - match kitty::create_txn_for_buy_kitty( - &db, - input_coins, - &kitty_dna, - buyer_public_key_h256.unwrap(), - seller_public_key_h256.unwrap(), - &output_amount, - &client, - ).await { - Ok(Some(txn)) => { - // Convert created_kitty to JSON and include it in the response - let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; - - let response = GetTxnAndUtxoListForList { - message: format!("Kitty name update txn created successfully"), - transaction: Some(txn), - input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), - }; - Json(response) - }, - Ok(None) => Json(GetTxnAndUtxoListForList { - message: format!("Kitty name update txn creation failed: No input returned"), - transaction:None, - input_utxo_list:None - }), - Err(err) => Json(GetTxnAndUtxoListForList { - message: format!("Error!! Kitty name update txn creation: {:?}", err), - transaction:None, - input_utxo_list:None - }), - } -} - -#[derive(Debug, Serialize)] -pub struct BuyTdKittyResponse { - pub message: String, - pub td_kitty:Option -} - - -pub async fn buy_kitty(body: Json) -> Result, Infallible> { - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(BuyTdKittyResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - match kitty::buy_kitty(&body.signed_transaction, - &client).await { - Ok(Some(traded_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = BuyTdKittyResponse { - message: format!("Kitty traded successfully"), - td_kitty: Some(traded_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(BuyTdKittyResponse { - message: format!("Kitty trade failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(BuyTdKittyResponse { - message: format!("Error in trading: {:?}", err), - td_kitty:None, - })), - } -} - - - -/* -pub async fn breed_kitty(body: Json) -> Result, Infallible> { - println!("update_td_kitty_price is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let db = sync_and_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(BreedKittyResponse { - message: format!("Error creating HTTP client: {:?}", err), - mom_kitty:None, - dad_kitty:None, - child_kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256_of_owner = H256::from_slice(&public_key_bytes); - - let ks = get_local_keystore().await.expect("Error"); - - match kitty::breed_kitty(&db, &client, &ks,BreedKittyArgs { - mom_name: body.mom_name.clone(), - dad_name: body.dad_name.clone(), - owner: public_key_h256_of_owner, - }).await { - Ok(Some(new_family)) => { - // Convert created_kitty to JSON and include it in the response - let response = BreedKittyResponse { - message: format!("breeding successfully"), - mom_kitty:Some(new_family[0].clone()), - dad_kitty:Some(new_family[1].clone()), - child_kitty:Some(new_family[2].clone()), - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(BreedKittyResponse { - message: format!("Error in breeding failed: No data returned"), - mom_kitty:None, - dad_kitty:None, - child_kitty:None, - })), - Err(err) => Ok(Json(BreedKittyResponse { - message: format!("Error in breeding : {:?}", err), - mom_kitty:None, - dad_kitty:None, - child_kitty:None, - })), - } -} -*/ \ No newline at end of file diff --git a/webservice-wallet-external-signing/src/serviceHandlers/moneyHandler/moneyServicehandler.rs b/webservice-wallet-external-signing/src/serviceHandlers/moneyHandler/moneyServicehandler.rs deleted file mode 100644 index 2b42a45cc..000000000 --- a/webservice-wallet-external-signing/src/serviceHandlers/moneyHandler/moneyServicehandler.rs +++ /dev/null @@ -1,124 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use jsonrpsee::http_client::HttpClientBuilder; -use crate::money; -use sp_core::H256; - -use crate::cli::MintCoinArgs; -use crate::original_get_db; - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; - - -use axum::{Json,http::HeaderMap}; - - - -#[derive(Debug, Deserialize)] -pub struct MintCoinsRequest { - pub amount: u128, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct MintCoinsResponse { - pub message: String, - - // Add any additional fields as needed -} - -pub async fn mint_coins(body: Json) -> Json { - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Json(MintCoinsResponse { - message: format!("Error creating HTTP client: {:?}", err), - }); - } - }; - - // Convert the hexadecimal string to bytes - //let public_key_bytes = hex::decode(SHAWN_PUB_KEY).expect("Invalid hexadecimal string"); - let public_key_bytes = hex::decode(body.owner_public_key.as_str()).expect("Invalid hexadecimal string"); - - // Convert the bytes to H256 - let public_key_h256 = H256::from_slice(&public_key_bytes); - // Call the mint_coins function from your CLI wallet module - match money::mint_coins(&client, MintCoinArgs { - amount: body.amount, - owner: public_key_h256, - }).await { - Ok(()) => Json(MintCoinsResponse { - message: format!("Coins minted successfully"), - }), - Err(err) => Json(MintCoinsResponse { - message: format!("Error minting coins: {:?}", err), - }), - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetCoinsResponse { - pub message: String, - pub coins:Option>, -} - -pub async fn get_all_coins() -> Json { - let db = original_get_db().await.expect("Error"); - - match crate::sync::get_all_coins(&db) { - Ok(all_coins) => { - - if !all_coins.is_empty() { - return Json(GetCoinsResponse { - message: format!("Success: Found Coins"), - coins: Some(all_coins), - }); - } - }, - Err(_) => { - return Json(GetCoinsResponse { - message: format!("Error: Can't find coins"), - coins: None, - }); - } - } - - Json(GetCoinsResponse { - message: format!("Error: Can't find coins"), - coins: None, - }) -} - -use std::str::FromStr; -pub async fn get_owned_coins(headers: HeaderMap) -> Json { - let public_key_header = headers.get("owner_public_key").expect("public_key_header is missing"); - let public_key_h256 = H256::from_str(public_key_header.to_str().expect("Failed to convert to H256")); - - let db = original_get_db().await.expect("Error"); - - match crate::sync::get_owned_coins(&db,&public_key_h256.unwrap()) { - Ok(all_coins) => { - - if !all_coins.is_empty() { - return Json(GetCoinsResponse { - message: format!("Success: Found Coins"), - coins: Some(all_coins), - }); - } - }, - Err(_) => { - return Json(GetCoinsResponse { - message: format!("Error: Can't find coins"), - coins: None, - }); - } - } - - Json(GetCoinsResponse { - message: format!("Error: Can't find coins"), - coins: None, - }) -} diff --git a/webservice-wallet-external-signing/src/sync.rs b/webservice-wallet-external-signing/src/sync.rs deleted file mode 100644 index 2bae5a886..000000000 --- a/webservice-wallet-external-signing/src/sync.rs +++ /dev/null @@ -1,1199 +0,0 @@ -//! This module is responsible for maintaining the wallet's local database of blocks -//! and owned UTXOs to the canonical database reported by the node. -//! -//! It is backed by a sled database -//! -//! ## Schema -//! -//! There are 4 tables in the database -//! BlockHashes block_number:u32 => block_hash:H256 -//! Blocks block_hash:H256 => block:Block -//! UnspentOutputs output_ref => (owner_pubkey, amount) -//! SpentOutputs output_ref => (owner_pubkey, amount) - -use std::path::PathBuf; - -use crate::rpc; -use anyhow::anyhow; -use parity_scale_codec::{Decode, Encode}; -use sled::Db; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use tuxedo_core::{ - dynamic_typing::UtxoData, - types::{Input, OutputRef}, -}; - - - -use jsonrpsee::http_client::HttpClient; -use runtime::kitties::KittyDNA; -use runtime::kitties::KittyData; - -use runtime::{ - money::Coin, timestamp::Timestamp, tradable_kitties::TradableKittyData, Block, OuterVerifier, - Transaction, -}; - -/*Todo: Do we need all the data of kitty here -use runtime::{ - kitties::{KittyData, Parent,KittyHelpers,MomKittyStatus,DadKittyStatus, - KittyDNA,FreeKittyConstraintChecker} -}; -*/ - -/// The identifier for the blocks tree in the db. -const BLOCKS: &str = "blocks"; - -/// The identifier for the block_hashes tree in the db. -const BLOCK_HASHES: &str = "block_hashes"; - -/// The identifier for the unspent tree in the db. -const UNSPENT: &str = "unspent"; - -/// The identifier for the spent tree in the db. -const SPENT: &str = "spent"; - -/// The identifier for the owned kitties in the db. -const FRESH_KITTY: &str = "fresh_kitty"; - -/// The identifier for the owned kitties in the db. -const USED_KITTY: &str = "used_kitty"; - -/// The identifier for the owned kitties in the db. -const FRESH_TRADABLE_KITTY: &str = "fresh_tradable_kitty"; - -/// The identifier for the owned kitties in the db. -const USED_TRADABLE_KITTY: &str = "used_tradable_kitty"; - -/// 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 -/// If an empty database is opened, it is initialized with the expected genesis hash and genesis block -pub(crate) fn open_db( - db_path: PathBuf, - expected_genesis_hash: H256, - expected_genesis_block: Block, -) -> anyhow::Result { - //TODO figure out why this assertion fails. - //assert_eq!(BlakeTwo256::hash_of(&expected_genesis_block.encode()), expected_genesis_hash, "expected block hash does not match expected block"); - - let db = sled::open(db_path)?; - - // Open the tables we'll need - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - let wallet_blocks_tree = db.open_tree("blocks")?; - - // If the database is already populated, just make sure it is for the same genesis block - if height(&db)?.is_some() { - // There are database blocks, so do a quick precheck to make sure they use the same genesis block. - let wallet_genesis_ivec = wallet_block_hashes_tree - .get(0.encode())? - .expect("We know there are some blocks, so there should be a 0th block."); - let wallet_genesis_hash = H256::decode(&mut &wallet_genesis_ivec[..])?; - log::debug!("Found existing database."); - if expected_genesis_hash != wallet_genesis_hash { - log::error!("Wallet's genesis does not match expected. Aborting database opening."); - return Err(anyhow!("Node reports a different genesis block than wallet. Wallet: {wallet_genesis_hash:?}. Expected: {expected_genesis_hash:?}. Aborting all operations")); - } - return Ok(db); - } - - // If there are no local blocks yet, initialize the tables - log::info!( - "Initializing fresh sync from genesis {:?}", - expected_genesis_hash - ); - - // Update both tables - wallet_block_hashes_tree.insert(0u32.encode(), expected_genesis_hash.encode())?; - wallet_blocks_tree.insert( - expected_genesis_hash.encode(), - expected_genesis_block.encode(), - )?; - - Ok(db) -} - -/// Synchronize the local database to the database of the running node. -/// The wallet entirely trusts the data the node feeds it. In the bigger -/// picture, that means run your own (light) node. -pub(crate) async fn synchronize bool>( - db: &Db, - client: &HttpClient, - filter: &F, -) -> anyhow::Result<()> { - //log::info!("Synchronizing wallet with node."); - println!("Synchronizing wallet with node."); - - // Start the algorithm at the height that the wallet currently thinks is best. - // Fetch the block hash at that height from both the wallet's local db and the node - let mut height: u32 = height(db)?.ok_or(anyhow!("tried to sync an uninitialized database"))?; - let mut wallet_hash = get_block_hash(db, height)? - .expect("Local database should have a block hash at the height reported as best"); - let mut node_hash: Option = rpc::node_get_block_hash(height, client).await?; - - // There may have been a re-org since the last time the node synced. So we loop backwards from the - // best height the wallet knows about checking whether the wallet knows the same block as the node. - // If not, we roll this block back on the wallet's local db, and then check the next ancestor. - // When the wallet and the node agree on the best block, the wallet can re-sync following the node. - // In the best case, where there is no re-org, this loop will execute zero times. - while Some(wallet_hash) != node_hash { - log::info!("Divergence at height {height}. Node reports block: {node_hash:?}. Reverting wallet block: {wallet_hash:?}."); - - unapply_highest_block(db).await?; - - // Update for the next iteration - height -= 1; - wallet_hash = get_block_hash(db, height)? - .expect("Local database should have a block hash at the height reported as best"); - node_hash = rpc::node_get_block_hash(height, client).await?; - } - - // Orphaned blocks (if any) have been discarded at this point. - // So we prepare our variables for forward syncing. - log::debug!("Resyncing from common ancestor {node_hash:?} - {wallet_hash:?}"); - height += 1; - node_hash = rpc::node_get_block_hash(height, client).await?; - - // Now that we have checked for reorgs and rolled back any orphan blocks, we can go ahead and sync forward. - while let Some(hash) = node_hash { - log::debug!("Forward syncing height {height}, hash {hash:?}"); - - // Fetch the entire block in order to apply its transactions - let block = rpc::node_get_block(hash, client) - .await? - .expect("Node should be able to return a block whose hash it already returned"); - - // Apply the new block - apply_block(db, block, hash, filter).await?; - - height += 1; - - node_hash = rpc::node_get_block_hash(height, client).await?; - } - - log::debug!("Done with forward sync up to {}", height - 1); - println!("Done with forward sync up to {}", height - 1); - if let Err(err) = db.flush() { - println!("Error flushing Sled database: {}", err); - } - Ok(()) -} - -/// Gets the owner and amount associated with an output ref from the unspent table -/// -/// Some if the output ref exists, None if it doesn't -pub(crate) fn get_unspent(db: &Db, output_ref: &OutputRef) -> anyhow::Result> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - let Some(ivec) = wallet_unspent_tree.get(output_ref.encode())? else { - return Ok(None); - }; - - Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) -} - -/// Picks an arbitrary set of unspent outputs from the database for spending. -/// The set's token values must add up to at least the specified target value. -/// -/// The return value is None if the total value of the database is less than the target -/// It is Some(Vec![...]) when it is possible -pub(crate) fn get_arbitrary_unspent_set( - db: &Db, - target: u128, -) -> anyhow::Result>> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - - let mut total = 0u128; - let mut keepers = Vec::new(); - - let mut unspent_iter = wallet_unspent_tree.iter(); - while total < target { - let Some(pair) = unspent_iter.next() else { - return Ok(None); - }; - - let (output_ref_ivec, owner_amount_ivec) = pair?; - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..])?; - println!( - "in Sync::get_arbitrary_unspent_set output_ref = {:?}", - output_ref - ); - let (_owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - total += amount; - keepers.push(output_ref); - } - - Ok(Some(keepers)) -} - -/// Gets the block hash from the local database given a block height. Similar the Node's RPC. -/// -/// Some if the block exists, None if the block does not exist. -pub(crate) fn get_block_hash(db: &Db, height: u32) -> anyhow::Result> { - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - let Some(ivec) = wallet_block_hashes_tree.get(height.encode())? else { - return Ok(None); - }; - - let hash = H256::decode(&mut &ivec[..])?; - - Ok(Some(hash)) -} - -// This is part of what I expect to be a useful public interface. For now it is not used. -#[allow(dead_code)] -/// Gets the block from the local database given a block hash. Similar to the Node's RPC. -pub(crate) fn get_block(db: &Db, hash: H256) -> anyhow::Result> { - let wallet_blocks_tree = db.open_tree(BLOCKS)?; - let Some(ivec) = wallet_blocks_tree.get(hash.encode())? else { - return Ok(None); - }; - - let block = Block::decode(&mut &ivec[..])?; - - Ok(Some(block)) -} - -/// Apply a block to the local database -pub(crate) async fn apply_block bool>( - db: &Db, - b: Block, - block_hash: H256, - filter: &F, -) -> anyhow::Result<()> { - //log::info!("Applying Block {:?}, Block_Hash {:?}", b, block_hash); - //println!("Applying Block {:?}, Block_Hash {:?}", b, block_hash); - // Write the hash to the block_hashes table - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - wallet_block_hashes_tree.insert(b.header.number.encode(), block_hash.encode())?; - - // Write the block to the blocks table - let wallet_blocks_tree = db.open_tree(BLOCKS)?; - wallet_blocks_tree.insert(block_hash.encode(), b.encode())?; - - // Iterate through each transaction - for tx in b.extrinsics { - apply_transaction(db, tx, filter).await?; - } - if let Err(err) = db.flush() { - println!("Error flushing Sled database: {}", err); - } - - Ok(()) -} - -/// Apply a single transaction to the local database -/// The owner-specific tables are mappings from output_refs to coin amounts -async fn apply_transaction bool>( - db: &Db, - tx: Transaction, - filter: &F, -) -> anyhow::Result<()> { - let tx_hash = BlakeTwo256::hash_of(&tx.encode()); - log::debug!("syncing transaction {tx_hash:?}"); - - // Insert all new outputs - for (index, output) in tx - .outputs - .iter() - .filter(|o| filter(&o.verifier)) - .enumerate() - { - match output.payload.type_id { - Coin::<0>::TYPE_ID => { - crate::money::apply_transaction(db, tx_hash, index as u32, output)?; - } - - // I dont want to store the time stamp for now - Timestamp::TYPE_ID => { - crate::timestamp::apply_transaction(db, output)?; - } - - KittyData::TYPE_ID => { - crate::kitty::apply_transaction(db, tx_hash, index as u32, output)?; - } - TradableKittyData::TYPE_ID => { - crate::kitty::apply_td_transaction(db, tx_hash, index as u32, output)?; - } - - _ => continue, - } - } - - log::debug!("about to spend all inputs"); - // Spend all the inputs - for Input { output_ref, .. } in tx.inputs { - spend_output(db, &output_ref)?; - mark_as_used_kitties(db, &output_ref)?; - mark_as_used_tradable_kitties(db, &output_ref)?; - } - - if let Err(err) = db.flush() { - println!("Error flushing Sled database: {}", err); - } - - Ok(()) -} - -/// Add a new output to the database updating all tables. -pub(crate) fn add_unspent_output( - db: &Db, - output_ref: &OutputRef, - owner_pubkey: &H256, - amount: &u128, -) -> anyhow::Result<()> { - let unspent_tree = db.open_tree(UNSPENT)?; - unspent_tree.insert(output_ref.encode(), (owner_pubkey, amount).encode())?; - - Ok(()) -} - -/// Remove an output from the database updating all tables. -fn remove_unspent_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let unspent_tree = db.open_tree(UNSPENT)?; - - unspent_tree.remove(output_ref.encode())?; - - Ok(()) -} - -/// Mark an existing output as spent. This does not purge all record of the output from the db. -/// It just moves the record from the unspent table to the spent table -fn spend_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let unspent_tree = db.open_tree(UNSPENT)?; - let spent_tree = db.open_tree(SPENT)?; - - let Some(ivec) = unspent_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - let (owner, amount) = <(H256, u128)>::decode(&mut &ivec[..])?; - spent_tree.insert(output_ref.encode(), (owner, amount).encode())?; - - Ok(()) -} - -/// Mark an output that was previously spent back as unspent. -fn unspend_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let unspent_tree = db.open_tree(UNSPENT)?; - let spent_tree = db.open_tree(SPENT)?; - - let Some(ivec) = spent_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - let (owner, amount) = <(H256, u128)>::decode(&mut &ivec[..])?; - unspent_tree.insert(output_ref.encode(), (owner, amount).encode())?; - - Ok(()) -} - -/// Run a transaction backwards against a database. Mark all of the Inputs -/// as unspent, and drop all of the outputs. -fn unapply_transaction(db: &Db, tx: &Transaction) -> anyhow::Result<()> { - // Loop through the inputs moving each from spent to unspent - for Input { output_ref, .. } in &tx.inputs { - unspend_output(db, output_ref)?; - } - - // Loop through the outputs pruning them from unspent and dropping all record - let tx_hash = BlakeTwo256::hash_of(&tx.encode()); - - for i in 0..tx.outputs.len() { - let output_ref = OutputRef { - tx_hash, - index: i as u32, - }; - remove_unspent_output(db, &output_ref)?; - } - - Ok(()) -} - -/// Unapply the best block that the wallet currently knows about -pub(crate) async fn unapply_highest_block(db: &Db) -> anyhow::Result { - let wallet_blocks_tree = db.open_tree(BLOCKS)?; - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - - // Find the best height - let height = height(db)?.ok_or(anyhow!("Cannot unapply block from uninitialized database"))?; - - // Take the hash from the block_hashes tables - let Some(ivec) = wallet_block_hashes_tree.remove(height.encode())? else { - return Err(anyhow!( - "No block hash found at height reported as best. DB is inconsistent." - )); - }; - let hash = H256::decode(&mut &ivec[..])?; - - // Take the block from the blocks table - let Some(ivec) = wallet_blocks_tree.remove(hash.encode())? else { - return Err(anyhow!( - "Block was not present in db but block hash was. DB is corrupted." - )); - }; - - let block = Block::decode(&mut &ivec[..])?; - - // Loop through the transactions in reverse order calling unapply - for tx in block.extrinsics.iter().rev() { - unapply_transaction(db, tx)?; - } - - Ok(block) -} - -/// Get the block height that the wallet is currently synced to -/// -/// None means the db is not yet initialized with a genesis block -pub(crate) fn height(db: &Db) -> anyhow::Result> { - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - let num_blocks = wallet_block_hashes_tree.len(); - - Ok(if num_blocks == 0 { - None - } else { - Some(num_blocks as u32 - 1) - }) -} - -// This is part of what I expect to be a useful public interface. For now it is not used. -#[allow(dead_code)] -/// Debugging use. Print out the entire block_hashes tree. -pub(crate) fn print_block_hashes_tree(db: &Db) -> anyhow::Result<()> { - for height in 0..height(db)?.unwrap() { - let hash = get_block_hash(db, height)?; - println!("height: {height}, hash: {hash:?}"); - } - - Ok(()) -} - -/// Debugging use. Print the entire unspent outputs tree. -pub(crate) fn print_unspent_tree(db: &Db) -> anyhow::Result<()> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - for x in wallet_unspent_tree.iter() { - let (output_ref_ivec, owner_amount_ivec) = x?; - let output_ref = hex::encode(output_ref_ivec); - let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - println!("{output_ref}: owner {owner_pubkey:?}, amount {amount}"); - } - - Ok(()) -} - -/// Iterate the entire unspent set summing the values of the coins -/// on a per-address basis. -pub(crate) fn get_balances(db: &Db) -> anyhow::Result> { - let mut balances = std::collections::HashMap::::new(); - - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - - for raw_data in wallet_unspent_tree.iter() { - let (_output_ref_ivec, owner_amount_ivec) = raw_data?; - let (owner, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - balances - .entry(owner) - .and_modify(|old| *old += amount) - .or_insert(amount); - } - - Ok(balances.into_iter()) -} - -// Kitty related functions -/// Add kitties to the database updating all tables. -pub fn add_fresh_kitty_to_db( - db: &Db, - output_ref: &OutputRef, - owner_pubkey: &H256, - kitty: &KittyData, -) -> anyhow::Result<()> { - let kitty_owned_tree = db.open_tree(FRESH_KITTY)?; - println!("add_fresh_kitty_to_db {:?}",kitty); - kitty_owned_tree.insert(output_ref.encode(), (owner_pubkey, kitty).encode())?; - - Ok(()) -} - -pub fn add_fresh_tradable_kitty_to_db( - db: &Db, - output_ref: &OutputRef, - owner_pubkey: &H256, - kitty: &TradableKittyData, -) -> anyhow::Result<()> { - let tradable_kitty_owned_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - println!("add_fresh_tradable_kitty_to_db {:?}",kitty); - tradable_kitty_owned_tree.insert(output_ref.encode(), (owner_pubkey, kitty).encode())?; - - Ok(()) -} - -/// Remove an output from the database updating all tables. -fn remove_used_kitty_from_db(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let kitty_owned_tree = db.open_tree(FRESH_KITTY)?; - println!("remove_used_kitty_from_db {:?}",output_ref); - kitty_owned_tree.remove(output_ref.encode())?; - - Ok(()) -} - -/// Remove an output from the database updating all tables. -fn remove_used_tradable_kitty_from_db(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let tradable_kitty_owned_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - println!("remove_used_tradable_kitty_from_dbb {:?}",output_ref); - tradable_kitty_owned_tree.remove(output_ref.encode())?; - - Ok(()) -} - -/// Mark an existing output as spent. This does not purge all record of the output from the db. -/// It just moves the record from the unspent table to the spent table -fn mark_as_used_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let fresh_kitty_tree = db.open_tree(FRESH_KITTY)?; - let used_kitty_tree = db.open_tree(USED_KITTY)?; - - let Some(ivec) = fresh_kitty_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; - used_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; - println!("mark_as_used_kitties is called "); - Ok(()) -} - -/// Mark an existing output as spent. This does not purge all record of the output from the db. -/// It just moves the record from the unspent table to the spent table -fn mark_as_used_tradable_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let fresh_tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let used_tradable_kitty_tree = db.open_tree(USED_TRADABLE_KITTY)?; - - let Some(ivec) = fresh_tradable_kitty_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; - used_tradable_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; - println!("mark_as_used_tradable_kitties is called "); - Ok(()) -} - -/// Mark an output that was previously spent back as unspent. -fn unmark_as_used_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let fresh_kitty_tree = db.open_tree(FRESH_KITTY)?; - let used_kitty_tree = db.open_tree(USED_KITTY)?; - - let Some(ivec) = used_kitty_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; - fresh_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; - - Ok(()) -} - -/// Mark an output that was previously spent back as unspent. -fn unmark_as_used_tradable_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let fresh_Tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let used_Tradable_kitty_tree = db.open_tree(USED_TRADABLE_KITTY)?; - - let Some(ivec) = used_Tradable_kitty_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; - fresh_Tradable_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; - - Ok(()) -} - -/// Iterate the entire owned kitty -/// on a per-address basis. -pub(crate) fn get_all_kitties_from_local_db<'a>( - db: &'a Db, -) -> anyhow::Result + 'a> { - get_all_kitties_and_td_kitties_from_local_db(db, FRESH_KITTY) -} - -/// Iterate the entire owned tradable kitty -/// on a per-address basis. -pub(crate) fn get_all_tradable_kitties_from_local_db<'a>( - db: &'a Db, -) -> anyhow::Result + 'a> { - get_all_kitties_and_td_kitties_from_local_db(db, FRESH_TRADABLE_KITTY) -} - -pub(crate) fn get_kitty_from_local_db_based_on_name( - db: &Db, - name: String, -) -> anyhow::Result> { - get_data_from_local_db_based_on_name(db, FRESH_KITTY, name, |kitty: &KittyData| &kitty.name) -} - -pub(crate) fn get_tradable_kitty_from_local_db_based_on_name( - db: &Db, - name: String, -) -> anyhow::Result> { - get_data_from_local_db_based_on_name( - db, - FRESH_TRADABLE_KITTY, - name, - |kitty: &TradableKittyData| &kitty.kitty_basic_data.name, - ) -} - -pub(crate) fn get_kitty_from_local_db_based_on_dna( - db: &Db, - dna: &str, -) -> anyhow::Result> { - get_data_from_local_db_based_on_dna(db, FRESH_KITTY, dna, |kitty: &KittyData| kitty.dna.clone()) -} - -pub(crate) fn get_tradable_kitty_from_local_db_based_on_dna( - db: &Db, - dna: &str, -) -> anyhow::Result> { - get_data_from_local_db_based_on_dna( - db, - FRESH_TRADABLE_KITTY, - dna, - |kitty: &TradableKittyData| kitty.kitty_basic_data.dna.clone(), - ) -} - -/// Iterate the entire owned kitty -/// on a per-address basis. -pub(crate) fn get_owned_kitties_from_local_db<'a>( - db: &'a Db, - owner_pub_key: &'a H256, -) -> anyhow::Result + 'a> { - get_any_owned_kitties_from_local_db(db, FRESH_KITTY, &owner_pub_key) -} - -/// Iterate the entire owned tradable kitty -/// on a per-address basis. -pub(crate) fn get_owned_tradable_kitties_from_local_db<'a>( - db: &'a Db, - owner_pub_key: &'a H256, -) -> anyhow::Result + 'a> { - get_any_owned_kitties_from_local_db(db, FRESH_TRADABLE_KITTY, &owner_pub_key) -} - -pub(crate) fn get_all_coins(db: &Db) -> anyhow::Result> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - let mut result = Vec::new(); - - for x in wallet_unspent_tree.iter() { - let (output_ref_ivec, owner_amount_ivec) = x?; - let output_ref = hex::encode(output_ref_ivec); - let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - result.push((output_ref, owner_pubkey, amount)); - } - - Ok(result) -} - -pub(crate) fn get_owned_coins(db: &Db, owner_pub_key: &H256) -> anyhow::Result> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - let mut result = Vec::new(); - - for x in wallet_unspent_tree.iter() { - let (output_ref_ivec, owner_amount_ivec) = x?; - let output_ref = hex::encode(output_ref_ivec); - let (coin_owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - // Check if the coin's owner public key matches the provided owner_pub_key - if &coin_owner_pubkey == owner_pub_key { - result.push((output_ref, coin_owner_pubkey, amount)); - } - } - - Ok(result) -} - -pub(crate) fn is_kitty_name_duplicate( - db: &Db, - name: String, - owner_pubkey: &H256, -) -> anyhow::Result> { - is_name_duplicate(db, name, owner_pubkey, FRESH_KITTY, |kitty: &KittyData| { - &kitty.name - }) -} - -pub(crate) fn is_td_kitty_name_duplicate( - db: &Db, - name: String, - owner_pubkey: &H256, -) -> anyhow::Result> { - is_name_duplicate( - db, - name, - owner_pubkey, - FRESH_TRADABLE_KITTY, - |kitty: &TradableKittyData| &kitty.kitty_basic_data.name, - ) -} - -/// Gets the owner and amount associated with an output ref from the unspent table -/// -/// Some if the output ref exists, None if it doesn't -pub(crate) fn get_kitty_fromlocaldb( - db: &Db, - output_ref: &OutputRef, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - let Some(ivec) = wallet_owned_kitty_tree.get(output_ref.encode())? else { - return Ok(None); - }; - - Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) -} - -/// Gets the owner and amount associated with an output ref from the unspent table -/// -/// Some if the output ref exists, None if it doesn't -pub(crate) fn get_tradable_kitty_fromlocaldb( - db: &Db, - output_ref: &OutputRef, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let Some(ivec) = wallet_owned_kitty_tree.get(output_ref.encode())? else { - return Ok(None); - }; - - Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) -} - -////////////////////////// -// Private Functions -////////////////////////// - -fn get_all_kitties_and_td_kitties_from_local_db<'a, T>( - db: &'a Db, - tree_name: &'a str, -) -> anyhow::Result + 'a> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_tradable_kitty_tree = db.open_tree(tree_name)?; - - Ok(wallet_owned_tradable_kitty_tree - .iter() - .filter_map(|raw_data| { - let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - Some((owner, kitty)) - })) -} - -fn get_data_from_local_db_based_on_dna( - db: &Db, - tree_name: &str, - dna: &str, - dna_extractor: impl Fn(&T) -> KittyDNA, -) -> anyhow::Result> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_kitty_tree = db.open_tree(tree_name)?; - - let dna_bytes = hex::decode(dna).expect("Invalid hexadecimal string"); - let kitty_dna = KittyDNA(H256::from_slice(&dna_bytes)); - - let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree - .iter() - .filter_map(|raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - println!("Owner = {:?} Dna : {:?} -> output_ref {:?}", owner, kitty_dna, output_ref.clone()); - - if dna_extractor(&kitty) == kitty_dna { - println!(" Name : {:?} matched", kitty_dna); - Some((Some(kitty), output_ref)) - } else { - println!(" Name : {:?} NOTmatched", kitty_dna); - None - } - }) - .next() - .unwrap_or(( - None, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )); // Use unwrap_or to handle the Option - - println!("output_ref = {:?}", output_ref); - println!("kitty dna {:?} found_status = {:?}", kitty_dna,found_kitty); - - Ok(found_kitty.map(|kitty| (kitty, output_ref))) -} - -fn get_data_from_local_db_based_on_name( - db: &Db, - tree_name: &str, - name: String, - name_extractor: impl Fn(&T) -> &[u8; 4], -) -> anyhow::Result> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_kitty_tree = db.open_tree(tree_name)?; - - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree - .iter() - .filter_map(|raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - println!("Owner = {:?} Name : {:?} -> output_ref {:?}", owner, name, output_ref.clone()); - - if name_extractor(&kitty) == kitty_name { - println!(" Name : {:?} matched", name); - Some((Some(kitty), output_ref)) - } else { - println!(" Name : {:?} NOTmatched", name); - None - } - }) - .next() - .unwrap_or(( - None, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )); // Use unwrap_or to handle the Option - - println!("output_ref = {:?}", output_ref); - println!("kitty Name {} found_status = {:?}", name,found_kitty); - - Ok(found_kitty.map(|kitty| (kitty, output_ref))) -} - -fn get_any_owned_kitties_from_local_db<'a, T>( - db: &'a Db, - tree_name: &'a str, - owner_pubkey: &'a H256, -) -> anyhow::Result + 'a> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_kitty_tree = db.open_tree(tree_name)?; - - Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - if owner == *owner_pubkey { - Some((owner, kitty, output_ref)) - } else { - None - } - })) -} - -fn is_name_duplicate( - db: &Db, - name: String, - owner_pubkey: &H256, - tree_name: &str, - name_extractor: impl Fn(&T) -> &[u8; 4], -) -> anyhow::Result> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_kitty_tree = db.open_tree(tree_name)?; - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let found_kitty: Option = wallet_owned_kitty_tree - .iter() - .filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - println!("Name : {:?}", name); - - if *name_extractor(&kitty) == kitty_name[..] && owner == *owner_pubkey { - Some(Some(kitty)) - } else { - None - } - }) - .next() - .unwrap_or(None); // Use unwrap_or to handle the Option - - println!("found_kitty = {:?}", found_kitty); - let is_kitty_found = match found_kitty { - Some(k) => Some(true), - None => Some(false), - }; - Ok(is_kitty_found) -} - -fn string_to_h256(s: &str) -> Result { - let bytes = hex::decode(s)?; - // Assuming H256 is a fixed-size array with 32 bytes - let mut h256 = [0u8; 32]; - h256.copy_from_slice(&bytes); - Ok(h256.into()) -} - -/* -pub(crate) fn is_kitty_name_duplicate1( - db: &Db, - owner_pubkey: &H256, - name: String, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let found_kitty: (Option) = wallet_owned_kitty_tree - .iter() - .filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - println!("Name : {:?}", name); - - if kitty.name == &kitty_name[..] && owner == *owner_pubkey { - Some(Some(kitty)) - } else { - None - } - }) - .next() - .unwrap_or(None); // Use unwrap_or to handle the Option - - println!("found_kitty = {:?}", found_kitty); - let is_kitty_found = match found_kitty { - Some(k) => Some(true), - None => Some(false), - }; - Ok(is_kitty_found) -} - -pub(crate) fn is_tradable_kitty_name_duplicate1( - db: &Db, - owner_pubkey: &H256, - name: String, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let found_kitty: (Option) = wallet_owned_kitty_tree - .iter() - .filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - println!("Name : {:?}", name); - - if kitty.kitty_basic_data.name == &kitty_name[..] && owner == *owner_pubkey { - Some(Some(kitty)) - } else { - None - } - }) - .next() - .unwrap_or(None); // Use unwrap_or to handle the Option - - println!("found_kitty = {:?}", found_kitty); - let is_kitty_found = match found_kitty { - Some(k) => Some(true), - None => Some(false), - }; - Ok(is_kitty_found) -} - -/// Debugging use. Print the entire unspent outputs tree. -pub(crate) fn print_owned_kitties(db: &Db) -> anyhow::Result<()> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - for x in wallet_unspent_tree.iter() { - let (output_ref_ivec, owner_amount_ivec) = x?; - let output_ref = hex::encode(output_ref_ivec); - let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - println!("{output_ref}: owner {owner_pubkey:?}, amount {amount}"); - } - - Ok(()) -} - -pub(crate) fn get_all_kitties_from_local_db1( - db: &Db, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - - Ok(wallet_owned_kitty_tree.iter().filter_map(|raw_data| { - let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - Some((owner, kitty)) - })) -} - -/// Iterate the entire owned kitty -/// on a per-address basis. -pub(crate) fn get_all_tradable_kitties_from_local_db1( - db: &Db, -) -> anyhow::Result> { - let wallet_owned_tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - - Ok(wallet_owned_tradable_kitty_tree.iter().filter_map(|raw_data| { - let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - Some((owner, kitty)) - })) -} - -pub(crate) fn get_owned_kitties_from_local_db<'a>( - db: &'a Db, - args: &'a ShowOwnedKittyArgs, -) -> anyhow::Result + 'a> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - - Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref_str = hex::encode(output_ref_ivec.clone()); - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - if owner == args.owner { - Some((owner, kitty, output_ref)) - } else { - None - } - })) -} - - -pub(crate) fn get_owned_tradable_kitties_from_local_db<'a>( - db: &'a Db, - args: &'a ShowOwnedKittyArgs, -) -> anyhow::Result + 'a> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - - Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref_str = hex::encode(output_ref_ivec.clone()); - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - if owner == args.owner { - Some((owner, kitty, output_ref)) - } else { - None - } - })) -} - - -pub(crate) fn get_kitty_from_local_db_based_on_name_bk( - db: &Db, - name: String, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let (found_kitty, output_ref) = wallet_owned_kitty_tree - .iter() - .filter_map(|raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - println!("Name : {:?} -> output_ref {:?}", name, output_ref.clone()); - - if kitty.name == &kitty_name[..] { - Some((Some(kitty), output_ref)) - } else { - None - } - }) - .next() - .unwrap_or(( - None, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )); // Use unwrap_or to handle the Option - - println!("output_ref = {:?}", output_ref); - println!("found_kitty = {:?}", found_kitty); - - Ok(found_kitty.map(|kitty| (kitty, output_ref))) -} - - -pub(crate) fn get_tradable_kitty_from_local_db_based_on_name( - db: &Db, - name: String, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree - .iter() - .filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref_str = hex::encode(output_ref_ivec.clone()); - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - println!("Name : {:?} -> output_ref {:?}", name, output_ref.clone()); - - if kitty.kitty_basic_data.name == &kitty_name[..] { - Some((Some(kitty), output_ref)) - } else { - None - } - }) - .next() - .unwrap_or(( - None, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )); // Use unwrap_or to handle the Option - - println!("output_ref = {:?}", output_ref); - println!("found_kitty = {:?}", found_kitty); - - Ok(found_kitty.map(|kitty| (kitty, output_ref))) -} - -*/ diff --git a/webservice-wallet-external-signing/src/timestamp.rs b/webservice-wallet-external-signing/src/timestamp.rs deleted file mode 100644 index d5da075a0..000000000 --- a/webservice-wallet-external-signing/src/timestamp.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Wallet features related to on-chain timestamps. - -use anyhow::anyhow; -use parity_scale_codec::{Decode, Encode}; -use runtime::{timestamp::Timestamp, OuterVerifier}; -use sled::Db; -use tuxedo_core::types::Output; - -/// The identifier for the current timestamp in the db. -const TIMESTAMP: &str = "timestamp"; - -pub(crate) fn apply_transaction(db: &Db, output: &Output) -> anyhow::Result<()> { - let timestamp = output.payload.extract::()?.time; - let timestamp_tree = db.open_tree(TIMESTAMP)?; - timestamp_tree.insert([0], timestamp.encode())?; - Ok(()) -} - -/// Apply a transaction to the local database, storing the new timestamp. -pub(crate) fn get_timestamp(db: &Db) -> anyhow::Result { - let timestamp_tree = db.open_tree(TIMESTAMP)?; - let timestamp = timestamp_tree - .get([0])? - .ok_or_else(|| anyhow!("Could not find timestamp in database."))?; - u64::decode(&mut ×tamp[..]) - .map_err(|_| anyhow!("Could not decode timestamp from database.")) -} diff --git a/webservice-wallet-with-inbuilt-key-store/Cargo.toml b/webservice-wallet-with-inbuilt-key-store/Cargo.toml deleted file mode 100644 index 68ba3558f..000000000 --- a/webservice-wallet-with-inbuilt-key-store/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -description = "A simple example / template wallet built for the tuxedo template runtime" -edition = "2021" -license = "Apache-2.0" -name = "tuxedo-template-web-service-wallet" -repository = "https://github.com/Off-Narrative-Labs/Tuxedo" -version = "1.0.0-dev" - -[dependencies] -runtime = { package = "tuxedo-template-runtime", path = "../tuxedo-template-runtime" } -tuxedo-core = { path = "../tuxedo-core" } - -anyhow = { workspace = true } -clap = { features = [ "derive" ], workspace = true } -directories = { workspace = true } -env_logger = { workspace = true } -futures = { workspace = true } -hex = { workspace = true } -hex-literal = { workspace = true } -jsonrpsee = { features = [ "http-client" ], workspace = true } -log = { workspace = true } -parity-scale-codec = { workspace = true } -serde_json = { workspace = true } -sled = { workspace = true } -tokio = { features = [ "full" ], workspace = true } - -rand = "0.8" - -sc-keystore = { workspace = true } -sp-core = { workspace = true } -sp-keystore = { workspace = true } -sp-runtime = { workspace = true } - -axum = "0.5.16" -serde = { version = "1.0", features = ["derive"] } -tower-http = { version = "0.3.4", features = ["cors"] } diff --git a/webservice-wallet-with-inbuilt-key-store/Dockerfile b/webservice-wallet-with-inbuilt-key-store/Dockerfile deleted file mode 100644 index b685c5129..000000000 --- a/webservice-wallet-with-inbuilt-key-store/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -# This is a multi-stage docker file. See https://docs.docker.com/build/building/multi-stage/ -# for details about this pattern. - - - -# For the build stage, we use an image provided by Parity -FROM docker.io/paritytech/ci-linux:production as builder -WORKDIR /wallet -#TODO The Workdir and Copy command is different here than in the node... -COPY . . -RUN cargo build --locked --release -p tuxedo-template-wallet - - -# For the second stage, we use a minimal Ubuntu image -FROM docker.io/library/ubuntu:20.04 -LABEL description="Tuxedo Templet Wallet" - -COPY --from=builder /wallet/target/release/tuxedo-template-wallet /usr/local/bin - -RUN useradd -m -u 1000 -U -s /bin/sh -d /node-dev node-dev && \ - mkdir -p /wallet-data /node-dev/.local/share && \ - chown -R node-dev:node-dev /wallet-data && \ - # Make the wallet data directory available outside the container. - ln -s /wallet-data /node-dev/.local/share/tuxedo-template-wallet && \ - # unclutter and minimize the attack surface - rm -rf /usr/bin /usr/sbin && \ - # check if executable works in this container - /usr/local/bin/tuxedo-template-wallet --version - -USER node-dev - -EXPOSE 9944 -VOLUME ["/wallet-data"] - -ENTRYPOINT ["/usr/local/bin/tuxedo-template-wallet"] \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/README.md b/webservice-wallet-with-inbuilt-key-store/README.md deleted file mode 100644 index 8b272ccb1..000000000 --- a/webservice-wallet-with-inbuilt-key-store/README.md +++ /dev/null @@ -1,299 +0,0 @@ -# Tuxedo Template Wallet - -A cli wallet for the Tuxedo Node Template - -## Overview - -This wallet works with the Tuxedo Node Template and Tuxedo Template Runtime which is also contained in this repository. - -Like many UTXO wallets, this one synchronizes a local-to-the-wallet database of UTXOs that exist on the current best chain. -The wallet does not sync the entire blockchain state. -Rather, it syncs a subset of the state that it considers "relevant". -Currently, the wallet syncs any UTXOs that contain tokens owned by a key in the wallet's keystore. -However, the wallet is designed so that this notion of "relevance" is generalizeable. -This design allows developers building chains with Tuxedo to extend the wallet for their own needs. -However, because this is a text- based wallet, it is likely not well-suited for end users of popular dapps. - -## CLI Documentation - -The node application has a thorough help page that you can access on the CLI. It also has help pages for all subcommands. Please explore and read these docs thoroughly. - -```sh -# Show the wallet's main help page -$ tuxedo-template-wallet --help - -A simple example / template wallet built for the tuxedo template runtime - -Usage: tuxedo-template-wallet [OPTIONS] - -Commands: - -... - -# Show the help for a subcommand -$ tuxedo-template-wallet verify-coin --help -Verify that a particular coin exists. - -Show its value and owner from both chain storage and the local database. - -Usage: tuxedo-template-wallet verify-coin - -Arguments: - - A hex-encoded output reference - -Options: - -h, --help - Print help (see a summary with '-h') -``` - -## Guided Tour - -This guided tour shows off some of the most common and important wallet features. It can serve as a quickstart, but is not a substitute for reading the help pages mentioned above. (Seriously, please rtfm). - -To follow this walkthrough, you should already have a fresh tuxedo template dev node running as described in the [main readme](../README.md). For example, `node-template --dev`. - -### Syncing up an Initial Wallet - -The wallet is not a long-running process. -The wallet starts up, syncs with the latest chain state, performs the action invoked, and exits. - -Let's begin by just starting a new wallet and letting it sync. - -```sh -$ tuxedo-template-wallet - -[2023-04-11T17:44:40Z INFO tuxedo_template_wallet::sync] Initializing fresh sync from genesis 0x12aba3510dc0918aec178a32927f145d22d62afe63392713cb65b85570206327 -[2023-04-11T17:44:40Z INFO tuxedo_template_wallet] Number of blocks in the db: 0 -[2023-04-11T17:44:40Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 20 -[2023-04-11T17:44:40Z INFO tuxedo_template_wallet] No Wallet Command invoked. Exiting. -``` - -The logs indicate that a fresh database was created and had no blocks in it. Then, by communicating with the node, the wallet was able to sync 20 blocks. Finally it tells us that we didn't ask the wallet to tell us any specific information or send any transactions, so it just exits. - -Let's run the same command again and see that the wallet persists state. - -```sh -$ tuxedo-template-wallet - -[2023-04-11T17:46:17Z INFO tuxedo_template_wallet] Number of blocks in the db: 20 -[2023-04-11T17:46:17Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 52 -[2023-04-11T17:46:17Z INFO tuxedo_template_wallet] No Wallet Command invoked. Exiting. -``` - -This time, it is not a fresh database. In fact it starts from block 20, where it left off previously, and syncs up to block 52. Again, we didn't tell the wallet any specific action to take, so it just exits. - -We can also tell the wallet to skip the initial sync if we want to for any reason. -```sh -$ tuxedo-template-wallet --no-sync - -[2023-04-11T17:47:48Z INFO tuxedo_template_wallet] Number of blocks in the db: 52 -[2023-04-11T17:47:48Z WARN tuxedo_template_wallet] Skipping sync with node. Using previously synced information. -[2023-04-11T17:47:48Z INFO tuxedo_template_wallet] No Wallet Command invoked. Exiting. -``` - -Now that we understand that the wallet syncs up with the node each time it starts, let's explore our first wallet command. Like most wallets, it will tell you how many tokens you own. - -```sh -$ tuxedo-template-wallet show-balance - -[2023-04-11T18:07:52Z INFO tuxedo_template_wallet] Number of blocks in the db: 52 -[2023-04-11T18:07:52Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 55 -Balance Summary -0xd2bf…df67: 100 --------------------- -total : 100 -``` -The wallet begins by syncing the blockchain state, as usual. -Then it shows us that it knows about this `0xd2bf...` account. -This is the test account, or the "SHAWN" account. -The wallet already contains these keys so you can start learning quickly. -And it seems this account has some money. -Let's look further. - -### Exploring the Genesis Coin - -The chain begins with a single coin in storage. -We can confirm that the node and the wallet are familiar with the genesis coin using the `verify-coin` subcommand. - -```sh -$ tuxedo-template-wallet verify-coin 000000000000000000000000000000000000000000000000000000000000000000000000 - -[2023-04-11T17:50:04Z INFO tuxedo_template_wallet] Number of blocks in the db: 55 -[2023-04-11T17:50:04Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 80 -Details of coin 000000000000000000000000000000000000000000000000000000000000000000000000: -Found in storage. Value: 100, owned by 0xd2bf…df67 -Found in local db. Value: 100, owned by 0xd2bf…df67 -``` - -After syncing, it tells us the status of the coin that we are asking about. -That number with all the `0`s is called an `OutputRef` and it is a unique way to refer to a utxo. -The wallet tells us that the coin is found in the chain's storage and in the wallet's own local db. -Both sources agree that the coin exists, is worth 100, and is owned by Shawn. - -Let's "split" this coin by creating a transaction that spends it and creates two new coins worth 40 and 50, burning the remaining 10. - -```sh -$ tuxedo-template-wallet spend-coins \ - --output-amount 40 \ - --output-amount 50 - -[2023-04-11T17:58:00Z INFO tuxedo_template_wallet] Number of blocks in the db: 80 -[2023-04-11T17:58:00Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 87 -[2023-04-11T17:58:00Z INFO tuxedo_template_wallet] Node's response to spend transaction: Ok("0xad0de5922a27fab1a3ce116868ada789677c80a0e70018bd32464b2e737d3546") - -Created "9b3b0d17ad5f7784e840c40089d4d0aa0de990c5c620d49a0729c3a45afa35bf00000000" worth 40. owned by 0xd2bf…df67 -Created "9b3b0d17ad5f7784e840c40089d4d0aa0de990c5c620d49a0729c3a45afa35bf01000000" worth 50. owned by 0xd2bf…df67 -``` - -Our command told the wallet to create a transaction that spends some coins (in this case the genesis coin) and creates two new coins with the given amounts, burning the remaining 10. -It also tells us the `OutputRef`s of the new coins created. - -A balance check reveals that our balance has decreased by the 10 burnt tokes as expected. - -```sh -$ tuxedo-template-wallet show-balance - -[2023-04-11T18:52:26Z INFO tuxedo_template_wallet] Number of blocks in the db: 87 -[2023-04-11T18:52:26Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 95 - -Balance Summary -0xd2bf…df67: 90 --------------------- -total : 90 - -``` - -In this case we didn't specify a recipient of the new outputs, so the same default address was used. Next let's explore using some other keys. - -### Using Your Own Keys - -Of course we can use other keys than the example Shawn key. -The wallet supports generating our own keys, or inserting pre-existing keys. -To follow this guide as closely as possible, you should insert the same key we generated. - -```sh -# Generate a new key -$ tuxedo-template-wallet generate-key - - Generated public key is f41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b (5HamRMAa...) - Generated Phrase is decide city tattoo arrest jeans split main sad slam blame crack farm - -# Or, to continue on with demo, insert the same generated key -$ tuxedo-template-wallet insert-key "decide city tattoo arrest jeans split main sad slam blame crack farm" - - The generated public key is f41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b (5HamRMAa...) -``` - -With our new keys in the keystore, let's send some coins from Shawn to our own key. - -```sh -$ tuxedo-template-wallet spend-coins \ - --recipient f41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b \ - --output-amount 20 \ - --output-amount 10 - -[2023-04-11T18:53:46Z INFO tuxedo_template_wallet] Number of blocks in the db: 95 -[2023-04-11T18:53:46Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 99 -[2023-04-11T18:53:46Z INFO tuxedo_template_wallet::money] Node's response to spend transaction: Ok("0x7b8466f6c418637958f8090304dbdd7f115c27abf787b8f034a41d522bdf2baf") - -Created "90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d00000000" worth 20. owned by 0xf41a…e06b -Created "90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d01000000" worth 10. owned by 0xf41a…e06b -``` - -This command will consume one of the existing coins, and create two new ones owned by our key. -Our new coins will be worth 20 and 10 tokens. -Let's check the balance summary to confirm. - -```sh -$ tuxedo-template-wallet show-balance - -[2023-04-11T18:54:42Z INFO tuxedo_template_wallet] Number of blocks in the db: 99 -[2023-04-11T18:54:42Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 101 - -Balance Summary -0xd2bf…df67: 50 -0xf41a…e06b: 30 --------------------- -total : 80 -``` -It is possible to create new coins using the wallet. Let's explore how to do it. - -### Minting coins - -We can optionally pass the amount and public key of the owner as arguments to mint_coins. -If optional arguments are not passed below are the default values: -Amount is `100` and Public key of owner is Shawn key. - -```sh -$ tuxedo-template-wallet mint-coins \ - --owner 0xdeba7f5d5088cda3e32ccaf479056dd934d87fa8129987ca6db57c122bd73341 \ - --amount 200 \ - -[2024-01-18T14:22:19Z INFO tuxedo_template_wallet] Number of blocks in the db: 6 -[2024-01-18T14:22:19Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 14 -[2024-01-18T14:22:19Z INFO tuxedo_template_wallet::money] Node's response to mint-coin transaction: Ok("0xaff830b7755fee67c288afe18dfa6eabffe06286005b0fd6cb8e57b246c08df6") -Created "f76373909591d85f796c36ed4b265e46efabdf5b5c493b94246d590823cc42a500000000" worth 200. owned by 0xdeba…3341 -``` -It is possible to verify a newly minted coin exists in both chain storage and the local database using verify-coin command. - -### Manually Selecting Inputs - -So far, we have let the wallet select which inputs to spend on our behalf. -This is typically fine, but some users like to select specific inputs for their transactions. -The wallet supports this. -But before we can spend specific inputs, let's learn how to print the complete list of unspent outputs. - -```sh -$ tuxedo-template-wallet show-all-outputs - -[2023-04-11T18:55:23Z INFO tuxedo_template_wallet] Number of blocks in the db: 101 -[2023-04-11T18:55:23Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 104 - -###### Unspent outputs ########### -90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d00000000: owner 0xf41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b, amount 20 -90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d01000000: owner 0xf41a866782d45a4d2d8a623a097c62aee6955a9e580985e3910ba49eded9e06b, amount 10 -9b3b0d17ad5f7784e840c40089d4d0aa0de990c5c620d49a0729c3a45afa35bf01000000: owner 0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67, amount 50 -``` - -Now that we know precisely which outputs exist in our chain, we can choose to spend a specific one. -Let's consume our 20 token input and send 15 of its coins to Shawn, burning the remaining 5. -Because we are sending to Shawn, and Shawn is the default recipient, we could leave off the `--recipient` flag, but I'll choose to include it anyway. - -```sh -# The input value has to be copied from your own `show-all-outputs` results -$ tuxedo-template-wallet spend-coins \ - --input 90695702dabcca93d2c5f84a45b07bf59626ddb49a9b5255e202777127a3323d00000000 \ - --recipient 0xd2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 \ - --output-amount 15 - -[2023-04-11T18:57:20Z INFO tuxedo_template_wallet] Number of blocks in the db: 94 -[2023-04-11T18:57:20Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 133 -[2023-04-11T18:57:20Z INFO tuxedo_template_wallet::money] Node's response to spend transaction: Ok("0x80018b868d1e29be5cb758e15618091da8185cd7256ae3338df4605732fcfe9f") - -Created "4788fd9d517af94c2cfac80cb23fa6a63c41784b6fab01efd5d33b907af2550500000000" worth 15. owned by 0xd2bf…df67 -``` - -You should confirm for yourself that both the balance summary and the complete list of UTXOs look as you expect. - -### Multi Owner - -The final wallet feature that we will demonstrate is its ability to construct transactions with inputs coming from multiple different owners. - -Here we will create a transaction with a single output worth 70 units owned by some address that we'll call Jose, and we'll let the wallet select the inputs itself. -This will require inputs from both Shawn and us, and the wallet is able to handle this. - -```sh -$ tuxedo-template-wallet spend-coins \ - --recipient 0x066ae8f6f5c3f04e7fc163555d6ef62f6f8878435a931ba7eaf02424a16afe62 \ - --output-amount 70 - -[2023-04-11T18:59:18Z INFO tuxedo_template_wallet] Number of blocks in the db: 146 -[2023-04-11T18:59:18Z INFO tuxedo_template_wallet] Wallet database synchronized with node to height 173 -[2023-04-11T18:59:19Z INFO tuxedo_template_wallet::money] Node's response to spend transaction: Ok("0x04efb1c55f4efacbe41d00d3c5fe554470328a37150df6053bd48088e73a023c") - -Created "d0f722019e05863769e64ac6d33ad3ebeb359ce0469e93a9856bfcc236c4bad700000000" worth 70. owned by 0x066a…fe62 -``` - -Now we check the balance summary and find it is empty. -That is because Jose's keys are not in the keystore, so the wallet does not track his tokens. diff --git a/webservice-wallet-with-inbuilt-key-store/src/TradableKitties.rs b/webservice-wallet-with-inbuilt-key-store/src/TradableKitties.rs deleted file mode 100644 index 6c3867bd2..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/TradableKitties.rs +++ /dev/null @@ -1,443 +0,0 @@ -use crate::money::get_coin_from_storage; -use crate::rpc::fetch_storage; -use crate::sync; - -//use crate::cli::BreedArgs; -use tuxedo_core::{ - types::{Input, Output, OutputRef}, - verifier::Sr25519Signature, -}; - -use crate::kitty; -use crate::kitty::Gender; -use anyhow::anyhow; -use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; -use parity_scale_codec::Encode; -use rand::distributions::Alphanumeric; -use rand::Rng; -use sc_keystore::LocalKeystore; -use sled::Db; -use sp_core::sr25519::Public; -use sp_runtime::traits::{BlakeTwo256, Hash}; -//use crate::kitty::get_kitty_name; - -use runtime::{ - kitties::{ - DadKittyStatus, FreeKittyConstraintChecker, KittyDNA, KittyData, KittyHelpers, - MomKittyStatus, Parent, - }, - money::{Coin, MoneyConstraintChecker}, - tradable_kitties::TradableKittyConstraintChecker, - tradable_kitties::TradableKittyData, - OuterVerifier, Transaction, -}; - -use crate::cli::BuyKittyArgs; - -fn generate_random_string(length: usize) -> String { - let rng = rand::thread_rng(); - let random_string: String = rng - .sample_iter(&Alphanumeric) - .take(length) - .map(char::from) - .collect(); - random_string -} -/* -pub async fn set_kitty_property( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: KittyPropertyArgs, -) -> anyhow::Result<()> { - log::info!("The set_kitty_property are:: {:?}", args); - - let kitty_to_be_updated = - crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, args.current_name.clone()); - let Some((kitty_info, kitty_out_ref)) = kitty_to_be_updated.unwrap() else { - println!("No kitty with name : {}",args.current_name.clone() ); - return Err(anyhow!("No kitty with name {}",args.current_name.clone())); - }; - let kitty_ref = Input { - output_ref: kitty_out_ref, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - let inputs: Vec = vec![kitty_ref]; - // inputs.push(kitty_ref); - - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(args.new_name.clone().as_bytes()); - &array - }; - - // Create the KittyData - let mut output_kitty = kitty_info.clone(); - output_kitty.kitty_basic_data.name = *kitty_name; - output_kitty.price = Some(args.price); - output_kitty.is_available_for_sale = args.is_available_for_sale; - - let output = Output { - payload: output_kitty.into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::UpdateProperties.into(), - }; - - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - - // insert the proof - input.redeemer = redeemer; - } - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - println!("Node's response to spawn transaction: {:?}", spawn_response); - - // Print new output refs for user to check later - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_kitty_ref = OutputRef { - tx_hash, - index: i as u32, - }; - let new_kitty = output.payload.extract::()?.kitty_basic_data.dna.0; - - print!( - "Created {:?} worth {:?}. ", - hex::encode(new_kitty_ref.encode()), - new_kitty - ); - crate::pretty_print_verifier(&output.verifier); - } - - Ok(()) -} - - -pub async fn breed_kitty( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: BreedKittyArgs, -) -> anyhow::Result<()> { - log::info!("The Breed kittyArgs are:: {:?}", args); - - let kitty_mom = crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, args.mom_name.clone()); - let Some((kitty_mom_info, out_ref_mom)) = kitty_mom.unwrap() else { - return Err(anyhow!("No kitty with name {}",args.mom_name)); // Todo this needs to error - }; - - let kitty_dad = crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, args.dad_name.clone()); - let Some((kitty_dad_info, out_ref_dad)) = kitty_dad.unwrap() else { - return Err(anyhow!("No kitty with name {}",args.dad_name)); // Todo this needs to error - }; - log::info!("kitty_mom_ref:: {:?}", out_ref_mom); - log::info!("kitty_dad_ref:: {:?}", out_ref_dad); - - let mom_ref = Input { - output_ref: out_ref_mom, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - - let dad_ref = Input { - output_ref: out_ref_dad, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - - let mut inputs: Vec = vec![]; - inputs.push(mom_ref); - inputs.push(dad_ref); - - let mut new_mom: TradableKittyData = kitty_mom_info; - new_mom.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::RearinToGo); - if new_mom.kitty_basic_data.num_breedings >= 2 { - new_mom.kitty_basic_data.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); - } - new_mom.kitty_basic_data.num_breedings += 1; - new_mom.kitty_basic_data.free_breedings -= 1; - - // Create the Output mom - let output_mom = Output { - payload: new_mom.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let mut new_dad:TradableKittyData = kitty_dad_info; - new_dad.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::RearinToGo); - if new_dad.kitty_basic_data.num_breedings >= 2 { - new_dad.kitty_basic_data.parent = Parent::Dad(DadKittyStatus::Tired); - } - - new_dad.kitty_basic_data.num_breedings += 1; - new_dad.kitty_basic_data.free_breedings -= 1; - // Create the Output dada - let output_dad = Output { - payload: new_dad.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let child_gender = match gen_random_gender() { - Gender::Male => Parent::dad(), - Gender::Female => Parent::mom(), - }; - let child_kitty = KittyData { - parent: child_gender, - free_breedings: 2, - name: *b"tomy", - dna: KittyDNA(BlakeTwo256::hash_of(&( - new_mom.kitty_basic_data.dna.clone(), - new_dad.kitty_basic_data.dna.clone(), - new_mom.kitty_basic_data.num_breedings, - new_dad.kitty_basic_data.num_breedings, - ))), - num_breedings: 0, - }; - - let child = TradableKittyData { - kitty_basic_data: child_kitty, - price: None, - is_available_for_sale: false, - }; - println!("New mom Dna = {:?}", new_mom.kitty_basic_data.dna); - println!("New Dad Dna = {:?}", new_dad.kitty_basic_data.dna); - println!("Child Dna = {:?}", child.kitty_basic_data.dna); - // Create the Output child - let output_child = Output { - payload: child.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let new_family = Box::new(vec![output_mom, output_dad, output_child]); - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: (&[ - new_family[0].clone(), - new_family[1].clone(), - new_family[2].clone(), - ]) - .to_vec(), - checker: TradableKittyConstraintChecker::Breed.into(), - }; - - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - - // insert the proof - input.redeemer = redeemer; - } - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - - // Send the transaction to the node - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - println!("Node's response to spawn transaction: {:?}", spawn_response); - - // Print new output refs for user to check later - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_kitty_ref = OutputRef { - tx_hash, - index: i as u32, - }; - let new_kitty = output.payload.extract::()?.kitty_basic_data.dna.0; - - print!( - "Created {:?} Tradable Kitty {:?}. ", - hex::encode(new_kitty_ref.encode()), - new_kitty - ); - crate::pretty_print_verifier(&output.verifier); - } - - Ok(()) -} - -pub async fn buy_kitty( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: BuyKittyArgs, -) -> anyhow::Result<()> { - log::info!("The Buy kittyArgs are:: {:?}", args); - - let kitty_to_be_bought = - crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, args.kitty_name); - - let Some((kitty_info, kitty_out_ref)) = kitty_to_be_bought.unwrap() else { - return Err(anyhow!( - "Not enough value in database to construct transaction" - ))?; - }; - let kitty_ref = Input { - output_ref: kitty_out_ref, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - let mut inputs: Vec = vec![]; - inputs.push(kitty_ref); - - // Create the KittyData - let mut output_kitty = kitty_info.clone(); - - let output = Output { - payload: output_kitty.into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::Buy.into() - }; - - // Construct each output and then push to the transactions for Money - let mut total_output_amount = 0; - for amount in &args.output_amount { - let output = Output { - payload: Coin::<0>::new(*amount).into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.seller, - }), - }; - total_output_amount += amount; - transaction.outputs.push(output); - if total_output_amount >= kitty_info.price.unwrap().into() { - break; - } - } - - let mut total_input_amount = 0; - let mut all_input_refs = args.input; - for output_ref in &all_input_refs { - let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( - "user-specified output ref not found in local database" - ))?; - total_input_amount += amount; - } - - if total_input_amount < total_output_amount { - match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { - Some(more_inputs) => { - all_input_refs.extend(more_inputs); - } - None => Err(anyhow!( - "Not enough value in database to construct transaction" - ))?, - } - } - - // Make sure each input decodes and is still present in the node's storage, - // and then push to transaction. - for output_ref in &all_input_refs { - get_coin_from_storage(output_ref, client).await?; - transaction.inputs.push(Input { - output_ref: output_ref.clone(), - redeemer: vec![], // We will sign the total transaction so this should be empty - }); - } - - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - - let public = Public::from_h256(owner_pubkey); - - log::info!("owner_pubkey:: {:?}", owner_pubkey); - crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - - // insert the proof - input.redeemer = redeemer; - } - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - println!("Node's response to spawn transaction: {:?}", spawn_response); - - // Print new output refs for user to check later - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_kitty_ref = OutputRef { - tx_hash, - index: i as u32, - }; - let new_kitty = output.payload.extract::()?.kitty_basic_data.dna.0; - - print!( - "Created {:?} worth {:?}. ", - hex::encode(new_kitty_ref.encode()), - new_kitty - ); - crate::pretty_print_verifier(&output.verifier); - } - - Ok(()) -} -*/ diff --git a/webservice-wallet-with-inbuilt-key-store/src/amoeba.rs b/webservice-wallet-with-inbuilt-key-store/src/amoeba.rs deleted file mode 100644 index 4ed66b282..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/amoeba.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! Toy off-chain process to create an amoeba and perform mitosis on it - -use crate::rpc::fetch_storage; - -use std::{thread::sleep, time::Duration}; - -use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; -use parity_scale_codec::Encode; -use runtime::{ - amoeba::{AmoebaCreation, AmoebaDetails, AmoebaMitosis}, - OuterVerifier, Transaction, -}; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use tuxedo_core::{ - types::{Input, Output, OutputRef}, - verifier::UpForGrabs, -}; - -pub async fn amoeba_demo(client: &HttpClient) -> anyhow::Result<()> { - // Construct a simple amoeba spawning transaction (no signature required) - let eve = AmoebaDetails { - generation: 0, - four_bytes: *b"eve_", - }; - let spawn_tx = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: vec![Output { - payload: eve.into(), - verifier: UpForGrabs.into(), - }], - checker: AmoebaCreation.into(), - }; - - // Calculate the OutputRef which also serves as the storage location - let eve_ref = OutputRef { - tx_hash: ::hash_of(&spawn_tx.encode()), - index: 0, - }; - - // Send the transaction - let spawn_hex = hex::encode(spawn_tx.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - println!("Node's response to spawn transaction: {:?}", spawn_response); - - // Wait a few seconds to make sure a block has been authored. - sleep(Duration::from_secs(3)); - - // Check that the amoeba is in storage and print its details - let eve_from_storage: AmoebaDetails = fetch_storage::(&eve_ref, client) - .await? - .payload - .extract()?; - println!("Eve Amoeba retrieved from storage: {:?}", eve_from_storage); - - // Create a mitosis transaction on the Eve amoeba - let cain = AmoebaDetails { - generation: 1, - four_bytes: *b"cain", - }; - let able = AmoebaDetails { - generation: 1, - four_bytes: *b"able", - }; - let mitosis_tx = Transaction { - inputs: vec![Input { - output_ref: eve_ref, - redeemer: Vec::new(), - }], - peeks: Vec::new(), - outputs: vec![ - Output { - payload: cain.into(), - verifier: UpForGrabs.into(), - }, - Output { - payload: able.into(), - verifier: UpForGrabs.into(), - }, - ], - checker: AmoebaMitosis.into(), - }; - - // Calculate the two OutputRefs for the daughters - let cain_ref = OutputRef { - tx_hash: ::hash_of(&mitosis_tx.encode()), - index: 0, - }; - let able_ref = OutputRef { - tx_hash: ::hash_of(&mitosis_tx.encode()), - index: 1, - }; - - // Send the mitosis transaction - let mitosis_hex = hex::encode(mitosis_tx.encode()); - let params = rpc_params![mitosis_hex]; - let mitosis_response: Result = - client.request("author_submitExtrinsic", params).await; - println!( - "Node's response to mitosis transaction: {:?}", - mitosis_response - ); - - // Wait a few seconds to make sure a block has been authored. - sleep(Duration::from_secs(3)); - - // Check that the daughters are in storage and print their details - let cain_from_storage: AmoebaDetails = fetch_storage::(&cain_ref, client) - .await? - .payload - .extract()?; - println!( - "Cain Amoeba retrieved from storage: {:?}", - cain_from_storage - ); - let able_from_storage: AmoebaDetails = fetch_storage::(&able_ref, client) - .await? - .payload - .extract()?; - println!( - "Able Amoeba retrieved from storage: {:?}", - able_from_storage - ); - - Ok(()) -} diff --git a/webservice-wallet-with-inbuilt-key-store/src/cli.rs b/webservice-wallet-with-inbuilt-key-store/src/cli.rs deleted file mode 100644 index c5dbd3a48..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/cli.rs +++ /dev/null @@ -1,388 +0,0 @@ -//! Tuxedo Template Wallet's Command Line Interface. -//! -//! Built with clap's derive macros. - -use std::path::PathBuf; - -use clap::{ArgAction::Append, Args, Parser, Subcommand}; -use sp_core::H256; -use tuxedo_core::types::OutputRef; - -use crate::{h256_from_string, keystore::SHAWN_PUB_KEY, output_ref_from_string, DEFAULT_ENDPOINT}; - -/// The default number of coins to be minted. -pub const DEFAULT_MINT_VALUE: &str = "100"; - -/// Default recipient is SHAWN_KEY and output amount is 0 -pub const DEFAULT_RECIPIENT: &str = "d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67 0"; - -/// The default name of the kitty to be created. Show be 4 character long -pub const DEFAULT_KITTY_NAME: &str = " "; - -/// The default gender of the kitty to be created. -pub const DEFAULT_KITTY_GENDER: &str = "female"; -use crate::keystore; - - -fn parse_recipient_coins(s: &str) -> Result<(H256, Vec), &'static str> { - println!("In parse_recipient_coins"); - let parts: Vec<&str> = s.split_whitespace().collect(); - if parts.len() >= 2 { - let mut recipient = h256_from_string(parts[0]); - let coins = parts[1..].iter().filter_map(|&c| c.parse().ok()).collect(); - match recipient { - Ok(r) => { - println!("Recipient: {}", r); - return Ok((r, coins)); - }, - _ => {}, - }; - } - println!("Sending the error value "); - Err("Invalid input format") -} - - - - -/// The wallet's main CLI struct -#[derive(Debug, Parser)] -#[command(about, version)] -pub struct Cli { - #[arg(long, short, default_value_t = DEFAULT_ENDPOINT.to_string())] - /// RPC endpoint of the node that this wallet will connect to. - pub endpoint: String, - - #[arg(long, short)] - /// Path where the wallet data is stored. Default value is platform specific. - pub path: Option, - - #[arg(long, verbatim_doc_comment)] - /// Skip the initial sync that the wallet typically performs with the node. - /// The wallet will use the latest data it had previously synced. - pub no_sync: bool, - - #[arg(long)] - /// A temporary directory will be created to store the configuration and will be deleted at the end of the process. - /// path will be ignored if this is set. - pub tmp: bool, - - #[arg(long, verbatim_doc_comment)] - /// Specify a development wallet instance, using a temporary directory (like --tmp). - /// The keystore will contain the development key Shawn. - pub dev: bool, - - #[command(subcommand)] - pub command: Option, -} - -/// The tasks supported by the wallet -#[derive(Debug, Subcommand)] -pub enum Command { - /// Print the block based on block height. - /// get the block hash ad print the block. - getBlock { - /// Input the blockheight to be retrived. - block_height: Option, // fixme - }, - - /* - /// Demonstrate creating an amoeba and performing mitosis on it. - AmoebaDemo, - */ - /// Mint coins , optionally amount and publicKey of owner can be passed - /// if amount is not passed , 100 coins are minted - /// If publickKey of owner is not passed , then by default SHAWN_PUB_KEY is used. - #[command(verbatim_doc_comment)] - MintCoins(MintCoinArgs), - - /// Verify that a particular coin exists. - /// Show its value and owner from both chain storage and the local database. - #[command(verbatim_doc_comment)] - VerifyCoin { - /// A hex-encoded output reference - #[arg(value_parser = output_ref_from_string)] - output_ref: OutputRef, - }, - - //Some(Command::MintCoins { amount }) => money::mint_coins(&db, &client, &keystore,amount).await, - /// Spend some coins. - /// For now, all outputs in a single transaction go to the same recipient. - // FixMe: #62 - #[command(verbatim_doc_comment)] - SpendCoins(SpendArgs), - - /// Insert a private key into the keystore to later use when signing transactions. - InsertKey { - /// Seed phrase of the key to insert. - seed: String, - // /// Height from which the blockchain should be scanned to sync outputs - // /// belonging to this address. If non is provided, no re-syncing will - // /// happen and this key will be treated like a new key. - // sync_height: Option, - }, - - /// Generate a private key using either some or no password and insert into the keystore. - GenerateKey { - /// Initialize a public/private key pair with a password - password: Option, - }, - - /// Show public information about all the keys in the keystore. - ShowKeys, - - /// Remove a specific key from the keystore. - /// WARNING! This will permanently delete the private key information. - /// Make sure your keys are backed up somewhere safe. - #[command(verbatim_doc_comment)] - RemoveKey { - /// The public key to remove - #[arg(value_parser = h256_from_string)] - pub_key: H256, - }, - - /// For each key tracked by the wallet, shows the sum of all UTXO values owned by that key. - /// This sum is sometimes known as the "balance". - #[command(verbatim_doc_comment)] - ShowBalance, - - /// Show the complete list of UTXOs known to the wallet. - ShowAllOutputs, - - /// Show the latest on-chain timestamp. - ShowTimestamp, - - /// Create Kitty without mom and dad. - CreateKitty(CreateKittyArgs), - - /// Verify that a particular kitty exists. - /// Show its details and owner from both chain storage and the local database. - - #[command(verbatim_doc_comment)] - VerifyKitty { - /// A hex-encoded output reference - #[arg(value_parser = output_ref_from_string)] - output_ref: OutputRef, - }, - - /// Breed Kitties. - #[command(verbatim_doc_comment)] - BreedKitty(BreedKittyArgs), - - /// List Kitty for sale. - ListKittyForSale(ListKittyForSaleArgs), - - /// Delist Kitty from sale. - DelistKittyFromSale(DelistKittyFromSaleArgs), - - /// Update kitty name. - UpdateKittyName(UpdateKittyNameArgs), - - /// Update kitty price. Applicable only to tradable kitties - UpdateKittyPrice(UpdateKittyPriceArgs), - - /// Buy Kitty. - #[command(verbatim_doc_comment)] - BuyKitty(BuyKittyArgs), - - /// Show all kitties key tracked by the wallet. - #[command(verbatim_doc_comment)] - ShowAllKitties, - - /// ShowOwnedKitties. - /// Shows the kitties owned by owner. - #[command(verbatim_doc_comment)] - ShowOwnedKitties(ShowOwnedKittyArgs), -} - -#[derive(Debug, Args)] -pub struct MintCoinArgs { - /// Pass the amount to be minted. - #[arg(long, short, verbatim_doc_comment, action = Append,default_value = DEFAULT_MINT_VALUE)] - pub amount: u128, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -/* -// Old implementation -#[derive(Debug, Args)] -pub struct SpendArgs { - /// An input to be consumed by this transaction. This argument may be specified multiple times. - /// They must all be coins. - #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] - pub input: Vec, - - // /// All inputs to the transaction will be from this same sender. - // /// When not specified, inputs from any owner are chosen indiscriminantly - // #[arg(long, short, value_parser = h256_from_string)] - // sender: Option, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the recipient. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub recipient: H256, - - // The `action = Append` allows us to accept the same value multiple times. - /// An output amount. For the transaction to be valid, the outputs must add up to less than the sum of the inputs. - /// The wallet will not enforce this and will gladly send an invalid which will then be rejected by the node. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub output_amount: Vec, -} -*/ - -#[derive(Debug, Args,Clone)] -pub struct SpendArgs { - /// An input to be consumed by this transaction. This argument may be specified multiple times. - /// They must all be coins. - #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] - pub input: Vec, - - /// Variable number of recipients and their associated coins. - /// For example, "--recipients 0x1234 1 2 --recipients 0x5678 3 4 6" - // #[arg(long, short, verbatim_doc_comment, value_parser = parse_recipient_coins, action = Append)] - // pub recipients: Vec<(H256, Vec)>, - #[arg(long, short, verbatim_doc_comment, value_parser = parse_recipient_coins, action = Append, - default_value = DEFAULT_RECIPIENT)] - pub recipients: Vec<(H256, Vec)>, -} - -#[derive(Debug, Args)] -pub struct CreateKittyArgs { - - /// Pass the name of the kitty to be minted. - /// If kitty name is not passed ,name is choosen randomly from predefine name vector. - #[arg(long, short, action = Append, default_value = DEFAULT_KITTY_NAME)] - pub kitty_name: String, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct ShowOwnedKittyArgs { - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct BreedKittyArgs { - /// Name of Mom to be used for breeding . - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub mom_name: String, - - /// Name of Dad to be used for breeding . - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub dad_name: String, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct UpdateKittyNameArgs { - /// Current name of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub current_name: String, - - /// New name of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub new_name: String, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct UpdateKittyPriceArgs { - /// Current name of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub current_name: String, - - /// Price of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub price: u128, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct BuyKittyArgs { - /// An input to be consumed by this transaction. This argument may be specified multiple times. - /// They must all be coins. - #[arg(long, short, verbatim_doc_comment, value_parser = output_ref_from_string)] - pub input: Vec, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the seller of tradable kitty. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub seller: H256, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner of tradable kitty. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, - - /// Name of kitty to be bought. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub kitty_name: String, - - // The `action = Append` allows us to accept the same value multiple times. - /// An output amount. For the transaction to be valid, the outputs must add up to less than the sum of the inputs. - /// The wallet will not enforce this and will gladly send an invalid which will then be rejected by the node. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub output_amount: Vec, -} - -#[derive(Debug, Args)] -pub struct ListKittyForSaleArgs { - /// Pass the name of the kitty to be listed for sale. - #[arg(long, short, verbatim_doc_comment, action = Append, default_value = DEFAULT_KITTY_NAME)] - pub name: String, - - /// Price of Kitty. - #[arg(long, short, verbatim_doc_comment, action = Append)] - pub price: u128, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} - -#[derive(Debug, Args)] -pub struct DelistKittyFromSaleArgs { - /// Pass the name of the kitty to be delisted or removed from the sale . - #[arg(long, short, verbatim_doc_comment, action = Append, default_value = DEFAULT_KITTY_NAME)] - pub name: String, - - // https://docs.rs/clap/latest/clap/_derive/_cookbook/typed_derive/index.html - // shows how to specify a custom parsing function - /// Hex encoded address (sr25519 pubkey) of the owner. - #[arg(long, short, verbatim_doc_comment, value_parser = h256_from_string, default_value = SHAWN_PUB_KEY)] - pub owner: H256, -} diff --git a/webservice-wallet-with-inbuilt-key-store/src/keystore.rs b/webservice-wallet-with-inbuilt-key-store/src/keystore.rs deleted file mode 100644 index 34e6bb8d9..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/keystore.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! Wallet's local keystore. -//! -//! This is a thin wrapper around sc-cli for use in tuxedo wallets - -use anyhow::anyhow; -use parity_scale_codec::Encode; -use sc_keystore::LocalKeystore; -use sp_core::{ - crypto::Pair as PairT, - sr25519::{Pair, Public}, - H256, -}; -use sp_keystore::Keystore; -use sp_runtime::KeyTypeId; -use std::path::Path; -use crate::get_local_keystore; -/// A KeyTypeId to use in the keystore for Tuxedo transactions. We'll use this everywhere -/// until it becomes clear that there is a reason to use multiple of them -const KEY_TYPE: KeyTypeId = KeyTypeId(*b"_tux"); - -/// A default seed phrase for signing inputs when none is provided -/// Corresponds to the default pubkey. -pub const SHAWN_PHRASE: &str = - "news slush supreme milk chapter athlete soap sausage put clutch what kitten"; - -/// The public key corresponding to the default seed above. -pub const SHAWN_PUB_KEY: &str = "d2bf4b844dfefd6772a8843e669f943408966a977e3ae2af1dd78e0f55f4df67"; - -/// Insert the example "Shawn" key into the keystore for the current session only. -pub fn insert_development_key_for_this_session(keystore: &LocalKeystore) -> anyhow::Result<()> { - keystore.sr25519_generate_new(KEY_TYPE, Some(SHAWN_PHRASE))?; - Ok(()) -} - -/// Sign a given message with the private key that corresponds to the given public key. -/// -/// Returns an error if the keystore itself errors, or does not contain the requested key. -pub fn sign_with( - keystore: &LocalKeystore, - public: &Public, - message: &[u8], -) -> anyhow::Result> { - - let sig = keystore - .sr25519_sign(KEY_TYPE, public, message)? - .ok_or(anyhow!("Key doesn't exist in keystore"))?; - - Ok(sig.encode()) -} - -/// Insert the private key associated with the given seed into the keystore for later use. -pub fn insert_key(keystore: &LocalKeystore, seed: &str) -> anyhow::Result<()> { - // We need to provide a public key to the keystore manually, so let's calculate it. - let public_key = Pair::from_phrase(seed, None)?.0.public(); - println!("The generated public key is {:?}", public_key); - keystore - .insert(KEY_TYPE, seed, public_key.as_ref()) - .map_err(|()| anyhow!("Error inserting key"))?; - Ok(()) -} - -/// Generate a new key from system entropy and insert it into the keystore, optionally -/// protected by a password. -/// -/// TODO there is no password support when using keys later when signing. -/* -pub fn generate_key(keystore: &LocalKeystore, password: Option) -> anyhow::Result<()> { - - let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); - println!("Generated public key is {:?}", pair.public()); - println!("Generated Phrase is {}", phrase); - keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; - Ok(()) -} - - -pub async fn generate_key(password: Option) -> anyhow::Result<(String, String)> { - let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); - let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); - println!("Generated public key is {:?}", pair.public()); - println!("Generated Phrase is {}", phrase); - keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; - insert_key(&keystore,&phrase.as_str()); - - get_keys(&keystore)?.for_each(|pubkey| { - println!("key: 0x{}", hex::encode(pubkey)); - }); - - Ok((pair.public().to_string(), phrase)) -} - -pub fn get_keys(keystore: &LocalKeystore) -> anyhow::Result>> { - Ok(keystore.keys(KEY_TYPE)?.into_iter()) -} -*/ -pub async fn generate_key(password: Option) -> anyhow::Result<(String, String)> { - let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); - let (pair, phrase, _) = Pair::generate_with_phrase(password.as_deref()); - println!("Generated public key is {:?}", pair.public()); - println!("Generated Phrase is {}", phrase); - - // Insert the generated key pair into the keystore - - keystore.sr25519_generate_new(KEY_TYPE, Some(phrase.as_str()))?; - insert_key(&keystore,&phrase.as_str()); - get_keys().await?.for_each(|pubkey| { - println!("key: 0x{}", hex::encode(pubkey)); - }); - - let public_key_hex = hex::encode(pair.public()); - - Ok((public_key_hex.to_string(), phrase)) -} - - -/// Check whether a specific key is in the keystore -pub fn has_key(keystore: &LocalKeystore, pubkey: &H256) -> bool { - keystore.has_keys(&[(pubkey.encode(), KEY_TYPE)]) -} - -pub async fn get_keys() -> anyhow::Result>> { - let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); - - Ok(keystore.keys(KEY_TYPE)?.into_iter()) -} - - -/// Caution. Removes key from keystore. Call with care. -pub fn remove_key(keystore_path: &Path, pub_key: &H256) -> anyhow::Result<()> { - // The keystore doesn't provide an API for removing keys, so we - // remove them from the filesystem directly - let filename = format!("{}{}", hex::encode(KEY_TYPE.0), hex::encode(pub_key.0)); - let path = keystore_path.join(filename); - - std::fs::remove_file(path)?; - - Ok(()) -} diff --git a/webservice-wallet-with-inbuilt-key-store/src/kitty.rs b/webservice-wallet-with-inbuilt-key-store/src/kitty.rs deleted file mode 100644 index b07b38888..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/kitty.rs +++ /dev/null @@ -1,1045 +0,0 @@ -use crate::money::get_coin_from_storage; -use crate::rpc::fetch_storage; -use crate::sync; - -//use crate::cli::BreedArgs; -use tuxedo_core::{ - dynamic_typing::UtxoData, - types::{Input, Output, OutputRef}, - verifier::Sr25519Signature, -}; - -use anyhow::anyhow; -use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; -use parity_scale_codec::Encode; -use rand::distributions::Alphanumeric; -use rand::Rng; -use sc_keystore::LocalKeystore; -use sled::Db; -use sp_core::sr25519::Public; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use crate::get_local_keystore; - -use runtime::{ - kitties::{ - DadKittyStatus, FreeKittyConstraintChecker, KittyDNA, KittyData, KittyHelpers, - MomKittyStatus, Parent, - }, - money::{Coin, MoneyConstraintChecker}, - tradable_kitties::{TradableKittyConstraintChecker, TradableKittyData}, - OuterVerifier, Transaction, -}; - -use crate::cli::DEFAULT_KITTY_NAME; - -use crate::cli::{ - BreedKittyArgs, BuyKittyArgs, CreateKittyArgs, DelistKittyFromSaleArgs, ListKittyForSaleArgs, - UpdateKittyNameArgs, UpdateKittyPriceArgs, -}; -use parity_scale_codec::Decode; - -static MALE_KITTY_NAMES: [&str; 50] = [ - "Whis", "Purr", "Feli", "Leo", "Maxi", "Osca", "Simb", "Char", "Milo", "Tige", "Jasp", - "Smokey", "Oliv", "Loki", "Boot", "Gizm", "Rock", "Budd", "Shad", "Zeus", "Tedd", "Samm", - "Rust", "Tom", "Casp", "Blue", "Coop", "Coco", "Mitt", "Bent", "Geor", "Luck", "Rome", "Moch", - "Muff", "Ches", "Whis", "Appl", "Hunt", "Toby", "Finn", "Frod", "Sale", "Kobe", "Dext", "Jinx", - "Mick", "Pump", "Thor", "Sunn", -]; - -// Female kitty names -static FEMALE_KITTY_NAMES: [&str; 50] = [ - "Luna", "Mist", "Cleo", "Bell", "Lucy", "Nala", "Zoe", "Dais", "Lily", "Mia", "Chlo", "Stel", - "Coco", "Will", "Ruby", "Grac", "Sash", "Moll", "Lola", "Zara", "Mist", "Ange", "Rosi", "Soph", - "Zeld", "Layl", "Ambe", "Prin", "Cind", "Moch", "Zara", "Dais", "Cinn", "Oliv", "Peac", "Pixi", - "Harl", "Mimi", "Pipe", "Whis", "Cher", "Fion", "Kiki", "Suga", "Peac", "Ange", "Mapl", "Zigg", - "Lily", "Nova", -]; - -pub fn generate_random_string(length: usize) -> String { - let rng = rand::thread_rng(); - let random_string: String = rng - .sample_iter(&Alphanumeric) - .take(length) - .map(char::from) - .collect(); - random_string -} - -#[derive(Encode, Decode, Debug, Clone, PartialEq)] -pub enum Gender { - Male, - Female, -} - -fn create_tx_input_based_on_td_kittyName( - db: &Db, - name: String, -) -> anyhow::Result<(TradableKittyData, Input)> { - let tradable_kitty = - crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, name.clone()); - let Some((tradable_kitty_info, out_ref)) = tradable_kitty.unwrap() else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let input = Input { - output_ref: out_ref, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - Ok((tradable_kitty_info, input)) -} - -fn create_tx_input_based_on_kitty_name<'a>( - db: &Db, - name: String, -) -> anyhow::Result<(KittyData, Input)> { - //let kitty = crate::sync::get_kitty_from_local_db_based_on_name(&db, name.clone()); - //let name_extractor_kitty_data = move |kitty: &'a KittyData| -> &'a [u8; 4] { &kitty.name }; - - let kitty = crate::sync::get_kitty_from_local_db_based_on_name(&db, name.clone()); - let Some((kitty_info, out_ref)) = kitty.unwrap() else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let input = Input { - output_ref: out_ref, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - Ok((kitty_info, input)) -} - -fn create_tx_input_based_on_td_kitty_name<'a>( - db: &Db, - name: String, -) -> anyhow::Result<(TradableKittyData, Input)> { - let kitty = crate::sync::get_tradable_kitty_from_local_db_based_on_name(&db, name.clone()); - let Some((kitty_info, out_ref)) = kitty.unwrap() else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let input = Input { - output_ref: out_ref, - redeemer: vec![], // We will sign the total transaction so this should be empty - }; - Ok((kitty_info, input)) -} - -fn print_new_output(transaction: &Transaction) -> anyhow::Result<()> { - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_ref = OutputRef { - tx_hash, - index: i as u32, - }; - match output.payload.type_id { - KittyData::TYPE_ID => { - let new_kitty = output.payload.extract::()?.dna.0; - print!( - "Created {:?} basic Kitty {:?}. ", - hex::encode(new_ref.encode()), - new_kitty - ); - } - TradableKittyData::TYPE_ID => { - let new_kitty = output - .payload - .extract::()? - .kitty_basic_data - .dna - .0; - print!( - "Created {:?} TradableKitty {:?}. ", - hex::encode(new_ref.encode()), - new_kitty - ); - } - Coin::<0>::TYPE_ID => { - let amount = output.payload.extract::>()?.0; - print!( - "Created {:?} worth {amount}. ", - hex::encode(new_ref.encode()) - ); - } - - _ => continue, - } - crate::pretty_print_verifier(&output.verifier); - } - Ok(()) -} - -async fn send_signed_tx( - transaction: &Transaction, - client: &HttpClient, -) -> anyhow::Result<()> { - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - - println!("Node's response to spawn transaction: {:?}", spawn_response); - match spawn_response { - Ok(_) => Ok(()), - Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), - } -} - -async fn send_tx( - transaction: &mut Transaction, - client: &HttpClient, - local_keystore: Option<&LocalKeystore>, -) -> anyhow::Result<()> { - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - - let _ = match local_keystore { - Some(ks) => { - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(ks, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - // insert the proof - input.redeemer = redeemer; - } - } - None => {} - }; - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - - println!("Node's response to spawn transaction: {:?}", spawn_response); - match spawn_response { - Ok(_) => Ok(()), - Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), - } -} - - - -fn gen_random_gender() -> Gender { - // Create a local random number generator - let mut rng = rand::thread_rng(); - - // Generate a random number between 0 and 1 - let random_number = rng.gen_range(0..=1); - - // We Use the random number to determine the gender - match random_number { - 0 => Gender::Male, - _ => Gender::Female, - } -} - -fn get_random_kitty_name_from_pre_defined_vec(g: Gender, name_slice: &mut [u8; 4]) { - // Create a local random number generator - let mut rng = rand::thread_rng(); - - // Generate a random number between 0 and 1 - let random_number = rng.gen_range(0..=50); - - // We Use the random number to determine the gender - - let name = match g { - Gender::Male => MALE_KITTY_NAMES[random_number], - Gender::Female => FEMALE_KITTY_NAMES[random_number], - }; - //name.to_string() - name_slice.copy_from_slice(name.clone().as_bytes()); -} - -fn validate_kitty_name_from_db( - db: &Db, - owner_pubkey: &H256, - name: String, - name_slice: &mut [u8; 4], -) -> anyhow::Result<()> { - if name.len() != 4 { - return Err(anyhow!( - "Please input a name of length 4 characters. Current length: {}", - name.len() - )); - } - - match crate::sync::is_kitty_name_duplicate(&db, name.clone(), &owner_pubkey) { - Ok(Some(true)) => { - println!("Kitty name is duplicate , select another name"); - return Err(anyhow!( - "Please input a non-duplicate name of length 4 characters" - )); - } - _ => {} - }; - name_slice.copy_from_slice(name.clone().as_bytes()); - - return Ok(()); -} - -fn create_new_family( - new_mom: &mut KittyData, - new_dad: &mut KittyData, - new_child: &mut KittyData, -) -> anyhow::Result<()> { - new_mom.parent = Parent::Mom(MomKittyStatus::RearinToGo); - if new_mom.num_breedings >= 2 { - new_mom.parent = Parent::Mom(MomKittyStatus::HadBirthRecently); - } - new_mom.num_breedings = new_mom.num_breedings.checked_add(1).expect("REASON"); - new_mom.free_breedings = new_mom.free_breedings.checked_sub(1).expect("REASON"); - - new_dad.parent = Parent::Dad(DadKittyStatus::RearinToGo); - if new_dad.num_breedings >= 2 { - new_dad.parent = Parent::Dad(DadKittyStatus::Tired); - } - - new_dad.num_breedings = new_dad.num_breedings.checked_add(1).expect("REASON"); - new_dad.free_breedings = new_dad.free_breedings.checked_sub(1).expect("REASON"); - - let child_gender = match gen_random_gender() { - Gender::Male => Parent::dad(), - Gender::Female => Parent::mom(), - }; - - let child = KittyData { - parent: child_gender, - free_breedings: 2, - name: *b"tomy", // Name of child kitty need to be generated in better way - dna: KittyDNA(BlakeTwo256::hash_of(&( - new_mom.dna.clone(), - new_dad.dna.clone(), - new_mom.num_breedings, - new_dad.num_breedings, - ))), - num_breedings: 0, - // price: None, - // is_available_for_sale: false, - }; - *new_child = child; - Ok(()) -} - -pub async fn create_kitty( - db: &Db, - client: &HttpClient, - args: CreateKittyArgs, -) -> anyhow::Result> { - let mut kitty_name = [0; 4]; - - let g = gen_random_gender(); - let gender = match g { - Gender::Male => Parent::dad(), - Gender::Female => Parent::mom(), - }; - - if args.kitty_name.clone() == DEFAULT_KITTY_NAME.to_string() { - get_random_kitty_name_from_pre_defined_vec(g, &mut kitty_name); - } else { - validate_kitty_name_from_db(&db, &args.owner, args.kitty_name.clone(), &mut kitty_name)?; - } - // Generate a random string of length 5 - let random_string = generate_random_string(5) + args.kitty_name.as_str(); - let dna_preimage: &[u8] = random_string.as_bytes(); - - let child_kitty = KittyData { - parent: gender, - dna: KittyDNA(BlakeTwo256::hash(dna_preimage)), - name: kitty_name, // Default value for now, as the provided code does not specify a value - ..Default::default() - }; - - // Create the Output - let output = Output { - payload: child_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - // Create the Transaction - let mut transaction = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: vec![output], - checker: FreeKittyConstraintChecker::Create.into(), - }; - - send_tx(&mut transaction, &client, None).await?; - print_new_output(&transaction)?; - Ok(Some(child_kitty)) -} - -pub async fn list_kitty_for_sale( - signed_transaction: &Transaction, - db: &Db, - client: &HttpClient, -) -> anyhow::Result> { - let tradable_kitty: TradableKittyData = signed_transaction.outputs[0].payload - .extract::() - .map_err(|_| anyhow!("Invalid output, Expected TradableKittyData"))?; - log::info!("The list_kitty_for_sale args : {:?}", signed_transaction); - send_signed_tx(&signed_transaction, &client).await?; - print_new_output(&signed_transaction)?; - Ok(Some(tradable_kitty)) -} - -pub async fn delist_kitty_from_sale( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: DelistKittyFromSaleArgs, -) -> anyhow::Result> { - log::info!("The list_kitty_for_sale args : {:?}", args); - let Ok((td_kitty_info, input)) = create_tx_input_based_on_td_kittyName(db, args.name.clone()) - else { - return Err(anyhow!("No kitty with name {} in localdb", args.name)); - }; - - let inputs: Vec = vec![input]; - let basic_kitty = td_kitty_info.kitty_basic_data; - - // Create the Output - let output = Output { - payload: basic_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::DelistKittiesFromSale.into(), - }; - - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - Ok(Some(basic_kitty)) -} - -pub async fn breed_kitty( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: BreedKittyArgs, -) -> anyhow::Result>> { - log::info!("The Breed kittyArgs are:: {:?}", args); - - let Ok((mom_kitty_info, mom_ref)) = - create_tx_input_based_on_kitty_name(db, args.mom_name.clone()) - else { - return Err(anyhow!("No kitty with name {} in localdb", args.mom_name)); - }; - - let Ok((dad_kitty_info, dad_ref)) = - create_tx_input_based_on_kitty_name(db, args.dad_name.clone()) - else { - return Err(anyhow!("No kitty with name {} in localdb", args.dad_name)); - }; - - let inputs: Vec = vec![mom_ref, dad_ref]; - - let mut new_mom: KittyData = mom_kitty_info; - - let mut new_dad = dad_kitty_info; - - let mut child: KittyData = Default::default(); - - create_new_family(&mut new_mom, &mut new_dad, &mut child)?; - // Create the Output mom - println!("New mom Dna = {:?}", new_mom.dna); - println!("New Dad Dna = {:?}", new_dad.dna); - println!("Child Dna = {:?}", child.dna); - - let output_mom = Output { - payload: new_mom.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - // Create the Output dada - let output_dad = Output { - payload: new_dad.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let output_child = Output { - payload: child.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let new_family = Box::new(vec![output_mom, output_dad, output_child]); - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: (&[ - new_family[0].clone(), - new_family[1].clone(), - new_family[2].clone(), - ]) - .to_vec(), - checker: FreeKittyConstraintChecker::Breed.into(), - }; - - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - Ok(vec![ - new_mom.clone(), - new_dad.clone(), - child.clone(), - ].into()) -} - -pub async fn buy_kitty( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: BuyKittyArgs, -) -> anyhow::Result> { - log::info!("The Buy kittyArgs are:: {:?}", args); - - let Ok((kitty_info, kitty_ref)) = - create_tx_input_based_on_td_kittyName(db, args.kitty_name.clone()) - else { - return Err(anyhow!("No kitty with name {} in localdb", args.kitty_name)); - }; - - let inputs: Vec = vec![kitty_ref]; - // Create the KittyData - let mut output_kitty = kitty_info.clone(); - - let output = Output { - payload: output_kitty.into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::Buy.into(), - }; - - // Construct each output and then push to the transactions for Money - let mut total_output_amount = 0; - for amount in &args.output_amount { - let output = Output { - payload: Coin::<0>::new(*amount).into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.seller, - }), - }; - total_output_amount += amount; - transaction.outputs.push(output); - if total_output_amount >= kitty_info.price.into() { - break; - } - } - - let mut total_input_amount = 0; - let mut all_input_refs = args.input; - for output_ref in &all_input_refs { - let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( - "user-specified output ref not found in local database" - ))?; - total_input_amount += amount; - } - - if total_input_amount < total_output_amount { - match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { - Some(more_inputs) => { - all_input_refs.extend(more_inputs); - } - None => Err(anyhow!( - "Not enough value in database to construct transaction" - ))?, - } - } - - // Make sure each input decodes and is still present in the node's storage, - // and then push to transaction. - for output_ref in &all_input_refs { - get_coin_from_storage(output_ref, client).await?; - transaction.inputs.push(Input { - output_ref: output_ref.clone(), - redeemer: vec![], // We will sign the total transaction so this should be empty - }); - } - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - - Ok(Some(kitty_info)) -} - -pub async fn update_td_kitty_name( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: UpdateKittyNameArgs, -) -> anyhow::Result> { - log::info!("The set_kitty_property are:: {:?}", args); - - let Ok((td_kitty_info, td_kitty_ref)) = create_tx_input_based_on_td_kitty_name(db, args.current_name.clone()) else { - return Err(anyhow!("No kitty with name {} in localdb",args.current_name)); - }; - - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(args.new_name.as_bytes()); - &array - }; - - let inputs: Vec = vec![td_kitty_ref.clone()]; - let mut updated_kitty: TradableKittyData = td_kitty_info; - updated_kitty.kitty_basic_data.name = *kitty_name; - let output = Output { - payload: updated_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::UpdateKittiesName.into(), - }; - - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - Ok(Some(updated_kitty)) -} - -pub async fn update_kitty_name( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: UpdateKittyNameArgs, -) -> anyhow::Result> { - log::info!("The set_kitty_property are:: {:?}", args); - - let Ok((input_kitty_info, input_kitty_ref)) = create_tx_input_based_on_kitty_name(db,args.current_name.clone()) else { - return Err(anyhow!("No kitty with name {} in localdb",args.current_name)); - }; - - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(args.new_name.as_bytes()); - &array - }; - - let inputs: Vec = vec![input_kitty_ref]; - - let mut updated_kitty: KittyData = input_kitty_info.clone(); - updated_kitty.name = *kitty_name; - let output = Output { - payload: updated_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: FreeKittyConstraintChecker::UpdateKittiesName.into(), - }; - - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - Ok(Some(updated_kitty)) -} - -pub async fn update_kitty_price( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: UpdateKittyPriceArgs, -) -> anyhow::Result> { - log::info!("The set_kitty_property are:: {:?}", args); - let Ok((input_kitty_info, input_kitty_ref)) = - create_tx_input_based_on_td_kitty_name(db, args.current_name.clone()) - else { - return Err(anyhow!( - "No kitty with name {} in localdb", - args.current_name - )); - }; - - let inputs: Vec = vec![input_kitty_ref]; - let mut updated_kitty: TradableKittyData = input_kitty_info; - updated_kitty.price = args.price; - let output = Output { - payload: updated_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::UpdateKittiesPrice.into(), - }; - - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - Ok(Some(updated_kitty)) -} - -/// Apply a transaction to the local database, storing the new coins. -pub(crate) fn apply_transaction( - db: &Db, - tx_hash: ::Output, - index: u32, - output: &Output, -) -> anyhow::Result<()> { - let kitty_detail: KittyData = output.payload.extract()?; - - let output_ref = OutputRef { tx_hash, index }; - println!("in kitty:apply_transaction output_ref = {:?}", output_ref); - match output.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - // Add it to the global unspent_outputs table - crate::sync::add_fresh_kitty_to_db(db, &output_ref, &owner_pubkey, &kitty_detail) - } - _ => Err(anyhow!("{:?}", ())), - } -} - -pub(crate) fn convert_kitty_name_string(kitty: &KittyData) -> Option { - if let Ok(kitty_name) = std::str::from_utf8(&kitty.name) { - return Some(kitty_name.to_string()); - } else { - println!("Invalid UTF-8 data in the Kittyname"); - } - None -} - -/// Given an output ref, fetch the details about this coin from the node's -/// storage. -// Need to revisit this for tradableKitty -pub async fn get_kitty_from_storage( - output_ref: &OutputRef, - client: &HttpClient, -) -> anyhow::Result<(KittyData, OuterVerifier)> { - let utxo = fetch_storage::(output_ref, client).await?; - - let kitty_in_storage: KittyData = utxo.payload.extract()?; - - Ok((kitty_in_storage, utxo.verifier)) -} - -/// Apply a transaction to the local database, storing the new coins. -pub(crate) fn apply_td_transaction( - db: &Db, - tx_hash: ::Output, - index: u32, - output: &Output, -) -> anyhow::Result<()> { - let tradable_kitty_detail: TradableKittyData = output.payload.extract()?; - - let output_ref = OutputRef { tx_hash, index }; - println!( - "in Tradable kitty:apply_transaction output_ref = {:?}", - output_ref - ); - match output.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - // Add it to the global unspent_outputs table - crate::sync::add_fresh_tradable_kitty_to_db( - db, - &output_ref, - &owner_pubkey, - &tradable_kitty_detail, - ) - } - _ => Err(anyhow!("{:?}", ())), - } -} - -pub(crate) fn convert_td_kitty_name_string(tradable_kitty: &TradableKittyData) -> Option { - if let Ok(kitty_name) = std::str::from_utf8(&tradable_kitty.kitty_basic_data.name) { - return Some(kitty_name.to_string()); - } else { - println!("Invalid UTF-8 data in the Kittyname"); - } - None -} - -/// Given an output ref, fetch the details about this coin from the node's -/// storage. -pub async fn get_td_kitty_from_storage( - output_ref: &OutputRef, - client: &HttpClient, -) -> anyhow::Result<(TradableKittyData, OuterVerifier)> { - let utxo = fetch_storage::(output_ref, client).await?; - let kitty_in_storage: TradableKittyData = utxo.payload.extract()?; - - Ok((kitty_in_storage, utxo.verifier)) -} - -////////////////////////////////////////////////////////////////// -// Below is for web server handling -////////////////////////////////////////////////////////////////// - - -async fn send_tx_with_signed_inputs( - transaction: &mut Transaction, - client: &HttpClient, - local_keystore: Option<&LocalKeystore>, - signed_inputs: Vec, -) -> anyhow::Result<()> { - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - transaction.inputs = signed_inputs; - - let _ = match local_keystore { - Some(ks) => { - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(ks, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - // insert the proof - input.redeemer = redeemer; - } - } - None => {} - }; - - // Encode the transaction - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let spawn_response: Result = client.request("author_submitExtrinsic", params).await; - - println!("Node's response to spawn transaction: {:?}", spawn_response); - match spawn_response { - Ok(_) => Ok(()), - Err(err) => Err(anyhow::Error::msg(format!("Error sending transaction: {:?}", err))), - } -} - -pub async fn create_txn_for_list_kitty( - db: &Db, - name: String, - price:u128, - publick_key:H256, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, name.clone()) else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let inputs: Vec = vec![input]; - - let tradable_kitty = TradableKittyData { - kitty_basic_data: kitty_info, - price: price, - }; - - // Create the Output - let output = Output { - payload: tradable_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: publick_key, - }), - }; - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::ListKittiesForSale.into(), - }; - - Ok(Some(transaction)) -} - -pub async fn create_inpututxo_list( - transaction: &mut Transaction, - client: &HttpClient, -) -> anyhow::Result>>> { - let mut utxo_list: Vec> = Vec::new(); - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - utxo_list.push(utxo); - } - Ok(Some(utxo_list)) -} - -// Below function will not be used in real usage , it is just for test purpose - -pub async fn create_signed_txn_for_list_kitty( - db: &Db, - name: String, - price:u128, - publick_key:H256, - client: &HttpClient, -) -> anyhow::Result> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, name.clone()) else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let inputs: Vec = vec![input]; - - let tradable_kitty = TradableKittyData { - kitty_basic_data: kitty_info, - price: price, - }; - - // Create the Output - let output = Output { - payload: tradable_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: publick_key, - }), - }; - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::ListKittiesForSale.into(), - }; - - let stripped_encoded_transaction = transaction.clone().encode(); - let local_keystore = get_local_keystore().await.expect("Key store error"); - - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(&local_keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - // insert the proof - input.redeemer = redeemer; - } - - Ok(Some(transaction)) -} - - -/* -pub async fn create_inputs_for_list_kitty( - db: &Db, - name: String, - publick_key:H256, -) -> anyhow::Result>> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, name.clone()) else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let inputs: Vec = vec![input]; - - Ok(Some(inputs)) -} -pub async fn list_kitty_for_sale_based_on_inputs( - db: &Db, - name: String, - price: u128, - inputs: Vec, - public_key:H256, -) -> anyhow::Result>> { - // Need to filter based on name and publick key. - // Ideally need to filter based on DNA. - let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, name.clone()) else { - return Err(anyhow!("No kitty with name {} in localdb", name)); - }; - - let inputs: Vec = vec![input]; - - - Ok(Some(inputs)) -} -pub async fn list_kitty_for_sale( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: ListKittyForSaleArgs, -) -> anyhow::Result> { - log::info!("The list_kitty_for_sale args : {:?}", args); - - let Ok((kitty_info, input)) = create_tx_input_based_on_kitty_name(db, args.name.clone()) else { - return Err(anyhow!("No kitty with name {} in localdb", args.name)); - }; - - let inputs: Vec = vec![input]; - - let tradable_kitty = TradableKittyData { - kitty_basic_data: kitty_info, - price: args.price, - }; - - // Create the Output - let output = Output { - payload: tradable_kitty.clone().into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - }; - - // Create the Transaction - let mut transaction = Transaction { - inputs: inputs, - peeks: Vec::new(), - outputs: vec![output], - checker: TradableKittyConstraintChecker::ListKittiesForSale.into(), - }; - send_tx(&mut transaction, &client, Some(&keystore)).await?; - print_new_output(&transaction)?; - Ok(Some(tradable_kitty)) -} -*/ \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/main.rs b/webservice-wallet-with-inbuilt-key-store/src/main.rs deleted file mode 100644 index e245b62f5..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/main.rs +++ /dev/null @@ -1,311 +0,0 @@ -//! A simple CLI wallet. For now it is a toy just to start testing things out. - -use clap::Parser; -use jsonrpsee::http_client::HttpClientBuilder; -use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; -use parity_scale_codec::{Decode, Encode}; -use runtime::OuterVerifier; -use std::path::PathBuf; -use sled::Db; -//use crate::kitty::{create_kitty,list_kitty_for_sale}; -use tuxedo_core::{types::OutputRef, verifier::*}; -use sp_core::H256; -use sc_keystore::LocalKeystore; - -//mod amoeba; -mod TradableKitties; -mod cli; -mod req_resp; -mod keystore; -mod kitty; -mod money; -mod output_filter; -mod rpc; -mod sync; -mod timestamp; - -use cli::{Cli, Command}; -use crate::cli::MintCoinArgs; -use crate::cli::CreateKittyArgs; - -//use moneyServicehandler::{MintCoinsRequest, MintCoinsResponse}; -mod serviceHandlers { - - pub mod blockHandler { - pub mod blockServicehandler; - } - - pub mod moneyHandler { - pub mod moneyServicehandler; - } - - pub mod kittyHandler { - pub mod kittyServicehandler; - } - - pub mod keyHandler { - pub mod keyServicehandler; - } -} - -use serviceHandlers::keyHandler::keyServicehandler::{ - GenerateKeyRequest, GenerateKeyResponse, generate_key, - GetKeyResponse, get_keys, -}; - -use serviceHandlers::moneyHandler::moneyServicehandler::{MintCoinsRequest, MintCoinsResponse, mint_coins}; - -use serviceHandlers::kittyHandler::kittyServicehandler::{ - CreateKittyRequest, CreateKittyResponse, create_kitty, - GetTxnAndUtxoListForListKittyForSaleResponse, get_txn_and_inpututxolist_for_list_kitty_for_sale, - debug_get_signed_txn_for_list_kitty_for_sale,// added just for debug - ListKittyForSaleRequest, ListKittyForSaleResponse, list_kitty_for_sale, - DelistKittyFromSaleRequest, DelistKittyFromSaleResponse, delist_kitty_from_sale, - UpdateKittyNameRequest, UpdateKittyNameResponse, update_kitty_name, - UpdateTdKittyNameRequest, UpdateTdKittyNameResponse, update_td_kitty_name, - UpdateTdKittyPriceRequest, UpdateTdKittyPriceResponse, update_td_kitty_price, - BuyTdKittyRequest, BuyTdKittyResponse, buy_kitty, - BreedKittyRequest, BreedKittyResponse, breed_kitty, -}; - -use serviceHandlers::blockHandler::blockServicehandler::{ BlockResponse, get_block}; - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; -use crate::{ keystore::SHAWN_PUB_KEY}; - - -use axum::{http::StatusCode, response::IntoResponse, routing::{get, post, put},Json, Router}; -use axum::{response::Html,}; -use std::net::SocketAddr; -use tower_http::cors::{Any, CorsLayer}; -use runtime::{opaque::Block as OpaqueBlock, Block}; -use anyhow::bail; -use serde::{Deserialize, Serialize}; - - -#[tokio::main] -async fn main() { - let cors = CorsLayer::new().allow_origin(Any); - - let app = Router::new() - .route("/get-block", get(get_block)) - .route("/mint-coins", post(mint_coins)) - .route("/create-kitty", post(create_kitty)) - .route("/get-txn-and-inpututxolist-for-listkitty-forsale", get(get_txn_and_inpututxolist_for_list_kitty_for_sale)) - .route("/debug-get-signed-for-listkitty", get(debug_get_signed_txn_for_list_kitty_for_sale)) - .route("/listkitty-for-sale", post(list_kitty_for_sale)) - //.route("/spend-coins", put(spend_coins)) - .layer(cors); - - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - println!("In the main"); - axum::Server::bind(&addr) - .serve(app.into_make_service()) - .await - .unwrap(); -} - - -async fn original_get_db() -> anyhow::Result { - - let keystore = get_local_keystore().await.unwrap_or_else(|_| panic!("Error in extracting local key store")); - - let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; - - // Read node's genesis block. - let node_genesis_hash = rpc::node_get_block_hash(0, &client) - .await? - .expect("node should be able to return some genesis hash"); - let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) - .await? - .expect("node should be able to return some genesis block"); - log::debug!("Node's Genesis block::{:?}", node_genesis_hash); - - // Open the local database - let data_path = temp_dir(); - let db_path = data_path.join("wallet_database"); - 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}"); - - // No filter at-all - let keystore_filter = |_v: &OuterVerifier| -> bool { - true - }; - - if !sled::Db::was_recovered(&db) { - println!("!sled::Db::was_recovered(&db) called "); - // This is a new instance, so we need to apply the genesis block to the database. - async { - sync::apply_block(&db, node_genesis_block, node_genesis_hash, &keystore_filter) - .await; - }; - } - - sync::synchronize(&db, &client, &keystore_filter).await?; - - log::info!( - "Wallet database synchronized with node to height {:?}", - sync::height(&db)?.expect("We just synced, so there is a height available") - ); - - if let Err(err) = db.flush() { - println!("Error flushing Sled database: {}", err); - } - - Ok(db) -} - - -async fn get_db() -> anyhow::Result { - let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; - let data_path = temp_dir(); - let db_path = data_path.join("wallet_database"); - let node_genesis_hash = rpc::node_get_block_hash(0, &client) - .await? - .expect("node should be able to return some genesis hash"); - let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) - .await? - .expect("node should be able to return some genesis block"); - println!("Node's Genesis block::{:?}", node_genesis_hash); - - let db = sync::open_db(db_path, node_genesis_hash, node_genesis_block.clone())?; - Ok(db) -} - - -async fn get_local_keystore() -> anyhow::Result { - let data_path = temp_dir(); - let keystore_path = data_path.join("keystore"); - println!("keystore_path: {:?}", keystore_path); - let keystore = sc_keystore::LocalKeystore::open(keystore_path.clone(), None)?; - keystore::insert_development_key_for_this_session(&keystore)?; - Ok(keystore) -} - -async fn sync_db bool>( - db: &Db, - client: &HttpClient, - filter: &F) -> anyhow::Result<()> { - - if !sled::Db::was_recovered(&db) { - let node_genesis_hash = rpc::node_get_block_hash(0, &client) - .await? - .expect("node should be able to return some genesis hash"); - let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) - .await? - .expect("node should be able to return some genesis block"); - - - println!(" in sync_db !sled::Db::was_recovered(&db)"); - async { - sync::apply_block(&db, node_genesis_block, node_genesis_hash, &filter) - .await; - }; - } - println!(" sync::synchronize will be called!!"); - sync::synchronize(&db, &client, &filter).await?; - - log::info!( - "Wallet database synchronized with node to height {:?}", - sync::height(&db)?.expect("We just synced, so there is a height available") - ); - Ok(()) -} - -async fn sync_and_get_db() -> anyhow::Result { - let db = get_db().await?; - let keystore = get_local_keystore().await?; - let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; - let keystore_filter = |v: &OuterVerifier| -> bool { - matches![v, - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) - if crate::keystore::has_key(&keystore, owner_pubkey) - ] || matches![v, OuterVerifier::UpForGrabs(UpForGrabs)] // used for timestamp - }; - sync_db(&db, &client, &keystore_filter).await?; - Ok(db) -} - -/// 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); - - let mut bytes: [u8; 32] = [0; 32]; - hex::decode_to_slice(s, &mut bytes as &mut [u8]) - .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; - Ok(H256::from(bytes)) -} - -/// Parse an output ref from a string -fn output_ref_from_string(s: &str) -> Result { - let s = strip_0x_prefix(s); - let bytes = - hex::decode(s).map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; - - OutputRef::decode(&mut &bytes[..]) - .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation)) -} - -/// Takes a string and checks for a 0x prefix. Returns a string without a 0x prefix. -fn strip_0x_prefix(s: &str) -> &str { - if &s[..2] == "0x" { - &s[2..] - } else { - s - } -} - -/// Generate a plaform-specific temporary directory for the wallet -fn temp_dir() -> PathBuf { - // Since it is only used for testing purpose, we don't need a secure temp dir, just a unique one. - /* - std::env::temp_dir().join(format!( - "tuxedo-wallet-{}", - std::time::UNIX_EPOCH.elapsed().unwrap().as_millis(), - )) - */ - std::env::temp_dir().join(format!( - "tuxedo-wallet" - )) -} - -/// Generate the platform-specific default data path for the wallet -fn default_data_path() -> PathBuf { - // This uses the directories crate. - // https://docs.rs/directories/latest/directories/struct.ProjectDirs.html - - // Application developers may want to put actual qualifiers or organization here - let qualifier = ""; - let organization = ""; - let application = env!("CARGO_PKG_NAME"); - - directories::ProjectDirs::from(qualifier, organization, application) - .expect("app directories exist on all supported platforms; qed") - .data_dir() - .into() -} - -/// Utility to pretty print an outer verifier -pub fn pretty_print_verifier(v: &OuterVerifier) { - match v { - OuterVerifier::Sr25519Signature(sr25519_signature) => { - println! {"owned by {}", sr25519_signature.owner_pubkey} - } - OuterVerifier::UpForGrabs(_) => println!("that can be spent by anyone"), - OuterVerifier::ThresholdMultiSignature(multi_sig) => { - let string_sigs: Vec<_> = multi_sig - .signatories - .iter() - .map(|sig| format!("0x{}", hex::encode(sig))) - .collect(); - println!( - "Owned by {:?}, with a threshold of {} sigs necessary", - string_sigs, multi_sig.threshold - ); - } - } -} diff --git a/webservice-wallet-with-inbuilt-key-store/src/money.rs b/webservice-wallet-with-inbuilt-key-store/src/money.rs deleted file mode 100644 index 3f8931713..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/money.rs +++ /dev/null @@ -1,339 +0,0 @@ -//! Wallet features related to spending money and checking balances. - -use crate::{cli::MintCoinArgs, cli::SpendArgs,rpc::fetch_storage, sync}; - -use anyhow::anyhow; -use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; -use parity_scale_codec::Encode; -use runtime::{ - money::{Coin, MoneyConstraintChecker}, - OuterConstraintChecker, OuterVerifier, Transaction, -}; -use sc_keystore::LocalKeystore; -use sled::Db; -use sp_core::sr25519::Public; -use sp_runtime::traits::{BlakeTwo256, Hash}; -use tuxedo_core::{ - types::{Input, Output, OutputRef}, - verifier::Sr25519Signature, -}; - -/// Create and send a transaction that mints the coins on the network -pub async fn mint_coins(client: &HttpClient, args: MintCoinArgs) -> anyhow::Result<()> { - log::debug!("The args are:: {:?}", args); - - let transaction = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: vec![( - Coin::<0>::new(args.amount), - OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.owner, - }), - ) - .into()], - checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Mint), - }; - - let spawn_hex = hex::encode(transaction.encode()); - let params = rpc_params![spawn_hex]; - let _spawn_response: Result = client.request("author_submitExtrinsic", params).await; - - log::info!( - "Node's response to mint-coin transaction: {:?}", - _spawn_response - ); - - let minted_coin_ref = OutputRef { - tx_hash: ::hash_of(&transaction.encode()), - index: 0, - }; - let output = &transaction.outputs[0]; - let amount = output.payload.extract::>()?.0; - print!( - "Minted {:?} worth {amount}. ", - hex::encode(minted_coin_ref.encode()) - ); - crate::pretty_print_verifier(&output.verifier); - - Ok(()) -} -use sp_core::H256; -struct recipient_output { - pub recipient:H256, - pub output_amount:Vec -} -fn extract_recipient_list_from_args(args: SpendArgs,) -> Vec { - let mut recipient_list:Vec = Vec::new(); - for i in args.recipients { - let rec_pient = recipient_output { - recipient:i.0, - output_amount:i.1, - }; - recipient_list.push(rec_pient); - } - recipient_list -} -/// Create and send a transaction that spends coins on the network -pub async fn spend_coins( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: SpendArgs, -) -> anyhow::Result<()> { - - log::info!("In the spend_coins_to_multiple_recipient The args are:: {:?}", args); - let mut transaction = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: Vec::new(), - checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend), - }; - - let mut recipient_list:Vec = extract_recipient_list_from_args(args.clone()); - - let mut total_output_amount = 0; - for recipient in &recipient_list { - for amount in &recipient.output_amount { - let output = Output { - payload: Coin::<0>::new(*amount).into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: recipient.recipient, - }), - }; - total_output_amount += amount; - transaction.outputs.push(output); - } - } - - // The total input set will consist of any manually chosen inputs - // plus any automatically chosen to make the input amount high enough - let mut total_input_amount = 0; - let mut all_input_refs = args.input; - for output_ref in &all_input_refs { - let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( - "user-specified output ref not found in local database" - ))?; - total_input_amount += amount; - } - //TODO filtering on a specific sender - - // If the supplied inputs are not valuable enough to cover the output amount - // we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.) - if total_input_amount < total_output_amount { - match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { - Some(more_inputs) => { - all_input_refs.extend(more_inputs); - } - None => Err(anyhow!( - "Not enough value in database to construct transaction" - ))?, - } - } - - // Make sure each input decodes and is still present in the node's storage, - // and then push to transaction. - for output_ref in &all_input_refs { - get_coin_from_storage(output_ref, client).await?; - transaction.inputs.push(Input { - output_ref: output_ref.clone(), - redeemer: vec![], // We will sign the total transaction so this should be empty - }); - } - - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - - // insert the proof - input.redeemer = redeemer; - } - - // Send the transaction - let genesis_spend_hex = hex::encode(transaction.encode()); - let params = rpc_params![genesis_spend_hex]; - let genesis_spend_response: Result = - client.request("author_submitExtrinsic", params).await; - log::info!( - "Node's response to spend transaction: {:?}", - genesis_spend_response - ); - - // Print new output refs for user to check later - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_coin_ref = OutputRef { - tx_hash, - index: i as u32, - }; - let amount = output.payload.extract::>()?.0; - - print!( - "Created {:?} worth {amount}. ", - hex::encode(new_coin_ref.encode()) - ); - crate::pretty_print_verifier(&output.verifier); - } - - Ok(()) -} -/* -/// Create and send a transaction that spends coins on the network -pub async fn spend_coins1( - db: &Db, - client: &HttpClient, - keystore: &LocalKeystore, - args: SpendArgs, -) -> anyhow::Result<()> { - log::info!("The args are:: {:?}", args); - - // Construct a template Transaction to push coins into later - let mut transaction = Transaction { - inputs: Vec::new(), - peeks: Vec::new(), - outputs: Vec::new(), - checker: OuterConstraintChecker::Money(MoneyConstraintChecker::Spend), - }; - - // Construct each output and then push to the transactions - let mut total_output_amount = 0; - for amount in &args.output_amount { - let output = Output { - payload: Coin::<0>::new(*amount).into(), - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: args.recipient, - }), - }; - total_output_amount += amount; - transaction.outputs.push(output); - } - - // The total input set will consist of any manually chosen inputs - // plus any automatically chosen to make the input amount high enough - let mut total_input_amount = 0; - let mut all_input_refs = args.input; - for output_ref in &all_input_refs { - let (_owner_pubkey, amount) = sync::get_unspent(db, output_ref)?.ok_or(anyhow!( - "user-specified output ref not found in local database" - ))?; - total_input_amount += amount; - } - //TODO filtering on a specific sender - - // If the supplied inputs are not valuable enough to cover the output amount - // we select the rest arbitrarily from the local db. (In many cases, this will be all the inputs.) - if total_input_amount < total_output_amount { - match sync::get_arbitrary_unspent_set(db, total_output_amount - total_input_amount)? { - Some(more_inputs) => { - all_input_refs.extend(more_inputs); - } - None => Err(anyhow!( - "Not enough value in database to construct transaction" - ))?, - } - } - - // Make sure each input decodes and is still present in the node's storage, - // and then push to transaction. - for output_ref in &all_input_refs { - get_coin_from_storage(output_ref, client).await?; - transaction.inputs.push(Input { - output_ref: output_ref.clone(), - redeemer: vec![], // We will sign the total transaction so this should be empty - }); - } - - // Keep a copy of the stripped encoded transaction for signing purposes - let stripped_encoded_transaction = transaction.clone().encode(); - - // Iterate back through the inputs, signing, and putting the signatures in place. - for input in &mut transaction.inputs { - // Fetch the output from storage - let utxo = fetch_storage::(&input.output_ref, client).await?; - - // Construct the proof that it can be consumed - let redeemer = match utxo.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - let public = Public::from_h256(owner_pubkey); - crate::keystore::sign_with(keystore, &public, &stripped_encoded_transaction)? - } - OuterVerifier::UpForGrabs(_) => Vec::new(), - OuterVerifier::ThresholdMultiSignature(_) => todo!(), - }; - - // insert the proof - input.redeemer = redeemer; - } - - // Send the transaction - let genesis_spend_hex = hex::encode(transaction.encode()); - let params = rpc_params![genesis_spend_hex]; - let genesis_spend_response: Result = - client.request("author_submitExtrinsic", params).await; - log::info!( - "Node's response to spend transaction: {:?}", - genesis_spend_response - ); - - // Print new output refs for user to check later - let tx_hash = ::hash_of(&transaction.encode()); - for (i, output) in transaction.outputs.iter().enumerate() { - let new_coin_ref = OutputRef { - tx_hash, - index: i as u32, - }; - let amount = output.payload.extract::>()?.0; - - print!( - "Created {:?} worth {amount}. ", - hex::encode(new_coin_ref.encode()) - ); - crate::pretty_print_verifier(&output.verifier); - } - - Ok(()) -} -*/ - -/// Given an output ref, fetch the details about this coin from the node's -/// storage. -pub async fn get_coin_from_storage( - output_ref: &OutputRef, - client: &HttpClient, -) -> anyhow::Result<(Coin<0>, OuterVerifier)> { - let utxo = fetch_storage::(output_ref, client).await?; - let coin_in_storage: Coin<0> = utxo.payload.extract()?; - - Ok((coin_in_storage, utxo.verifier)) -} - -/// Apply a transaction to the local database, storing the new coins. -pub(crate) fn apply_transaction( - db: &Db, - tx_hash: ::Output, - index: u32, - output: &Output, -) -> anyhow::Result<()> { - let amount = output.payload.extract::>()?.0; - let output_ref = OutputRef { tx_hash, index }; - match output.verifier { - OuterVerifier::Sr25519Signature(Sr25519Signature { owner_pubkey }) => { - // Add it to the global unspent_outputs table - crate::sync::add_unspent_output(db, &output_ref, &owner_pubkey, &amount) - } - _ => Err(anyhow!("{:?}", ())), - } -} diff --git a/webservice-wallet-with-inbuilt-key-store/src/output_filter.rs b/webservice-wallet-with-inbuilt-key-store/src/output_filter.rs deleted file mode 100644 index 555fc5a55..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/output_filter.rs +++ /dev/null @@ -1,137 +0,0 @@ -use runtime::{OuterVerifier, Output}; -use sp_core::H256; -use tuxedo_core::types::OutputRef; - -pub type OutputInfo = (Output, OutputRef); - -type TxHash = H256; -/// The Filter type which is the closure signature used by functions to filter UTXOS -pub type Filter = Box Result, ()>>; - -pub trait OutputFilter { - /// The Filter type which is the closure signature used by functions to filter UTXOS - type Filter; - /// Builds a filter to be passed to wallet sync functions to sync the chosen outputs - /// to the users DB. - fn build_filter(verifier: OuterVerifier) -> Self::Filter; -} - -pub struct Sr25519SignatureFilter; -impl OutputFilter for Sr25519SignatureFilter { - // Todo Add filter error - type Filter = Result; - - fn build_filter(verifier: OuterVerifier) -> Self::Filter { - Ok(Box::new(move |outputs, tx_hash| { - let filtered_outputs = outputs - .iter() - .enumerate() - .map(|(i, output)| { - ( - output.clone(), - OutputRef { - tx_hash: *tx_hash, - index: i as u32, - }, - ) - }) - .filter(|(output, _)| output.verifier == verifier) - .collect::>(); - Ok(filtered_outputs) - })) - } -} - -mod tests { - use super::*; - - #[cfg(test)] - use tuxedo_core::{dynamic_typing::DynamicallyTypedData, verifier::*}; - - pub struct TestSr25519SignatureFilter; - impl OutputFilter for TestSr25519SignatureFilter { - type Filter = Result; - - fn build_filter(_verifier: OuterVerifier) -> Self::Filter { - Ok(Box::new(move |_outputs, _tx_hash| { - println!("printed something"); - Ok(vec![]) - })) - } - } - - #[test] - fn filter_prints() { - let verifier = OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: H256::zero(), - }); - let output = Output { - verifier: verifier.clone(), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }; - - let my_filter = - TestSr25519SignatureFilter::build_filter(verifier).expect("Can build print filter"); - let _ = my_filter(&[output], &H256::zero()); - } - - #[test] - fn filter_sr25519_signature_works() { - let verifier = OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: H256::zero(), - }); - - let outputs_to_filter = vec![ - Output { - verifier: verifier.clone(), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }, - Output { - verifier: OuterVerifier::Sr25519Signature(Sr25519Signature { - owner_pubkey: H256::from_slice(b"asdfasdfasdfasdfasdfasdfasdfasdf"), - }), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }, - Output { - verifier: OuterVerifier::ThresholdMultiSignature(ThresholdMultiSignature { - threshold: 1, - signatories: vec![H256::zero()], - }), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }, - ]; - - let expected_filtered_output_infos = vec![( - Output { - verifier: verifier.clone(), - payload: DynamicallyTypedData { - data: vec![], - type_id: *b"1234", - }, - }, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )]; - - let my_filter = Sr25519SignatureFilter::build_filter(verifier) - .expect("Can build Sr25519Signature filter"); - let filtered_output_infos = my_filter(&outputs_to_filter, &H256::zero()) - .expect("Can filter the outputs by verifier correctly"); - - assert_eq!(filtered_output_infos, expected_filtered_output_infos); - } -} diff --git a/webservice-wallet-with-inbuilt-key-store/src/req_resp.rs b/webservice-wallet-with-inbuilt-key-store/src/req_resp.rs deleted file mode 100644 index 1f31acaf0..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/req_resp.rs +++ /dev/null @@ -1,24 +0,0 @@ -use serde::{Deserialize, Serialize}; - - -#[derive(Debug, Deserialize)] -pub struct MintCoinsRequest { - pub amount: u32, -} - -#[derive(Debug, Serialize)] -pub struct MintCoinsResponse { - pub message: String, - // Add any additional fields as needed -} - -#[derive(Debug, Deserialize)] -pub struct CreateKittyRequest { - pub name: String, -} - -#[derive(Debug, Serialize)] -pub struct CreateKittyResponse { - pub message: String, - // Add any additional fields as needed -} \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/rpc.rs b/webservice-wallet-with-inbuilt-key-store/src/rpc.rs deleted file mode 100644 index 0d5466e6d..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/rpc.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! 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> { - let params = rpc_params![Some(height)]; - let rpc_response: Option = client.request("chain_getBlockHash", params).await?; - let maybe_hash = rpc_response.map(|s| crate::h256_from_string(&s).unwrap()); - Ok(maybe_hash) -} - -/// Typed helper to get the node's full block at a particular hash -pub async fn node_get_block(hash: H256, client: &HttpClient) -> anyhow::Result> { - let s = hex::encode(hash.0); - let params = rpc_params![s]; - - let maybe_rpc_response: Option = - client.request("chain_getBlock", params).await?; - let rpc_response = maybe_rpc_response.unwrap(); - - let json_opaque_block = rpc_response.get("block").cloned().unwrap(); - let opaque_block: OpaqueBlock = serde_json::from_value(json_opaque_block).unwrap(); - - // I need a structured block, not an opaque one. To achieve that, I'll - // scale encode it, then once again decode it. - // Feels kind of like a hack, but I honestly don't know what else to do. - // I don't see any way to get the bytes out of an OpaqueExtrinsic. - let scale_bytes = opaque_block.encode(); - - let structured_block = Block::decode(&mut &scale_bytes[..]).unwrap(); - - Ok(Some(structured_block)) -} - -/// 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) -} diff --git a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/blockHandler/blockServicehandler.rs b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/blockHandler/blockServicehandler.rs deleted file mode 100644 index 1b1a1b91b..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/blockHandler/blockServicehandler.rs +++ /dev/null @@ -1,74 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use jsonrpsee::http_client::HttpClientBuilder; -use parity_scale_codec::{Decode, Encode}; -use runtime::OuterVerifier; -use std::path::PathBuf; -use sled::Db; -use crate::money; -use sp_core::H256; -use crate::rpc; - -use crate::cli::MintCoinArgs; -use crate::cli::CreateKittyArgs; - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; -use crate::{ keystore::SHAWN_PUB_KEY}; - - -use axum::{http::StatusCode, response::IntoResponse, routing::{get, post},Json, Router,http::HeaderMap}; - -use std::net::SocketAddr; -use tower_http::cors::{Any, CorsLayer}; -use runtime::{opaque::Block as OpaqueBlock, Block}; -use anyhow::bail; - -#[derive(Debug, Serialize)] -pub struct BlockResponse { - pub message: String, -} - -pub async fn get_block(headers: HeaderMap) -> Json { - let block_number_header = headers.get("Block-Number").unwrap_or_else(|| { - panic!("Block-Number header is missing"); - }); - let block_number = block_number_header.to_str().unwrap_or_else(|_| { - panic!("Failed to parse Block-Number header"); - }); - - // Convert the block number to the appropriate type if needed - let block_number: u128 = block_number.parse().unwrap_or_else(|_| { - panic!("Failed to parse block number as u128"); - }); - - match get_blocks(block_number).await { - Ok(Some(node_block)) => Json(BlockResponse { - message: format!("block found {:?}",node_block), - }), - - Ok(None) => Json(BlockResponse { - message: format!("Node's block not found"), - }), - Err(err) => Json(BlockResponse { - message: format!("Error getting the block: {:?}", err), - }), - Err(_) => Json(BlockResponse { - message: format!("Unknown Error getting the block: "), - }), - } -} - -async fn get_blocks(number: u128) -> anyhow::Result> { - let client = HttpClientBuilder::default().build(DEFAULT_ENDPOINT)?; - let node_block_hash = rpc::node_get_block_hash(number.try_into().unwrap(), &client) - .await? - .expect("node should be able to return some genesis hash"); - println!("Get blocks node_block_hash {:?} ",node_block_hash); - let maybe_block = rpc::node_get_block(node_block_hash, &client).await?; - println!("BlockData {:?} ",maybe_block.clone().unwrap()); - match maybe_block { - Some(block) => Ok(Some(block)), - None => bail!("Block not found for hash: {:?}", node_block_hash), - } -} \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/keyHandler/keyServicehandler.rs b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/keyHandler/keyServicehandler.rs deleted file mode 100644 index 7e9dd02d2..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/keyHandler/keyServicehandler.rs +++ /dev/null @@ -1,69 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use sp_core::H256; -use crate::keystore; - -use crate::{ keystore::SHAWN_PUB_KEY}; - -use axum::{http::StatusCode, response::IntoResponse, routing::{get, post},Json, Router}; -use axum::{response::Html,}; -use std::net::SocketAddr; -use tower_http::cors::{Any, CorsLayer}; -use runtime::{opaque::Block as OpaqueBlock, Block}; -use anyhow::bail; - - -#[derive(Debug, Deserialize)] -pub struct GenerateKeyRequest { - pub password: Option, -} - -#[derive(Debug, Serialize)] -pub struct GenerateKeyResponse { - pub message: String, - pub public_key: Option, - pub phrase: Option, -} - -pub async fn generate_key(body: Json) -> Json { - match keystore::generate_key(body.password.clone()).await { - Ok((public_key, phrase)) => Json(GenerateKeyResponse { - message: format!("Keys generated successfully"), - public_key: Some(public_key), - phrase: Some(phrase), - }), - Err(err) => Json(GenerateKeyResponse { - message: format!("Error generating keys: {:?}", err), - public_key: None, - phrase: None, - }), - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// get keys -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#[derive(Debug, Serialize)] -pub struct GetKeyResponse { - pub message: String, - pub keys: Option>, -} - -pub async fn get_keys() -> Json { - match keystore::get_keys().await { - Ok(keys_iter) => { - // Lets collect keys into a vector of strings - let keys: Vec = keys_iter.map(|key| hex::encode(key)).collect(); - - Json(GetKeyResponse { - message: format!("Keys retrieved successfully"), - keys: Some(keys), - }) - } - Err(err) => Json(GetKeyResponse { - message: format!("Error retrieving keys: {:?}", err), - keys: None, - }), - } -} diff --git a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/kittyHandler/kittyServicehandler.rs b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/kittyHandler/kittyServicehandler.rs deleted file mode 100644 index 8ef1b9cd5..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/kittyHandler/kittyServicehandler.rs +++ /dev/null @@ -1,841 +0,0 @@ -use serde::{Deserialize, Serialize}; - - -use jsonrpsee::http_client::HttpClientBuilder; -use parity_scale_codec::{Decode, Encode}; -use std::path::PathBuf; -use sled::Db; -use crate::kitty; -use sp_core::H256; - -use crate::cli::{CreateKittyArgs, ListKittyForSaleArgs, - DelistKittyFromSaleArgs, UpdateKittyNameArgs, UpdateKittyPriceArgs, - BuyKittyArgs, BreedKittyArgs}; - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; -use crate::{ keystore::SHAWN_PUB_KEY}; - -use crate::get_db; -use crate::get_local_keystore; -use crate::sync_and_get_db; -use crate::original_get_db; - - - -use axum::{http::StatusCode, response::IntoResponse, routing::{get, post, put, patch},Json, Router,http::HeaderMap}; - -use std::convert::Infallible; -use axum::{response::Html,}; -use std::net::SocketAddr; -use tower_http::cors::{Any, CorsLayer}; -use runtime::{opaque::Block as OpaqueBlock, Block}; -use anyhow::bail; -//use parity_scale_codec::Input; -use tuxedo_core::types::Input; - - -use runtime::{ - kitties::{ - DadKittyStatus, FreeKittyConstraintChecker, KittyDNA, KittyData, KittyHelpers, - MomKittyStatus, Parent, - }, - money::{Coin, MoneyConstraintChecker}, - tradable_kitties::{TradableKittyConstraintChecker, TradableKittyData}, - OuterVerifier, Transaction, -}; -use tuxedo_core::types::OutputRef; -use tuxedo_core::types::Output; - -#[derive(Debug, Deserialize)] -pub struct CreateKittyRequest { - pub name: String, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct CreateKittyResponse { - pub message: String, - pub kitty:Option - // Add any additional fields as needed -} - -pub async fn create_kitty(body: Json) -> Result, Infallible> { - println!("create_kitties called "); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - //let db = sync_and_get_db().await.expect("Error"); - let db = original_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(CreateKittyResponse { - message: format!("Error creating HTTP client: {:?}", err), - kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - - match kitty::create_kitty(&db, &client, CreateKittyArgs { - kitty_name: body.name.to_string(), - owner: public_key_h256, - }).await { - Ok(Some(created_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = CreateKittyResponse { - message: format!("Kitty created successfully"), - kitty: Some(created_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(CreateKittyResponse { - message: format!("Kitty creation failed: No data returned"), - kitty:None, - })), - Err(err) => Ok(Json(CreateKittyResponse { - message: format!("Error creating kitty: {:?}", err), - kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// List kitty for Sale -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Serialize, Deserialize)] -pub struct GetTxnAndUtxoListForListKittyForSaleResponse { - pub message: String, - pub transaction: Option, - pub input_utxo_list:Option>>, -} - -pub async fn get_txn_and_inpututxolist_for_list_kitty_for_sale(headers: HeaderMap) -> Json { - // create_tx_for_list_kitty - println!("Headers map = {:?}",headers); - let name_header = headers.get("kitty-name").unwrap_or_else(|| { - panic!("Kitty name is missing"); - }); - - let name_str = name_header.to_str().unwrap_or_else(|_| { - panic!("Failed to parse name header"); - }); - - // ------------------------------- - let price_header = headers.get("kitty-price").unwrap_or_else(|| { - panic!("Kitty price is missing"); - }); - let price_str = price_header.to_str().unwrap_or_else(|_| { - panic!("Failed to parse priceheader"); - }); - - // Convert the block number to the appropriate type if needed - let price_number: u128 = price_str.parse().unwrap_or_else(|_| { - panic!("Failed to parse price number as u128"); - }); - - // ------------------------------- - - let publick_key_header = headers.get("owner_public_key").unwrap_or_else(|| { - panic!("publick_key_header is missing"); - }); - - let publick_key_str = publick_key_header.to_str().unwrap_or_else(|_| { - panic!("publick_key_header to parse"); - }); - - let public_key_bytes = hex::decode(publick_key_str.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - //let db = sync_and_get_db().await.expect("Error"); - let db = original_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Json(GetTxnAndUtxoListForListKittyForSaleResponse { - message: format!("Error creating HTTP client: {:?}", err), - transaction:None, - input_utxo_list:None - }); - } - }; - - match kitty::create_txn_for_list_kitty(&db, - name_str.to_string(), - price_number, - public_key_h256, - ).await { - Ok(Some(txn)) => { - // Convert created_kitty to JSON and include it in the response - let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; - - let response = GetTxnAndUtxoListForListKittyForSaleResponse { - message: format!("Kitty listed for sale successfully"), - transaction: Some(txn), - input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), - }; - Json(response) - }, - Ok(None) => Json(GetTxnAndUtxoListForListKittyForSaleResponse { - message: format!("Kitty listing forsale failed: No input returned"), - transaction:None, - input_utxo_list:None - }), - Err(err) => Json(GetTxnAndUtxoListForListKittyForSaleResponse { - message: format!("Error!! listing forsale: {:?}", err), - transaction:None, - input_utxo_list:None - }), - } -} - -pub async fn debug_get_signed_txn_for_list_kitty_for_sale(headers: HeaderMap) -> Json { - // create_tx_for_list_kitty - println!("Headers map = {:?}",headers); - let name_header = headers.get("kitty-name").unwrap_or_else(|| { - panic!("Kitty name is missing"); - }); - - let name_str = name_header.to_str().unwrap_or_else(|_| { - panic!("Failed to parse name header"); - }); - - // ------------------------------- - let price_header = headers.get("kitty-price").unwrap_or_else(|| { - panic!("Kitty price is missing"); - }); - let price_str = price_header.to_str().unwrap_or_else(|_| { - panic!("Failed to parse priceheader"); - }); - - // Convert the block number to the appropriate type if needed - let price_number: u128 = price_str.parse().unwrap_or_else(|_| { - panic!("Failed to parse price number as u128"); - }); - - // ------------------------------- - - let publick_key_header = headers.get("owner_public_key").unwrap_or_else(|| { - panic!("publick_key_header is missing"); - }); - - let publick_key_str = publick_key_header.to_str().unwrap_or_else(|_| { - panic!("publick_key_header to parse"); - }); - - let public_key_bytes = hex::decode(publick_key_str.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - //let db = sync_and_get_db().await.expect("Error"); - let db = original_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Json(GetTxnAndUtxoListForListKittyForSaleResponse { - message: format!("Error creating HTTP client: {:?}", err), - transaction:None, - input_utxo_list:None - }); - } - }; - match kitty::create_signed_txn_for_list_kitty(&db, - name_str.to_string(), - price_number, - public_key_h256, - &client, - ).await { - Ok(Some(txn)) => { - // Convert created_kitty to JSON and include it in the response - let utxo_list = kitty::create_inpututxo_list(&mut txn.clone(),&client).await; - - let response = GetTxnAndUtxoListForListKittyForSaleResponse { - message: format!("Kitty listed for sale successfully"), - transaction: Some(txn), - input_utxo_list:utxo_list.expect("Cant crate the Utxo List"), - }; - Json(response) - }, - Ok(None) => Json(GetTxnAndUtxoListForListKittyForSaleResponse { - message: format!("Kitty listing forsale failed: No input returned"), - transaction:None, - input_utxo_list:None - }), - Err(err) => Json(GetTxnAndUtxoListForListKittyForSaleResponse { - message: format!("Error!! listing forsale: {:?}", err), - transaction:None, - input_utxo_list:None - }), - } -} - -#[derive(Debug, Deserialize)] -pub struct ListKittyForSaleRequest { - pub signed_transaction: Transaction, -} - -#[derive(Debug, Serialize)] -pub struct ListKittyForSaleResponse { - pub message: String, - pub td_kitty:Option - // Add any additional fields as needed -} - -pub async fn list_kitty_for_sale (body: Json) -> Result, Infallible> { - println!("List kitties for sale is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - //let db = sync_and_get_db().await.expect("Error"); - let db = original_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(ListKittyForSaleResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - match kitty::list_kitty_for_sale(&body.signed_transaction, - &db, &client).await { - Ok(Some(listed_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = ListKittyForSaleResponse { - message: format!("Kitty listed for sale successfully"), - td_kitty: Some(listed_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(ListKittyForSaleResponse { - message: format!("Kitty listing forsale failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(ListKittyForSaleResponse { - message: format!("Error listing forsale: {:?}", err), - td_kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// De-list kitty from Sale -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Deserialize)] -pub struct DelistKittyFromSaleRequest { - pub name: String, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct DelistKittyFromSaleResponse { - pub message: String, - pub kitty:Option -} -pub async fn delist_kitty_from_sale (body: Json) -> Result, Infallible> { - println!("delist_kitty_from_sale is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - //let db = sync_and_get_db().await.expect("Error"); - let db = original_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(DelistKittyFromSaleResponse { - message: format!("Error creating HTTP client: {:?}", err), - kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - let ks = get_local_keystore().await.expect("Error"); - - match kitty::delist_kitty_from_sale(&db, &client, &ks,DelistKittyFromSaleArgs { - name: body.name.to_string(), - owner: public_key_h256, - }).await { - Ok(Some(delisted_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = DelistKittyFromSaleResponse { - message: format!("Kitty listed for sale successfully"), - kitty: Some(delisted_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(DelistKittyFromSaleResponse { - message: format!("Kitty listing forsale failed: No data returned"), - kitty:None, - })), - Err(err) => Ok(Json(DelistKittyFromSaleResponse { - message: format!("Error listing forsale: {:?}", err), - kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// Update kitty name -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Deserialize)] -pub struct UpdateKittyNameRequest { - pub current_name: String, - pub new_name:String, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct UpdateKittyNameResponse { - pub message: String, - pub kitty:Option -} -pub async fn update_kitty_name(body: Json) -> Result, Infallible> { - println!("update_kitty_name is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let db = sync_and_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(UpdateKittyNameResponse { - message: format!("Error creating HTTP client: {:?}", err), - kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - let ks = get_local_keystore().await.expect("Error"); - - match kitty::update_kitty_name(&db, &client, &ks,UpdateKittyNameArgs { - current_name: body.current_name.to_string(), - new_name: body.new_name.to_string(), - owner: public_key_h256, - }).await { - Ok(Some(updated_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = UpdateKittyNameResponse { - message: format!("Kitty listed for sale successfully"), - kitty: Some(updated_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(UpdateKittyNameResponse { - message: format!("Kitty listing forsale failed: No data returned"), - kitty:None, - })), - Err(err) => Ok(Json(UpdateKittyNameResponse { - message: format!("Error listing forsale: {:?}", err), - kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// Update tradable kitty name -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Deserialize)] -pub struct UpdateTdKittyNameRequest { - pub current_name: String, - pub new_name:String, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct UpdateTdKittyNameResponse { - pub message: String, - pub td_kitty:Option -} -pub async fn update_td_kitty_name(body: Json) -> Result, Infallible> { - println!("update_td_kitty_name is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let db = sync_and_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(UpdateTdKittyNameResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - let ks = get_local_keystore().await.expect("Error"); - - match kitty::update_td_kitty_name(&db, &client, &ks,UpdateKittyNameArgs { - current_name: body.current_name.to_string(), - new_name: body.new_name.to_string(), - owner: public_key_h256, - }).await { - Ok(Some(updated_td_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = UpdateTdKittyNameResponse { - message: format!("Kitty listed for sale successfully"), - td_kitty: Some(updated_td_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(UpdateTdKittyNameResponse { - message: format!("Kitty listing forsale failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(UpdateTdKittyNameResponse { - message: format!("Error listing forsale: {:?}", err), - td_kitty:None, - })), - } -} - -//////////////////////////////////////////////////////////////////// -// Update tradable kitty price -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Deserialize)] -pub struct UpdateTdKittyPriceRequest { - pub current_name: String, - pub price:u128, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct UpdateTdKittyPriceResponse { - pub message: String, - pub td_kitty:Option -} - -pub async fn update_td_kitty_price(body: Json) -> Result, Infallible> { - println!("update_td_kitty_price is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let db = sync_and_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(UpdateTdKittyPriceResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - let ks = get_local_keystore().await.expect("Error"); - - match kitty::update_kitty_price(&db, &client, &ks,UpdateKittyPriceArgs { - current_name: body.current_name.to_string(), - price: body.price, - owner: public_key_h256, - }).await { - Ok(Some(updated_td_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = UpdateTdKittyPriceResponse { - message: format!("Kitty listed for sale successfully"), - td_kitty: Some(updated_td_kitty), - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(UpdateTdKittyPriceResponse { - message: format!("Kitty listing forsale failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(UpdateTdKittyPriceResponse { - message: format!("Error listing forsale: {:?}", err), - td_kitty:None, - })), - } -} - - -//////////////////////////////////////////////////////////////////// -// Buy kitty -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Deserialize)] -pub struct BuyTdKittyRequest { - pub input_coins: Vec, - pub kitty_name: String, - pub owner_public_key:String, - pub seller_public_key:String, - pub output_amount: Vec, -} - -#[derive(Debug, Serialize)] -pub struct BuyTdKittyResponse { - pub message: String, - pub td_kitty:Option -} - -pub async fn buy_kitty(body: Json) -> Result, Infallible> { - println!("update_td_kitty_price is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let db = sync_and_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(BuyTdKittyResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256_of_owner = H256::from_slice(&public_key_bytes); - - let public_key_bytes = hex::decode(body.seller_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256_of_seller = H256::from_slice(&public_key_bytes); - - let ks = get_local_keystore().await.expect("Error"); - - match kitty::buy_kitty(&db, &client, &ks,BuyKittyArgs { - input: body.input_coins.clone(), - kitty_name: body.kitty_name.clone(), - seller: public_key_h256_of_seller, - owner: public_key_h256_of_owner, - output_amount: body.output_amount.clone(), - }).await { - Ok(Some(updated_td_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = BuyTdKittyResponse { - message: format!("Kitty listed for sale successfully"), - td_kitty: Some(updated_td_kitty), - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(BuyTdKittyResponse { - message: format!("Kitty listing forsale failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(BuyTdKittyResponse { - message: format!("Error listing forsale: {:?}", err), - td_kitty:None, - })), - } -} - - -//////////////////////////////////////////////////////////////////// -// Breed kitty -//////////////////////////////////////////////////////////////////// - -#[derive(Debug, Deserialize)] -pub struct BreedKittyRequest { - pub mom_name: String, - pub dad_name: String, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct BreedKittyResponse { - pub message: String, - pub mom_kitty:Option, - pub dad_kitty:Option, - pub child_kitty:Option, -} - -pub async fn breed_kitty(body: Json) -> Result, Infallible> { - println!("update_td_kitty_price is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let db = sync_and_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(BreedKittyResponse { - message: format!("Error creating HTTP client: {:?}", err), - mom_kitty:None, - dad_kitty:None, - child_kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256_of_owner = H256::from_slice(&public_key_bytes); - - let ks = get_local_keystore().await.expect("Error"); - - match kitty::breed_kitty(&db, &client, &ks,BreedKittyArgs { - mom_name: body.mom_name.clone(), - dad_name: body.dad_name.clone(), - owner: public_key_h256_of_owner, - }).await { - Ok(Some(new_family)) => { - // Convert created_kitty to JSON and include it in the response - let response = BreedKittyResponse { - message: format!("breeding successfully"), - mom_kitty:Some(new_family[0].clone()), - dad_kitty:Some(new_family[1].clone()), - child_kitty:Some(new_family[2].clone()), - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(BreedKittyResponse { - message: format!("Error in breeding failed: No data returned"), - mom_kitty:None, - dad_kitty:None, - child_kitty:None, - })), - Err(err) => Ok(Json(BreedKittyResponse { - message: format!("Error in breeding : {:?}", err), - mom_kitty:None, - dad_kitty:None, - child_kitty:None, - })), - } -} - -/* - -#[derive(Debug, Deserialize)] -pub struct ListKittyForSaleRequest { - pub name: String, - pub price: u128, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct ListKittyForSaleResponse { - pub message: String, - pub td_kitty:Option - // Add any additional fields as needed -} - -pub async fn list_kitty_for_sale (body: Json) -> Result, Infallible> { - println!("List kitties for sale is called {:?}",body); - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - //let db = sync_and_get_db().await.expect("Error"); - let db = original_get_db().await.expect("Error"); - - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Ok(Json(ListKittyForSaleResponse { - message: format!("Error creating HTTP client: {:?}", err), - td_kitty:None, - })); - } - }; - - // Convert the hexadecimal string to bytes - let public_key_bytes = hex::decode(body.owner_public_key.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - let ks = get_local_keystore().await.expect("Error"); - - match kitty::list_kitty_for_sale(&db, &client, &ks,ListKittyForSaleArgs { - name: body.name.to_string(), - price: body.price, - owner: public_key_h256, - }).await { - Ok(Some(listed_kitty)) => { - // Convert created_kitty to JSON and include it in the response - let response = ListKittyForSaleResponse { - message: format!("Kitty listed for sale successfully"), - td_kitty: Some(listed_kitty), // Include the created kitty in the response - }; - Ok(Json(response)) - }, - Ok(None) => Ok(Json(ListKittyForSaleResponse { - message: format!("Kitty listing forsale failed: No data returned"), - td_kitty:None, - })), - Err(err) => Ok(Json(ListKittyForSaleResponse { - message: format!("Error listing forsale: {:?}", err), - td_kitty:None, - })), - } -} - -/////////////////////////////////// -#[derive(Debug, Serialize, Deserialize)] -pub struct GetTxnForListKittyForSaleResponse { - pub message: String, - pub list_kitty_inputs:Option> -} - -pub async fn get_inputs_for_list_kitty_for_sale(headers: HeaderMap) -> Json { - // create_tx_for_list_kitty - let name_header = headers.get("kitty-name").unwrap_or_else(|| { - panic!("Kitty name is missing"); - }); - - let name_str = name_header.to_str().unwrap_or_else(|_| { - panic!("Failed to parse name header"); - }); - - // ------------------------------- - let price_header = headers.get("kitty-price").unwrap_or_else(|| { - panic!("Kitty price is missing"); - }); - let price_str = price_header.to_str().unwrap_or_else(|_| { - panic!("Failed to parse priceheader"); - }); - - // Convert the block number to the appropriate type if needed - let price_number: u128 = price_str.parse().unwrap_or_else(|_| { - panic!("Failed to parse price number as u128"); - }); - - // ------------------------------- - - let publick_key_header = headers.get("kitty-name").unwrap_or_else(|| { - panic!("publick_key_header is missing"); - }); - - let publick_key_str = publick_key_header.to_str().unwrap_or_else(|_| { - panic!("publick_key_header to parse"); - }); - - let public_key_bytes = hex::decode(publick_key_str.clone()).expect("Invalid hexadecimal string"); - let public_key_h256 = H256::from_slice(&public_key_bytes); - - let db = original_get_db().await.expect("Error"); - - match kitty::create_inputs_for_list_kitty(&db, - name_str.to_string(), - // price_number, - public_key_h256, - ).await { - Ok(Some(inputs)) => { - // Convert created_kitty to JSON and include it in the response - let response = GetTxnForListKittyForSaleResponse { - message: format!("Kitty listed for sale successfully"), - list_kitty_inputs: Some(inputs), // Include the created kitty in the response - }; - Json(response) - }, - Ok(None) => Json(GetTxnForListKittyForSaleResponse { - message: format!("Kitty listing forsale failed: No input returned"), - list_kitty_inputs:None, - }), - Err(err) => Json(GetTxnForListKittyForSaleResponse { - message: format!("Error listing forsale: {:?}", err), - list_kitty_inputs:None, - }), - } -} - -*/ \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/moneyHandler/moneyServicehandler.rs b/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/moneyHandler/moneyServicehandler.rs deleted file mode 100644 index b46680b9a..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/serviceHandlers/moneyHandler/moneyServicehandler.rs +++ /dev/null @@ -1,68 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use jsonrpsee::http_client::HttpClientBuilder; -use parity_scale_codec::{Decode, Encode}; -use runtime::OuterVerifier; -use std::path::PathBuf; -use sled::Db; -use crate::money; -use sp_core::H256; - -use crate::cli::MintCoinArgs; - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; -use crate::{ keystore::SHAWN_PUB_KEY}; - - -use axum::{http::StatusCode, response::IntoResponse, routing::{get, post},Json, Router}; -use axum::{response::Html,}; -use std::net::SocketAddr; -use tower_http::cors::{Any, CorsLayer}; -use runtime::{opaque::Block as OpaqueBlock, Block}; -use anyhow::bail; - - -#[derive(Debug, Deserialize)] -pub struct MintCoinsRequest { - pub amount: u128, - pub owner_public_key:String, -} - -#[derive(Debug, Serialize)] -pub struct MintCoinsResponse { - pub message: String, - - // Add any additional fields as needed -} - -pub async fn mint_coins(body: Json) -> Json { - let client_result = HttpClientBuilder::default().build(DEFAULT_ENDPOINT); - let client = match client_result { - Ok(client) => client, - Err(err) => { - return Json(MintCoinsResponse { - message: format!("Error creating HTTP client: {:?}", err), - }); - } - }; - - // Convert the hexadecimal string to bytes - //let public_key_bytes = hex::decode(SHAWN_PUB_KEY).expect("Invalid hexadecimal string"); - let public_key_bytes = hex::decode(body.owner_public_key.as_str()).expect("Invalid hexadecimal string"); - - // Convert the bytes to H256 - let public_key_h256 = H256::from_slice(&public_key_bytes); - // Call the mint_coins function from your CLI wallet module - match money::mint_coins(&client, MintCoinArgs { - amount: body.amount, - owner: public_key_h256, - }).await { - Ok(()) => Json(MintCoinsResponse { - message: format!("Coins minted successfully"), - }), - Err(err) => Json(MintCoinsResponse { - message: format!("Error minting coins: {:?}", err), - }), - } -} \ No newline at end of file diff --git a/webservice-wallet-with-inbuilt-key-store/src/sync.rs b/webservice-wallet-with-inbuilt-key-store/src/sync.rs deleted file mode 100644 index bb35b8ab8..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/sync.rs +++ /dev/null @@ -1,1096 +0,0 @@ -//! This module is responsible for maintaining the wallet's local database of blocks -//! and owned UTXOs to the canonical database reported by the node. -//! -//! It is backed by a sled database -//! -//! ## Schema -//! -//! There are 4 tables in the database -//! BlockHashes block_number:u32 => block_hash:H256 -//! Blocks block_hash:H256 => block:Block -//! UnspentOutputs output_ref => (owner_pubkey, amount) -//! SpentOutputs output_ref => (owner_pubkey, amount) - -use std::path::PathBuf; - -use crate::rpc; -use anyhow::anyhow; -use parity_scale_codec::{Decode, Encode}; -use sled::Db; -use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, Hash, Zero}; -use tuxedo_core::{ - dynamic_typing::UtxoData, - types::{Input, OutputRef}, -}; - -use crate::cli::ShowOwnedKittyArgs; -use anyhow::Error; -use jsonrpsee::http_client::HttpClient; -use runtime::kitties::KittyDNA; -use runtime::kitties::KittyData; - -use runtime::{ - money::Coin, timestamp::Timestamp, tradable_kitties::TradableKittyData, Block, OuterVerifier, - Transaction, -}; - -/*Todo: Do we need all the data of kitty here -use runtime::{ - kitties::{KittyData, Parent,KittyHelpers,MomKittyStatus,DadKittyStatus, - KittyDNA,FreeKittyConstraintChecker} -}; -*/ - -/// The identifier for the blocks tree in the db. -const BLOCKS: &str = "blocks"; - -/// The identifier for the block_hashes tree in the db. -const BLOCK_HASHES: &str = "block_hashes"; - -/// The identifier for the unspent tree in the db. -const UNSPENT: &str = "unspent"; - -/// The identifier for the spent tree in the db. -const SPENT: &str = "spent"; - -/// The identifier for the owned kitties in the db. -const FRESH_KITTY: &str = "fresh_kitty"; - -/// The identifier for the owned kitties in the db. -const USED_KITTY: &str = "used_kitty"; - -/// The identifier for the owned kitties in the db. -const FRESH_TRADABLE_KITTY: &str = "fresh_tradable_kitty"; - -/// The identifier for the owned kitties in the db. -const USED_TRADABLE_KITTY: &str = "used_tradable_kitty"; - -/// 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 -/// If an empty database is opened, it is initialized with the expected genesis hash and genesis block -pub(crate) fn open_db( - db_path: PathBuf, - expected_genesis_hash: H256, - expected_genesis_block: Block, -) -> anyhow::Result { - //TODO figure out why this assertion fails. - //assert_eq!(BlakeTwo256::hash_of(&expected_genesis_block.encode()), expected_genesis_hash, "expected block hash does not match expected block"); - - let db = sled::open(db_path)?; - - // Open the tables we'll need - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - let wallet_blocks_tree = db.open_tree("blocks")?; - - // If the database is already populated, just make sure it is for the same genesis block - if height(&db)?.is_some() { - // There are database blocks, so do a quick precheck to make sure they use the same genesis block. - let wallet_genesis_ivec = wallet_block_hashes_tree - .get(0.encode())? - .expect("We know there are some blocks, so there should be a 0th block."); - let wallet_genesis_hash = H256::decode(&mut &wallet_genesis_ivec[..])?; - log::debug!("Found existing database."); - if expected_genesis_hash != wallet_genesis_hash { - log::error!("Wallet's genesis does not match expected. Aborting database opening."); - return Err(anyhow!("Node reports a different genesis block than wallet. Wallet: {wallet_genesis_hash:?}. Expected: {expected_genesis_hash:?}. Aborting all operations")); - } - return Ok(db); - } - - // If there are no local blocks yet, initialize the tables - log::info!( - "Initializing fresh sync from genesis {:?}", - expected_genesis_hash - ); - - // Update both tables - wallet_block_hashes_tree.insert(0u32.encode(), expected_genesis_hash.encode())?; - wallet_blocks_tree.insert( - expected_genesis_hash.encode(), - expected_genesis_block.encode(), - )?; - - Ok(db) -} - -/// Synchronize the local database to the database of the running node. -/// The wallet entirely trusts the data the node feeds it. In the bigger -/// picture, that means run your own (light) node. -pub(crate) async fn synchronize bool>( - db: &Db, - client: &HttpClient, - filter: &F, -) -> anyhow::Result<()> { - //log::info!("Synchronizing wallet with node."); - println!("Synchronizing wallet with node."); - - // Start the algorithm at the height that the wallet currently thinks is best. - // Fetch the block hash at that height from both the wallet's local db and the node - let mut height: u32 = height(db)?.ok_or(anyhow!("tried to sync an uninitialized database"))?; - let mut wallet_hash = get_block_hash(db, height)? - .expect("Local database should have a block hash at the height reported as best"); - let mut node_hash: Option = rpc::node_get_block_hash(height, client).await?; - - // There may have been a re-org since the last time the node synced. So we loop backwards from the - // best height the wallet knows about checking whether the wallet knows the same block as the node. - // If not, we roll this block back on the wallet's local db, and then check the next ancestor. - // When the wallet and the node agree on the best block, the wallet can re-sync following the node. - // In the best case, where there is no re-org, this loop will execute zero times. - while Some(wallet_hash) != node_hash { - log::info!("Divergence at height {height}. Node reports block: {node_hash:?}. Reverting wallet block: {wallet_hash:?}."); - - unapply_highest_block(db).await?; - - // Update for the next iteration - height -= 1; - wallet_hash = get_block_hash(db, height)? - .expect("Local database should have a block hash at the height reported as best"); - node_hash = rpc::node_get_block_hash(height, client).await?; - } - - // Orphaned blocks (if any) have been discarded at this point. - // So we prepare our variables for forward syncing. - log::debug!("Resyncing from common ancestor {node_hash:?} - {wallet_hash:?}"); - height += 1; - node_hash = rpc::node_get_block_hash(height, client).await?; - - // Now that we have checked for reorgs and rolled back any orphan blocks, we can go ahead and sync forward. - while let Some(hash) = node_hash { - log::debug!("Forward syncing height {height}, hash {hash:?}"); - - // Fetch the entire block in order to apply its transactions - let block = rpc::node_get_block(hash, client) - .await? - .expect("Node should be able to return a block whose hash it already returned"); - - // Apply the new block - apply_block(db, block, hash, filter).await?; - - height += 1; - - node_hash = rpc::node_get_block_hash(height, client).await?; - } - - log::debug!("Done with forward sync up to {}", height - 1); - println!("Done with forward sync up to {}", height - 1); - if let Err(err) = db.flush() { - println!("Error flushing Sled database: {}", err); - } - Ok(()) -} - -/// Gets the owner and amount associated with an output ref from the unspent table -/// -/// Some if the output ref exists, None if it doesn't -pub(crate) fn get_unspent(db: &Db, output_ref: &OutputRef) -> anyhow::Result> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - let Some(ivec) = wallet_unspent_tree.get(output_ref.encode())? else { - return Ok(None); - }; - - Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) -} - -/// Picks an arbitrary set of unspent outputs from the database for spending. -/// The set's token values must add up to at least the specified target value. -/// -/// The return value is None if the total value of the database is less than the target -/// It is Some(Vec![...]) when it is possible -pub(crate) fn get_arbitrary_unspent_set( - db: &Db, - target: u128, -) -> anyhow::Result>> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - - let mut total = 0u128; - let mut keepers = Vec::new(); - - let mut unspent_iter = wallet_unspent_tree.iter(); - while total < target { - let Some(pair) = unspent_iter.next() else { - return Ok(None); - }; - - let (output_ref_ivec, owner_amount_ivec) = pair?; - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..])?; - println!( - "in Sync::get_arbitrary_unspent_set output_ref = {:?}", - output_ref - ); - let (_owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - total += amount; - keepers.push(output_ref); - } - - Ok(Some(keepers)) -} - -/// Gets the block hash from the local database given a block height. Similar the Node's RPC. -/// -/// Some if the block exists, None if the block does not exist. -pub(crate) fn get_block_hash(db: &Db, height: u32) -> anyhow::Result> { - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - let Some(ivec) = wallet_block_hashes_tree.get(height.encode())? else { - return Ok(None); - }; - - let hash = H256::decode(&mut &ivec[..])?; - - Ok(Some(hash)) -} - -// This is part of what I expect to be a useful public interface. For now it is not used. -#[allow(dead_code)] -/// Gets the block from the local database given a block hash. Similar to the Node's RPC. -pub(crate) fn get_block(db: &Db, hash: H256) -> anyhow::Result> { - let wallet_blocks_tree = db.open_tree(BLOCKS)?; - let Some(ivec) = wallet_blocks_tree.get(hash.encode())? else { - return Ok(None); - }; - - let block = Block::decode(&mut &ivec[..])?; - - Ok(Some(block)) -} - -/// Apply a block to the local database -pub(crate) async fn apply_block bool>( - db: &Db, - b: Block, - block_hash: H256, - filter: &F, -) -> anyhow::Result<()> { - //log::info!("Applying Block {:?}, Block_Hash {:?}", b, block_hash); - //println!("Applying Block {:?}, Block_Hash {:?}", b, block_hash); - // Write the hash to the block_hashes table - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - wallet_block_hashes_tree.insert(b.header.number.encode(), block_hash.encode())?; - - // Write the block to the blocks table - let wallet_blocks_tree = db.open_tree(BLOCKS)?; - wallet_blocks_tree.insert(block_hash.encode(), b.encode())?; - - // Iterate through each transaction - for tx in b.extrinsics { - apply_transaction(db, tx, filter).await?; - } - if let Err(err) = db.flush() { - println!("Error flushing Sled database: {}", err); - } - - Ok(()) -} - -/// Apply a single transaction to the local database -/// The owner-specific tables are mappings from output_refs to coin amounts -async fn apply_transaction bool>( - db: &Db, - tx: Transaction, - filter: &F, -) -> anyhow::Result<()> { - let tx_hash = BlakeTwo256::hash_of(&tx.encode()); - log::debug!("syncing transaction {tx_hash:?}"); - - // Insert all new outputs - for (index, output) in tx - .outputs - .iter() - .filter(|o| filter(&o.verifier)) - .enumerate() - { - match output.payload.type_id { - Coin::<0>::TYPE_ID => { - crate::money::apply_transaction(db, tx_hash, index as u32, output)?; - } - Timestamp::TYPE_ID => { - crate::timestamp::apply_transaction(db, output)?; - } - KittyData::TYPE_ID => { - crate::kitty::apply_transaction(db, tx_hash, index as u32, output)?; - } - TradableKittyData::TYPE_ID => { - crate::kitty::apply_td_transaction(db, tx_hash, index as u32, output)?; - } - - _ => continue, - } - } - - log::debug!("about to spend all inputs"); - // Spend all the inputs - for Input { output_ref, .. } in tx.inputs { - spend_output(db, &output_ref)?; - mark_as_used_kitties(db, &output_ref)?; - mark_as_used_tradable_kitties(db, &output_ref)?; - } - - if let Err(err) = db.flush() { - println!("Error flushing Sled database: {}", err); - } - - Ok(()) -} - -/// Add a new output to the database updating all tables. -pub(crate) fn add_unspent_output( - db: &Db, - output_ref: &OutputRef, - owner_pubkey: &H256, - amount: &u128, -) -> anyhow::Result<()> { - let unspent_tree = db.open_tree(UNSPENT)?; - unspent_tree.insert(output_ref.encode(), (owner_pubkey, amount).encode())?; - - Ok(()) -} - -/// Remove an output from the database updating all tables. -fn remove_unspent_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let unspent_tree = db.open_tree(UNSPENT)?; - - unspent_tree.remove(output_ref.encode())?; - - Ok(()) -} - -/// Mark an existing output as spent. This does not purge all record of the output from the db. -/// It just moves the record from the unspent table to the spent table -fn spend_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let unspent_tree = db.open_tree(UNSPENT)?; - let spent_tree = db.open_tree(SPENT)?; - - let Some(ivec) = unspent_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - let (owner, amount) = <(H256, u128)>::decode(&mut &ivec[..])?; - spent_tree.insert(output_ref.encode(), (owner, amount).encode())?; - - Ok(()) -} - -/// Mark an output that was previously spent back as unspent. -fn unspend_output(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let unspent_tree = db.open_tree(UNSPENT)?; - let spent_tree = db.open_tree(SPENT)?; - - let Some(ivec) = spent_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - let (owner, amount) = <(H256, u128)>::decode(&mut &ivec[..])?; - unspent_tree.insert(output_ref.encode(), (owner, amount).encode())?; - - Ok(()) -} - -/// Run a transaction backwards against a database. Mark all of the Inputs -/// as unspent, and drop all of the outputs. -fn unapply_transaction(db: &Db, tx: &Transaction) -> anyhow::Result<()> { - // Loop through the inputs moving each from spent to unspent - for Input { output_ref, .. } in &tx.inputs { - unspend_output(db, output_ref)?; - } - - // Loop through the outputs pruning them from unspent and dropping all record - let tx_hash = BlakeTwo256::hash_of(&tx.encode()); - - for i in 0..tx.outputs.len() { - let output_ref = OutputRef { - tx_hash, - index: i as u32, - }; - remove_unspent_output(db, &output_ref)?; - } - - Ok(()) -} - -/// Unapply the best block that the wallet currently knows about -pub(crate) async fn unapply_highest_block(db: &Db) -> anyhow::Result { - let wallet_blocks_tree = db.open_tree(BLOCKS)?; - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - - // Find the best height - let height = height(db)?.ok_or(anyhow!("Cannot unapply block from uninitialized database"))?; - - // Take the hash from the block_hashes tables - let Some(ivec) = wallet_block_hashes_tree.remove(height.encode())? else { - return Err(anyhow!( - "No block hash found at height reported as best. DB is inconsistent." - )); - }; - let hash = H256::decode(&mut &ivec[..])?; - - // Take the block from the blocks table - let Some(ivec) = wallet_blocks_tree.remove(hash.encode())? else { - return Err(anyhow!( - "Block was not present in db but block hash was. DB is corrupted." - )); - }; - - let block = Block::decode(&mut &ivec[..])?; - - // Loop through the transactions in reverse order calling unapply - for tx in block.extrinsics.iter().rev() { - unapply_transaction(db, tx)?; - } - - Ok(block) -} - -/// Get the block height that the wallet is currently synced to -/// -/// None means the db is not yet initialized with a genesis block -pub(crate) fn height(db: &Db) -> anyhow::Result> { - let wallet_block_hashes_tree = db.open_tree(BLOCK_HASHES)?; - let num_blocks = wallet_block_hashes_tree.len(); - - Ok(if num_blocks == 0 { - None - } else { - Some(num_blocks as u32 - 1) - }) -} - -// This is part of what I expect to be a useful public interface. For now it is not used. -#[allow(dead_code)] -/// Debugging use. Print out the entire block_hashes tree. -pub(crate) fn print_block_hashes_tree(db: &Db) -> anyhow::Result<()> { - for height in 0..height(db)?.unwrap() { - let hash = get_block_hash(db, height)?; - println!("height: {height}, hash: {hash:?}"); - } - - Ok(()) -} - -/// Debugging use. Print the entire unspent outputs tree. -pub(crate) fn print_unspent_tree(db: &Db) -> anyhow::Result<()> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - for x in wallet_unspent_tree.iter() { - let (output_ref_ivec, owner_amount_ivec) = x?; - let output_ref = hex::encode(output_ref_ivec); - let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - println!("{output_ref}: owner {owner_pubkey:?}, amount {amount}"); - } - - Ok(()) -} - -/// Iterate the entire unspent set summing the values of the coins -/// on a per-address basis. -pub(crate) fn get_balances(db: &Db) -> anyhow::Result> { - let mut balances = std::collections::HashMap::::new(); - - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - - for raw_data in wallet_unspent_tree.iter() { - let (_output_ref_ivec, owner_amount_ivec) = raw_data?; - let (owner, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - balances - .entry(owner) - .and_modify(|old| *old += amount) - .or_insert(amount); - } - - Ok(balances.into_iter()) -} - -// Kitty related functions -/// Add kitties to the database updating all tables. -pub fn add_fresh_kitty_to_db( - db: &Db, - output_ref: &OutputRef, - owner_pubkey: &H256, - kitty: &KittyData, -) -> anyhow::Result<()> { - let kitty_owned_tree = db.open_tree(FRESH_KITTY)?; - kitty_owned_tree.insert(output_ref.encode(), (owner_pubkey, kitty).encode())?; - - Ok(()) -} - -pub fn add_fresh_tradable_kitty_to_db( - db: &Db, - output_ref: &OutputRef, - owner_pubkey: &H256, - kitty: &TradableKittyData, -) -> anyhow::Result<()> { - let tradable_kitty_owned_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - tradable_kitty_owned_tree.insert(output_ref.encode(), (owner_pubkey, kitty).encode())?; - - Ok(()) -} - -/// Remove an output from the database updating all tables. -fn remove_used_kitty_from_db(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let kitty_owned_tree = db.open_tree(FRESH_KITTY)?; - kitty_owned_tree.remove(output_ref.encode())?; - - Ok(()) -} - -/// Remove an output from the database updating all tables. -fn remove_used_tradable_kitty_from_db(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let tradable_kitty_owned_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - tradable_kitty_owned_tree.remove(output_ref.encode())?; - - Ok(()) -} - -/// Mark an existing output as spent. This does not purge all record of the output from the db. -/// It just moves the record from the unspent table to the spent table -fn mark_as_used_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let fresh_kitty_tree = db.open_tree(FRESH_KITTY)?; - let used_kitty_tree = db.open_tree(USED_KITTY)?; - - let Some(ivec) = fresh_kitty_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; - used_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; - - Ok(()) -} - -/// Mark an existing output as spent. This does not purge all record of the output from the db. -/// It just moves the record from the unspent table to the spent table -fn mark_as_used_tradable_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let fresh_tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let used_tradable_kitty_tree = db.open_tree(USED_TRADABLE_KITTY)?; - - let Some(ivec) = fresh_tradable_kitty_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; - used_tradable_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; - - Ok(()) -} - -/// Mark an output that was previously spent back as unspent. -fn unmark_as_used_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let fresh_kitty_tree = db.open_tree(FRESH_KITTY)?; - let used_kitty_tree = db.open_tree(USED_KITTY)?; - - let Some(ivec) = used_kitty_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; - fresh_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; - - Ok(()) -} - -/// Mark an output that was previously spent back as unspent. -fn unmark_as_used_tradable_kitties(db: &Db, output_ref: &OutputRef) -> anyhow::Result<()> { - let fresh_Tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let used_Tradable_kitty_tree = db.open_tree(USED_TRADABLE_KITTY)?; - - let Some(ivec) = used_Tradable_kitty_tree.remove(output_ref.encode())? else { - return Ok(()); - }; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &ivec[..])?; - fresh_Tradable_kitty_tree.insert(output_ref.encode(), (owner, kitty).encode())?; - - Ok(()) -} - -/// Iterate the entire owned kitty -/// on a per-address basis. -pub(crate) fn get_all_kitties_from_local_db<'a>( - db: &'a Db, -) -> anyhow::Result + 'a> { - get_all_kitties_and_td_kitties_from_local_db(db, FRESH_KITTY) -} - -/// Iterate the entire owned tradable kitty -/// on a per-address basis. -pub(crate) fn get_all_tradable_kitties_from_local_db<'a>( - db: &'a Db, -) -> anyhow::Result + 'a> { - get_all_kitties_and_td_kitties_from_local_db(db, FRESH_TRADABLE_KITTY) -} - -pub(crate) fn get_kitty_from_local_db_based_on_name( - db: &Db, - name: String, -) -> anyhow::Result> { - get_data_from_local_db_based_on_name(db, FRESH_KITTY, name, |kitty: &KittyData| &kitty.name) -} - -pub(crate) fn get_tradable_kitty_from_local_db_based_on_name( - db: &Db, - name: String, -) -> anyhow::Result> { - get_data_from_local_db_based_on_name( - db, - FRESH_TRADABLE_KITTY, - name, - |kitty: &TradableKittyData| &kitty.kitty_basic_data.name, - ) -} - -/// Iterate the entire owned kitty -/// on a per-address basis. -pub(crate) fn get_owned_kitties_from_local_db<'a>( - db: &'a Db, - args: &'a ShowOwnedKittyArgs, -) -> anyhow::Result + 'a> { - get_any_owned_kitties_from_local_db(db, FRESH_KITTY, &args.owner) -} - -/// Iterate the entire owned tradable kitty -/// on a per-address basis. -pub(crate) fn get_owned_tradable_kitties_from_local_db<'a>( - db: &'a Db, - args: &'a ShowOwnedKittyArgs, -) -> anyhow::Result + 'a> { - get_any_owned_kitties_from_local_db(db, FRESH_TRADABLE_KITTY, &args.owner) -} - -pub(crate) fn is_kitty_name_duplicate( - db: &Db, - name: String, - owner_pubkey: &H256, -) -> anyhow::Result> { - is_name_duplicate(db, name, owner_pubkey, FRESH_KITTY, |kitty: &KittyData| { - &kitty.name - }) -} - -pub(crate) fn is_td_kitty_name_duplicate( - db: &Db, - name: String, - owner_pubkey: &H256, -) -> anyhow::Result> { - is_name_duplicate( - db, - name, - owner_pubkey, - FRESH_TRADABLE_KITTY, - |kitty: &TradableKittyData| &kitty.kitty_basic_data.name, - ) -} - -/// Gets the owner and amount associated with an output ref from the unspent table -/// -/// Some if the output ref exists, None if it doesn't -pub(crate) fn get_kitty_fromlocaldb( - db: &Db, - output_ref: &OutputRef, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - let Some(ivec) = wallet_owned_kitty_tree.get(output_ref.encode())? else { - return Ok(None); - }; - - Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) -} - -/// Gets the owner and amount associated with an output ref from the unspent table -/// -/// Some if the output ref exists, None if it doesn't -pub(crate) fn get_tradable_kitty_fromlocaldb( - db: &Db, - output_ref: &OutputRef, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let Some(ivec) = wallet_owned_kitty_tree.get(output_ref.encode())? else { - return Ok(None); - }; - - Ok(Some(<(H256, u128)>::decode(&mut &ivec[..])?)) -} - -////////////////////////// -// Private Functions -////////////////////////// - -fn get_all_kitties_and_td_kitties_from_local_db<'a, T>( - db: &'a Db, - tree_name: &'a str, -) -> anyhow::Result + 'a> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_tradable_kitty_tree = db.open_tree(tree_name)?; - - Ok(wallet_owned_tradable_kitty_tree - .iter() - .filter_map(|raw_data| { - let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - Some((owner, kitty)) - })) -} - -fn get_data_from_local_db_based_on_name( - db: &Db, - tree_name: &str, - name: String, - name_extractor: impl Fn(&T) -> &[u8; 4], -) -> anyhow::Result> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_kitty_tree = db.open_tree(tree_name)?; - - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree - .iter() - .filter_map(|raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - println!("Owner = {:?} Name : {:?} -> output_ref {:?}", owner, name, output_ref.clone()); - - if name_extractor(&kitty) == kitty_name { - println!(" Name : {:?} matched", name); - Some((Some(kitty), output_ref)) - } else { - println!(" Name : {:?} NOTmatched", name); - None - } - }) - .next() - .unwrap_or(( - None, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )); // Use unwrap_or to handle the Option - - println!("output_ref = {:?}", output_ref); - println!("kitty Name {} found_status = {:?}", name,found_kitty); - - Ok(found_kitty.map(|kitty| (kitty, output_ref))) -} - -fn get_any_owned_kitties_from_local_db<'a, T>( - db: &'a Db, - tree_name: &'a str, - owner_pubkey: &'a H256, -) -> anyhow::Result + 'a> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_kitty_tree = db.open_tree(tree_name)?; - - Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref_str = hex::encode(output_ref_ivec.clone()); - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - if owner == *owner_pubkey { - Some((owner, kitty, output_ref)) - } else { - None - } - })) -} - -fn is_name_duplicate( - db: &Db, - name: String, - owner_pubkey: &H256, - tree_name: &str, - name_extractor: impl Fn(&T) -> &[u8; 4], -) -> anyhow::Result> -where - T: Decode + Clone + std::fmt::Debug, -{ - let wallet_owned_kitty_tree = db.open_tree(tree_name)?; - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let found_kitty: (Option) = wallet_owned_kitty_tree - .iter() - .filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, T)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - println!("Name : {:?}", name); - - if *name_extractor(&kitty) == kitty_name[..] && owner == *owner_pubkey { - Some(Some(kitty)) - } else { - None - } - }) - .next() - .unwrap_or(None); // Use unwrap_or to handle the Option - - println!("found_kitty = {:?}", found_kitty); - let is_kitty_found = match found_kitty { - Some(k) => Some(true), - None => Some(false), - }; - Ok(is_kitty_found) -} - -fn string_to_h256(s: &str) -> Result { - let bytes = hex::decode(s)?; - // Assuming H256 is a fixed-size array with 32 bytes - let mut h256 = [0u8; 32]; - h256.copy_from_slice(&bytes); - Ok(h256.into()) -} - -/* -pub(crate) fn is_kitty_name_duplicate1( - db: &Db, - owner_pubkey: &H256, - name: String, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let found_kitty: (Option) = wallet_owned_kitty_tree - .iter() - .filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - println!("Name : {:?}", name); - - if kitty.name == &kitty_name[..] && owner == *owner_pubkey { - Some(Some(kitty)) - } else { - None - } - }) - .next() - .unwrap_or(None); // Use unwrap_or to handle the Option - - println!("found_kitty = {:?}", found_kitty); - let is_kitty_found = match found_kitty { - Some(k) => Some(true), - None => Some(false), - }; - Ok(is_kitty_found) -} - -pub(crate) fn is_tradable_kitty_name_duplicate1( - db: &Db, - owner_pubkey: &H256, - name: String, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let found_kitty: (Option) = wallet_owned_kitty_tree - .iter() - .filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - println!("Name : {:?}", name); - - if kitty.kitty_basic_data.name == &kitty_name[..] && owner == *owner_pubkey { - Some(Some(kitty)) - } else { - None - } - }) - .next() - .unwrap_or(None); // Use unwrap_or to handle the Option - - println!("found_kitty = {:?}", found_kitty); - let is_kitty_found = match found_kitty { - Some(k) => Some(true), - None => Some(false), - }; - Ok(is_kitty_found) -} - -/// Debugging use. Print the entire unspent outputs tree. -pub(crate) fn print_owned_kitties(db: &Db) -> anyhow::Result<()> { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - for x in wallet_unspent_tree.iter() { - let (output_ref_ivec, owner_amount_ivec) = x?; - let output_ref = hex::encode(output_ref_ivec); - let (owner_pubkey, amount) = <(H256, u128)>::decode(&mut &owner_amount_ivec[..])?; - - println!("{output_ref}: owner {owner_pubkey:?}, amount {amount}"); - } - - Ok(()) -} - -pub(crate) fn get_all_kitties_from_local_db1( - db: &Db, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - - Ok(wallet_owned_kitty_tree.iter().filter_map(|raw_data| { - let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - Some((owner, kitty)) - })) -} - -/// Iterate the entire owned kitty -/// on a per-address basis. -pub(crate) fn get_all_tradable_kitties_from_local_db1( - db: &Db, -) -> anyhow::Result> { - let wallet_owned_tradable_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - - Ok(wallet_owned_tradable_kitty_tree.iter().filter_map(|raw_data| { - let (_output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - - Some((owner, kitty)) - })) -} - -pub(crate) fn get_owned_kitties_from_local_db<'a>( - db: &'a Db, - args: &'a ShowOwnedKittyArgs, -) -> anyhow::Result + 'a> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - - Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref_str = hex::encode(output_ref_ivec.clone()); - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - if owner == args.owner { - Some((owner, kitty, output_ref)) - } else { - None - } - })) -} - - -pub(crate) fn get_owned_tradable_kitties_from_local_db<'a>( - db: &'a Db, - args: &'a ShowOwnedKittyArgs, -) -> anyhow::Result + 'a> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - - Ok(wallet_owned_kitty_tree.iter().filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref_str = hex::encode(output_ref_ivec.clone()); - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - if owner == args.owner { - Some((owner, kitty, output_ref)) - } else { - None - } - })) -} - - -pub(crate) fn get_kitty_from_local_db_based_on_name_bk( - db: &Db, - name: String, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_KITTY)?; - - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let (found_kitty, output_ref) = wallet_owned_kitty_tree - .iter() - .filter_map(|raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, KittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - println!("Name : {:?} -> output_ref {:?}", name, output_ref.clone()); - - if kitty.name == &kitty_name[..] { - Some((Some(kitty), output_ref)) - } else { - None - } - }) - .next() - .unwrap_or(( - None, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )); // Use unwrap_or to handle the Option - - println!("output_ref = {:?}", output_ref); - println!("found_kitty = {:?}", found_kitty); - - Ok(found_kitty.map(|kitty| (kitty, output_ref))) -} - - -pub(crate) fn get_tradable_kitty_from_local_db_based_on_name( - db: &Db, - name: String, -) -> anyhow::Result> { - let wallet_owned_kitty_tree = db.open_tree(FRESH_TRADABLE_KITTY)?; - let mut array = [0; 4]; - let kitty_name: &[u8; 4] = { - array.copy_from_slice(name.as_bytes()); - &array - }; - - let (found_kitty, output_ref): (Option, OutputRef) = wallet_owned_kitty_tree - .iter() - .filter_map(move |raw_data| { - let (output_ref_ivec, owner_kitty_ivec) = raw_data.ok()?; - let (owner, kitty) = <(H256, TradableKittyData)>::decode(&mut &owner_kitty_ivec[..]).ok()?; - let output_ref_str = hex::encode(output_ref_ivec.clone()); - let output_ref = OutputRef::decode(&mut &output_ref_ivec[..]).ok()?; - println!("Name : {:?} -> output_ref {:?}", name, output_ref.clone()); - - if kitty.kitty_basic_data.name == &kitty_name[..] { - Some((Some(kitty), output_ref)) - } else { - None - } - }) - .next() - .unwrap_or(( - None, - OutputRef { - tx_hash: H256::zero(), - index: 0, - }, - )); // Use unwrap_or to handle the Option - - println!("output_ref = {:?}", output_ref); - println!("found_kitty = {:?}", found_kitty); - - Ok(found_kitty.map(|kitty| (kitty, output_ref))) -} - -*/ diff --git a/webservice-wallet-with-inbuilt-key-store/src/timestamp.rs b/webservice-wallet-with-inbuilt-key-store/src/timestamp.rs deleted file mode 100644 index d5da075a0..000000000 --- a/webservice-wallet-with-inbuilt-key-store/src/timestamp.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Wallet features related to on-chain timestamps. - -use anyhow::anyhow; -use parity_scale_codec::{Decode, Encode}; -use runtime::{timestamp::Timestamp, OuterVerifier}; -use sled::Db; -use tuxedo_core::types::Output; - -/// The identifier for the current timestamp in the db. -const TIMESTAMP: &str = "timestamp"; - -pub(crate) fn apply_transaction(db: &Db, output: &Output) -> anyhow::Result<()> { - let timestamp = output.payload.extract::()?.time; - let timestamp_tree = db.open_tree(TIMESTAMP)?; - timestamp_tree.insert([0], timestamp.encode())?; - Ok(()) -} - -/// Apply a transaction to the local database, storing the new timestamp. -pub(crate) fn get_timestamp(db: &Db) -> anyhow::Result { - let timestamp_tree = db.open_tree(TIMESTAMP)?; - let timestamp = timestamp_tree - .get([0])? - .ok_or_else(|| anyhow!("Could not find timestamp in database."))?; - u64::decode(&mut ×tamp[..]) - .map_err(|_| anyhow!("Could not decode timestamp from database.")) -}