From b152b393506dc56befc77618c74ec124b9443260 Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Thu, 2 May 2024 17:08:13 +0545 Subject: [PATCH 01/11] ocw --- Cargo.lock | 23 ++++++++++++ pallets/template/Cargo.toml | 13 ++++++- pallets/template/src/lib.rs | 72 ++++++++++++++++++++++++++++++++++++- runtime/src/lib.rs | 3 ++ 4 files changed, 109 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 943315a..12cd2ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4061,6 +4061,24 @@ dependencies = [ "keystream", ] +[[package]] +name = "lite-json" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0e787ffe1153141a0f6f6d759fdf1cc34b1226e088444523812fd412a5cca2" +dependencies = [ + "lite-parser", +] + +[[package]] +name = "lite-parser" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d5f9dc37c52d889a21fd701983d02bb6a84f852c5140a6c80ef4557f7dc29e" +dependencies = [ + "paste", +] + [[package]] name = "lock_api" version = "0.4.11" @@ -4920,11 +4938,16 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "lite-json", + "log", "parity-scale-codec", "scale-info", + "serde", + "serde_json", "sp-core", "sp-io", "sp-runtime", + "sp-std 8.0.0", ] [[package]] diff --git a/pallets/template/Cargo.toml b/pallets/template/Cargo.toml index 424b6d8..eedb92a 100644 --- a/pallets/template/Cargo.toml +++ b/pallets/template/Cargo.toml @@ -16,14 +16,19 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } +lite-json = { version = "0.2.0", default-features = false } +serde = { version = "1.0.197", features = ["derive"], optional = true } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } +log = { version = "0.4.21", default-features = false } +sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } +sp-std = { version = "8.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } [dev-dependencies] sp-core = { version = "21.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } -sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } sp-runtime = { version = "24.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } [features] @@ -34,6 +39,12 @@ std = [ "frame-support/std", "frame-system/std", "scale-info/std", + "log/std", + "sp-io/std", + "serde", + "sp-std/std", + "lite-json/std", + "serde_json/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index 9550d3d..de8d4dc 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -4,7 +4,6 @@ /// Learn more about FRAME and the core library of Substrate FRAME pallets: /// pub use pallet::*; - #[cfg(test)] mod mock; @@ -15,6 +14,8 @@ mod tests; mod benchmarking; pub mod weights; pub use weights::*; +use frame_support::sp_runtime::offchain::{http, Duration}; + #[frame_support::pallet] pub mod pallet { @@ -25,6 +26,15 @@ pub mod pallet { #[pallet::pallet] pub struct Pallet(_); + #[pallet::hooks] + impl Hooks> for Pallet { + /// Offchain worker enety point. + fn offchain_worker(_block_number: BlockNumberFor) { + Self::fetch_properties().unwrap(); + log::info!("Hello world fromoffchain workers!"); + } + } + /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { @@ -32,6 +42,8 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Type representing the weight of this pallet type WeightInfo: WeightInfo; + #[pallet::constant] + type StringLimit: Get; } // The pallet's runtime storage items. @@ -52,6 +64,23 @@ pub mod pallet { SomethingStored { something: u32, who: T::AccountId }, } + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] + #[scale_info(skip_type_params(T))] + pub struct Property { + id: i64, + bedrooms: i32, + bathrooms: i32, + number_of_images: i32, + number_of_floorplans: i32, + number_of_virtual_tours: i32, + summary:i32, + display_address:i32, + country_code:i32, + location:i32, + } + + // Errors inform users that something went wrong. #[pallet::error] pub enum Error { @@ -105,4 +134,45 @@ pub mod pallet { } } } + + impl Pallet { + fn fetch_properties() -> Result<(), http::Error> { + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); + let request = + http::Request::get("https://ipfs.io/ipfs/QmRYy9yPQYHUH2He18MFnnDEyfwST27GwywLB8N2jJ9fo2?filename=p.json"); + + let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; + let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err(http::Error::Unknown) + } + + let body = response.body().collect::>(); + + let body_str = sp_std::str::from_utf8(&body).map_err(|_| { + log::warn!("No UTF8 body"); + http::Error::Unknown + })?; + + let price = match Self::parse_price(body_str) { + Some(price) => Ok(price), + None => { + log::warn!("Unable to extract price from the response: {:?}", body_str); + Err(http::Error::Unknown) + }, + }?; + + log::info!("{:?}", price); + + Ok(()) + } + + fn parse_price(price_str: &str) -> Option { + + let my_struct: Property = serde_json::from_str(&price_str).expect("Failed to deserialize JSON"); + Some(my_struct) + } + } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 7670ebd..a63f564 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -233,6 +233,7 @@ impl pallet_balances::Config for Runtime { parameter_types! { pub FeeMultiplier: Multiplier = Multiplier::one(); + pub const StringLimit: u32 = 5000; } impl pallet_transaction_payment::Config for Runtime { @@ -250,10 +251,12 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = pallet_sudo::weights::SubstrateWeight; } + /// Configure the pallet-template in pallets/template. impl pallet_template::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_template::weights::SubstrateWeight; + type StringLimit = StringLimit; } // Create the runtime by composing the FRAME pallets that were previously configured. From ba0c46f36e83e5a143210014306fd2d721733375 Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Fri, 3 May 2024 19:03:00 +0545 Subject: [PATCH 02/11] update --- pallets/template/Cargo.toml | 8 ++- pallets/template/src/lib.rs | 139 ++++++++++++++++-------------------- 2 files changed, 66 insertions(+), 81 deletions(-) diff --git a/pallets/template/Cargo.toml b/pallets/template/Cargo.toml index eedb92a..7b09c1a 100644 --- a/pallets/template/Cargo.toml +++ b/pallets/template/Cargo.toml @@ -17,15 +17,17 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = "derive", ] } lite-json = { version = "0.2.0", default-features = false } + serde = { version = "1.0.197", features = ["derive"], optional = true } serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } log = { version = "0.4.21", default-features = false } -sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } -sp-std = { version = "8.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } +sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } +sp-std = { version = "8.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } [dev-dependencies] sp-core = { version = "21.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } @@ -41,7 +43,7 @@ std = [ "scale-info/std", "log/std", "sp-io/std", - "serde", + "serde/std", "sp-std/std", "lite-json/std", "serde_json/std", diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index de8d4dc..191f3d7 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -13,121 +13,87 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub mod weights; -pub use weights::*; use frame_support::sp_runtime::offchain::{http, Duration}; - +pub use weights::*; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - #[pallet::pallet] pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet { - /// Offchain worker enety point. fn offchain_worker(_block_number: BlockNumberFor) { - Self::fetch_properties().unwrap(); - log::info!("Hello world fromoffchain workers!"); + let _ = Self::fetch_n_parse().unwrap(); } } - /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { - /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// Type representing the weight of this pallet type WeightInfo: WeightInfo; #[pallet::constant] - type StringLimit: Get; + type StringLimit: Get; } - // The pallet's runtime storage items. - // https://docs.substrate.io/main-docs/build/runtime-storage/ #[pallet::storage] #[pallet::getter(fn something)] - // Learn more about declaring storage items: - // https://docs.substrate.io/main-docs/build/runtime-storage/#declaring-storage-items pub type Something = StorageValue<_, u32>; - // Pallets use events to inform users when important changes are made. - // https://docs.substrate.io/main-docs/build/events-errors/ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Event documentation should end with an array that provides descriptive names for event - /// parameters. [something, who] SomethingStored { something: u32, who: T::AccountId }, } + // #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Encode, Decode, Default, RuntimeDebug, scale_info::TypeInfo))] + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(T))] - pub struct Property { - id: i64, - bedrooms: i32, - bathrooms: i32, - number_of_images: i32, - number_of_floorplans: i32, - number_of_virtual_tours: i32, - summary:i32, - display_address:i32, - country_code:i32, - location:i32, + struct HackerNewsInfo { + by: BoundedVec::StringLimit>, + title: BoundedVec::StringLimit>, + url: BoundedVec::StringLimit>, + descendants: u32, } - - // Errors inform users that something went wrong. + #[pallet::error] pub enum Error { - /// Error names should be descriptive. NoneValue, - /// Errors should have helpful documentation associated with them. StorageOverflow, + HttpFetchingError, + DeserializeToStrError, + DeserializeToObjError, } - // Dispatchable functions allows users to interact with the pallet and invoke state changes. - // These functions materialize as "extrinsics", which are often compared to transactions. - // Dispatchable functions must be annotated with a weight and must return a DispatchResult. #[pallet::call] impl Pallet { - /// An example dispatchable that takes a singles value as a parameter, writes the value to - /// storage and emits an event. This function must be dispatched by a signed extrinsic. #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::do_something())] pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { - // Check that the extrinsic was signed and get the signer. - // This function will return an error if the extrinsic is not signed. - // https://docs.substrate.io/main-docs/build/origins/ let who = ensure_signed(origin)?; // Update storage. >::put(something); - // Emit an event. Self::deposit_event(Event::SomethingStored { something, who }); - // Return a successful DispatchResultWithPostInfo Ok(()) } - /// An example dispatchable that may throw a custom error. #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::cause_error())] pub fn cause_error(origin: OriginFor) -> DispatchResult { let _who = ensure_signed(origin)?; - // Read a value from storage. match >::get() { - // Return an error if the value has not been set. None => return Err(Error::::NoneValue.into()), Some(old) => { - // Increment the value read from storage; will error in the event of overflow. let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; - // Update the value in storage with the incremented result. >::put(new); Ok(()) }, @@ -135,44 +101,61 @@ pub mod pallet { } } - impl Pallet { - fn fetch_properties() -> Result<(), http::Error> { - let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); - let request = - http::Request::get("https://ipfs.io/ipfs/QmRYy9yPQYHUH2He18MFnnDEyfwST27GwywLB8N2jJ9fo2?filename=p.json"); - - let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; - let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; - - if response.code != 200 { - log::warn!("Unexpected status code: {}", response.code); - return Err(http::Error::Unknown) - } + const HTTP_REMOTE_REQUEST: &str = "https://hacker-news.firebaseio.com/v0/item/9129911.json"; - let body = response.body().collect::>(); + const FETCH_TIMEOUT_PERIOD: u64 = 3000; // in milli-seconds + use scale_info::prelude::{string::String, vec::Vec}; - let body_str = sp_std::str::from_utf8(&body).map_err(|_| { - log::warn!("No UTF8 body"); - http::Error::Unknown + impl Pallet { + fn fetch_n_parse() -> Result, Error> { + let resp_bytes = Self::fetch_from_remote().map_err(|e| { + log::error!("fetch_from_remote error: {:?}", e); + >::HttpFetchingError })?; - - let price = match Self::parse_price(body_str) { - Some(price) => Ok(price), - None => { - log::warn!("Unable to extract price from the response: {:?}", body_str); - Err(http::Error::Unknown) - }, - }?; - log::info!("{:?}", price); - Ok(()) + let resp_strr = + String::from_utf8(resp_bytes).map_err(|_| >::DeserializeToStrError)?; + + let resp_str = resp_strr.as_str(); + log::info!("fetch_n_parse: {}", resp_str); + + let info: HackerNewsInfo = + serde_json::from_str(&resp_str).map_err(|_| >::DeserializeToObjError)?; + Ok(info) } - fn parse_price(price_str: &str) -> Option { + fn fetch_from_remote() -> Result, Error> { + let request = http::Request::get(HTTP_REMOTE_REQUEST); + + let timeout = + sp_io::offchain::timestamp().add(Duration::from_millis(FETCH_TIMEOUT_PERIOD)); + + let pending = request + .deadline(timeout) // Setting the timeout time + .send() // Sending the request out by the host + .map_err(|e| { + log::error!("{:?}", e); + >::HttpFetchingError + })?; + + let response = pending + .try_wait(timeout) + .map_err(|e| { + log::error!("{:?}", e); + >::HttpFetchingError + })? + .map_err(|e| { + log::error!("{:?}", e); + >::HttpFetchingError + })?; + + if response.code != 200 { + log::error!("Unexpected http request status code: {}", response.code); + return Err(>::HttpFetchingError); + } - let my_struct: Property = serde_json::from_str(&price_str).expect("Failed to deserialize JSON"); - Some(my_struct) + Ok(response.body().collect::>()) } } } From eeabb1d506518395b254487df0800b5aacf38efb Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Sat, 4 May 2024 11:44:24 +0545 Subject: [PATCH 03/11] update --- pallets/template/Cargo.toml | 2 +- pallets/template/src/lib.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pallets/template/Cargo.toml b/pallets/template/Cargo.toml index 7b09c1a..9e18fc6 100644 --- a/pallets/template/Cargo.toml +++ b/pallets/template/Cargo.toml @@ -43,7 +43,7 @@ std = [ "scale-info/std", "log/std", "sp-io/std", - "serde/std", + "serde", "sp-std/std", "lite-json/std", "serde_json/std", diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index 191f3d7..c7fcc4a 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -49,8 +49,6 @@ pub mod pallet { SomethingStored { something: u32, who: T::AccountId }, } - // #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Encode, Decode, Default, RuntimeDebug, scale_info::TypeInfo))] - #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(T))] @@ -122,6 +120,10 @@ pub mod pallet { let info: HackerNewsInfo = serde_json::from_str(&resp_str).map_err(|_| >::DeserializeToObjError)?; + + let c = + codec::json + Ok(info) } From 47b59f94275f2b4acf656f5108429f76e7b853a8 Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Sat, 4 May 2024 14:22:42 +0545 Subject: [PATCH 04/11] update --- Cargo.lock | 119 +++--- pallets/template/Cargo.toml | 4 +- pallets/template/src/lib.rs | 793 ++++++++++++++++++++++++++++++------ runtime/src/lib.rs | 14 +- 4 files changed, 754 insertions(+), 176 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 12cd2ad..7989355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,47 +136,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -1186,9 +1187,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "comfy-table" @@ -1225,9 +1226,9 @@ checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -1619,15 +1620,15 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c01c06f5f429efdf2bae21eb67c28b3df3cf85b7dd2d8ef09c0838dac5d33e" +checksum = "f1559b6cba622276d6d63706db152618eeb15b89b3e4041446b05876e352e639" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1635,9 +1636,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0047d07f2c89b17dd631c80450d69841a6b5d7fb17278cbc43d7e4cfcf2576f3" +checksum = "332d754c0af53bc87c108fed664d121ecf59207ec4196041f04d6ab9002ad33f" dependencies = [ "data-encoding", "syn 1.0.109", @@ -2082,9 +2083,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fdlimit" @@ -2183,9 +2184,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "libz-sys", @@ -3003,7 +3004,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.14", - "socket2 0.5.6", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -3020,7 +3021,7 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.21.11", + "rustls 0.21.12", "rustls-native-certs", "tokio", "tokio-rustls", @@ -3232,7 +3233,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.6", + "socket2 0.5.7", "widestring", "windows-sys 0.48.0", "winreg", @@ -3255,6 +3256,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -3272,9 +3279,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685a7d121ee3f65ae4fddd72b25a04bb36b6af81bc0828f7d5434c0fe60fa3a2" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -5172,9 +5179,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "311fb059dee1a7b802f036316d790138c613a4e8b180c822e3925a662e9f0c95" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" dependencies = [ "memchr", "thiserror", @@ -5183,9 +5190,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73541b156d32197eecda1a4014d7f868fd2bcb3c550d5386087cfba442bf69c" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" dependencies = [ "pest", "pest_generator", @@ -5193,9 +5200,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c35eeed0a3fab112f75165fdc026b3913f4183133f19b49be773ac9ea966e8bd" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" dependencies = [ "pest", "pest_meta", @@ -5206,9 +5213,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.9" +version = "2.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2adbf29bb9776f28caece835398781ab24435585fe0d4dc1374a61db5accedca" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" dependencies = [ "once_cell", "pest", @@ -5324,9 +5331,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" +checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" dependencies = [ "cfg-if", "concurrent-queue", @@ -6085,9 +6092,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.11" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", @@ -7505,9 +7512,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -7792,7 +7799,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -7831,7 +7838,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" dependencies = [ "proc-macro2", "quote", @@ -7852,7 +7859,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" dependencies = [ "environmental", "parity-scale-codec", @@ -8037,7 +8044,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -8069,7 +8076,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" dependencies = [ "Inflector", "expander", @@ -8161,7 +8168,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0 [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" [[package]] name = "sp-storage" @@ -8179,7 +8186,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8216,7 +8223,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" dependencies = [ "parity-scale-codec", "tracing", @@ -8316,7 +8323,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#921265ca7889b9c9bc615af0eced9c6918c8af9f" +source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" dependencies = [ "impl-trait-for-tuples", "log", @@ -8793,7 +8800,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite 0.2.14", "signal-hook-registry", - "socket2 0.5.6", + "socket2 0.5.7", "tokio-macros", "windows-sys 0.48.0", ] @@ -8826,7 +8833,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.11", + "rustls 0.21.12", "tokio", ] @@ -9254,9 +9261,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "unicode-xid" diff --git a/pallets/template/Cargo.toml b/pallets/template/Cargo.toml index 9e18fc6..d090e1e 100644 --- a/pallets/template/Cargo.toml +++ b/pallets/template/Cargo.toml @@ -28,10 +28,10 @@ frame-system = { version = "4.0.0-dev", default-features = false, git = "https:/ log = { version = "0.4.21", default-features = false } sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } sp-std = { version = "8.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } +sp-core = { version = "21.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } +sp-runtime = { version = "24.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0", default-features = false } [dev-dependencies] -sp-core = { version = "21.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } -sp-runtime = { version = "24.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } [features] default = ["std"] diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index c7fcc4a..746b968 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -1,163 +1,724 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! +//! # Offchain Worker Example Pallet +//! +//! The Offchain Worker Example: A simple pallet demonstrating +//! concepts, APIs and structures common to most offchain workers. +//! +//! Run `cargo doc --package pallet-example-offchain-worker --open` to view this module's +//! documentation. +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +//! +//! **This pallet serves as an example showcasing Substrate off-chain worker and is not meant to +//! be used in production.** +//! +//! ## Overview +//! +//! In this example we are going to build a very simplistic, naive and definitely NOT +//! production-ready oracle for BTC/USD price. +//! Offchain Worker (OCW) will be triggered after every block, fetch the current price +//! and prepare either signed or unsigned transaction to feed the result back on chain. +//! The on-chain logic will simply aggregate the results and store last `64` values to compute +//! the average price. +//! Additional logic in OCW is put in place to prevent spamming the network with both signed +//! and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only +//! one unsigned transaction floating in the network. + #![cfg_attr(not(feature = "std"), no_std)] -/// Edit this file to define custom logic or remove it if it is not needed. -/// Learn more about FRAME and the core library of Substrate FRAME pallets: -/// -pub use pallet::*; -#[cfg(test)] -mod mock; +use codec::{Decode, Encode}; +use frame_support::traits::Get; +use frame_system::{ + self as system, + offchain::{ + AppCrypto, CreateSignedTransaction, SendSignedTransaction, SendUnsignedTransaction, + SignedPayload, Signer, SigningTypes, SubmitTransaction, + }, + pallet_prelude::BlockNumberFor, +}; +use lite_json::json::JsonValue; +use sp_core::crypto::KeyTypeId; +use sp_runtime::{ + offchain::{ + http, + storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, + Duration, + }, + traits::Zero, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + RuntimeDebug, +}; +use sp_std::vec::Vec; #[cfg(test)] mod tests; -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking; -pub mod weights; -use frame_support::sp_runtime::offchain::{http, Duration}; -pub use weights::*; +/// Defines application identifier for crypto keys of this module. +/// +/// Every module that deals with signatures needs to declare its unique identifier for +/// its crypto keys. +/// When offchain worker is signing transactions it's going to request keys of type +/// `KeyTypeId` from the keystore and use the ones it finds to sign the transaction. +/// The keys can be inserted manually via RPC (see `author_insertKey`). +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"btc!"); + +/// Based on the above `KeyTypeId` we need to generate a pallet-specific crypto type wrappers. +/// We can use from supported crypto kinds (`sr25519`, `ed25519` and `ecdsa`) and augment +/// the types with this pallet-specific identifier. +pub mod crypto { + use super::KEY_TYPE; + use sp_core::sr25519::Signature as Sr25519Signature; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::Verify, + MultiSignature, MultiSigner, + }; + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } + + // implemented for mock runtime in test + impl frame_system::offchain::AppCrypto<::Signer, Sr25519Signature> + for TestAuthId + { + type RuntimeAppPublic = Public; + type GenericSignature = sp_core::sr25519::Signature; + type GenericPublic = sp_core::sr25519::Public; + } +} + +pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + + /// This pallet's configuration trait + #[pallet::config] + pub trait Config: CreateSignedTransaction> + frame_system::Config { + /// The identifier type for an offchain worker. + type AuthorityId: AppCrypto; + + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Configuration parameters + + /// A grace period after we send transaction. + /// + /// To avoid sending too many transactions, we only attempt to send one + /// every `GRACE_PERIOD` blocks. We use Local Storage to coordinate + /// sending between distinct runs of this offchain worker. + #[pallet::constant] + type GracePeriod: Get>; + + /// Number of blocks of cooldown after unsigned transaction is included. + /// + /// This ensures that we only accept unsigned transactions once, every `UnsignedInterval` + /// blocks. + #[pallet::constant] + type UnsignedInterval: Get>; + + /// A configuration for base priority of unsigned transactions. + /// + /// This is exposed so that it can be tuned for particular runtime, when + /// multiple pallets send unsigned transactions. + #[pallet::constant] + type UnsignedPriority: Get; + + /// Maximum number of prices. + #[pallet::constant] + type MaxPrices: Get; + } + #[pallet::pallet] pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet { - fn offchain_worker(_block_number: BlockNumberFor) { - let _ = Self::fetch_n_parse().unwrap(); + /// Offchain Worker entry point. + /// + /// By implementing `fn offchain_worker` you declare a new offchain worker. + /// This function will be called when the node is fully synced and a new best block is + /// successfully imported. + /// Note that it's not guaranteed for offchain workers to run on EVERY block, there might + /// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs), + /// so the code should be able to handle that. + /// You can use `Local Storage` API to coordinate runs of the worker. + fn offchain_worker(block_number: BlockNumberFor) { + // Note that having logs compiled to WASM may cause the size of the blob to increase + // significantly. You can use `RuntimeDebug` custom derive to hide details of the types + // in WASM. The `sp-api` crate also provides a feature `disable-logging` to disable + // all logging and thus, remove any logging from the WASM. + log::info!("Hello World from offchain workers!"); + + // Since off-chain workers are just part of the runtime code, they have direct access + // to the storage and other included pallets. + // + // We can easily import `frame_system` and retrieve a block hash of the parent block. + let parent_hash = >::block_hash(block_number - 1u32.into()); + log::debug!("Current block: {:?} (parent hash: {:?})", block_number, parent_hash); + + // It's a good practice to keep `fn offchain_worker()` function minimal, and move most + // of the code to separate `impl` block. + // Here we call a helper function to calculate current average price. + // This function reads storage entries of the current state. + let average: Option = Self::average_price(); + log::debug!("Current price: {:?}", average); + + // For this example we are going to send both signed and unsigned transactions + // depending on the block number. + // Usually it's enough to choose one or the other. + let should_send = Self::choose_transaction_type(block_number); + let res = match should_send { + TransactionType::Signed => Self::fetch_price_and_send_signed(), + TransactionType::UnsignedForAny => + Self::fetch_price_and_send_unsigned_for_any_account(block_number), + TransactionType::UnsignedForAll => + Self::fetch_price_and_send_unsigned_for_all_accounts(block_number), + TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number), + TransactionType::None => Ok(()), + }; + if let Err(e) = res { + log::error!("Error: {}", e); + } } } - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type WeightInfo: WeightInfo; - #[pallet::constant] - type StringLimit: Get; - } + /// A public part of the pallet. + #[pallet::call] + impl Pallet { + /// Submit new price to the list. + /// + /// This method is a public function of the module and can be called from within + /// a transaction. It appends given `price` to current list of prices. + /// In our example the `offchain worker` will create, sign & submit a transaction that + /// calls this function passing the price. + /// + /// The transaction needs to be signed (see `ensure_signed`) check, so that the caller + /// pays a fee to execute it. + /// This makes sure that it's not easy (or rather cheap) to attack the chain by submitting + /// excessive transactions, but note that it doesn't ensure the price oracle is actually + /// working and receives (and provides) meaningful data. + /// This example is not focused on correctness of the oracle itself, but rather its + /// purpose is to showcase offchain worker capabilities. + #[pallet::call_index(0)] + #[pallet::weight({0})] + pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo { + // Retrieve sender of the transaction. + let who = ensure_signed(origin)?; + // Add the price to the on-chain list. + Self::add_price(Some(who), price); + Ok(().into()) + } - #[pallet::storage] - #[pallet::getter(fn something)] - pub type Something = StorageValue<_, u32>; + /// Submit new price to the list via unsigned transaction. + /// + /// Works exactly like the `submit_price` function, but since we allow sending the + /// transaction without a signature, and hence without paying any fees, + /// we need a way to make sure that only some transactions are accepted. + /// This function can be called only once every `T::UnsignedInterval` blocks. + /// Transactions that call that function are de-duplicated on the pool level + /// via `validate_unsigned` implementation and also are rendered invalid if + /// the function has already been called in current "session". + /// + /// It's important to specify `weight` for unsigned calls as well, because even though + /// they don't charge fees, we still don't want a single block to contain unlimited + /// number of such transactions. + /// + /// This example is not focused on correctness of the oracle itself, but rather its + /// purpose is to showcase offchain worker capabilities. + #[pallet::call_index(1)] + #[pallet::weight({0})] + pub fn submit_price_unsigned( + origin: OriginFor, + _block_number: BlockNumberFor, + price: u32, + ) -> DispatchResultWithPostInfo { + // This ensures that the function can only be called via unsigned transaction. + ensure_none(origin)?; + // Add the price to the on-chain list, but mark it as coming from an empty address. + Self::add_price(None, price); + // now increment the block number at which we expect next unsigned transaction. + let current_block = >::block_number(); + >::put(current_block + T::UnsignedInterval::get()); + Ok(().into()) + } + #[pallet::call_index(2)] + #[pallet::weight({0})] + pub fn submit_price_unsigned_with_signed_payload( + origin: OriginFor, + price_payload: PricePayload>, + _signature: T::Signature, + ) -> DispatchResultWithPostInfo { + // This ensures that the function can only be called via unsigned transaction. + ensure_none(origin)?; + // Add the price to the on-chain list, but mark it as coming from an empty address. + Self::add_price(None, price_payload.price); + // now increment the block number at which we expect next unsigned transaction. + let current_block = >::block_number(); + >::put(current_block + T::UnsignedInterval::get()); + Ok(().into()) + } + } + + /// Events for the pallet. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - SomethingStored { something: u32, who: T::AccountId }, + /// Event generated when new price is accepted to contribute to the average. + NewPrice { price: u32, maybe_who: Option }, } - #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] - #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] - #[scale_info(skip_type_params(T))] - struct HackerNewsInfo { - by: BoundedVec::StringLimit>, - title: BoundedVec::StringLimit>, - url: BoundedVec::StringLimit>, - descendants: u32, + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + /// Validate unsigned call to this module. + /// + /// By default unsigned transactions are disallowed, but implementing the validator + /// here we make sure that some particular calls (the ones produced by offchain worker) + /// are being whitelisted and marked as valid. + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + // Firstly let's check that we call the right function. + if let Call::submit_price_unsigned_with_signed_payload { + price_payload: ref payload, + ref signature, + } = call + { + let signature_valid = + SignedPayload::::verify::(payload, signature.clone()); + if !signature_valid { + return InvalidTransaction::BadProof.into() + } + Self::validate_transaction_parameters(&payload.block_number, &payload.price) + } else if let Call::submit_price_unsigned { block_number, price: new_price } = call { + Self::validate_transaction_parameters(block_number, new_price) + } else { + InvalidTransaction::Call.into() + } + } } + /// A vector of recently submitted prices. + /// + /// This is used to calculate average price, should have bounded size. + #[pallet::storage] + pub(super) type Prices = StorageValue<_, BoundedVec, ValueQuery>; - #[pallet::error] - pub enum Error { - NoneValue, - StorageOverflow, - HttpFetchingError, - DeserializeToStrError, - DeserializeToObjError, + /// Defines the block when next unsigned transaction will be accepted. + /// + /// To prevent spam of unsigned (and unpaid!) transactions on the network, + /// we only allow one transaction every `T::UnsignedInterval` blocks. + /// This storage entry defines when new transaction is going to be accepted. + #[pallet::storage] + pub(super) type NextUnsignedAt = StorageValue<_, BlockNumberFor, ValueQuery>; +} + +/// Payload used by this example crate to hold price +/// data required to submit a transaction. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +pub struct PricePayload { + block_number: BlockNumber, + price: u32, + public: Public, +} + +impl SignedPayload for PricePayload> { + fn public(&self) -> T::Public { + self.public.clone() } +} - #[pallet::call] - impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::do_something())] - pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { - let who = ensure_signed(origin)?; +enum TransactionType { + Signed, + UnsignedForAny, + UnsignedForAll, + Raw, + None, +} - // Update storage. - >::put(something); +impl Pallet { + /// Chooses which transaction type to send. + /// + /// This function serves mostly to showcase `StorageValue` helper + /// and local storage usage. + /// + /// Returns a type of transaction that should be produced in current run. + fn choose_transaction_type(block_number: BlockNumberFor) -> TransactionType { + /// A friendlier name for the error that is going to be returned in case we are in the grace + /// period. + const RECENTLY_SENT: () = (); - Self::deposit_event(Event::SomethingStored { something, who }); - Ok(()) + // Start off by creating a reference to Local Storage value. + // Since the local storage is common for all offchain workers, it's a good practice + // to prepend your entry with the module name. + let val = StorageValueRef::persistent(b"example_ocw::last_send"); + // The Local Storage is persisted and shared between runs of the offchain workers, + // and offchain workers may run concurrently. We can use the `mutate` function, to + // write a storage entry in an atomic fashion. Under the hood it uses `compare_and_set` + // low-level method of local storage API, which means that only one worker + // will be able to "acquire a lock" and send a transaction if multiple workers + // happen to be executed concurrently. + let res = + val.mutate(|last_send: Result>, StorageRetrievalError>| { + match last_send { + // If we already have a value in storage and the block number is recent enough + // we avoid sending another transaction at this time. + Ok(Some(block)) if block_number < block + T::GracePeriod::get() => + Err(RECENTLY_SENT), + // In every other case we attempt to acquire the lock and send a transaction. + _ => Ok(block_number), + } + }); + + // The result of `mutate` call will give us a nested `Result` type. + // The first one matches the return of the closure passed to `mutate`, i.e. + // if we return `Err` from the closure, we get an `Err` here. + // In case we return `Ok`, here we will have another (inner) `Result` that indicates + // if the value has been set to the storage correctly - i.e. if it wasn't + // written to in the meantime. + match res { + // The value has been set correctly, which means we can safely send a transaction now. + Ok(block_number) => { + // We will send different transactions based on a random number. + // Note that this logic doesn't really guarantee that the transactions will be sent + // in an alternating fashion (i.e. fairly distributed). Depending on the execution + // order and lock acquisition, we may end up for instance sending two `Signed` + // transactions in a row. If a strict order is desired, it's better to use + // the storage entry for that. (for instance store both block number and a flag + // indicating the type of next transaction to send). + let transaction_type = block_number % 4u32.into(); + if transaction_type == Zero::zero() { + TransactionType::Signed + } else if transaction_type == BlockNumberFor::::from(1u32) { + TransactionType::UnsignedForAny + } else if transaction_type == BlockNumberFor::::from(2u32) { + TransactionType::UnsignedForAll + } else { + TransactionType::Raw + } + }, + // We are in the grace period, we should not send a transaction this time. + Err(MutateStorageError::ValueFunctionFailed(RECENTLY_SENT)) => TransactionType::None, + // We wanted to send a transaction, but failed to write the block number (acquire a + // lock). This indicates that another offchain worker that was running concurrently + // most likely executed the same logic and succeeded at writing to storage. + // Thus we don't really want to send the transaction, knowing that the other run + // already did. + Err(MutateStorageError::ConcurrentModification(_)) => TransactionType::None, } + } - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::cause_error())] - pub fn cause_error(origin: OriginFor) -> DispatchResult { - let _who = ensure_signed(origin)?; - - match >::get() { - None => return Err(Error::::NoneValue.into()), - Some(old) => { - let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; - >::put(new); - Ok(()) + /// A helper function to fetch the price and send signed transaction. + fn fetch_price_and_send_signed() -> Result<(), &'static str> { + let signer = Signer::::all_accounts(); + if !signer.can_sign() { + return Err( + "No local accounts available. Consider adding one via `author_insertKey` RPC.", + ) + } + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // Using `send_signed_transaction` associated type we create and submit a transaction + // representing the call, we've just created. + // Submit signed will return a vector of results for all accounts that were found in the + // local keystore with expected `KEY_TYPE`. + let results = signer.send_signed_transaction(|_account| { + // Received price is wrapped into a call to `submit_price` public function of this + // pallet. This means that the transaction, when executed, will simply call that + // function passing `price` as an argument. + Call::submit_price { price } + }); + + for (acc, res) in &results { + match res { + Ok(()) => log::info!("[{:?}] Submitted price of {} cents", acc.id, price), + Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e), + } + } + + Ok(()) + } + + /// A helper function to fetch the price and send a raw unsigned transaction. + fn fetch_price_and_send_raw_unsigned( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // Received price is wrapped into a call to `submit_price_unsigned` public function of this + // pallet. This means that the transaction, when executed, will simply call that function + // passing `price` as an argument. + let call = Call::submit_price_unsigned { block_number, price }; + + // Now let's create a transaction out of this call and submit it to the pool. + // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) + // + // By default unsigned transactions are disallowed, so we need to whitelist this case + // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefully + // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam + // attack vectors. See validation logic docs for more details. + // + SubmitTransaction::>::submit_unsigned_transaction(call.into()) + .map_err(|()| "Unable to submit unsigned transaction.")?; + + Ok(()) + } + + /// A helper function to fetch the price, sign payload and send an unsigned transaction + fn fetch_price_and_send_unsigned_for_any_account( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // -- Sign using any account + let (_, result) = Signer::::any_account() + .send_unsigned_transaction( + |account| PricePayload { price, block_number, public: account.public.clone() }, + |payload, signature| Call::submit_price_unsigned_with_signed_payload { + price_payload: payload, + signature, }, + ) + .ok_or("No local accounts accounts available.")?; + result.map_err(|()| "Unable to submit transaction")?; + + Ok(()) + } + + /// A helper function to fetch the price, sign payload and send an unsigned transaction + fn fetch_price_and_send_unsigned_for_all_accounts( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // -- Sign using all accounts + let transaction_results = Signer::::all_accounts() + .send_unsigned_transaction( + |account| PricePayload { price, block_number, public: account.public.clone() }, + |payload, signature| Call::submit_price_unsigned_with_signed_payload { + price_payload: payload, + signature, + }, + ); + for (_account_id, result) in transaction_results.into_iter() { + if result.is_err() { + return Err("Unable to submit transaction") } } + + Ok(()) } - const HTTP_REMOTE_REQUEST: &str = "https://hacker-news.firebaseio.com/v0/item/9129911.json"; + /// Fetch current price and return the result in cents. + fn fetch_price() -> Result { + // We want to keep the offchain worker execution time reasonable, so we set a hard-coded + // deadline to 2s to complete the external call. + // You can also wait indefinitely for the response, however you may still get a timeout + // coming from the host machine. + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); + // Initiate an external HTTP GET request. + // This is using high-level wrappers from `sp_runtime`, for the low-level calls that + // you can find in `sp_io`. The API is trying to be similar to `request`, but + // since we are running in a custom WASM execution environment we can't simply + // import the library here. + let request = + http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD"); + // We set the deadline for sending of the request, note that awaiting response can + // have a separate deadline. Next we send the request, before that it's also possible + // to alter request headers or stream body content in case of non-GET requests. + let pending = request.deadline(deadline).send().map_err(|_| http::Error::IoError)?; + + // The request is already being processed by the host, we are free to do anything + // else in the worker (we can send multiple concurrent requests too). + // At some point however we probably want to check the response though, + // so we can block current thread and wait for it to finish. + // Note that since the request is being driven by the host, we don't have to wait + // for the request to have it complete, we will just not read the response. + let response = pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + // Let's check the status code before we proceed to reading the response. + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err(http::Error::Unknown) + } - const FETCH_TIMEOUT_PERIOD: u64 = 3000; // in milli-seconds - use scale_info::prelude::{string::String, vec::Vec}; + // Next we want to fully read the response body and collect it to a vector of bytes. + // Note that the return object allows you to read the body in chunks as well + // with a way to control the deadline. + let body = response.body().collect::>(); - impl Pallet { - fn fetch_n_parse() -> Result, Error> { - let resp_bytes = Self::fetch_from_remote().map_err(|e| { - log::error!("fetch_from_remote error: {:?}", e); - >::HttpFetchingError - })?; - - - let resp_strr = - String::from_utf8(resp_bytes).map_err(|_| >::DeserializeToStrError)?; - - let resp_str = resp_strr.as_str(); - log::info!("fetch_n_parse: {}", resp_str); - - let info: HackerNewsInfo = - serde_json::from_str(&resp_str).map_err(|_| >::DeserializeToObjError)?; - - let c = - codec::json - - Ok(info) - } - - fn fetch_from_remote() -> Result, Error> { - let request = http::Request::get(HTTP_REMOTE_REQUEST); - - let timeout = - sp_io::offchain::timestamp().add(Duration::from_millis(FETCH_TIMEOUT_PERIOD)); - - let pending = request - .deadline(timeout) // Setting the timeout time - .send() // Sending the request out by the host - .map_err(|e| { - log::error!("{:?}", e); - >::HttpFetchingError - })?; - - let response = pending - .try_wait(timeout) - .map_err(|e| { - log::error!("{:?}", e); - >::HttpFetchingError - })? - .map_err(|e| { - log::error!("{:?}", e); - >::HttpFetchingError - })?; - - if response.code != 200 { - log::error!("Unexpected http request status code: {}", response.code); - return Err(>::HttpFetchingError); + // Create a str slice from the body. + let body_str = sp_std::str::from_utf8(&body).map_err(|_| { + log::warn!("No UTF8 body"); + http::Error::Unknown + })?; + + let price = match Self::parse_price(body_str) { + Some(price) => Ok(price), + None => { + log::warn!("Unable to extract price from the response: {:?}", body_str); + Err(http::Error::Unknown) + }, + }?; + + log::warn!("Got price: {} cents", price); + + Ok(price) + } + + /// Parse the price from the given JSON string using `lite-json`. + /// + /// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful. + fn parse_price(price_str: &str) -> Option { + let val = lite_json::parse_json(price_str); + let price = match val.ok()? { + JsonValue::Object(obj) => { + let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?; + match v { + JsonValue::Number(number) => number, + _ => return None, + } + }, + _ => return None, + }; + + let exp = price.fraction_length.saturating_sub(2); + Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32) + } + + /// Add new price to the list. + fn add_price(maybe_who: Option, price: u32) { + log::info!("Adding to the average: {}", price); + >::mutate(|prices| { + if prices.try_push(price).is_err() { + prices[(price % T::MaxPrices::get()) as usize] = price; } + }); + + let average = Self::average_price() + .expect("The average is not empty, because it was just mutated; qed"); + log::info!("Current average price is: {}", average); + // here we are raising the NewPrice event + Self::deposit_event(Event::NewPrice { price, maybe_who }); + } - Ok(response.body().collect::>()) + /// Calculate current average price. + fn average_price() -> Option { + let prices = Prices::::get(); + if prices.is_empty() { + None + } else { + Some(prices.iter().fold(0_u32, |a, b| a.saturating_add(*b)) / prices.len() as u32) } } -} + + fn validate_transaction_parameters( + block_number: &BlockNumberFor, + new_price: &u32, + ) -> TransactionValidity { + // Now let's check if the transaction has any chance to succeed. + let next_unsigned_at = NextUnsignedAt::::get(); + if &next_unsigned_at > block_number { + return InvalidTransaction::Stale.into() + } + // Let's make sure to reject transactions from the future. + let current_block = >::block_number(); + if ¤t_block < block_number { + return InvalidTransaction::Future.into() + } + + // We prioritize transactions that are more far away from current average. + // + // Note this doesn't make much sense when building an actual oracle, but this example + // is here mostly to show off offchain workers capabilities, not about building an + // oracle. + let avg_price = Self::average_price() + .map(|price| if &price > new_price { price - new_price } else { new_price - price }) + .unwrap_or(0); + + ValidTransaction::with_tag_prefix("ExampleOffchainWorker") + // We set base priority to 2**20 and hope it's included before any other + // transactions in the pool. Next we tweak the priority depending on how much + // it differs from the current average. (the more it differs the more priority it + // has). + .priority(T::UnsignedPriority::get().saturating_add(avg_price as _)) + // This transaction does not require anything else to go before into the pool. + // In theory we could require `previous_unsigned_at` transaction to go first, + // but it's not necessary in our case. + //.and_requires() + // We set the `provides` tag to be the same as `next_unsigned_at`. This makes + // sure only one transaction produced after `next_unsigned_at` will ever + // get to the transaction pool and will end up in the block. + // We can still have multiple transactions compete for the same "spot", + // and the one with higher priority will replace other one in the pool. + .and_provides(next_unsigned_at) + // The transaction is only valid for next 5 blocks. After that it's + // going to be revalidated by the pool. + .longevity(5) + // It's fine to propagate that transaction to other peers, which means it can be + // created even by nodes that don't produce blocks. + // Note that sometimes it's better to keep it for yourself (if you are the block + // producer), since for instance in some schemes others may copy your solution and + // claim a reward. + .propagate(true) + .build() + } +} \ No newline at end of file diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a63f564..8fe931c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -252,11 +252,21 @@ impl pallet_sudo::Config for Runtime { } +parameter_types! { + pub const UnsignedPriority: u64 = 1 << 20; +} + /// Configure the pallet-template in pallets/template. impl pallet_template::Config for Runtime { + type AuthorityId = pallet_template::crypto::TestAuthId; + type GracePeriod = ConstU32<5>; + type UnsignedInterval = ConstU32<2>; + type UnsignedPriority = UnsignedPriority; + type MaxPrices = ConstU32<64>; type RuntimeEvent = RuntimeEvent; - type WeightInfo = pallet_template::weights::SubstrateWeight; - type StringLimit = StringLimit; + // type WeightInfo = pallet_template::weights::SubstrateWeight; + // type StringLimit = StringLimit; + } // Create the runtime by composing the FRAME pallets that were previously configured. From 67aa97a779b1a970e07971ecf6b558cb835d5190 Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Sat, 4 May 2024 16:45:02 +0545 Subject: [PATCH 05/11] upate --- Cargo.lock | 176 ++++++++++++++++++++++++--------------------- runtime/Cargo.toml | 2 + runtime/src/lib.rs | 73 +++++++++++++++++-- 3 files changed, 164 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7989355..91d0ea4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -586,7 +586,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix 0.38.33", + "rustix 0.38.34", "slab", "tracing", "windows-sys 0.52.0", @@ -638,9 +638,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" @@ -1003,9 +1003,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" dependencies = [ "jobserver", "libc", @@ -2122,9 +2122,9 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c007b1ae3abe1cb6f85a16305acd418b7ca6343b953633fee2b76d8f108b830f" +checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" [[package]] name = "file-per-thread-logger" @@ -2160,7 +2160,7 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "scale-info", ] @@ -2831,9 +2831,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash 0.8.11", "allocator-api2", @@ -2845,7 +2845,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3167,7 +3167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -3348,7 +3348,7 @@ dependencies = [ "globset", "hyper", "jsonrpsee-types", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "rustc-hash", "serde", @@ -3483,7 +3483,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7a85fe66f9ff9cd74e169fdd2c94c6e1e74c412c99a73b4df3200b5d3760b2" dependencies = [ "kvdb", - "parking_lot 0.12.1", + "parking_lot 0.12.2", ] [[package]] @@ -3494,7 +3494,7 @@ checksum = "b644c70b92285f66bfc2032922a79000ea30af7bc2ab31902992a5dcb9b434f6" dependencies = [ "kvdb", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "regex", "rocksdb", "smallvec", @@ -3514,9 +3514,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libloading" @@ -3608,7 +3608,7 @@ dependencies = [ "multihash", "multistream-select", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project", "quick-protobuf", "rand", @@ -3628,7 +3628,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "smallvec", "trust-dns-resolver", ] @@ -3790,7 +3790,7 @@ dependencies = [ "libp2p-identity", "libp2p-tls", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "quinn-proto", "rand", "rustls 0.20.9", @@ -3906,7 +3906,7 @@ dependencies = [ "futures-rustls", "libp2p-core", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "quicksink", "rw-stream-sink", "soketto", @@ -4088,9 +4088,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -4255,7 +4255,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2cffa4ad52c6f791f4f8b15f0c05f9824b2ced1160e88cc393d64fff9a8ac64" dependencies = [ - "rustix 0.38.33", + "rustix 0.38.34", ] [[package]] @@ -4339,7 +4339,7 @@ dependencies = [ "hashlink", "lioness", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "rand_chacha", "rand_distr", @@ -4624,6 +4624,7 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", + "log", "pallet-aura", "pallet-balances", "pallet-grandpa", @@ -4741,9 +4742,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -5035,7 +5036,7 @@ dependencies = [ "log", "lz4", "memmap2", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "siphasher", "snap", @@ -5100,12 +5101,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -5124,15 +5125,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -5339,7 +5340,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite 0.2.14", - "rustix 0.38.33", + "rustix 0.38.34", "tracing", "windows-sys 0.52.0", ] @@ -5540,7 +5541,7 @@ dependencies = [ "fnv", "lazy_static", "memchr", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "thiserror", ] @@ -5552,7 +5553,7 @@ checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" dependencies = [ "dtoa", "itoa", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "prometheus-client-derive-encode", ] @@ -5816,6 +5817,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -6067,9 +6077,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.33" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -6308,7 +6318,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-executor", "sc-transaction-pool-api", "sc-utils", @@ -6339,7 +6349,7 @@ dependencies = [ "log", "parity-db", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-state-db", "schnellru", @@ -6363,7 +6373,7 @@ dependencies = [ "libp2p-identity", "log", "mockall", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-utils", "serde", @@ -6421,7 +6431,7 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "sc-block-builder", "sc-chain-spec", @@ -6477,7 +6487,7 @@ version = "0.10.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0#481165d92297d7dfd5eaf9c7f442441761fc0a12" dependencies = [ "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-executor-common", "sc-executor-wasmtime", "schnellru", @@ -6514,7 +6524,7 @@ dependencies = [ "cfg-if", "libc", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rustix 0.36.17", "sc-allocator", "sc-executor-common", @@ -6546,7 +6556,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0#481165d92297d7dfd5eaf9c7f442441761fc0a12" dependencies = [ "array-bytes 6.2.2", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "serde_json", "sp-application-crypto", "sp-core", @@ -6570,7 +6580,7 @@ dependencies = [ "mixnet", "multiaddr", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-network", "sc-transaction-pool-api", @@ -6603,7 +6613,7 @@ dependencies = [ "log", "mockall", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "partial_sort", "pin-project", "rand", @@ -6775,7 +6785,7 @@ dependencies = [ "num_cpus", "once_cell", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "sc-client-api", "sc-network", @@ -6810,7 +6820,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -6880,7 +6890,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-chain-spec", "sc-client-api", "sc-transaction-pool-api", @@ -6910,7 +6920,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project", "rand", "sc-chain-spec", @@ -6967,7 +6977,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0 dependencies = [ "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sp-core", ] @@ -6978,7 +6988,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0 dependencies = [ "log", "parity-db", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-keystore", "sp-api", @@ -7019,7 +7029,7 @@ dependencies = [ "futures", "libp2p", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project", "rand", "sc-utils", @@ -7041,7 +7051,7 @@ dependencies = [ "libc", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "regex", "rustc-hash", "sc-client-api", @@ -7081,7 +7091,7 @@ dependencies = [ "linked-hash-map", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sc-client-api", "sc-transaction-pool-api", "sc-utils", @@ -7122,7 +7132,7 @@ dependencies = [ "futures-timer", "lazy_static", "log", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "prometheus", "sp-arithmetic", ] @@ -7304,9 +7314,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] @@ -7322,9 +7332,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -7636,7 +7646,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "schnellru", "sp-api", "sp-consensus", @@ -7750,7 +7760,7 @@ dependencies = [ "log", "merlin", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "paste", "primitive-types", "rand", @@ -7822,7 +7832,7 @@ version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0#481165d92297d7dfd5eaf9c7f442441761fc0a12" dependencies = [ "kvdb", - "parking_lot 0.12.1", + "parking_lot 0.12.2", ] [[package]] @@ -7931,7 +7941,7 @@ version = "0.27.0" source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0#481165d92297d7dfd5eaf9c7f442441761fc0a12" dependencies = [ "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "sp-core", "sp-externalities 0.19.0", "thiserror", @@ -8123,7 +8133,7 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "smallvec", "sp-core", @@ -8266,7 +8276,7 @@ dependencies = [ "memory-db", "nohash-hasher", "parity-scale-codec", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "scale-info", "schnellru", @@ -8647,7 +8657,7 @@ checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "rustix 0.38.33", + "rustix 0.38.34", "windows-sys 0.52.0", ] @@ -8666,7 +8676,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.33", + "rustix 0.38.34", "windows-sys 0.48.0", ] @@ -8797,7 +8807,7 @@ dependencies = [ "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "pin-project-lite 0.2.14", "signal-hook-registry", "socket2 0.5.7", @@ -8926,7 +8936,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.6", + "winnow 0.6.7", ] [[package]] @@ -9145,7 +9155,7 @@ dependencies = [ "ipconfig", "lazy_static", "lru-cache", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "resolv-conf", "smallvec", "thiserror", @@ -9780,14 +9790,14 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.33", + "rustix 0.38.34", ] [[package]] name = "wide" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a1851a719f11d1d2fea40e15c72f6c00de8c142d7ac47c1441cc7e4d0d5bc6" +checksum = "0f0e39d2c603fdc0504b12b458cf1f34e0b937ed2f4f2dc20796e3e86f34e11f" dependencies = [ "bytemuck", "safe_arch", @@ -9817,11 +9827,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -10074,9 +10084,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" +checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" dependencies = [ "memchr", ] @@ -10150,7 +10160,7 @@ dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.12.1", + "parking_lot 0.12.2", "rand", "static_assertions", ] diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 83a7c12..032d08c 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -13,6 +13,7 @@ repository = "https://github.com/substrate-developer-hub/substrate-node-template targets = ["x86_64-unknown-linux-gnu"] [dependencies] +log = { version = '0.4.14', default-features = false } codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } @@ -96,6 +97,7 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", + "log/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8fe931c..3384652 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -5,11 +5,12 @@ // Make the WASM binary available. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); - +// use crate::pallet_template::Call; +// use frame_system::Call; use pallet_grandpa::AuthorityId as GrandpaId; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata, Encode}; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, traits::{BlakeTwo256, Block as BlockT, IdentifyAccount, NumberFor, One, Verify}, @@ -20,6 +21,8 @@ use sp_std::prelude::*; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; +use sp_runtime::SaturatedConversion; +// use codec::Encode; use frame_support::genesis_builder_helper::{build_config, create_default_config}; // A few exports that help ease life for downstream crates. @@ -251,6 +254,12 @@ impl pallet_sudo::Config for Runtime { type WeightInfo = pallet_sudo::weights::SubstrateWeight; } +// impl pallet_ocw::Config for Runtime { +// type AuthorityId = pallet_template::crypto::TestAuthId; +// type Call = Call; +// type Event = Event; +// } + parameter_types! { pub const UnsignedPriority: u64 = 1 << 20; @@ -264,11 +273,65 @@ impl pallet_template::Config for Runtime { type UnsignedPriority = UnsignedPriority; type MaxPrices = ConstU32<64>; type RuntimeEvent = RuntimeEvent; - // type WeightInfo = pallet_template::weights::SubstrateWeight; - // type StringLimit = StringLimit; } + +impl frame_system::offchain::CreateSignedTransaction for Runtime +where + RuntimeCall: From, +{ + fn create_transaction>( + call: RuntimeCall, + public: ::Signer, + account: AccountId, + nonce: Nonce, + ) -> Option<(RuntimeCall, ::SignaturePayload)> { + let tip = 0; + // take the biggest period possible. + let period = + BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64; + let current_block = System::block_number() + .saturated_into::() + // The `System::block_number` is initialized with `n+1`, + // so the actual block number is `n`. + .saturating_sub(1); + + let extra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::mortal(period, current_block)), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + ); + let raw_payload = SignedPayload::new(call, extra) + .map_err(|e| { + log::warn!("Unable to create signed payload: {:?}", e); + }) + .ok()?; + let signature: MultiSignature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; + let address = account; + let (call, extra, _) = raw_payload.deconstruct(); + Some((call, (sp_runtime::MultiAddress::Id(address), signature.into(), extra))) + } +} + +impl frame_system::offchain::SigningTypes for Runtime { + type Public = ::Signer; + type Signature = Signature; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = UncheckedExtrinsic; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub struct Runtime { @@ -340,6 +403,8 @@ mod benches { } impl_runtime_apis! { + + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION From a54a6906b590b45516084f7d8b0227180c1d8d73 Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Sat, 4 May 2024 17:52:22 +0545 Subject: [PATCH 06/11] update --- pallets/template/src/lib.rs | 410 ++++++++++++++++++++++-------------- runtime/src/lib.rs | 1 + 2 files changed, 257 insertions(+), 154 deletions(-) diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index 746b968..f49af6b 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -117,7 +117,7 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::pallet_prelude::{OptionQuery, *}; use frame_system::pallet_prelude::*; /// This pallet's configuration trait @@ -156,6 +156,19 @@ pub mod pallet { /// Maximum number of prices. #[pallet::constant] type MaxPrices: Get; + + #[pallet::constant] + type StringLimit: Get; + } + + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] + #[derive(Encode, Decode, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] + #[scale_info(skip_type_params(T))] + pub struct PropertyInfoData { + pub id: u32, + pub bedrooms: u32, + pub bathrooms: u32, + } #[pallet::pallet] @@ -197,18 +210,18 @@ pub mod pallet { // depending on the block number. // Usually it's enough to choose one or the other. let should_send = Self::choose_transaction_type(block_number); - let res = match should_send { - TransactionType::Signed => Self::fetch_price_and_send_signed(), - TransactionType::UnsignedForAny => - Self::fetch_price_and_send_unsigned_for_any_account(block_number), - TransactionType::UnsignedForAll => - Self::fetch_price_and_send_unsigned_for_all_accounts(block_number), - TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number), - TransactionType::None => Ok(()), - }; - if let Err(e) = res { - log::error!("Error: {}", e); - } + // let res = match should_send { + // // TransactionType::Signed => Self::fetch_price_and_send_signed(), + // TransactionType::UnsignedForAny => + // Self::fetch_price_and_send_unsigned_for_any_account(block_number), + // TransactionType::UnsignedForAll => + // Self::fetch_price_and_send_unsigned_for_all_accounts(block_number), + // TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number), + // TransactionType::None => Ok(()), + // }; + // if let Err(e) = res { + // log::error!("Error: {}", e); + // } } } @@ -229,15 +242,15 @@ pub mod pallet { /// working and receives (and provides) meaningful data. /// This example is not focused on correctness of the oracle itself, but rather its /// purpose is to showcase offchain worker capabilities. - #[pallet::call_index(0)] - #[pallet::weight({0})] - pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo { - // Retrieve sender of the transaction. - let who = ensure_signed(origin)?; - // Add the price to the on-chain list. - Self::add_price(Some(who), price); - Ok(().into()) - } + // #[pallet::call_index(0)] + // #[pallet::weight({0})] + // pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo { + // // Retrieve sender of the transaction. + // let who = ensure_signed(origin)?; + // // Add the price to the on-chain list. + // Self::add_price(Some(who), price); + // Ok(().into()) + // } /// Submit new price to the list via unsigned transaction. /// @@ -341,6 +354,11 @@ pub mod pallet { /// This storage entry defines when new transaction is going to be accepted. #[pallet::storage] pub(super) type NextUnsignedAt = StorageValue<_, BlockNumberFor, ValueQuery>; + + // A List of test properties + #[pallet::storage] + #[pallet::getter(fn testproperties)] + pub type TestProperties = StorageValue<_, PropertyInfoData, OptionQuery>; } /// Payload used by this example crate to hold price @@ -439,137 +457,138 @@ impl Pallet { } /// A helper function to fetch the price and send signed transaction. - fn fetch_price_and_send_signed() -> Result<(), &'static str> { - let signer = Signer::::all_accounts(); - if !signer.can_sign() { - return Err( - "No local accounts available. Consider adding one via `author_insertKey` RPC.", - ) - } - // Make an external HTTP request to fetch the current price. - // Note this call will block until response is received. - let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - - // Using `send_signed_transaction` associated type we create and submit a transaction - // representing the call, we've just created. - // Submit signed will return a vector of results for all accounts that were found in the - // local keystore with expected `KEY_TYPE`. - let results = signer.send_signed_transaction(|_account| { - // Received price is wrapped into a call to `submit_price` public function of this - // pallet. This means that the transaction, when executed, will simply call that - // function passing `price` as an argument. - Call::submit_price { price } - }); - - for (acc, res) in &results { - match res { - Ok(()) => log::info!("[{:?}] Submitted price of {} cents", acc.id, price), - Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e), - } - } - - Ok(()) - } + // fn fetch_price_and_send_signed() -> Result<(), &'static str> { + // let signer = Signer::::all_accounts(); + // if !signer.can_sign() { + // return Err( + // "No local accounts available. Consider adding one via `author_insertKey` RPC.", + // ) + // } + // // Make an external HTTP request to fetch the current price. + // // Note this call will block until response is received. + // let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // // Using `send_signed_transaction` associated type we create and submit a transaction + // // representing the call, we've just created. + // // Submit signed will return a vector of results for all accounts that were found in the + // // local keystore with expected `KEY_TYPE`. + // let results = signer.send_signed_transaction(|_account| { + // // Received price is wrapped into a call to `submit_price` public function of this + // // pallet. This means that the transaction, when executed, will simply call that + // // function passing `price` as an argument. + // // Call::submit_price { price } + // }); + + // for (acc, res) in &results { + // match res { + // Ok(()) => log::info!("[{:?}] Submitted price of {} cents", acc.id, price), + // Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e), + // } + // } + + // Ok(()) + // } /// A helper function to fetch the price and send a raw unsigned transaction. - fn fetch_price_and_send_raw_unsigned( - block_number: BlockNumberFor, - ) -> Result<(), &'static str> { - // Make sure we don't fetch the price if unsigned transaction is going to be rejected - // anyway. - let next_unsigned_at = NextUnsignedAt::::get(); - if next_unsigned_at > block_number { - return Err("Too early to send unsigned transaction") - } - - // Make an external HTTP request to fetch the current price. - // Note this call will block until response is received. - let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - - // Received price is wrapped into a call to `submit_price_unsigned` public function of this - // pallet. This means that the transaction, when executed, will simply call that function - // passing `price` as an argument. - let call = Call::submit_price_unsigned { block_number, price }; - - // Now let's create a transaction out of this call and submit it to the pool. - // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) - // - // By default unsigned transactions are disallowed, so we need to whitelist this case - // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefully - // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam - // attack vectors. See validation logic docs for more details. - // - SubmitTransaction::>::submit_unsigned_transaction(call.into()) - .map_err(|()| "Unable to submit unsigned transaction.")?; - - Ok(()) - } + // fn fetch_price_and_send_raw_unsigned( + // block_number: BlockNumberFor, + // ) -> Result<(), &'static str> { + // // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // // anyway. + // let next_unsigned_at = NextUnsignedAt::::get(); + // if next_unsigned_at > block_number { + // return Err("Too early to send unsigned transaction") + // } + + // // Make an external HTTP request to fetch the current price. + // // Note this call will block until response is received. + // let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + // TestProperties::::put(price); + + // // Received price is wrapped into a call to `submit_price_unsigned` public function of this + // // pallet. This means that the transaction, when executed, will simply call that function + // // passing `price` as an argument. + // // let call = Call::submit_price_unsigned { block_number, price }; + + // // Now let's create a transaction out of this call and submit it to the pool. + // // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) + // // + // // By default unsigned transactions are disallowed, so we need to whitelist this case + // // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefully + // // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam + // // attack vectors. See validation logic docs for more details. + // // + // SubmitTransaction::>::submit_unsigned_transaction(call.into()) + // .map_err(|()| "Unable to submit unsigned transaction.")?; + + // Ok(()) + // } /// A helper function to fetch the price, sign payload and send an unsigned transaction - fn fetch_price_and_send_unsigned_for_any_account( - block_number: BlockNumberFor, - ) -> Result<(), &'static str> { - // Make sure we don't fetch the price if unsigned transaction is going to be rejected - // anyway. - let next_unsigned_at = NextUnsignedAt::::get(); - if next_unsigned_at > block_number { - return Err("Too early to send unsigned transaction") - } - - // Make an external HTTP request to fetch the current price. - // Note this call will block until response is received. - let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - - // -- Sign using any account - let (_, result) = Signer::::any_account() - .send_unsigned_transaction( - |account| PricePayload { price, block_number, public: account.public.clone() }, - |payload, signature| Call::submit_price_unsigned_with_signed_payload { - price_payload: payload, - signature, - }, - ) - .ok_or("No local accounts accounts available.")?; - result.map_err(|()| "Unable to submit transaction")?; - - Ok(()) - } + // fn fetch_price_and_send_unsigned_for_any_account( + // block_number: BlockNumberFor, + // ) -> Result<(), &'static str> { + // // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // // anyway. + // let next_unsigned_at = NextUnsignedAt::::get(); + // if next_unsigned_at > block_number { + // return Err("Too early to send unsigned transaction") + // } + + // // Make an external HTTP request to fetch the current price. + // // Note this call will block until response is received. + // let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // // -- Sign using any account + // let (_, result) = Signer::::any_account() + // .send_unsigned_transaction( + // |account| PricePayload { price, block_number, public: account.public.clone() }, + // |payload, signature| Call::submit_price_unsigned_with_signed_payload { + // price_payload: payload, + // signature, + // }, + // ) + // .ok_or("No local accounts accounts available.")?; + // result.map_err(|()| "Unable to submit transaction")?; + + // Ok(()) + // } /// A helper function to fetch the price, sign payload and send an unsigned transaction - fn fetch_price_and_send_unsigned_for_all_accounts( - block_number: BlockNumberFor, - ) -> Result<(), &'static str> { - // Make sure we don't fetch the price if unsigned transaction is going to be rejected - // anyway. - let next_unsigned_at = NextUnsignedAt::::get(); - if next_unsigned_at > block_number { - return Err("Too early to send unsigned transaction") - } - - // Make an external HTTP request to fetch the current price. - // Note this call will block until response is received. - let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - - // -- Sign using all accounts - let transaction_results = Signer::::all_accounts() - .send_unsigned_transaction( - |account| PricePayload { price, block_number, public: account.public.clone() }, - |payload, signature| Call::submit_price_unsigned_with_signed_payload { - price_payload: payload, - signature, - }, - ); - for (_account_id, result) in transaction_results.into_iter() { - if result.is_err() { - return Err("Unable to submit transaction") - } - } - - Ok(()) - } + // fn fetch_price_and_send_unsigned_for_all_accounts( + // block_number: BlockNumberFor, + // ) -> Result<(), &'static str> { + // // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // // anyway. + // let next_unsigned_at = NextUnsignedAt::::get(); + // if next_unsigned_at > block_number { + // return Err("Too early to send unsigned transaction") + // } + + // // Make an external HTTP request to fetch the current price. + // // Note this call will block until response is received. + // let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // // -- Sign using all accounts + // let transaction_results = Signer::::all_accounts() + // .send_unsigned_transaction( + // |account| PricePayload { price, block_number, public: account.public.clone() }, + // |payload, signature| Call::submit_price_unsigned_with_signed_payload { + // price_payload: payload, + // signature, + // }, + // ); + // for (_account_id, result) in transaction_results.into_iter() { + // if result.is_err() { + // return Err("Unable to submit transaction") + // } + // } + + // Ok(()) + // } /// Fetch current price and return the result in cents. - fn fetch_price() -> Result { + fn fetch_price() -> Result { // We want to keep the offchain worker execution time reasonable, so we set a hard-coded // deadline to 2s to complete the external call. // You can also wait indefinitely for the response, however you may still get a timeout @@ -580,8 +599,9 @@ impl Pallet { // you can find in `sp_io`. The API is trying to be similar to `request`, but // since we are running in a custom WASM execution environment we can't simply // import the library here. - let request = - http::Request::get("https://min-api.cryptocompare.com/data/price?fsym=BTC&tsyms=USD"); + let request = http::Request::get( + "https://ipfs.io/ipfs/QmRYy9yPQYHUH2He18MFnnDEyfwST27GwywLB8N2jJ9fo2?filename=p.json", + ); // We set the deadline for sending of the request, note that awaiting response can // have a separate deadline. Next we send the request, before that it's also possible // to alter request headers or stream body content in case of non-GET requests. @@ -619,7 +639,7 @@ impl Pallet { }, }?; - log::warn!("Got price: {} cents", price); + // log::warn!("Got price: {} cents", price); Ok(price) } @@ -627,11 +647,11 @@ impl Pallet { /// Parse the price from the given JSON string using `lite-json`. /// /// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful. - fn parse_price(price_str: &str) -> Option { + fn parse_price(price_str: &str) -> Option { let val = lite_json::parse_json(price_str); - let price = match val.ok()? { + let id = match val.ok()? { JsonValue::Object(obj) => { - let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("USD".chars()))?; + let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("id".chars()))?; match v { JsonValue::Number(number) => number, _ => return None, @@ -640,8 +660,90 @@ impl Pallet { _ => return None, }; - let exp = price.fraction_length.saturating_sub(2); - Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32) + // let propertyType = match val.ok()? { + // JsonValue::Object(obj) => { + // let (_, v) = obj.into_iter().find(|(k, _)| + // k.iter().copied().eq("propertyType".chars()))?; match v { + // JsonValue::String(s) => s.into_bytes().collect::>(), + // _ => return None, + // } + // }, + // _ => return None, + // }; + + let val = lite_json::parse_json(price_str); + let bedrooms = match val.ok()? { + JsonValue::Object(obj) => { + let (_, v) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("bedrooms".chars()))?; + match v { + JsonValue::Number(number) => number, + _ => return None, + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let bathrooms = match val.ok()? { + JsonValue::Object(obj) => { + let (_, v) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("bathrooms".chars()))?; + match v { + JsonValue::Number(number) => number, + _ => return None, + } + }, + _ => return None, + }; + + // let city = match val.ok()? { + // JsonValue::Object(obj) => { + // let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("city".chars()))?; + // match v { + // JsonValue::String(s) => s.as_bytes().to_vec(), + // _ => return None, + // } + // }, + // _ => return None, + // }; + + // let postCode = match val.ok()? { + // JsonValue::Object(obj) => { + // let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("postCode".chars()))?; + // match v { + // JsonValue::String(s) => s.as_bytes().to_vec(), + // _ => return None, + // } + // }, + // _ => return None, + // }; + + // let keyFeatures = match val.ok()? { + // JsonValue::Object(obj) => { + // let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("keyFeatures".chars()))?; + // match v { + // JsonValue::String(s) => s.as_bytes().to_vec(), + // _ => return None, + // } + // }, + // _ => return None, + // }; + + let id = id.fraction_length.saturating_sub(2); + let bedrooms = bedrooms.fraction_length.saturating_sub(2); + let bathrooms = bathrooms.fraction_length.saturating_sub(2); + + let property = PropertyInfoData { + id, + bedrooms, + bathrooms, + + }; + + Some(property) + + // Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32) } /// Add new price to the list. @@ -721,4 +823,4 @@ impl Pallet { .propagate(true) .build() } -} \ No newline at end of file +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3384652..e7e92fe 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -273,6 +273,7 @@ impl pallet_template::Config for Runtime { type UnsignedPriority = UnsignedPriority; type MaxPrices = ConstU32<64>; type RuntimeEvent = RuntimeEvent; + type StringLimit = StringLimit; } From a930d1e7c39ffa07b84421b1139f8b4b89714cba Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Sat, 4 May 2024 18:41:58 +0545 Subject: [PATCH 07/11] updateing --- pallets/template/src/lib.rs | 321 ++++++++++++++++++------------------ 1 file changed, 160 insertions(+), 161 deletions(-) diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index f49af6b..56eb0df 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -162,13 +162,12 @@ pub mod pallet { } #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] - #[derive(Encode, Decode, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] + #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct PropertyInfoData { pub id: u32, pub bedrooms: u32, pub bathrooms: u32, - } #[pallet::pallet] @@ -192,6 +191,9 @@ pub mod pallet { // all logging and thus, remove any logging from the WASM. log::info!("Hello World from offchain workers!"); + let price = Self::fetch_price().map_err(|_| "Failed to fetch price"); + TestProperties::::put(price.unwrap()); + // Since off-chain workers are just part of the runtime code, they have direct access // to the storage and other included pallets. // @@ -242,15 +244,17 @@ pub mod pallet { /// working and receives (and provides) meaningful data. /// This example is not focused on correctness of the oracle itself, but rather its /// purpose is to showcase offchain worker capabilities. - // #[pallet::call_index(0)] - // #[pallet::weight({0})] - // pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo { - // // Retrieve sender of the transaction. - // let who = ensure_signed(origin)?; - // // Add the price to the on-chain list. - // Self::add_price(Some(who), price); - // Ok(().into()) - // } + #[pallet::call_index(0)] + #[pallet::weight({0})] + pub fn submit_price(origin: OriginFor, price: PropertyInfoData) -> DispatchResultWithPostInfo { + // Retrieve sender of the transaction. + let who = ensure_signed(origin)?; + // Add the price to the on-chain list. + // Self::add_price(Some(who), price); + TestProperties::::put(price.clone()); + Self::deposit_event(Event::NewPrice { price: price.clone(), who }); + Ok(().into()) + } /// Submit new price to the list via unsigned transaction. /// @@ -273,12 +277,12 @@ pub mod pallet { pub fn submit_price_unsigned( origin: OriginFor, _block_number: BlockNumberFor, - price: u32, + price: PropertyInfoData, ) -> DispatchResultWithPostInfo { // This ensures that the function can only be called via unsigned transaction. ensure_none(origin)?; // Add the price to the on-chain list, but mark it as coming from an empty address. - Self::add_price(None, price); + // Self::add_price(None, price); // now increment the block number at which we expect next unsigned transaction. let current_block = >::block_number(); >::put(current_block + T::UnsignedInterval::get()); @@ -295,7 +299,7 @@ pub mod pallet { // This ensures that the function can only be called via unsigned transaction. ensure_none(origin)?; // Add the price to the on-chain list, but mark it as coming from an empty address. - Self::add_price(None, price_payload.price); + // Self::add_price(None, price_payload.price); // now increment the block number at which we expect next unsigned transaction. let current_block = >::block_number(); >::put(current_block + T::UnsignedInterval::get()); @@ -308,7 +312,7 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Event generated when new price is accepted to contribute to the average. - NewPrice { price: u32, maybe_who: Option }, + NewPrice { price: PropertyInfoData, who: T::AccountId }, } #[pallet::validate_unsigned] @@ -366,7 +370,7 @@ pub mod pallet { #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] pub struct PricePayload { block_number: BlockNumber, - price: u32, + price: PropertyInfoData, public: Public, } @@ -457,135 +461,135 @@ impl Pallet { } /// A helper function to fetch the price and send signed transaction. - // fn fetch_price_and_send_signed() -> Result<(), &'static str> { - // let signer = Signer::::all_accounts(); - // if !signer.can_sign() { - // return Err( - // "No local accounts available. Consider adding one via `author_insertKey` RPC.", - // ) - // } - // // Make an external HTTP request to fetch the current price. - // // Note this call will block until response is received. - // let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - - // // Using `send_signed_transaction` associated type we create and submit a transaction - // // representing the call, we've just created. - // // Submit signed will return a vector of results for all accounts that were found in the - // // local keystore with expected `KEY_TYPE`. - // let results = signer.send_signed_transaction(|_account| { - // // Received price is wrapped into a call to `submit_price` public function of this - // // pallet. This means that the transaction, when executed, will simply call that - // // function passing `price` as an argument. - // // Call::submit_price { price } - // }); + fn fetch_price_and_send_signed() -> Result<(), &'static str> { + let signer = Signer::::all_accounts(); + if !signer.can_sign() { + return Err( + "No local accounts available. Consider adding one via `author_insertKey` RPC.", + ) + } + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // Using `send_signed_transaction` associated type we create and submit a transaction + // representing the call, we've just created. + // Submit signed will return a vector of results for all accounts that were found in the + // local keystore with expected `KEY_TYPE`. + let results = signer.send_signed_transaction(|_account| { + // Received price is wrapped into a call to `submit_price` public function of this + // pallet. This means that the transaction, when executed, will simply call that + // function passing `price` as an argument. + Call::submit_price { price: price.clone() } + }); - // for (acc, res) in &results { - // match res { - // Ok(()) => log::info!("[{:?}] Submitted price of {} cents", acc.id, price), - // Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e), - // } - // } + for (acc, res) in &results { + match res { + Ok(()) => log::info!("[{:?}] Submitted price of {:?} cents", acc.id, price), + Err(e) => log::error!("[{:?}] Failed to submit transaction: {:?}", acc.id, e), + } + } - // Ok(()) - // } + Ok(()) + } /// A helper function to fetch the price and send a raw unsigned transaction. - // fn fetch_price_and_send_raw_unsigned( - // block_number: BlockNumberFor, - // ) -> Result<(), &'static str> { - // // Make sure we don't fetch the price if unsigned transaction is going to be rejected - // // anyway. - // let next_unsigned_at = NextUnsignedAt::::get(); - // if next_unsigned_at > block_number { - // return Err("Too early to send unsigned transaction") - // } - - // // Make an external HTTP request to fetch the current price. - // // Note this call will block until response is received. - // let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - // TestProperties::::put(price); - - // // Received price is wrapped into a call to `submit_price_unsigned` public function of this - // // pallet. This means that the transaction, when executed, will simply call that function - // // passing `price` as an argument. - // // let call = Call::submit_price_unsigned { block_number, price }; - - // // Now let's create a transaction out of this call and submit it to the pool. - // // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) - // // - // // By default unsigned transactions are disallowed, so we need to whitelist this case - // // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefully - // // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam - // // attack vectors. See validation logic docs for more details. - // // - // SubmitTransaction::>::submit_unsigned_transaction(call.into()) - // .map_err(|()| "Unable to submit unsigned transaction.")?; - - // Ok(()) - // } + fn fetch_price_and_send_raw_unsigned( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + TestProperties::::put(price.clone()); + + // Received price is wrapped into a call to `submit_price_unsigned` public function of this + // pallet. This means that the transaction, when executed, will simply call that function + // passing `price` as an argument. + let call = Call::submit_price_unsigned { block_number, price }; + + // Now let's create a transaction out of this call and submit it to the pool. + // Here we showcase two ways to send an unsigned transaction / unsigned payload (raw) + // + // By default unsigned transactions are disallowed, so we need to whitelist this case + // by writing `UnsignedValidator`. Note that it's EXTREMELY important to carefully + // implement unsigned validation logic, as any mistakes can lead to opening DoS or spam + // attack vectors. See validation logic docs for more details. + // + SubmitTransaction::>::submit_unsigned_transaction(call.into()) + .map_err(|()| "Unable to submit unsigned transaction.")?; + + Ok(()) + } /// A helper function to fetch the price, sign payload and send an unsigned transaction - // fn fetch_price_and_send_unsigned_for_any_account( - // block_number: BlockNumberFor, - // ) -> Result<(), &'static str> { - // // Make sure we don't fetch the price if unsigned transaction is going to be rejected - // // anyway. - // let next_unsigned_at = NextUnsignedAt::::get(); - // if next_unsigned_at > block_number { - // return Err("Too early to send unsigned transaction") - // } - - // // Make an external HTTP request to fetch the current price. - // // Note this call will block until response is received. - // let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - - // // -- Sign using any account - // let (_, result) = Signer::::any_account() - // .send_unsigned_transaction( - // |account| PricePayload { price, block_number, public: account.public.clone() }, - // |payload, signature| Call::submit_price_unsigned_with_signed_payload { - // price_payload: payload, - // signature, - // }, - // ) - // .ok_or("No local accounts accounts available.")?; - // result.map_err(|()| "Unable to submit transaction")?; - - // Ok(()) - // } + fn fetch_price_and_send_unsigned_for_any_account( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } + + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // -- Sign using any account + let (_, result) = Signer::::any_account() + .send_unsigned_transaction( + |account| PricePayload { price: price.clone(), block_number, public: account.public.clone() }, + |payload, signature| Call::submit_price_unsigned_with_signed_payload { + price_payload: payload, + signature, + }, + ) + .ok_or("No local accounts accounts available.")?; + result.map_err(|()| "Unable to submit transaction")?; + + Ok(()) + } /// A helper function to fetch the price, sign payload and send an unsigned transaction - // fn fetch_price_and_send_unsigned_for_all_accounts( - // block_number: BlockNumberFor, - // ) -> Result<(), &'static str> { - // // Make sure we don't fetch the price if unsigned transaction is going to be rejected - // // anyway. - // let next_unsigned_at = NextUnsignedAt::::get(); - // if next_unsigned_at > block_number { - // return Err("Too early to send unsigned transaction") - // } - - // // Make an external HTTP request to fetch the current price. - // // Note this call will block until response is received. - // let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; - - // // -- Sign using all accounts - // let transaction_results = Signer::::all_accounts() - // .send_unsigned_transaction( - // |account| PricePayload { price, block_number, public: account.public.clone() }, - // |payload, signature| Call::submit_price_unsigned_with_signed_payload { - // price_payload: payload, - // signature, - // }, - // ); - // for (_account_id, result) in transaction_results.into_iter() { - // if result.is_err() { - // return Err("Unable to submit transaction") - // } - // } + fn fetch_price_and_send_unsigned_for_all_accounts( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { + // Make sure we don't fetch the price if unsigned transaction is going to be rejected + // anyway. + let next_unsigned_at = NextUnsignedAt::::get(); + if next_unsigned_at > block_number { + return Err("Too early to send unsigned transaction") + } - // Ok(()) - // } + // Make an external HTTP request to fetch the current price. + // Note this call will block until response is received. + let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?; + + // -- Sign using all accounts + let transaction_results = Signer::::all_accounts() + .send_unsigned_transaction( + |account| PricePayload { price: price.clone(), block_number, public: account.public.clone() }, + |payload, signature| Call::submit_price_unsigned_with_signed_payload { + price_payload: payload, + signature, + }, + ); + for (_account_id, result) in transaction_results.into_iter() { + if result.is_err() { + return Err("Unable to submit transaction") + } + } + + Ok(()) + } /// Fetch current price and return the result in cents. fn fetch_price() -> Result { @@ -639,7 +643,7 @@ impl Pallet { }, }?; - // log::warn!("Got price: {} cents", price); + log::warn!("Got property: {:?} cents", price); Ok(price) } @@ -734,12 +738,7 @@ impl Pallet { let bedrooms = bedrooms.fraction_length.saturating_sub(2); let bathrooms = bathrooms.fraction_length.saturating_sub(2); - let property = PropertyInfoData { - id, - bedrooms, - bathrooms, - - }; + let property = PropertyInfoData { id, bedrooms, bathrooms }; Some(property) @@ -747,20 +746,20 @@ impl Pallet { } /// Add new price to the list. - fn add_price(maybe_who: Option, price: u32) { - log::info!("Adding to the average: {}", price); - >::mutate(|prices| { - if prices.try_push(price).is_err() { - prices[(price % T::MaxPrices::get()) as usize] = price; - } - }); + // fn add_price(maybe_who: Option, price: u32) { + // log::info!("Adding to the average: {}", price); + // >::mutate(|prices| { + // if prices.try_push(price).is_err() { + // prices[(price % T::MaxPrices::get()) as usize] = price; + // } + // }); - let average = Self::average_price() - .expect("The average is not empty, because it was just mutated; qed"); - log::info!("Current average price is: {}", average); - // here we are raising the NewPrice event - Self::deposit_event(Event::NewPrice { price, maybe_who }); - } + // let average = Self::average_price() + // .expect("The average is not empty, because it was just mutated; qed"); + // log::info!("Current average price is: {}", average); + // // here we are raising the NewPrice event + // Self::deposit_event(Event::NewPrice { price, maybe_who }); + // } /// Calculate current average price. fn average_price() -> Option { @@ -774,7 +773,7 @@ impl Pallet { fn validate_transaction_parameters( block_number: &BlockNumberFor, - new_price: &u32, + new_price: &PropertyInfoData, ) -> TransactionValidity { // Now let's check if the transaction has any chance to succeed. let next_unsigned_at = NextUnsignedAt::::get(); @@ -792,16 +791,16 @@ impl Pallet { // Note this doesn't make much sense when building an actual oracle, but this example // is here mostly to show off offchain workers capabilities, not about building an // oracle. - let avg_price = Self::average_price() - .map(|price| if &price > new_price { price - new_price } else { new_price - price }) - .unwrap_or(0); + // let avg_price = Self::average_price() + // .map(|price| if &price > new_price { price - new_price } else { new_price - price }) + // .unwrap_or(0); ValidTransaction::with_tag_prefix("ExampleOffchainWorker") // We set base priority to 2**20 and hope it's included before any other // transactions in the pool. Next we tweak the priority depending on how much // it differs from the current average. (the more it differs the more priority it // has). - .priority(T::UnsignedPriority::get().saturating_add(avg_price as _)) + .priority(T::UnsignedPriority::get().saturating_add(100 as _)) // This transaction does not require anything else to go before into the pool. // In theory we could require `previous_unsigned_at` transaction to go first, // but it's not necessary in our case. From e40189aacdc789554f361b5307314e67178c0483 Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Sun, 5 May 2024 22:45:33 +0545 Subject: [PATCH 08/11] update --- Cargo.lock | 28 ++++----- node/Cargo.toml | 1 + node/src/service.rs | 17 ++++++ pallets/template/src/lib.rs | 115 ++++++++++++++---------------------- 4 files changed, 77 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 91d0ea4..4ea8bd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4606,6 +4606,7 @@ dependencies = [ "sp-inherents", "sp-io", "sp-keyring", + "sp-keystore", "sp-runtime", "sp-timestamp", "substrate-build-script-utils", @@ -5533,9 +5534,9 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ "cfg-if", "fnv", @@ -7267,11 +7268,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -7280,9 +7281,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -8861,9 +8862,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -8871,7 +8872,6 @@ dependencies = [ "futures-sink", "pin-project-lite 0.2.14", "tokio", - "tracing", ] [[package]] @@ -10176,18 +10176,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "087eca3c1eaf8c47b94d02790dd086cd594b912d2043d4de4bfdd466b3befb7c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "6f4b6c273f496d8fd4eaf18853e6b448760225dc030ff2c485a786859aea6393" dependencies = [ "proc-macro2", "quote", diff --git a/node/Cargo.toml b/node/Cargo.toml index 398c95a..4d8c781 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -41,6 +41,7 @@ sp-io = { version = "23.0.0", git = "https://github.com/paritytech/polkadot-sdk. sp-timestamp = { version = "4.0.0-dev", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } sp-inherents = { version = "4.0.0-dev", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } sp-keyring = { version = "24.0.0", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } +sp-keystore = {git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } frame-system = { version = "4.0.0-dev", git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/polkadot-sdk.git", tag = "polkadot-v1.6.0" } diff --git a/node/src/service.rs b/node/src/service.rs index fabacf9..bfb792f 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -66,6 +66,23 @@ pub fn new_partial( )?; let client = Arc::new(client); + + let keystore = keystore_container.keystore(); + if config.offchain_worker.enabled { + // Initialize seed for signing transaction using off-chain workers. This is a convenience + // so learners can see the transactions submitted simply running the node. + // Typically these keys should be inserted with RPC calls to `author_insertKey`. + + // For pallet-ocw + sp_keystore::Keystore::sr25519_generate_new( + &*keystore, + node_template_runtime::pallet_template::KEY_TYPE, + Some("//Alice"), + ).expect("Creating key with account Alice should succeed."); + + // For pallet-example-offchain-worker + } + let telemetry = telemetry.map(|(worker, telemetry)| { task_manager.spawn_handle().spawn("telemetry", None, worker.run()); telemetry diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index 56eb0df..e0ce82c 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -162,7 +162,7 @@ pub mod pallet { } #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] - #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] + #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, Debug, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct PropertyInfoData { pub id: u32, @@ -212,18 +212,18 @@ pub mod pallet { // depending on the block number. // Usually it's enough to choose one or the other. let should_send = Self::choose_transaction_type(block_number); - // let res = match should_send { - // // TransactionType::Signed => Self::fetch_price_and_send_signed(), - // TransactionType::UnsignedForAny => - // Self::fetch_price_and_send_unsigned_for_any_account(block_number), - // TransactionType::UnsignedForAll => - // Self::fetch_price_and_send_unsigned_for_all_accounts(block_number), - // TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number), - // TransactionType::None => Ok(()), - // }; - // if let Err(e) = res { - // log::error!("Error: {}", e); - // } + let res = match should_send { + TransactionType::Signed => Self::fetch_price_and_send_signed(), + TransactionType::UnsignedForAny => + Self::fetch_price_and_send_unsigned_for_any_account(block_number), + TransactionType::UnsignedForAll => + Self::fetch_price_and_send_unsigned_for_all_accounts(block_number), + TransactionType::Raw => Self::fetch_price_and_send_raw_unsigned(block_number), + TransactionType::None => Ok(()), + }; + if let Err(e) = res { + log::error!("Error: {}", e); + } } } @@ -246,7 +246,10 @@ pub mod pallet { /// purpose is to showcase offchain worker capabilities. #[pallet::call_index(0)] #[pallet::weight({0})] - pub fn submit_price(origin: OriginFor, price: PropertyInfoData) -> DispatchResultWithPostInfo { + pub fn submit_price( + origin: OriginFor, + price: PropertyInfoData, + ) -> DispatchResultWithPostInfo { // Retrieve sender of the transaction. let who = ensure_signed(origin)?; // Add the price to the on-chain list. @@ -367,7 +370,7 @@ pub mod pallet { /// Payload used by this example crate to hold price /// data required to submit a transaction. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, scale_info::TypeInfo)] pub struct PricePayload { block_number: BlockNumber, price: PropertyInfoData, @@ -546,7 +549,11 @@ impl Pallet { // -- Sign using any account let (_, result) = Signer::::any_account() .send_unsigned_transaction( - |account| PricePayload { price: price.clone(), block_number, public: account.public.clone() }, + |account| PricePayload { + price: price.clone(), + block_number, + public: account.public.clone(), + }, |payload, signature| Call::submit_price_unsigned_with_signed_payload { price_payload: payload, signature, @@ -576,7 +583,11 @@ impl Pallet { // -- Sign using all accounts let transaction_results = Signer::::all_accounts() .send_unsigned_transaction( - |account| PricePayload { price: price.clone(), block_number, public: account.public.clone() }, + |account| PricePayload { + price: price.clone(), + block_number, + public: account.public.clone(), + }, |payload, signature| Call::submit_price_unsigned_with_signed_payload { price_payload: payload, signature, @@ -604,8 +615,10 @@ impl Pallet { // since we are running in a custom WASM execution environment we can't simply // import the library here. let request = http::Request::get( - "https://ipfs.io/ipfs/QmRYy9yPQYHUH2He18MFnnDEyfwST27GwywLB8N2jJ9fo2?filename=p.json", + "https://ipfs.io/ipfs/QmZkZUkqnJHLnM9SvxDXWoo2WYHPgQyF9YRWp5nXWe6WZ8?filename=london_new_property.json", + ); + // We set the deadline for sending of the request, note that awaiting response can // have a separate deadline. Next we send the request, before that it's also possible // to alter request headers or stream body content in case of non-GET requests. @@ -663,18 +676,6 @@ impl Pallet { }, _ => return None, }; - - // let propertyType = match val.ok()? { - // JsonValue::Object(obj) => { - // let (_, v) = obj.into_iter().find(|(k, _)| - // k.iter().copied().eq("propertyType".chars()))?; match v { - // JsonValue::String(s) => s.into_bytes().collect::>(), - // _ => return None, - // } - // }, - // _ => return None, - // }; - let val = lite_json::parse_json(price_str); let bedrooms = match val.ok()? { JsonValue::Object(obj) => { @@ -687,7 +688,6 @@ impl Pallet { }, _ => return None, }; - let val = lite_json::parse_json(price_str); let bathrooms = match val.ok()? { JsonValue::Object(obj) => { @@ -700,45 +700,20 @@ impl Pallet { }, _ => return None, }; - - // let city = match val.ok()? { - // JsonValue::Object(obj) => { - // let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("city".chars()))?; - // match v { - // JsonValue::String(s) => s.as_bytes().to_vec(), - // _ => return None, - // } - // }, - // _ => return None, - // }; - - // let postCode = match val.ok()? { - // JsonValue::Object(obj) => { - // let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("postCode".chars()))?; - // match v { - // JsonValue::String(s) => s.as_bytes().to_vec(), - // _ => return None, - // } - // }, - // _ => return None, - // }; - - // let keyFeatures = match val.ok()? { - // JsonValue::Object(obj) => { - // let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("keyFeatures".chars()))?; - // match v { - // JsonValue::String(s) => s.as_bytes().to_vec(), - // _ => return None, - // } - // }, - // _ => return None, - // }; - - let id = id.fraction_length.saturating_sub(2); - let bedrooms = bedrooms.fraction_length.saturating_sub(2); - let bathrooms = bathrooms.fraction_length.saturating_sub(2); - - let property = PropertyInfoData { id, bedrooms, bathrooms }; + + + let id = id.integer as u32; + let bedrooms = bedrooms.integer as u32; + let bathrooms = bathrooms.integer as u32; + + + + let property = PropertyInfoData { + id, + bedrooms, + bathrooms, + + }; Some(property) From ccb8f4593f9a4c093917dbc6d4860177226e3eeb Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Mon, 6 May 2024 08:06:27 +0545 Subject: [PATCH 09/11] update --- pallets/template/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index e0ce82c..0c99993 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -615,7 +615,7 @@ impl Pallet { // since we are running in a custom WASM execution environment we can't simply // import the library here. let request = http::Request::get( - "https://ipfs.io/ipfs/QmZkZUkqnJHLnM9SvxDXWoo2WYHPgQyF9YRWp5nXWe6WZ8?filename=london_new_property.json", + "https://ipfs.io/ipfs/QmVHvCybit6DuMx5tkPdqnB6CJLG5ReCVHAEjfneqXiZaY?filename=new_property.json", ); From 5e9a40e2992dfa0b797026d8adef81083c00ab6e Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Mon, 6 May 2024 10:51:19 +0545 Subject: [PATCH 10/11] updating --- pallets/template/src/lib.rs | 166 +++++++++++++++++++++++++++--------- 1 file changed, 128 insertions(+), 38 deletions(-) diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index 0c99993..d60abd1 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -62,10 +62,7 @@ use sp_runtime::{ http, storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, Duration, - }, - traits::Zero, - transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, - RuntimeDebug, + }, traits::Zero, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, BoundedVec, RuntimeDebug }; use sp_std::vec::Vec; @@ -162,14 +159,17 @@ pub mod pallet { } #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] - #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, Debug, TypeInfo)] + #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] #[scale_info(skip_type_params(T))] - pub struct PropertyInfoData { + pub struct PropertyInfoData { pub id: u32, pub bedrooms: u32, pub bathrooms: u32, + pub summary: BoundedVec::StringLimit>, } + + #[pallet::pallet] pub struct Pallet(_); @@ -248,7 +248,7 @@ pub mod pallet { #[pallet::weight({0})] pub fn submit_price( origin: OriginFor, - price: PropertyInfoData, + price: PropertyInfoData, ) -> DispatchResultWithPostInfo { // Retrieve sender of the transaction. let who = ensure_signed(origin)?; @@ -280,7 +280,7 @@ pub mod pallet { pub fn submit_price_unsigned( origin: OriginFor, _block_number: BlockNumberFor, - price: PropertyInfoData, + price: PropertyInfoData, ) -> DispatchResultWithPostInfo { // This ensures that the function can only be called via unsigned transaction. ensure_none(origin)?; @@ -296,7 +296,7 @@ pub mod pallet { #[pallet::weight({0})] pub fn submit_price_unsigned_with_signed_payload( origin: OriginFor, - price_payload: PricePayload>, + price_payload: PricePayload, T>, _signature: T::Signature, ) -> DispatchResultWithPostInfo { // This ensures that the function can only be called via unsigned transaction. @@ -315,7 +315,7 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Event generated when new price is accepted to contribute to the average. - NewPrice { price: PropertyInfoData, who: T::AccountId }, + NewPrice { price: PropertyInfoData, who: T::AccountId }, } #[pallet::validate_unsigned] @@ -365,19 +365,19 @@ pub mod pallet { // A List of test properties #[pallet::storage] #[pallet::getter(fn testproperties)] - pub type TestProperties = StorageValue<_, PropertyInfoData, OptionQuery>; + pub type TestProperties = StorageValue<_, PropertyInfoData, OptionQuery>; } /// Payload used by this example crate to hold price /// data required to submit a transaction. #[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, scale_info::TypeInfo)] -pub struct PricePayload { +pub struct PricePayload { block_number: BlockNumber, - price: PropertyInfoData, + price: PropertyInfoData, public: Public, } -impl SignedPayload for PricePayload> { +impl SignedPayload for PricePayload, W> { fn public(&self) -> T::Public { self.public.clone() } @@ -603,7 +603,7 @@ impl Pallet { } /// Fetch current price and return the result in cents. - fn fetch_price() -> Result { + fn fetch_price() -> Result, http::Error> { // We want to keep the offchain worker execution time reasonable, so we set a hard-coded // deadline to 2s to complete the external call. // You can also wait indefinitely for the response, however you may still get a timeout @@ -615,7 +615,7 @@ impl Pallet { // since we are running in a custom WASM execution environment we can't simply // import the library here. let request = http::Request::get( - "https://ipfs.io/ipfs/QmVHvCybit6DuMx5tkPdqnB6CJLG5ReCVHAEjfneqXiZaY?filename=new_property.json", + "https://ipfs.io/ipfs/QmfMH6Xdyzq1QY5AQhLKuhpkqiMYW88DCmMjQyrW7Pv9h5?filename=new_property.json", ); @@ -656,7 +656,7 @@ impl Pallet { }, }?; - log::warn!("Got property: {:?} cents", price); + // log::warn!("Got property: {:?} cents", price); Ok(price) } @@ -664,54 +664,144 @@ impl Pallet { /// Parse the price from the given JSON string using `lite-json`. /// /// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful. - fn parse_price(price_str: &str) -> Option { + fn parse_price(price_str: &str) -> Option> { + // let val = lite_json::parse_json(price_str); + // let id = match val.ok()? { + // JsonValue::Object(obj) => { + // let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("id".chars()))?; + // match v { + // JsonValue::Number(number) => number, + // _ => return None, + // } + // }, + // _ => return None, + // }; let val = lite_json::parse_json(price_str); let id = match val.ok()? { - JsonValue::Object(obj) => { - let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("id".chars()))?; - match v { - JsonValue::Number(number) => number, - _ => return None, + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'id' field in the first object + if let Some((_, v)) = obj.into_iter().find(|(k, _ )| k.iter().copied().eq("id".chars())) { + // Check if the value associated with 'id' is a number + if let JsonValue::Number(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; } }, _ => return None, }; let val = lite_json::parse_json(price_str); let bedrooms = match val.ok()? { - JsonValue::Object(obj) => { - let (_, v) = - obj.into_iter().find(|(k, _)| k.iter().copied().eq("bedrooms".chars()))?; - match v { - JsonValue::Number(number) => number, - _ => return None, + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'bedrooms' field in the first object + if let Some((_, v)) = obj.into_iter().find(|(k, _ )| k.iter().copied().eq("bedrooms".chars())) { + // Check if the value associated with 'id' is a number + if let JsonValue::Number(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; } }, _ => return None, }; let val = lite_json::parse_json(price_str); let bathrooms = match val.ok()? { - JsonValue::Object(obj) => { - let (_, v) = - obj.into_iter().find(|(k, _)| k.iter().copied().eq("bathrooms".chars()))?; - match v { - JsonValue::Number(number) => number, - _ => return None, + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'bathrooms' field in the first object + if let Some((_, v)) = obj.into_iter().find(|(k, _ )| k.iter().copied().eq("bathrooms".chars())) { + // Check if the value associated with 'id' is a number + if let JsonValue::Number(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; } }, _ => return None, }; + + let val = lite_json::parse_json(price_str); + let summary = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'summary' field in the first object + if let Some((_, v)) = obj.into_iter().find(|(k, _ )| k.iter().copied().eq("summary".chars())) { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; let id = id.integer as u32; let bedrooms = bedrooms.integer as u32; let bathrooms = bathrooms.integer as u32; - - + let summary = summary.iter().collect::(); + + + let summary = "GB".as_bytes().to_vec().try_into().unwrap(); + let property = PropertyInfoData { id, bedrooms, bathrooms, + // summary: "GB".as_bytes().to_vec().try_into().unwrap() , + summary, }; @@ -748,7 +838,7 @@ impl Pallet { fn validate_transaction_parameters( block_number: &BlockNumberFor, - new_price: &PropertyInfoData, + new_price: &PropertyInfoData, ) -> TransactionValidity { // Now let's check if the transaction has any chance to succeed. let next_unsigned_at = NextUnsignedAt::::get(); From 826a6136dee2a696be6d016d3f890d7ccab991e6 Mon Sep 17 00:00:00 2001 From: Ganesh oli Date: Mon, 6 May 2024 15:48:57 +0545 Subject: [PATCH 11/11] update --- Cargo.lock | 24 ++-- pallets/template/src/lib.rs | 265 +++++++++++++++++++++++++++++------- 2 files changed, 229 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ea8bd6..77cdb8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7810,7 +7810,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.10.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -7849,7 +7849,7 @@ dependencies = [ [[package]] name = "sp-debug-derive" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "proc-macro2", "quote", @@ -7870,7 +7870,7 @@ dependencies = [ [[package]] name = "sp-externalities" version = "0.25.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "environmental", "parity-scale-codec", @@ -8055,7 +8055,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface" version = "24.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -8087,7 +8087,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "17.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "Inflector", "expander", @@ -8179,7 +8179,7 @@ source = "git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-v1.6.0 [[package]] name = "sp-std" version = "14.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" [[package]] name = "sp-storage" @@ -8197,7 +8197,7 @@ dependencies = [ [[package]] name = "sp-storage" version = "19.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "impl-serde", "parity-scale-codec", @@ -8234,7 +8234,7 @@ dependencies = [ [[package]] name = "sp-tracing" version = "16.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "parity-scale-codec", "tracing", @@ -8334,7 +8334,7 @@ dependencies = [ [[package]] name = "sp-wasm-interface" version = "20.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#c0234becc185f88445dd63105b6f363c9e5990ce" +source = "git+https://github.com/paritytech/polkadot-sdk#73c89d308fefcedfc3619f0273e13b6623766b81" dependencies = [ "impl-trait-for-tuples", "log", @@ -8936,7 +8936,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.7", + "winnow 0.6.8", ] [[package]] @@ -10084,9 +10084,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" +checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" dependencies = [ "memchr", ] diff --git a/pallets/template/src/lib.rs b/pallets/template/src/lib.rs index d60abd1..05779ca 100644 --- a/pallets/template/src/lib.rs +++ b/pallets/template/src/lib.rs @@ -56,16 +56,19 @@ use frame_system::{ pallet_prelude::BlockNumberFor, }; use lite_json::json::JsonValue; +use scale_info::prelude::string::String; use sp_core::crypto::KeyTypeId; use sp_runtime::{ offchain::{ http, storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, Duration, - }, traits::Zero, transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, BoundedVec, RuntimeDebug + }, + traits::Zero, + transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction}, + BoundedVec, RuntimeDebug, }; use sp_std::vec::Vec; - #[cfg(test)] mod tests; @@ -158,18 +161,32 @@ pub mod pallet { type StringLimit: Get; } + // Define string_limit variable of type Get + #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] - #[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)] + #[derive( + Encode, + Decode, + Clone, + PartialEq, + Eq, + MaxEncodedLen, + frame_support::pallet_prelude::RuntimeDebugNoBound, + TypeInfo, + )] #[scale_info(skip_type_params(T))] pub struct PropertyInfoData { pub id: u32, pub bedrooms: u32, pub bathrooms: u32, pub summary: BoundedVec::StringLimit>, + pub property_sub_type: BoundedVec::StringLimit>, + pub first_visible_date: BoundedVec::StringLimit>, + pub display_size: BoundedVec::StringLimit>, + pub display_address: BoundedVec::StringLimit>, + // pub property_images: BoundedVec::StringLimit>, } - - #[pallet::pallet] pub struct Pallet(_); @@ -255,7 +272,7 @@ pub mod pallet { // Add the price to the on-chain list. // Self::add_price(Some(who), price); TestProperties::::put(price.clone()); - Self::deposit_event(Event::NewPrice { price: price.clone(), who }); + Self::deposit_event(Event::NewPrice { price: price.id.clone(), who }); Ok(().into()) } @@ -296,7 +313,7 @@ pub mod pallet { #[pallet::weight({0})] pub fn submit_price_unsigned_with_signed_payload( origin: OriginFor, - price_payload: PricePayload, T>, + price_payload: PricePayload>, _signature: T::Signature, ) -> DispatchResultWithPostInfo { // This ensures that the function can only be called via unsigned transaction. @@ -315,7 +332,7 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Event generated when new price is accepted to contribute to the average. - NewPrice { price: PropertyInfoData, who: T::AccountId }, + NewPrice { price: u32, who: T::AccountId }, } #[pallet::validate_unsigned] @@ -339,9 +356,9 @@ pub mod pallet { if !signature_valid { return InvalidTransaction::BadProof.into() } - Self::validate_transaction_parameters(&payload.block_number, &payload.price) + Self::validate_transaction_parameters(&payload.block_number) } else if let Call::submit_price_unsigned { block_number, price: new_price } = call { - Self::validate_transaction_parameters(block_number, new_price) + Self::validate_transaction_parameters(block_number) } else { InvalidTransaction::Call.into() } @@ -370,14 +387,14 @@ pub mod pallet { /// Payload used by this example crate to hold price /// data required to submit a transaction. -#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, scale_info::TypeInfo)] -pub struct PricePayload { +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +pub struct PricePayload { block_number: BlockNumber, - price: PropertyInfoData, + price: u32, public: Public, } -impl SignedPayload for PricePayload, W> { +impl SignedPayload for PricePayload> { fn public(&self) -> T::Public { self.public.clone() } @@ -550,7 +567,7 @@ impl Pallet { let (_, result) = Signer::::any_account() .send_unsigned_transaction( |account| PricePayload { - price: price.clone(), + price: price.id.clone(), block_number, public: account.public.clone(), }, @@ -584,7 +601,7 @@ impl Pallet { let transaction_results = Signer::::all_accounts() .send_unsigned_transaction( |account| PricePayload { - price: price.clone(), + price: price.id.clone(), block_number, public: account.public.clone(), }, @@ -614,11 +631,11 @@ impl Pallet { // you can find in `sp_io`. The API is trying to be similar to `request`, but // since we are running in a custom WASM execution environment we can't simply // import the library here. + let request = http::Request::get( - "https://ipfs.io/ipfs/QmfMH6Xdyzq1QY5AQhLKuhpkqiMYW88DCmMjQyrW7Pv9h5?filename=new_property.json", - + "https://ipfs.io/ipfs/QmZ3Dn5B2UMuv9PFr1Ba3NGSKft2rwToBKCPaCTCmSab4k?filename=testing_data.json" ); - + // We set the deadline for sending of the request, note that awaiting response can // have a separate deadline. Next we send the request, before that it's also possible // to alter request headers or stream body content in case of non-GET requests. @@ -665,17 +682,6 @@ impl Pallet { /// /// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful. fn parse_price(price_str: &str) -> Option> { - // let val = lite_json::parse_json(price_str); - // let id = match val.ok()? { - // JsonValue::Object(obj) => { - // let (_, v) = obj.into_iter().find(|(k, _)| k.iter().copied().eq("id".chars()))?; - // match v { - // JsonValue::Number(number) => number, - // _ => return None, - // } - // }, - // _ => return None, - // }; let val = lite_json::parse_json(price_str); let id = match val.ok()? { JsonValue::Array(mut arr) => { @@ -684,7 +690,9 @@ impl Pallet { // Check if the first element is an object if let JsonValue::Object(obj) = obj { // Find the 'id' field in the first object - if let Some((_, v)) = obj.into_iter().find(|(k, _ )| k.iter().copied().eq("id".chars())) { + if let Some((_, v)) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("id".chars())) + { // Check if the value associated with 'id' is a number if let JsonValue::Number(number) = v { number @@ -711,7 +719,9 @@ impl Pallet { // Check if the first element is an object if let JsonValue::Object(obj) = obj { // Find the 'bedrooms' field in the first object - if let Some((_, v)) = obj.into_iter().find(|(k, _ )| k.iter().copied().eq("bedrooms".chars())) { + if let Some((_, v)) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("bedrooms".chars())) + { // Check if the value associated with 'id' is a number if let JsonValue::Number(number) = v { number @@ -738,7 +748,9 @@ impl Pallet { // Check if the first element is an object if let JsonValue::Object(obj) = obj { // Find the 'bathrooms' field in the first object - if let Some((_, v)) = obj.into_iter().find(|(k, _ )| k.iter().copied().eq("bathrooms".chars())) { + if let Some((_, v)) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("bathrooms".chars())) + { // Check if the value associated with 'id' is a number if let JsonValue::Number(number) = v { number @@ -758,7 +770,6 @@ impl Pallet { _ => return None, }; - let val = lite_json::parse_json(price_str); let summary = match val.ok()? { JsonValue::Array(mut arr) => { @@ -767,7 +778,102 @@ impl Pallet { // Check if the first element is an object if let JsonValue::Object(obj) = obj { // Find the 'summary' field in the first object - if let Some((_, v)) = obj.into_iter().find(|(k, _ )| k.iter().copied().eq("summary".chars())) { + if let Some((_, v)) = + obj.into_iter().find(|(k, _)| k.iter().copied().eq("summary".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let propertySubType = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'propertySubType' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("propertySubType".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let firstVisibleDate = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'firstVisibleDate' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("firstVisibleDate".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let displaySize = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'displaySize' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("displaySize".chars())) + { // Check if the value associated with 'id' is a number if let JsonValue::String(number) = v { number @@ -786,23 +892,89 @@ impl Pallet { }, _ => return None, }; - + + let val = lite_json::parse_json(price_str); + let displayAddress = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'displayAddress' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("displayAddress".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + + let val = lite_json::parse_json(price_str); + let propertyImages = match val.ok()? { + JsonValue::Array(mut arr) => { + // Check if the array has at least one element + if let Some(obj) = arr.pop() { + // Check if the first element is an object + if let JsonValue::Object(obj) = obj { + // Find the 'propertyImages' field in the first object + if let Some((_, v)) = obj + .into_iter() + .find(|(k, _)| k.iter().copied().eq("propertyImages".chars())) + { + // Check if the value associated with 'id' is a number + if let JsonValue::String(number) = v { + number + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + } else { + return None; + } + }, + _ => return None, + }; + let id = id.integer as u32; let bedrooms = bedrooms.integer as u32; let bathrooms = bathrooms.integer as u32; - let summary = summary.iter().collect::(); - - + let summary: &str = &summary.iter().collect::(); + let propertySubType: &str = &propertySubType.iter().collect::(); + let firstVisibleDate: &str = &firstVisibleDate.iter().collect::(); + let displaySize: &str = &displaySize.iter().collect::(); + let displayAddress: &str = &displayAddress.iter().collect::(); + // let propertyImages: &str = &propertyImages.iter().collect::(); - let summary = "GB".as_bytes().to_vec().try_into().unwrap(); - let property = PropertyInfoData { id, bedrooms, bathrooms, - // summary: "GB".as_bytes().to_vec().try_into().unwrap() , - summary, - + summary: summary.as_bytes().to_vec().try_into().unwrap(), + property_sub_type: propertySubType.as_bytes().to_vec().try_into().unwrap(), + first_visible_date: firstVisibleDate.as_bytes().to_vec().try_into().unwrap(), + display_size: displaySize.as_bytes().to_vec().try_into().unwrap(), + display_address: displayAddress.as_bytes().to_vec().try_into().unwrap(), + // property_images: propertyImages.as_bytes().to_vec().try_into().unwrap(), }; Some(property) @@ -836,10 +1008,7 @@ impl Pallet { } } - fn validate_transaction_parameters( - block_number: &BlockNumberFor, - new_price: &PropertyInfoData, - ) -> TransactionValidity { + fn validate_transaction_parameters(block_number: &BlockNumberFor) -> TransactionValidity { // Now let's check if the transaction has any chance to succeed. let next_unsigned_at = NextUnsignedAt::::get(); if &next_unsigned_at > block_number {