From cb806b16c43bb895b75d2a2aac208dd97a3b941a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 12 May 2022 17:24:30 +0100 Subject: [PATCH 001/373] read_wasm: return a Result instead of exiting --- apps/src/lib/cli/context.rs | 2 +- apps/src/lib/node/ledger/shell/init_chain.rs | 11 ++-- apps/src/lib/node/matchmaker.rs | 2 +- apps/src/lib/wasm_loader/mod.rs | 62 +++++--------------- 4 files changed, 25 insertions(+), 52 deletions(-) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 33b988c487..551c7abf15 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -166,7 +166,7 @@ impl Context { /// Read the given WASM file from the WASM directory or an absolute path. pub fn read_wasm(&self, file_name: impl AsRef) -> Vec { - wasm_loader::read_wasm(self.wasm_dir(), file_name) + wasm_loader::read_wasm(self.wasm_dir(), file_name).unwrap() } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 3d80e10fba..de53848ea0 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -92,9 +92,10 @@ where storage, } in genesis.established_accounts { - let vp_code = vp_code_cache - .get_or_insert_with(vp_code_path.clone(), || { + let vp_code = + vp_code_cache.get_or_insert_with(vp_code_path.clone(), || { wasm_loader::read_wasm(&self.wasm_dir, &vp_code_path) + .unwrap() }); // In dev, we don't check the hash @@ -147,9 +148,10 @@ where balances, } in genesis.token_accounts { - let vp_code = vp_code_cache - .get_or_insert_with(vp_code_path.clone(), || { + let vp_code = + vp_code_cache.get_or_insert_with(vp_code_path.clone(), || { wasm_loader::read_wasm(&self.wasm_dir, &vp_code_path) + .unwrap() }); // In dev, we don't check the hash @@ -191,6 +193,7 @@ where &self.wasm_dir, &validator.validator_vp_code_path, ) + .unwrap() }, ); diff --git a/apps/src/lib/node/matchmaker.rs b/apps/src/lib/node/matchmaker.rs index 177092b600..71f713183b 100644 --- a/apps/src/lib/node/matchmaker.rs +++ b/apps/src/lib/node/matchmaker.rs @@ -143,7 +143,7 @@ impl Runner { // Prepare a client for intent gossiper node connection let (listener, dialer) = ClientListener::new_pair(intent_gossiper_addr); - let tx_code = wasm_loader::read_wasm(&wasm_dir, tx_code_path); + let tx_code = wasm_loader::read_wasm(&wasm_dir, tx_code_path).unwrap(); ( Self { diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 6e174b7b5c..87b34eb729 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use std::fs; use std::path::Path; +use eyre::{eyre, WrapErr}; use futures::future::join_all; use hex; use serde::{Deserialize, Serialize}; @@ -260,67 +261,36 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { pub fn read_wasm( wasm_directory: impl AsRef, file_path: impl AsRef, -) -> Vec { +) -> eyre::Result> { // load json with wasm hashes let checksums = Checksums::read_checksums(&wasm_directory); if let Some(os_name) = file_path.as_ref().file_name() { if let Some(name) = os_name.to_str() { - match checksums.0.get(name) { + let wasm_path = match checksums.0.get(name) { Some(wasm_filename) => { - let wasm_path = wasm_directory.as_ref().join(wasm_filename); - match fs::read(&wasm_path) { - Ok(bytes) => { - return bytes; - } - Err(_) => { - eprintln!( - "File {} not found. ", - wasm_path.to_string_lossy() - ); - safe_exit(1); - } - } + wasm_directory.as_ref().join(wasm_filename) } None => { if !file_path.as_ref().is_absolute() { - match fs::read( - wasm_directory.as_ref().join(file_path.as_ref()), - ) { - Ok(bytes) => { - return bytes; - } - Err(_) => { - eprintln!( - "Could not read file {}. ", - file_path.as_ref().to_string_lossy() - ); - safe_exit(1); - } - } + wasm_directory.as_ref().join(file_path.as_ref()) } else { - match fs::read(file_path.as_ref()) { - Ok(bytes) => { - return bytes; - } - Err(_) => { - eprintln!( - "Could not read file {}. ", - file_path.as_ref().to_string_lossy() - ); - safe_exit(1); - } - } + file_path.as_ref().to_path_buf() } } - } + }; + return fs::read(&wasm_path).wrap_err_with(|| { + format!( + "Failed to read WASM from {}", + &wasm_path.to_string_lossy() + ) + }); } } - eprintln!( - "File {} does not exist.", + Err(eyre!( + "Could not read {}", file_path.as_ref().to_string_lossy() - ); - safe_exit(1); + )) } async fn download_wasm(url: String) -> Result, Error> { From c251a09376b8ba2d7288a06781a2fdedea5c067d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 16 May 2022 10:55:32 +0100 Subject: [PATCH 002/373] Context::read_wasm: cleanly exit if there is an error --- apps/src/lib/cli/context.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 551c7abf15..163df3ff62 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -166,7 +166,13 @@ impl Context { /// Read the given WASM file from the WASM directory or an absolute path. pub fn read_wasm(&self, file_name: impl AsRef) -> Vec { - wasm_loader::read_wasm(self.wasm_dir(), file_name).unwrap() + match wasm_loader::read_wasm(self.wasm_dir(), file_name) { + Ok(wasm) => wasm, + Err(err) => { + eprintln!("Error reading wasm: {}", err); + safe_exit(1); + } + } } } From 28fa988a0151bfe7229d395642db8a705c78df1c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 16 May 2022 11:48:09 +0100 Subject: [PATCH 003/373] Shell::init_chain: return an error if reading wasm fails --- apps/src/lib/node/ledger/shell/init_chain.rs | 15 ++++++++++----- apps/src/lib/node/ledger/shell/mod.rs | 2 ++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index de53848ea0..9e83e2d90e 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -92,11 +92,16 @@ where storage, } in genesis.established_accounts { - let vp_code = - vp_code_cache.get_or_insert_with(vp_code_path.clone(), || { - wasm_loader::read_wasm(&self.wasm_dir, &vp_code_path) - .unwrap() - }); + let vp_code = match vp_code_cache.get(&vp_code_path).cloned() { + Some(vp_code) => vp_code, + None => { + let wasm = + wasm_loader::read_wasm(&self.wasm_dir, &vp_code_path) + .map_err(Error::ReadingWasm)?; + vp_code_cache.insert(vp_code_path.clone(), wasm.clone()); + wasm + } + }; // In dev, we don't check the hash #[cfg(feature = "dev")] diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9b787a32cb..2ece764e90 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -106,6 +106,8 @@ pub enum Error { Broadcaster(tokio::sync::mpsc::error::TryRecvError), #[error("Error executing proposal {0}: {1}")] BadProposal(u64, String), + #[error("Error reading wasm: {0}")] + ReadingWasm(#[from] eyre::Error), } impl From for TxResult { From 3784c8291db06e0621d6a90926ae74a3d52c6004 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 20 Jun 2022 21:08:28 +0100 Subject: [PATCH 004/373] Add read_wasm_or_exit and use it in some places --- apps/src/lib/cli/context.rs | 8 +------- apps/src/lib/node/matchmaker.rs | 2 +- apps/src/lib/wasm_loader/mod.rs | 13 +++++++++++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 163df3ff62..1f5d75d230 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -166,13 +166,7 @@ impl Context { /// Read the given WASM file from the WASM directory or an absolute path. pub fn read_wasm(&self, file_name: impl AsRef) -> Vec { - match wasm_loader::read_wasm(self.wasm_dir(), file_name) { - Ok(wasm) => wasm, - Err(err) => { - eprintln!("Error reading wasm: {}", err); - safe_exit(1); - } - } + wasm_loader::read_wasm_or_exit(self.wasm_dir(), file_name) } } diff --git a/apps/src/lib/node/matchmaker.rs b/apps/src/lib/node/matchmaker.rs index 71f713183b..92998c7f5d 100644 --- a/apps/src/lib/node/matchmaker.rs +++ b/apps/src/lib/node/matchmaker.rs @@ -143,7 +143,7 @@ impl Runner { // Prepare a client for intent gossiper node connection let (listener, dialer) = ClientListener::new_pair(intent_gossiper_addr); - let tx_code = wasm_loader::read_wasm(&wasm_dir, tx_code_path).unwrap(); + let tx_code = wasm_loader::read_wasm_or_exit(&wasm_dir, tx_code_path); ( Self { diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index 87b34eb729..1d3d2eb361 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -293,6 +293,19 @@ pub fn read_wasm( )) } +pub fn read_wasm_or_exit( + wasm_directory: impl AsRef, + file_path: impl AsRef, +) -> Vec { + match read_wasm(wasm_directory, file_path) { + Ok(wasm) => wasm, + Err(err) => { + eprintln!("Error reading wasm: {}", err); + safe_exit(1); + } + } +} + async fn download_wasm(url: String) -> Result, Error> { tracing::info!("Downloading WASM {}...", url); let response = reqwest::get(&url).await; From 230f50b8b9315299bf7035ad26b614aee9582a8e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 20 Jun 2022 21:13:19 +0100 Subject: [PATCH 005/373] Add changelog --- .changelog/unreleased/bug-fixes/1099-wasm-reading.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1099-wasm-reading.md diff --git a/.changelog/unreleased/bug-fixes/1099-wasm-reading.md b/.changelog/unreleased/bug-fixes/1099-wasm-reading.md new file mode 100644 index 0000000000..2e5cce09f3 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1099-wasm-reading.md @@ -0,0 +1,2 @@ +- Make read_wasm return an error instead of exiting in InitChain + ([#1099](https://github.com/anoma/anoma/pull/1099)) \ No newline at end of file From 83b459c0d326f26ea0b8a2bf260c6f14f1f2c32d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 14 Jun 2022 17:19:01 +0100 Subject: [PATCH 006/373] Create Cargo workspace under wasm/ Include anoma_wasm, vp_template and tx_template crates --- wasm/{wasm_source => }/Cargo.lock | 22 ++++++++++++++++++++++ wasm/Cargo.toml | 28 ++++++++++++++++++++++++++++ wasm/tx_template/Cargo.toml | 17 ----------------- wasm/vp_template/Cargo.toml | 17 ----------------- wasm/wasm_source/Cargo.toml | 20 -------------------- 5 files changed, 50 insertions(+), 54 deletions(-) rename wasm/{wasm_source => }/Cargo.lock (99%) create mode 100644 wasm/Cargo.toml diff --git a/wasm/wasm_source/Cargo.lock b/wasm/Cargo.lock similarity index 99% rename from wasm/wasm_source/Cargo.lock rename to wasm/Cargo.lock index 619817c90f..9fe2086fa3 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/Cargo.lock @@ -2812,6 +2812,17 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "tx_template" +version = "0.6.0" +dependencies = [ + "anoma_tests", + "anoma_tx_prelude", + "borsh", + "getrandom", + "wee_alloc", +] + [[package]] name = "typenum" version = "1.15.0" @@ -2887,6 +2898,17 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vp_template" +version = "0.6.0" +dependencies = [ + "anoma_tests", + "anoma_vp_prelude", + "borsh", + "getrandom", + "wee_alloc", +] + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/wasm/Cargo.toml b/wasm/Cargo.toml new file mode 100644 index 0000000000..6c2fa15880 --- /dev/null +++ b/wasm/Cargo.toml @@ -0,0 +1,28 @@ +[workspace] +resolver = "2" + +members = [ + "wasm_source", + "tx_template", + "vp_template", +] + +[patch.crates-io] +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} +tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} +tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} +# TODO temp patch for , and more tba. +borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} + +[profile.release] +# smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) +lto = true +# simply terminate on panics, no unwinding +panic = "abort" +# tell llvm to optimize for size (https://rustwasm.github.io/book/reference/code-size.html#tell-llvm-to-optimize-for-size-instead-of-speed) +opt-level = 'z' \ No newline at end of file diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 96103ce897..3eefb3a3b6 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -17,20 +17,3 @@ getrandom = { version = "0.2", features = ["custom"] } [dev-dependencies] anoma_tests = {path = "../../tests"} - -[patch.crates-io] -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -# TODO temp patch for , and more tba. -borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} - -[profile.release] -# smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) -lto = true -# simply terminate on panics, no unwinding -panic = "abort" -# tell llvm to optimize for size (https://rustwasm.github.io/book/reference/code-size.html#tell-llvm-to-optimize-for-size-instead-of-speed) -opt-level = 'z' diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 20ac084350..87c31180a1 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -17,20 +17,3 @@ getrandom = { version = "0.2", features = ["custom"] } [dev-dependencies] anoma_tests = {path = "../../tests"} - -[patch.crates-io] -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -# TODO temp patch for , and more tba. -borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} - -[profile.release] -# smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) -lto = true -# simply terminate on panics, no unwinding -panic = "abort" -# tell llvm to optimize for size (https://rustwasm.github.io/book/reference/code-size.html#tell-llvm-to-optimize-for-size-instead-of-speed) -opt-level = 'z' diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 1cd672951e..6abf4992c6 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -48,23 +48,3 @@ anoma_vp_prelude = {path = "../../vp_prelude"} proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} - -[patch.crates-io] -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -# TODO temp patch for , and more tba. -borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} -borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} - -[profile.release] -# smaller and faster wasm (https://rustwasm.github.io/book/reference/code-size.html#compiling-with-link-time-optimizations-lto) -lto = true -# simply terminate on panics, no unwinding -panic = "abort" -# tell llvm to optimize for size (https://rustwasm.github.io/book/reference/code-size.html#tell-llvm-to-optimize-for-size-instead-of-speed) -opt-level = 'z' From d28d087965c58eb3e3f3094cb6a624a582eec3cb Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 12 May 2022 16:39:48 +0100 Subject: [PATCH 007/373] wasm/wasm_source/Makefile: copy .wasm from workspace target/ dir --- wasm/wasm_source/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 2dfaa74894..a12cb2df7b 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -51,7 +51,7 @@ fmt-check: # Linker flag "-s" for stripping (https://github.com/rust-lang/cargo/issues/3483#issuecomment-431209957) $(wasms): %: RUSTFLAGS='-C link-arg=-s' $(cargo) build --release --target wasm32-unknown-unknown --features $@ && \ - cp "./target/wasm32-unknown-unknown/release/anoma_wasm.wasm" ../$@.wasm + cp "../target/wasm32-unknown-unknown/release/anoma_wasm.wasm" ../$@.wasm # `cargo check` one of the wasms, e.g. `make check_tx_transfer` $(patsubst %,check_%,$(wasms)): check_%: From 7c0790945b5c04a1d4de9a0fc9b1792fadcf4d15 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 12 May 2022 16:40:47 +0100 Subject: [PATCH 008/373] .drone.yml: use wasm/target instead of wasm/wasm_source/target --- .drone.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.drone.yml b/.drone.yml index 0fed4e8790..6b5d2d3705 100644 --- a/.drone.yml +++ b/.drone.yml @@ -31,9 +31,9 @@ steps: archive_format: gzip backend: s3 bucket: heliax-drone-cache-v2 - cache_key: 1-54-0/wasm/{{ checksum "wasm/wasm_source/Cargo.lock" }} + cache_key: 1-54-0/wasm/{{ checksum "wasm/Cargo.lock" }} mount: - - wasm/wasm_source/target + - wasm/target region: eu-west-1 restore: true environment: @@ -83,10 +83,10 @@ steps: image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/wasm:latest pull: never commands: - - rm -f ./wasm/wasm_source/target/.rustc_info.json - - rm -rf ./wasm/wasm_source/target/debug - - find ./wasm/wasm_source/target/release -maxdepth 1 -type f -delete - - find ./wasm/wasm_source/target/wasm32-unknown-unknown -maxdepth 1 -type f -delete + - rm -f ./wasm/target/.rustc_info.json + - rm -rf ./wasm/target/debug + - find ./wasm/target/release -maxdepth 1 -type f -delete + - find ./wasm/target/wasm32-unknown-unknown -maxdepth 1 -type f -delete depends_on: - test-wasm - check-wasm @@ -101,9 +101,9 @@ steps: archive_format: gzip backend: s3 bucket: heliax-drone-cache-v2 - cache_key: 1-54-0/wasm/{{ checksum "wasm/wasm_source/Cargo.lock" }} + cache_key: 1-54-0/wasm/{{ checksum "wasm/Cargo.lock" }} mount: - - wasm/wasm_source/target + - wasm/target override: false region: eu-west-1 rebuild: true @@ -162,9 +162,9 @@ steps: archive_format: gzip backend: s3 bucket: heliax-drone-cache-v2 - cache_key: 1-54-0/wasm/{{ checksum "wasm/wasm_source/Cargo.lock" }} + cache_key: 1-54-0/wasm/{{ checksum "wasm/Cargo.lock" }} mount: - - wasm/wasm_source/target + - wasm/target region: eu-west-1 restore: true environment: @@ -228,10 +228,10 @@ steps: image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/wasm:latest pull: never commands: - - rm -f ./wasm/wasm_source/target/.rustc_info.json - - rm -rf ./wasm/wasm_source/target/debug - - find ./wasm/wasm_source/target/release -maxdepth 1 -type f -delete - - find ./wasm/wasm_source/target/wasm32-unknown-unknown -maxdepth 1 -type f -delete + - rm -f ./wasm/target/.rustc_info.json + - rm -rf ./wasm/target/debug + - find ./wasm/target/release -maxdepth 1 -type f -delete + - find ./wasm/target/wasm32-unknown-unknown -maxdepth 1 -type f -delete depends_on: - test-wasm - check-wasm @@ -247,9 +247,9 @@ steps: archive_format: gzip backend: s3 bucket: heliax-drone-cache-v2 - cache_key: 1-54-0/wasm/{{ checksum "wasm/wasm_source/Cargo.lock" }} + cache_key: 1-54-0/wasm/{{ checksum "wasm/Cargo.lock" }} mount: - - wasm/wasm_source/target + - wasm/target override: false region: eu-west-1 rebuild: true From a439f695160418b8cc386912a155d7dbcc292dfe Mon Sep 17 00:00:00 2001 From: AnomaBot Date: Mon, 16 May 2022 14:40:14 +0000 Subject: [PATCH 009/373] [ci]: update drone configuration --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 6b5d2d3705..9618a39ba2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1097,6 +1097,6 @@ clone: disable: true --- kind: signature -hmac: 4c6a2d9c84b634a7417a897f5af3ac91c0f06230198e824160603b8931457c7c +hmac: b58fe25806b89a82f4b5aa83e327bf9e5a6e1b861032f9048e65fa992970c7da ... From 42b394fde61c76442938b0e8018fda8fb3f8c2eb Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 31 May 2022 12:09:36 +0100 Subject: [PATCH 010/373] ci: allow specifying an absolute ANOMA_WASM_DIR --- apps/src/lib/cli.rs | 8 +++----- apps/src/lib/cli/context.rs | 15 --------------- apps/src/lib/config/mod.rs | 2 +- 3 files changed, 4 insertions(+), 21 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index d131c4c230..1218d4805b 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1527,11 +1527,9 @@ pub mod args { )) .arg(WASM_DIR.def().about( "Directory with built WASM validity predicates, \ - transactions and matchmaker files. This must not be an \ - absolute path as the directory is nested inside the \ - chain directory. This value can also be set via \ - `ANOMA_WASM_DIR` environment variable, but the argument \ - takes precedence, if specified.", + transactions and matchmaker files. This value can also \ + be set via `ANOMA_WASM_DIR` environment variable, but \ + the argument takes precedence, if specified.", )) .arg(MODE.def().about( "The mode in which to run Anoma. Options are \n\t * \ diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 33b988c487..6d8b3cd3f2 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -72,26 +72,11 @@ impl Context { // If the WASM dir specified, put it in the config match global_args.wasm_dir.as_ref() { Some(wasm_dir) => { - if wasm_dir.is_absolute() { - eprintln!( - "The arg `--wasm-dir` cannot be an absolute path. It \ - is nested inside the chain directory." - ); - safe_exit(1); - } config.wasm_dir = wasm_dir.clone(); } None => { if let Ok(wasm_dir) = env::var(ENV_VAR_WASM_DIR) { let wasm_dir: PathBuf = wasm_dir.into(); - if wasm_dir.is_absolute() { - eprintln!( - "The env var `{}` cannot be an absolute path. It \ - is nested inside the chain directory.", - ENV_VAR_WASM_DIR - ); - safe_exit(1); - } config.wasm_dir = wasm_dir; } } diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 7c31ef43d8..b315ff3bfd 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -33,7 +33,7 @@ use crate::cli; /// Base directory contains global config and chain directories. pub const DEFAULT_BASE_DIR: &str = ".anoma"; -/// Default WASM dir. Note that WASM dirs are nested in chain dirs. +/// Default WASM dir. pub const DEFAULT_WASM_DIR: &str = "wasm"; /// The WASM checksums file contains the hashes of built WASMs. It is inside the /// WASM dir. From 30d0abafb4f6d811d1cf0575dcc9a8b1ef036555 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Mon, 20 Jun 2022 21:52:51 +0100 Subject: [PATCH 011/373] Add changelog --- .../unreleased/improvements/1148-allow-absolute-wasm-dir.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1148-allow-absolute-wasm-dir.md diff --git a/.changelog/unreleased/improvements/1148-allow-absolute-wasm-dir.md b/.changelog/unreleased/improvements/1148-allow-absolute-wasm-dir.md new file mode 100644 index 0000000000..1e267d6faf --- /dev/null +++ b/.changelog/unreleased/improvements/1148-allow-absolute-wasm-dir.md @@ -0,0 +1,2 @@ +- Allow specifying an absolute path for the wasm directory + ([#1148](https://github.com/anoma/anoma/issues/1148)) \ No newline at end of file From f8d92f70c873b51e5d95406529442cbf9ca4c90e Mon Sep 17 00:00:00 2001 From: AnomaBot Date: Thu, 30 Jun 2022 13:58:54 +0000 Subject: [PATCH 012/373] [ci]: update wasm checksums --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 42eb0294e3..694cd9ba7f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.753f415cfc1ab36b23afc113e6b988fd84e24e493b651906bda007471c6d767d.wasm", - "tx_from_intent.wasm": "tx_from_intent.9309a7f0ac8f7b57d897cd69e913cb7ce183e2172e255e2d808c471217a7486b.wasm", - "tx_ibc.wasm": "tx_ibc.5f526fcc143bc57988a3015e5c93250406a4a9ea5467f44d940eece3e0af781d.wasm", - "tx_init_account.wasm": "tx_init_account.7523feaefe42396b98728b6b8993648041b99c2c4b97faa85e1016fe0ef35ce0.wasm", - "tx_init_nft.wasm": "tx_init_nft.92c350bd1640aec55618155573f2d520a85676959fbcec548f5c6378419124f0.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.7ac4859d1ebd536d119c8936336ddd7ea5cb491b079261b2f83f03bf89255d3f.wasm", - "tx_init_validator.wasm": "tx_init_validator.83a93ba9a1c40c03ddb92012ee354743f8dff8c5796ebdc557654c2bd95a497c.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9c582bc9d4ee0f185a66c2c468290ee0668064987050779529b1a4db39a3989b.wasm", - "tx_transfer.wasm": "tx_transfer.f2773e82cd6519662f7d9dbdeb10a2aff9a5fc8ca54d144e6cff571b1fca97cd.wasm", - "tx_unbond.wasm": "tx_unbond.b4ae4df26a3501af40ab409d95b72bff27f953d2e2ebf20c3f7395a2454dad68.wasm", - "tx_update_vp.wasm": "tx_update_vp.23a6a4f18e826c67708b32b98be67c7c241fd1478424196ae47f972c7a1334e0.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.466a6be90f5a5a79965a2106b8a5a385accc1cdb6f66345c9f39e6488e4e2a05.wasm", - "tx_withdraw.wasm": "tx_withdraw.aabfff97bf82723436bdddbd584e8199a18355decc2f1c10818ad84c57e92182.wasm", - "vp_nft.wasm": "vp_nft.d85a9628f4702f31f54888b704745f8e868b7945208d40d24ead134b338557d1.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.07226b30af3f6c091b5acfd0e6f7794f31e363cbbdb5db9acb8a0086fff82005.wasm", - "vp_token.wasm": "vp_token.bad2c74e2788089ec2692b1fce0549466f57769336df774c8582076cd3fad136.wasm", - "vp_user.wasm": "vp_user.9299851563f7fdb4e8bc9b0f23ae948655b0a049e402b090ddb453df37eb98ff.wasm" + "tx_bond.wasm": "tx_bond.c05b5d99fca48f67b96c8a8d79919b3e3951e3897106a171c6067ec004bb0401.wasm", + "tx_from_intent.wasm": "tx_from_intent.6a2430e58cb648741f98c048307b12a42ae9be144c087379028712fb1596465b.wasm", + "tx_ibc.wasm": "tx_ibc.2879e0723e5caeb74d3ba23faea6fe51cd28e9f004f54e3861df5598ace49b52.wasm", + "tx_init_account.wasm": "tx_init_account.8fac7693aa87f91bf14814c742b7303579ce17b9708ccc8fd441ef52cfd72a6c.wasm", + "tx_init_nft.wasm": "tx_init_nft.9095d685fd30e9b4e7cd0639065f666496c9298508aaeba4f1dfea2e6581421a.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.aa166fdc323a570610e40332ce8510dc9ab9fdd550a21a076e4b36d33878bc73.wasm", + "tx_init_validator.wasm": "tx_init_validator.54b4c9701a239cde24635a46402ac89124b32ac5f096c2f8a55133c22db3f560.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.90401d10c334b01303b4e6a0e328df2e35de7a9e7b369256c7cfaca923caab04.wasm", + "tx_transfer.wasm": "tx_transfer.b8578ab1ddc2ebf03ad7ecb13d6e2518da8a1a91a8430ba01c8e32f63ce1e9ac.wasm", + "tx_unbond.wasm": "tx_unbond.cf26d92348735310546446b143ab8d0c820bd98e7542bbe499bdf956a772fc4c.wasm", + "tx_update_vp.wasm": "tx_update_vp.8f591abf46dace23ea3ee13f05bc6dc27ea6864aced411ca8cad8052203e81bc.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.03fa881237bf992bc647f39a6553d0ce280e2daf8848c2ca42e0c087de25aaa0.wasm", + "tx_withdraw.wasm": "tx_withdraw.06eddda2068aa3d6979670259ca4d27551f0c19677c56998ad7b40e13eb6884d.wasm", + "vp_nft.wasm": "vp_nft.5b5b7c8a8ea29c2333151ecd95051e748dceab0b28962e628f7820300785aab6.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.dadc69be9fd14384530e2dc26473d98e3c7f4c1dcc73d8f79f490fc2506a5ff8.wasm", + "vp_token.wasm": "vp_token.551bdfd88689973404e0b118496b2409b9332bc25fc063cc4c766cd405f16294.wasm", + "vp_user.wasm": "vp_user.a5363e37f16b3d5e7e060ba0576dbaf6208c33cc9b0ff4e16e1a151722dbbfcc.wasm" } \ No newline at end of file From e1a909b88abca79d5a528c11311499337e8ab19f Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 26 May 2022 16:41:38 +0200 Subject: [PATCH 013/373] [fix]: governance overflow, proposal validation --- apps/src/lib/client/rpc.rs | 121 +++++------ apps/src/lib/client/tx.rs | 62 ++++-- .../lib/node/ledger/shell/finalize_block.rs | 184 +---------------- apps/src/lib/node/ledger/shell/governance.rs | 195 ++++++++++++++++++ apps/src/lib/node/ledger/shell/mod.rs | 1 + shared/src/ledger/governance/parameters.rs | 18 ++ shared/src/ledger/governance/utils.rs | 35 ++-- shared/src/types/token.rs | 6 + 8 files changed, 347 insertions(+), 275 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/governance.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d85ff0b7c0..683f8356ea 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -7,8 +7,9 @@ use std::fs::File; use std::io::{self, Write}; use std::iter::Iterator; +use anoma::ledger::governance::parameters::GovParams; use anoma::ledger::governance::storage as gov_storage; -use anoma::ledger::governance::utils::Votes; +use anoma::ledger::governance::utils::{Votes, VotePower}; use anoma::ledger::parameters::{storage as param_storage, EpochDuration}; use anoma::ledger::pos::types::{ Epoch as PosEpoch, VotingPower, WeightedValidator, @@ -431,7 +432,7 @@ pub async fn query_proposal_result( } }; } else { - eprintln!("Either id or offline should be used as arguments."); + eprintln!("Either --proposal-id or --data-path should be provided as arguments."); cli::safe_exit(1) } } @@ -444,45 +445,8 @@ pub async fn query_protocol_parameters( ) { let client = HttpClient::new(args.query.ledger_address).unwrap(); - println!("Goveranance parameters"); - let key = gov_storage::get_max_proposal_code_size_key(); - let max_proposal_code_size = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Max. proposal code size: {}", - "", max_proposal_code_size - ); - - let key = gov_storage::get_max_proposal_content_key(); - let max_proposal_content = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Max. proposal content size: {}", - "", max_proposal_content - ); - - let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!("{:4}Min. proposal funds: {}", "", min_proposal_fund); - - let key = gov_storage::get_min_proposal_grace_epoch_key(); - let min_proposal_grace_epoch = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Min. proposal grace epoch: {}", - "", min_proposal_grace_epoch - ); - - let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!("{:4}Min. proposal period: {}", "", min_proposal_period); + let gov_parameters = get_governance_parameters(&client).await; + println!("Governance Parameters\n {:4}", gov_parameters); println!("Protocol parameters"); let key = param_storage::get_epoch_storage_key(); @@ -1575,9 +1539,9 @@ pub async fn get_proposal_votes( query_storage_prefix::(client.clone(), vote_prefix_key) .await; - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1587,7 +1551,7 @@ pub async fn get_proposal_votes( if vote.is_yay() && validators.contains(&voter_address) { let amount = get_validator_stake(client, epoch, &voter_address).await; - yay_validators.insert(voter_address, amount); + yay_validators.insert(voter_address, VotePower::from(amount)); } else if !validators.contains(&voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key) @@ -1604,9 +1568,9 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators.insert(voter_address, amount); + yay_delegators.insert(voter_address, VotePower::from(amount)); } else { - nay_delegators.insert(voter_address, amount); + nay_delegators.insert(voter_address, VotePower::from(amount)); } } } @@ -1629,9 +1593,9 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1658,7 +1622,7 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators.insert(proposal_vote.address, amount); + yay_validators.insert(proposal_vote.address, VotePower::from(amount)); } else if is_delegator_at( client, &proposal_vote.address, @@ -1686,9 +1650,9 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators.insert(validator_address, amount); + yay_delegators.insert(validator_address, VotePower::from(amount)); } else { - nay_delegators.insert(validator_address, amount); + nay_delegators.insert(validator_address, VotePower::from(amount)); } } } @@ -1718,7 +1682,7 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_stacked_tokens = Amount::from(0); + let mut total_yay_stacked_tokens = VotePower::from(0 as u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -1805,8 +1769,8 @@ pub async fn get_total_staked_tokes( client: &HttpClient, epoch: Epoch, validators: &[Address], -) -> token::Amount { - let mut total = Amount::from(0); +) -> VotePower { + let mut total = VotePower::from(0 as u64); for validator in validators { total += get_validator_stake(client, epoch, validator).await; @@ -1818,7 +1782,7 @@ async fn get_validator_stake( client: &HttpClient, epoch: Epoch, validator: &Address, -) -> token::Amount { +) -> VotePower { let total_voting_power_key = pos::validator_total_deltas_key(validator); let total_voting_power = query_storage_value::( client, @@ -1828,9 +1792,12 @@ async fn get_validator_stake( .expect("Total deltas should be defined"); let epoched_total_voting_power = total_voting_power.get(epoch); if let Some(epoched_total_voting_power) = epoched_total_voting_power { - token::Amount::from_change(epoched_total_voting_power) + match VotePower::try_from(epoched_total_voting_power) { + Ok(voting_power) => voting_power, + Err(_) => VotePower::from(0 as u64), + } } else { - token::Amount::from(0) + VotePower::from(0 as u64) } } @@ -1853,3 +1820,39 @@ pub async fn get_delegators_delegation( } delegation_addresses } + +pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { + let key = gov_storage::get_max_proposal_code_size_key(); + let max_proposal_code_size = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_max_proposal_content_key(); + let max_proposal_content_size = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_fund_key(); + let min_proposal_fund = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_grace_epoch_key(); + let min_proposal_grace_epochs = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_period_key(); + let min_proposal_period = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + return GovParams { + min_proposal_fund: u64::from(min_proposal_fund), + max_proposal_code_size: u64::from(max_proposal_code_size), + min_proposal_period: u64::from(min_proposal_period), + max_proposal_content_size: u64::from(max_proposal_content_size), + min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), + } + +} \ No newline at end of file diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 0bc8b72954..ab74fe3a19 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -520,7 +520,43 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let proposal: Proposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let signer = WalletAddress::new(proposal.clone().author.to_string()); + let goverance_parameters = rpc::get_governance_parameters(&client).await; + let current_epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; + + if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { + eprintln!( + "Invalid proposal start epoch: {} must be greater than current \ + epoch {} and a multiple of 3", + proposal.voting_start_epoch, current_epoch + ); + safe_exit(1) + } else if proposal.voting_end_epoch <= proposal.voting_start_epoch + || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 + < goverance_parameters.min_proposal_period || proposal.voting_end_epoch.0 % 3 == 0 + { + eprintln!( + "Invalid proposal end epoch: difference between proposal start \ + and end epoch must be at least {} and end epoch must be a multiple of 3", + goverance_parameters.min_proposal_period + ); + safe_exit(1) + } else if proposal.grace_epoch <= proposal.voting_end_epoch + || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 + < goverance_parameters.min_proposal_grace_epochs + { + eprintln!( + "Invalid proposal grace epoch: difference between proposal grace \ + and end epoch must be at least {}", + goverance_parameters.min_proposal_grace_epochs + ); + safe_exit(1) + } if args.offline { let signer = ctx.get(&signer); @@ -544,8 +580,6 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } } } else { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let tx_data: Result = proposal.clone().try_into(); let init_proposal_data = if let Ok(data) = tx_data { data @@ -554,35 +588,21 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { safe_exit(1) }; - let min_proposal_funds_key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = - rpc::query_storage_value(&client, &min_proposal_funds_key) - .await - .unwrap(); let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < min_proposal_funds { + if balance < token::Amount::from(goverance_parameters.min_proposal_fund) { eprintln!( "Address {} doesn't have enough funds.", &proposal.author ); safe_exit(1); } - let min_proposal_funds_key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = - rpc::query_storage_value(&client, &min_proposal_funds_key) - .await - .unwrap(); - let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) - .await - .unwrap_or_default(); - if balance < min_proposal_funds { - eprintln!( - "Address {} doesn't have enough funds.", - &proposal.author - ); + if init_proposal_data.content.len() + > goverance_parameters.max_proposal_content_size as usize + { + eprintln!("Proposal content size too big.",); safe_exit(1); } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1b839a6825..337af29358 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,15 +1,6 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell -use anoma::ledger::governance::storage as gov_storage; -use anoma::ledger::governance::utils::{ - compute_tally, get_proposal_votes, ProposalEvent, -}; -use anoma::ledger::governance::vp::ADDRESS as gov_address; -use anoma::ledger::storage::types::encode; -use anoma::ledger::treasury::ADDRESS as treasury_address; -use anoma::types::address::{xan as m1t, Address}; -use anoma::types::governance::TallyResult; -use anoma::types::storage::{BlockHash, Epoch, Header}; +use anoma::types::storage::{BlockHash, Header}; #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::Misbehavior as Evidence; #[cfg(not(feature = "ABCI"))] @@ -19,8 +10,8 @@ use tendermint_proto_abci::abci::Evidence; #[cfg(feature = "ABCI")] use tendermint_proto_abci::crypto::PublicKey as TendermintPublicKey; +use super::governance::execute_governance_proposals; use super::*; -use crate::node::ledger::events::EventType; impl Shell where @@ -56,175 +47,8 @@ where let (height, new_epoch) = self.update_state(req.header, req.hash, req.byzantine_validators); - if new_epoch { - for id in std::mem::take(&mut self.proposal_data) { - let proposal_funds_key = gov_storage::get_funds_key(id); - let proposal_start_epoch_key = - gov_storage::get_voting_start_epoch_key(id); - - let funds = self - .read_storage_key::(&proposal_funds_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal funds.".to_string(), - ) - })?; - let proposal_start_epoch = self - .read_storage_key::(&proposal_start_epoch_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal start_epoch.".to_string(), - ) - })?; - - let votes = - get_proposal_votes(&self.storage, proposal_start_epoch, id); - let tally_result = - compute_tally(&self.storage, proposal_start_epoch, votes); - - let transfer_address = match tally_result { - TallyResult::Passed => { - let proposal_author_key = - gov_storage::get_author_key(id); - let proposal_author = self - .read_storage_key::
(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })?; - - let proposal_code_key = - gov_storage::get_proposal_code_key(id); - let proposal_code = - self.read_storage_key_bytes(&proposal_code_key); - match proposal_code { - Some(proposal_code) => { - let tx = - Tx::new(proposal_code, Some(encode(&id))); - let tx_type = TxType::Decrypted( - DecryptedTx::Decrypted(tx), - ); - let pending_execution_key = - gov_storage::get_proposal_execution_key(id); - self.storage - .write(&pending_execution_key, "") - .expect( - "Should be able to write to storage.", - ); - let tx_result = protocol::apply_tx( - tx_type, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ - &mut BlockGasMeter::default(), - &mut self.write_log, - &self.storage, - &mut self.vp_wasm_cache, - &mut self.tx_wasm_cache, - ); - self.storage - .delete(&pending_execution_key) - .expect( - "Should be able to delete the storage.", - ); - match tx_result { - Ok(tx_result) => { - if tx_result.is_accepted() { - self.write_log.commit_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed, - id, - true, - true, - ) - .into(); - response - .events - .push(proposal_event); - - proposal_author - } else { - self.write_log.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response - .events - .push(proposal_event); - - treasury_address - } - } - Err(_e) => { - self.write_log.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response.events.push(proposal_event); - - treasury_address - } - } - } - None => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - - proposal_author - } - } - } - TallyResult::Rejected | TallyResult::Unknown => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Rejected, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - - treasury_address - } - }; - - // transfer proposal locked funds - self.storage.transfer( - &m1t(), - funds, - &gov_address, - &transfer_address, - ); - } - } + let _proposals_result = + execute_governance_proposals(self, new_epoch, &mut response)?; for processed_tx in &req.txs { let tx = if let Ok(tx) = Tx::try_from(processed_tx.tx.as_ref()) { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs new file mode 100644 index 0000000000..e65ede6c07 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -0,0 +1,195 @@ +use anoma::ledger::governance::storage as gov_storage; +use anoma::ledger::governance::utils::{ + compute_tally, get_proposal_votes, ProposalEvent, +}; +use anoma::ledger::governance::vp::ADDRESS as gov_address; +use anoma::ledger::storage::types::encode; +use anoma::ledger::storage::{DBIter, StorageHasher, DB}; +use anoma::ledger::treasury::ADDRESS as treasury_address; +use anoma::types::address::{xan as m1t, Address}; +use anoma::types::governance::TallyResult; +use anoma::types::storage::Epoch; +use anoma::types::token; + +use super::*; +use crate::node::ledger::events::EventType; + +pub struct ProposalsResult { + passed: Vec, + rejected: Vec, +} + +pub fn execute_governance_proposals( + shell: &mut Shell, + new_epoch: bool, + response: &mut shim::response::FinalizeBlock, +) -> Result +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + let mut proposals_result = ProposalsResult { + passed: Vec::new(), + rejected: Vec::new(), + }; + + if !new_epoch { + return Ok(proposals_result); + } + + for id in std::mem::take(&mut shell.proposal_data) { + let proposal_funds_key = gov_storage::get_funds_key(id); + let proposal_start_epoch_key = + gov_storage::get_voting_start_epoch_key(id); + + let funds = shell + .read_storage_key::(&proposal_funds_key) + .ok_or_else(|| { + Error::BadProposal(id, "Invalid proposal funds.".to_string()) + })?; + let proposal_start_epoch = shell + .read_storage_key::(&proposal_start_epoch_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal start_epoch.".to_string(), + ) + })?; + + let votes = + get_proposal_votes(&shell.storage, proposal_start_epoch, id); + let tally_result = + compute_tally(&shell.storage, proposal_start_epoch, votes); + + let transfer_address = match tally_result { + TallyResult::Passed => { + let proposal_author_key = gov_storage::get_author_key(id); + let proposal_author = shell + .read_storage_key::
(&proposal_author_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })?; + + let proposal_code_key = gov_storage::get_proposal_code_key(id); + let proposal_code = + shell.read_storage_key_bytes(&proposal_code_key); + match proposal_code { + Some(proposal_code) => { + let tx = Tx::new(proposal_code, Some(encode(&id))); + let tx_type = + TxType::Decrypted(DecryptedTx::Decrypted(tx)); + let pending_execution_key = + gov_storage::get_proposal_execution_key(id); + shell + .storage + .write(&pending_execution_key, "") + .expect("Should be able to write to storage."); + let tx_result = protocol::apply_tx( + tx_type, + 0, /* this is used to compute the fee + * based on the code size. We dont + * need it here. */ + &mut BlockGasMeter::default(), + &mut shell.write_log, + &shell.storage, + &mut shell.vp_wasm_cache, + &mut shell.tx_wasm_cache, + ); + shell + .storage + .delete(&pending_execution_key) + .expect("Should be able to delete the storage."); + match tx_result { + Ok(tx_result) => { + if tx_result.is_accepted() { + shell.write_log.commit_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + true, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.passed.push(id); + + proposal_author + } else { + shell.write_log.drop_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + } + Err(_e) => { + shell.write_log.drop_tx(); + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + } + } + None => { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.passed.push(id); + + proposal_author + } + } + } + TallyResult::Rejected | TallyResult::Unknown => { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Rejected, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + }; + + // transfer proposal locked funds + shell + .storage + .transfer(&m1t(), funds, &gov_address, &transfer_address); + } + + Ok(proposals_result) +} diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 9b787a32cb..846fa51ae3 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -11,6 +11,7 @@ mod init_chain; mod prepare_proposal; mod process_proposal; mod queries; +mod governance; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; diff --git a/shared/src/ledger/governance/parameters.rs b/shared/src/ledger/governance/parameters.rs index 79c3b4d5b6..5b280491b9 100644 --- a/shared/src/ledger/governance/parameters.rs +++ b/shared/src/ledger/governance/parameters.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use borsh::{BorshDeserialize, BorshSerialize}; use super::storage as gov_storage; @@ -30,6 +32,22 @@ pub struct GovParams { pub min_proposal_grace_epochs: u64, } +impl Display for GovParams { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Min. proposal fund: {}\nMax. proposal code size: {}\nMin. \ + proposal period: {}\nMax. proposal content size: {}\nMin. \ + proposal grace epochs: {}", + self.min_proposal_fund, + self.max_proposal_code_size, + self.min_proposal_period, + self.max_proposal_content_size, + self.min_proposal_grace_epochs + ) + } +} + impl Default for GovParams { fn default() -> Self { Self { diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 60f3d4b1b5..256b86df10 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -15,15 +15,17 @@ use crate::types::governance::{ProposalVote, TallyResult}; use crate::types::storage::{Epoch, Key}; use crate::types::token; +pub type VotePower = u128; + /// Proposal structure holding votes information necessary to compute the /// outcome pub struct Votes { /// Map from validators who votes yay to their total stake amount - pub yay_validators: HashMap, + pub yay_validators: HashMap, /// Map from delegation who votes yay to their bond amount - pub yay_delegators: HashMap, + pub yay_delegators: HashMap, /// Map from delegation who votes nay to their bond amount - pub nay_delegators: HashMap, + pub nay_delegators: HashMap, } /// Proposal errors @@ -93,7 +95,7 @@ where nay_delegators, } = votes; - let mut total_yay_stacked_tokens = token::Amount::from(0); + let mut total_yay_stacked_tokens = VotePower::from(0 as u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -110,7 +112,7 @@ where if yay_validators.contains_key(&validator_address) { total_yay_stacked_tokens -= amount; } - } + } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { TallyResult::Passed @@ -205,9 +207,9 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -236,12 +238,12 @@ where if vote.is_yay() { yay_delegators.insert( address.clone(), - amount, + VotePower::from(amount), ); } else { nay_delegators.insert( address.clone(), - amount, + VotePower::from(amount), ); } } @@ -300,14 +302,14 @@ fn get_total_stacked_tokens( storage: &Storage, epoch: Epoch, validators: &[Address], -) -> token::Amount +) -> VotePower where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { return validators .iter() - .fold(token::Amount::from(0), |acc, validator| { + .fold(VotePower::from(0 as u64), |acc, validator| { acc + get_validator_stake(storage, epoch, validator) }); } @@ -316,7 +318,7 @@ fn get_validator_stake( storage: &Storage, epoch: Epoch, validator: &Address, -) -> token::Amount +) -> VotePower where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, @@ -331,9 +333,12 @@ where if let Some(total_delta) = total_delta { let epoched_total_delta = total_delta.get(epoch); if let Some(epoched_total_delta) = epoched_total_delta { - return token::Amount::from_change(epoched_total_delta); + match VotePower::try_from(epoched_total_delta) { + Ok(voting_power) => return voting_power, + Err(_) => return VotePower::from(0 as u64), + } } } } - token::Amount::from(0) + VotePower::from(0 as u64) } diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index f3e4cd4ed2..4942051826 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -139,6 +139,12 @@ impl From for u64 { } } +impl From for u128 { + fn from(amount: Amount) -> Self { + u128::from(amount.micro) + } +} + impl Add for Amount { type Output = Amount; From cbe403fc84fbddfb8a7ce94dde7199ef4287dec4 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 26 May 2022 17:23:36 +0200 Subject: [PATCH 014/373] [feat]: added total votes to query --- apps/src/lib/client/rpc.rs | 20 +++++++++++++++----- shared/src/ledger/governance/utils.rs | 4 +--- shared/src/types/governance.rs | 25 ++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 683f8356ea..d591f549f2 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -9,7 +9,7 @@ use std::iter::Iterator; use anoma::ledger::governance::parameters::GovParams; use anoma::ledger::governance::storage as gov_storage; -use anoma::ledger::governance::utils::{Votes, VotePower}; +use anoma::ledger::governance::utils::Votes; use anoma::ledger::parameters::{storage as param_storage, EpochDuration}; use anoma::ledger::pos::types::{ Epoch as PosEpoch, VotingPower, WeightedValidator, @@ -20,7 +20,7 @@ use anoma::ledger::pos::{ use anoma::ledger::treasury::storage as treasury_storage; use anoma::types::address::Address; use anoma::types::governance::{ - OfflineProposal, OfflineVote, ProposalVote, TallyResult, + OfflineProposal, OfflineVote, ProposalVote, TallyResult, VotePower, ProposalResult, }; use anoma::types::key::*; use anoma::types::storage::{Epoch, PrefixValue}; @@ -1671,7 +1671,7 @@ pub async fn compute_tally( client: &HttpClient, epoch: Epoch, votes: Votes, -) -> TallyResult { +) -> ProposalResult { let validators = get_all_validators(client, epoch).await; let total_stacked_tokens = get_total_staked_tokes(client, epoch, &validators).await; @@ -1702,9 +1702,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - TallyResult::Passed + return ProposalResult{ + result: TallyResult::Passed, + total_voting_power: total_stacked_tokens, + total_yay_power: total_yay_stacked_tokens, + total_nay_power: 0, + } } else { - TallyResult::Rejected + return ProposalResult{ + result: TallyResult::Rejected, + total_voting_power: total_stacked_tokens, + total_yay_power: total_yay_stacked_tokens, + total_nay_power: 0, + } } } diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 256b86df10..29a3f20931 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -11,12 +11,10 @@ use crate::ledger::pos; use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::types::address::Address; -use crate::types::governance::{ProposalVote, TallyResult}; +use crate::types::governance::{ProposalVote, TallyResult, VotePower}; use crate::types::storage::{Epoch, Key}; use crate::types::token; -pub type VotePower = u128; - /// Proposal structure holding votes information necessary to compute the /// outcome pub struct Votes { diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index c8bf57469f..8ff1d96993 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -15,6 +15,8 @@ use super::key::SigScheme; use super::storage::Epoch; use super::transaction::governance::InitProposalData; +pub type VotePower = u128; + #[derive( Debug, Clone, @@ -83,7 +85,28 @@ pub enum TallyResult { Unknown, } -impl fmt::Display for TallyResult { +/// The result with votes of a proposal +pub struct ProposalResult { + pub result: TallyResult, + pub total_voting_power: VotePower, + pub total_yay_power: VotePower, + pub total_nay_power: VotePower, +} + +impl Display for ProposalResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} with {} yay votes over {} ({:.2}%)", + self.result, + self.total_yay_power, + self.total_voting_power, + (self.total_yay_power / self.total_voting_power) * 100 + ) + } +} + +impl Display for TallyResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TallyResult::Passed => write!(f, "passed"), From 1883ad77ffe40090bb7a6501e383286df045c5cb Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 26 May 2022 17:54:34 +0200 Subject: [PATCH 015/373] [misc]: clippy, fmt --- apps/src/lib/client/rpc.rs | 44 ++++++++++++++++----------- apps/src/lib/client/tx.rs | 13 +++++--- apps/src/lib/node/ledger/shell/mod.rs | 2 +- shared/src/ledger/governance/utils.rs | 10 +++--- shared/src/types/governance.rs | 5 +++ 5 files changed, 46 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d591f549f2..1bb1d35291 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -20,7 +20,8 @@ use anoma::ledger::pos::{ use anoma::ledger::treasury::storage as treasury_storage; use anoma::types::address::Address; use anoma::types::governance::{ - OfflineProposal, OfflineVote, ProposalVote, TallyResult, VotePower, ProposalResult, + OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, + VotePower, }; use anoma::types::key::*; use anoma::types::storage::{Epoch, PrefixValue}; @@ -432,7 +433,10 @@ pub async fn query_proposal_result( } }; } else { - eprintln!("Either --proposal-id or --data-path should be provided as arguments."); + eprintln!( + "Either --proposal-id or --data-path should be provided \ + as arguments." + ); cli::safe_exit(1) } } @@ -1568,9 +1572,11 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators.insert(voter_address, VotePower::from(amount)); + yay_delegators + .insert(voter_address, VotePower::from(amount)); } else { - nay_delegators.insert(voter_address, VotePower::from(amount)); + nay_delegators + .insert(voter_address, VotePower::from(amount)); } } } @@ -1622,7 +1628,8 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators.insert(proposal_vote.address, VotePower::from(amount)); + yay_validators + .insert(proposal_vote.address, VotePower::from(amount)); } else if is_delegator_at( client, &proposal_vote.address, @@ -1650,9 +1657,11 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators.insert(validator_address, VotePower::from(amount)); + yay_delegators + .insert(validator_address, VotePower::from(amount)); } else { - nay_delegators.insert(validator_address, VotePower::from(amount)); + nay_delegators + .insert(validator_address, VotePower::from(amount)); } } } @@ -1682,7 +1691,7 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_stacked_tokens = VotePower::from(0 as u64); + let mut total_yay_stacked_tokens = VotePower::from(0_u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -1702,19 +1711,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - return ProposalResult{ + return ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - } + }; } else { - return ProposalResult{ + return ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - } + }; } } @@ -1780,7 +1789,7 @@ pub async fn get_total_staked_tokes( epoch: Epoch, validators: &[Address], ) -> VotePower { - let mut total = VotePower::from(0 as u64); + let mut total = VotePower::from(0_u64); for validator in validators { total += get_validator_stake(client, epoch, validator).await; @@ -1804,10 +1813,10 @@ async fn get_validator_stake( if let Some(epoched_total_voting_power) = epoched_total_voting_power { match VotePower::try_from(epoched_total_voting_power) { Ok(voting_power) => voting_power, - Err(_) => VotePower::from(0 as u64), + Err(_) => VotePower::from(0_u64), } } else { - VotePower::from(0 as u64) + VotePower::from(0_u64) } } @@ -1863,6 +1872,5 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { min_proposal_period: u64::from(min_proposal_period), max_proposal_content_size: u64::from(max_proposal_content_size), min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), - } - -} \ No newline at end of file + }; +} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ab74fe3a19..18000c24dd 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -529,7 +529,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { + if proposal.voting_start_epoch <= current_epoch + || proposal.voting_start_epoch.0 % 3 == 0 + { eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of 3", @@ -538,11 +540,13 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { safe_exit(1) } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - < goverance_parameters.min_proposal_period || proposal.voting_end_epoch.0 % 3 == 0 + < goverance_parameters.min_proposal_period + || proposal.voting_end_epoch.0 % 3 == 0 { eprintln!( "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and end epoch must be a multiple of 3", + and end epoch must be at least {} and end epoch must be a \ + multiple of 3", goverance_parameters.min_proposal_period ); safe_exit(1) @@ -591,7 +595,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < token::Amount::from(goverance_parameters.min_proposal_fund) { + if balance < token::Amount::from(goverance_parameters.min_proposal_fund) + { eprintln!( "Address {} doesn't have enough funds.", &proposal.author diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 846fa51ae3..0343da3432 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -6,12 +6,12 @@ //! (unless we can simply overwrite them in the next block). //! More info in . mod finalize_block; +mod governance; mod init_chain; #[cfg(not(feature = "ABCI"))] mod prepare_proposal; mod process_proposal; mod queries; -mod governance; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 29a3f20931..7957849dde 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -93,7 +93,7 @@ where nay_delegators, } = votes; - let mut total_yay_stacked_tokens = VotePower::from(0 as u64); + let mut total_yay_stacked_tokens = VotePower::from(0_u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -110,7 +110,7 @@ where if yay_validators.contains_key(&validator_address) { total_yay_stacked_tokens -= amount; } - } + } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { TallyResult::Passed @@ -307,7 +307,7 @@ where { return validators .iter() - .fold(VotePower::from(0 as u64), |acc, validator| { + .fold(VotePower::from(0_u64), |acc, validator| { acc + get_validator_stake(storage, epoch, validator) }); } @@ -333,10 +333,10 @@ where if let Some(epoched_total_delta) = epoched_total_delta { match VotePower::try_from(epoched_total_delta) { Ok(voting_power) => return voting_power, - Err(_) => return VotePower::from(0 as u64), + Err(_) => return VotePower::from(0_u64), } } } } - VotePower::from(0 as u64) + VotePower::from(0_u64) } diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index 8ff1d96993..abe65f9d37 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -15,6 +15,7 @@ use super::key::SigScheme; use super::storage::Epoch; use super::transaction::governance::InitProposalData; +/// Type alias for vote power pub type VotePower = u128; #[derive( @@ -87,9 +88,13 @@ pub enum TallyResult { /// The result with votes of a proposal pub struct ProposalResult { + /// The result of a proposal pub result: TallyResult, + /// The total voting power during the proposal tally pub total_voting_power: VotePower, + /// The total voting power from yay votes pub total_yay_power: VotePower, + /// The total voting power from nay votes (unused at the moment) pub total_nay_power: VotePower, } From 79acf9d97543eb5dfb305ab99152a483f639ce38 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 11:25:09 +0200 Subject: [PATCH 016/373] [fix]: clippy, fmt --- apps/src/lib/client/rpc.rs | 17 ++++++++--------- apps/src/lib/client/tx.rs | 12 +++++++----- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 1bb1d35291..ac9aa02fdd 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1555,7 +1555,7 @@ pub async fn get_proposal_votes( if vote.is_yay() && validators.contains(&voter_address) { let amount = get_validator_stake(client, epoch, &voter_address).await; - yay_validators.insert(voter_address, VotePower::from(amount)); + yay_validators.insert(voter_address, amount); } else if !validators.contains(&voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key) @@ -1628,8 +1628,7 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators - .insert(proposal_vote.address, VotePower::from(amount)); + yay_validators.insert(proposal_vote.address, amount); } else if is_delegator_at( client, &proposal_vote.address, @@ -1711,19 +1710,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - return ProposalResult { + ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - }; + } } else { - return ProposalResult { + ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - }; + } } } @@ -1866,11 +1865,11 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { .await .expect("Parameter should be definied."); - return GovParams { + GovParams { min_proposal_fund: u64::from(min_proposal_fund), max_proposal_code_size: u64::from(max_proposal_code_size), min_proposal_period: u64::from(min_proposal_period), max_proposal_content_size: u64::from(max_proposal_content_size), min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), - }; + } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 18000c24dd..fed5d07f47 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -12,7 +12,6 @@ use anoma::types::governance::{ use anoma::types::key::*; use anoma::types::nft::{self, Nft, NftToken}; use anoma::types::storage::Epoch; -use anoma::types::token::Amount; use anoma::types::transaction::governance::{ InitProposalData, VoteProposalData, }; @@ -529,13 +528,15 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch <= current_epoch + if proposal.voting_start_epoch >= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { eprintln!( "Invalid proposal start epoch: {} must be greater than current \ - epoch {} and a multiple of 3", - proposal.voting_start_epoch, current_epoch + epoch {} and a multiple of {}", + proposal.voting_start_epoch, + current_epoch, + goverance_parameters.min_proposal_period ); safe_exit(1) } else if proposal.voting_end_epoch <= proposal.voting_start_epoch @@ -546,7 +547,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { eprintln!( "Invalid proposal end epoch: difference between proposal start \ and end epoch must be at least {} and end epoch must be a \ - multiple of 3", + multiple of {}", + goverance_parameters.min_proposal_period, goverance_parameters.min_proposal_period ); safe_exit(1) From 6e6869b64df3fc9de31373d8898753c28c73b177 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 11:48:03 +0200 Subject: [PATCH 017/373] [fix]: clippy, fmt --- apps/src/lib/client/rpc.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ac9aa02fdd..4f0550ea3e 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1841,35 +1841,35 @@ pub async fn get_delegators_delegation( pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { let key = gov_storage::get_max_proposal_code_size_key(); - let max_proposal_code_size = query_storage_value::(&client, &key) + let max_proposal_code_size = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_max_proposal_content_key(); - let max_proposal_content_size = query_storage_value::(&client, &key) + let max_proposal_content_size = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(&client, &key) + let min_proposal_fund = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_grace_epoch_key(); - let min_proposal_grace_epochs = query_storage_value::(&client, &key) + let min_proposal_grace_epochs = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(&client, &key) + let min_proposal_period = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); GovParams { min_proposal_fund: u64::from(min_proposal_fund), - max_proposal_code_size: u64::from(max_proposal_code_size), - min_proposal_period: u64::from(min_proposal_period), - max_proposal_content_size: u64::from(max_proposal_content_size), - min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), + max_proposal_code_size, + min_proposal_period, + max_proposal_content_size, + min_proposal_grace_epochs, } } From ddc5d0e34971d8bf36c6c074951866b262c7ba96 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 12:47:28 +0200 Subject: [PATCH 018/373] [fix]: bad validation condition --- apps/src/lib/client/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index fed5d07f47..5831f187a1 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -528,7 +528,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch >= current_epoch + if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { eprintln!( From c27dc7d49433e60e3ff842921a068a7aa43745e8 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:37:28 +0200 Subject: [PATCH 019/373] [fix]: governance vp author address, proposal submission validation --- apps/src/lib/client/tx.rs | 6 ++++-- shared/src/ledger/governance/vp.rs | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 5831f187a1..5e93ce3304 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -529,8 +529,10 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; if proposal.voting_start_epoch <= current_epoch - || proposal.voting_start_epoch.0 % 3 == 0 + || proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period != 0 { + println!("{}", proposal.voting_start_epoch <= current_epoch); + println!("{}", proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period == 0); eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of {}", @@ -542,7 +544,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 < goverance_parameters.min_proposal_period - || proposal.voting_end_epoch.0 % 3 == 0 + || proposal.voting_end_epoch.0 % 3 != 0 { eprintln!( "Invalid proposal end epoch: difference between proposal start \ diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 6ccfc7a33e..aa66fa95d6 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -88,15 +88,18 @@ where let has_pre_author = ctx.has_key_pre(&author_key).ok(); match (has_pre_author, author) { (Some(has_pre_author), Some(author)) => { - // TODO: if author is an implicit address, we should asssume its - // existence we should reuse the same logic as in - // check_address_existence in shared/src/vm/host_env.rs - let address_exist_key = Key::validity_predicate(&author); - let address_exist = ctx.has_key_post(&address_exist_key).ok(); - if let Some(address_exist) = address_exist { - !has_pre_author && verifiers.contains(&author) && address_exist - } else { - false + match author { + Address::Established(_) => { + let address_exist_key = Key::validity_predicate(&author); + let address_exist = ctx.has_key_post(&address_exist_key).ok(); + if let Some(address_exist) = address_exist { + !has_pre_author && verifiers.contains(&author) && address_exist + } else { + false + } + }, + Address::Implicit(_) => !has_pre_author && verifiers.contains(&author), + Address::Internal(_) => return false, } } _ => false, From c9293aecf0aa612851cd98da3fe6006d4bd64fad Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:44:50 +0200 Subject: [PATCH 020/373] [feat]: vote transaction validation --- apps/src/lib/client/tx.rs | 28 +++++++++++++++++++++++++--- shared/src/ledger/governance/vp.rs | 30 ++++++++++++++++-------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 5e93ce3304..4e9ae7681f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -529,10 +529,17 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; if proposal.voting_start_epoch <= current_epoch - || proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period != 0 + || proposal.voting_start_epoch.0 + % goverance_parameters.min_proposal_period + != 0 { println!("{}", proposal.voting_start_epoch <= current_epoch); - println!("{}", proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period == 0); + println!( + "{}", + proposal.voting_start_epoch.0 + % goverance_parameters.min_proposal_period + == 0 + ); eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of {}", @@ -679,6 +686,8 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let current_epoch = + rpc::query_epoch(args::Query { ledger_address }).await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); @@ -692,6 +701,14 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { match proposal_start_epoch { Some(epoch) => { + if current_epoch < epoch { + eprintln!( + "Current epoch {} is not greater than proposal start \ + epoch ", + current_epoch, epoch + ); + safe_exit(1) + } let mut delegation_addresses = rpc::get_delegators_delegation( &client, &voter_address, @@ -739,7 +756,12 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { process_tx(ctx, &args.tx, tx, Some(signer)).await; } None => { - eprintln!("Proposal start epoch is not in the storage.") + eprintln!( + "Proposal start epoch is for proposal id {} is not \ + definied.", + proposal_id + ); + safe_exit(1) } } } diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index aa66fa95d6..7f50f85fc7 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -87,21 +87,23 @@ where let author = read(ctx, &author_key, ReadType::POST).ok(); let has_pre_author = ctx.has_key_pre(&author_key).ok(); match (has_pre_author, author) { - (Some(has_pre_author), Some(author)) => { - match author { - Address::Established(_) => { - let address_exist_key = Key::validity_predicate(&author); - let address_exist = ctx.has_key_post(&address_exist_key).ok(); - if let Some(address_exist) = address_exist { - !has_pre_author && verifiers.contains(&author) && address_exist - } else { - false - } - }, - Address::Implicit(_) => !has_pre_author && verifiers.contains(&author), - Address::Internal(_) => return false, + (Some(has_pre_author), Some(author)) => match author { + Address::Established(_) => { + let address_exist_key = Key::validity_predicate(&author); + let address_exist = ctx.has_key_post(&address_exist_key).ok(); + if let Some(address_exist) = address_exist { + !has_pre_author + && verifiers.contains(&author) + && address_exist + } else { + false + } } - } + Address::Implicit(_) => { + !has_pre_author && verifiers.contains(&author) + } + Address::Internal(_) => return false, + }, _ => false, } } From 5d98621cb01bad24a924220410580ce8ec575206 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:56:46 +0200 Subject: [PATCH 021/373] [fix]: error println --- apps/src/lib/client/tx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 4e9ae7681f..014ded569b 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -687,7 +687,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let current_epoch = - rpc::query_epoch(args::Query { ledger_address }).await; + rpc::query_epoch(args::Query { ledger_address: args.tx.ledger_address.clone() }).await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); @@ -704,7 +704,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { if current_epoch < epoch { eprintln!( "Current epoch {} is not greater than proposal start \ - epoch ", + epoch {}", current_epoch, epoch ); safe_exit(1) From 515709de4fc66bf634739adfb63368bca25e0bdf Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 16:08:45 +0200 Subject: [PATCH 022/373] [misc]: clippy, fmt --- apps/src/lib/client/tx.rs | 6 ++++-- shared/src/ledger/governance/vp.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 014ded569b..c4a2f4c83c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -686,8 +686,10 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let current_epoch = - rpc::query_epoch(args::Query { ledger_address: args.tx.ledger_address.clone() }).await; + let current_epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 7f50f85fc7..300787fba5 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -102,7 +102,7 @@ where Address::Implicit(_) => { !has_pre_author && verifiers.contains(&author) } - Address::Internal(_) => return false, + Address::Internal(_) => false, }, _ => false, } From f7ab15018effd8928454eccd22162e14320ea59e Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 16:52:07 +0200 Subject: [PATCH 023/373] [fix]: e2e test --- tests/src/e2e/ledger_tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 33643bf0f0..c4af49cd27 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1215,7 +1215,10 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction is invalid.")?; + client.exp_string( + "Invalid proposal end epoch: difference between proposal start and \ + end epoch must be at least 3 and end epoch must be a multiple of 3", + )?; client.assert_success(); // 7. Check invalid proposal was not accepted From 751a87938152db356ca54f4b26ce3f39559ee803 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 17:36:49 +0200 Subject: [PATCH 024/373] [fix]: e2e test --- tests/src/e2e/ledger_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index c4af49cd27..57618fde44 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1448,8 +1448,8 @@ fn proposal_offline() -> Result<()> { }, "author": albert, "voting_start_epoch": 3, - "voting_end_epoch": 6, - "grace_epoch": 6 + "voting_end_epoch": 9, + "grace_epoch": 18 } ); generate_proposal_json( From cad0c79fbbeac2324fbea5a421a42636fcaf035f Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 May 2022 18:17:20 +0200 Subject: [PATCH 025/373] Fixes e2e tests --- .gitignore | 5 +++++ tests/src/e2e/ledger_tests.rs | 6 +++--- tests/src/e2e/setup.rs | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 8092b5afd8..633762a23a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,8 @@ wasm/*.wasm # app version string file apps/version.rs + +# Governance artifacts +proposal-test-data +proposal +proposal-vote-* diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 57618fde44..b969cc7b1d 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1120,7 +1120,6 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 3. Query the proposal - let proposal_query_args = vec![ "query-proposal", "--proposal-id", @@ -1202,6 +1201,7 @@ fn proposal_submission() -> Result<()> { "grace_epoch": 10009, } ); + generate_proposal_json( invalid_proposal_json_path.clone(), invalid_proposal_json, @@ -1219,7 +1219,7 @@ fn proposal_submission() -> Result<()> { "Invalid proposal end epoch: difference between proposal start and \ end epoch must be at least 3 and end epoch must be a multiple of 3", )?; - client.assert_success(); + client.assert_failure(); // 7. Check invalid proposal was not accepted let proposal_query_args = vec![ @@ -1378,7 +1378,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, query_protocol_parameters, Some(30))?; - client.exp_regex(".*Min. proposal grace epoch: 9.*")?; + client.exp_regex(".*Min. proposal grace epochs: 9.*")?; client.assert_success(); Ok(()) diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 1342029ec0..7ecc2606b2 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -453,7 +453,6 @@ impl AnomaCmd { } /// Assert that the process exited with failure - #[allow(dead_code)] pub fn assert_failure(&self) { let status = self.session.wait().unwrap(); assert_ne!(WaitStatus::Exited(self.session.pid(), 0), status); From f89a0785aa225353831578115c0162b864f824e4 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 May 2022 17:16:56 +0200 Subject: [PATCH 026/373] Fixes test artifacts folder persistence --- apps/src/lib/client/rpc.rs | 13 ++++-- apps/src/lib/client/tx.rs | 22 +++++++--- tests/src/e2e/ledger_tests.rs | 83 +++++++++++------------------------ 3 files changed, 53 insertions(+), 65 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4f0550ea3e..f7e0e8116c 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -366,7 +366,14 @@ pub async fn query_proposal_result( if entry.file_name().eq(&"proposal") { is_proposal_present = true - } else { + } else if entry + .file_name() + .to_string_lossy() + .starts_with("proposal-vote-") + { + // Folder may contain other + // files than just the proposal + // and the votes files.insert(entry.path()); } } @@ -388,8 +395,8 @@ pub async fn query_proposal_result( if !is_proposal_present { eprintln!( - "The folder must contain a the offline \ - proposal in a file named proposal" + "The folder must contain the offline proposal \ + in a file named \"proposal\"" ); cli::safe_exit(1) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index c4a2f4c83c..7d6034f874 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -583,11 +583,18 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; let offline_proposal = OfflineProposal::new(proposal, signer, &signing_key); - let proposal_filename = "proposal".to_string(); + let proposal_filename = args + .proposal_data + .parent() + .expect("No parent found") + .join("proposal"); let out = File::create(&proposal_filename).unwrap(); match serde_json::to_writer_pretty(out, &offline_proposal) { Ok(_) => { - println!("Proposal created: {}.", proposal_filename); + println!( + "Proposal created: {}.", + proposal_filename.to_string_lossy() + ); } Err(e) => { eprintln!("Error while creating proposal file: {}.", e); @@ -672,12 +679,17 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { &signing_key, ); - let proposal_vote_filename = - format!("proposal-vote-{}", &signer.to_string()); + let proposal_vote_filename = proposal_file_path + .parent() + .expect("No parent found") + .join(format!("proposal-vote-{}", &signer.to_string())); let out = File::create(&proposal_vote_filename).unwrap(); match serde_json::to_writer_pretty(out, &offline_vote) { Ok(_) => { - println!("Proposal vote created: {}.", proposal_vote_filename); + println!( + "Proposal vote created: {}.", + proposal_vote_filename.to_string_lossy() + ); } Err(e) => { eprintln!("Error while creating proposal vote file: {}.", e); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index b969cc7b1d..ff736560f1 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -9,8 +9,6 @@ //! To keep the temporary files created by a test, use env var //! `ANOMA_E2E_KEEP_TEMP=true`. -use std::fs::{self, OpenOptions}; -use std::path::PathBuf; use std::process::Command; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -24,7 +22,6 @@ use color_eyre::eyre::Result; use serde_json::json; use setup::constants::*; -use super::setup::working_dir; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; @@ -1075,8 +1072,7 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let valid_proposal_json_path = - test.base_dir.path().join("valid_proposal.json"); + let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); let albert = find_address(&test, ALBERT)?; @@ -1100,18 +1096,18 @@ fn proposal_submission() -> Result<()> { "proposal_code_path": proposal_code.to_str().unwrap() } ); - - generate_proposal_json( - valid_proposal_json_path.clone(), - valid_proposal_json, - ); + let proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); + let proposal_path = proposal_file.path(); + let proposal_ref = proposal_path.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", "--data-path", - valid_proposal_json_path.to_str().unwrap(), + &proposal_ref, "--ledger-address", &validator_one_rpc, ]; @@ -1164,8 +1160,6 @@ fn proposal_submission() -> Result<()> { // 6. Submit an invalid proposal // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 - let invalid_proposal_json_path = - test.base_dir.path().join("invalid_proposal.json"); let albert = find_address(&test, ALBERT)?; let invalid_proposal_json = json!( { @@ -1201,16 +1195,17 @@ fn proposal_submission() -> Result<()> { "grace_epoch": 10009, } ); - - generate_proposal_json( - invalid_proposal_json_path.clone(), - invalid_proposal_json, - ); + let invalid_proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&invalid_proposal_file, &invalid_proposal_json) + .unwrap(); + let invalid_proposal_path = invalid_proposal_file.path(); + let invalid_proposal_ref = invalid_proposal_path.to_string_lossy(); let submit_proposal_args = vec![ "init-proposal", "--data-path", - invalid_proposal_json_path.to_str().unwrap(), + &invalid_proposal_ref, "--ledger-address", &validator_one_rpc, ]; @@ -1430,8 +1425,8 @@ fn proposal_offline() -> Result<()> { client.assert_success(); // 2. Create an offline proposal - let valid_proposal_json_path = - test.base_dir.path().join("valid_proposal.json"); + let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); + let albert = find_address(&test, ALBERT)?; let valid_proposal_json = json!( { @@ -1452,17 +1447,18 @@ fn proposal_offline() -> Result<()> { "grace_epoch": 18 } ); - generate_proposal_json( - valid_proposal_json_path.clone(), - valid_proposal_json, - ); + let proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); + let proposal_path = proposal_file.path(); + let proposal_ref = proposal_path.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let offline_proposal_args = vec![ "init-proposal", "--data-path", - valid_proposal_json_path.to_str().unwrap(), + &proposal_ref, "--offline", "--ledger-address", &validator_one_rpc, @@ -1479,7 +1475,7 @@ fn proposal_offline() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let proposal_path = working_dir().join("proposal"); + let proposal_path = test_dir.path().join("proposal"); let proposal_ref = proposal_path.to_string_lossy(); let submit_proposal_vote = vec![ "vote-proposal", @@ -1499,31 +1495,17 @@ fn proposal_offline() -> Result<()> { client.assert_success(); let expected_file_name = format!("proposal-vote-{}", albert); - let expected_path_vote = working_dir().join(&expected_file_name); + let expected_path_vote = test_dir.path().join(&expected_file_name); assert!(expected_path_vote.exists()); - let expected_path_proposal = working_dir().join("proposal"); + let expected_path_proposal = test_dir.path().join("proposal"); assert!(expected_path_proposal.exists()); // 4. Compute offline tally - let proposal_data_folder = working_dir().join("proposal-test-data"); - fs::create_dir_all(&proposal_data_folder) - .expect("Should create a new folder."); - fs::copy( - expected_path_proposal, - &proposal_data_folder.join("proposal"), - ) - .expect("Should copy proposal file."); - fs::copy( - expected_path_vote, - &proposal_data_folder.join(&expected_file_name), - ) - .expect("Should copy proposal vote file."); - let tally_offline = vec![ "query-proposal-result", "--data-path", - proposal_data_folder.to_str().unwrap(), + test_dir.path().to_str().unwrap(), "--offline", "--ledger-address", &validator_one_rpc, @@ -1536,19 +1518,6 @@ fn proposal_offline() -> Result<()> { Ok(()) } -fn generate_proposal_json( - proposal_path: PathBuf, - proposal_content: serde_json::Value, -) { - let intent_writer = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(proposal_path) - .unwrap(); - serde_json::to_writer(intent_writer, &proposal_content).unwrap(); -} - /// In this test we: /// 1. Setup 2 genesis validators /// 2. Initialize a new network with the 2 validators From 7204ee169dbbc1cc5804e39bfd004c1654e36117 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 May 2022 17:54:07 +0200 Subject: [PATCH 027/373] Revert gitignore --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index 633762a23a..8092b5afd8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,3 @@ wasm/*.wasm # app version string file apps/version.rs - -# Governance artifacts -proposal-test-data -proposal -proposal-vote-* From f9620816d84b65dd8bbdb35cb6141d81dfb4cf50 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Mon, 13 Jun 2022 16:32:24 +0200 Subject: [PATCH 028/373] [fix]: votes accumulation --- apps/src/lib/client/rpc.rs | 72 ++++++++++++++++++++------- apps/src/lib/client/tx.rs | 2 + shared/src/ledger/governance/utils.rs | 67 +++++++++++++++---------- shared/src/types/governance.rs | 5 +- 4 files changed, 99 insertions(+), 47 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index f7e0e8116c..91bca3ce65 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -249,6 +249,8 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Status: on-going", ""); } else { let votes = get_proposal_votes(client, start_epoch, id).await; + println!("{:?}", votes.yay_delegators); + println!("{:?}", votes.yay_validators); let proposal_result = compute_tally(client, start_epoch, votes).await; println!("{:4}Status: done", ""); @@ -1551,8 +1553,8 @@ pub async fn get_proposal_votes( .await; let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1579,11 +1581,25 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators - .insert(voter_address, VotePower::from(amount)); + match yay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + yay_delegators.insert(voter_address, delegations_map); + } + } } else { - nay_delegators - .insert(voter_address, VotePower::from(amount)); + match nay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + nay_delegators.insert(voter_address, delegations_map); + } + } } } } @@ -1607,8 +1623,8 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1663,11 +1679,25 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators - .insert(validator_address, VotePower::from(amount)); + match yay_delegators.get_mut(&proposal_vote.address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + yay_delegators.insert(proposal_vote.address.clone(), delegations_map); + } + } } else { - nay_delegators - .insert(validator_address, VotePower::from(amount)); + match nay_delegators.get_mut(&proposal_vote.address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + nay_delegators.insert(proposal_vote.address.clone(), delegations_map); + } + } } } } @@ -1703,20 +1733,24 @@ pub async fn compute_tally( } // YAY: Add delegator amount whose validator didn't vote / voted nay - for (validator_address, amount) in yay_delegators.into_iter() { - if !yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens += amount; + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if !yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens += vote_power; + } } } // NAY: Remove delegator amount whose validator validator vote yay - for (validator_address, amount) in nay_delegators.into_iter() { - if yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens -= amount; + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens -= vote_power; + } } } - if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { + if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 { ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 7d6034f874..39b3be7d5f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -754,6 +754,8 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { .await; } + println!("{:?}", delegation_addresses); + let tx_data = VoteProposalData { id: proposal_id, vote: args.vote, diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 7957849dde..300b6311c5 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -21,9 +21,9 @@ pub struct Votes { /// Map from validators who votes yay to their total stake amount pub yay_validators: HashMap, /// Map from delegation who votes yay to their bond amount - pub yay_delegators: HashMap, + pub yay_delegators: HashMap>, /// Map from delegation who votes nay to their bond amount - pub nay_delegators: HashMap, + pub nay_delegators: HashMap>, } /// Proposal errors @@ -98,21 +98,26 @@ where total_yay_stacked_tokens += amount; } + // YAY: Add delegator amount whose validator didn't vote / voted nay - for (validator_address, amount) in yay_delegators.into_iter() { - if !yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens += amount; + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if !yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens += vote_power; + } } } // NAY: Remove delegator amount whose validator validator vote yay - for (validator_address, amount) in nay_delegators.into_iter() { - if yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens -= amount; + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens -= vote_power; + } } } - if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { + if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 { TallyResult::Passed } else { TallyResult::Rejected @@ -205,9 +210,9 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators= HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -216,33 +221,43 @@ where (Some(key), Some(vote)) => { let voter_address = gov_storage::get_voter_address(&key); match voter_address { - Some(address) => { - if vote.is_yay() && validators.contains(address) { + Some(voter_address) => { + if vote.is_yay() && validators.contains(voter_address) { let amount = - get_validator_stake(storage, epoch, address); - yay_validators.insert(address.clone(), amount); - } else if !validators.contains(address) { + get_validator_stake(storage, epoch, voter_address); + yay_validators.insert(voter_address.clone(), amount); + } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key); match validator_address { Some(validator_address) => { let amount = get_bond_amount_at( storage, - address, + voter_address, validator_address, epoch, ); if let Some(amount) = amount { if vote.is_yay() { - yay_delegators.insert( - address.clone(), - VotePower::from(amount), - ); + match yay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address.clone(), VotePower::from(amount)); + }, + None => { + let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); + yay_delegators.insert(voter_address.clone(), delegations_map); + } + } } else { - nay_delegators.insert( - address.clone(), - VotePower::from(amount), - ); + match nay_delegators.get_mut(&voter_address.clone()) { + Some(map) => { + map.insert(validator_address.clone(), VotePower::from(amount)); + }, + None => { + let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); + nay_delegators.insert(voter_address.clone(), delegations_map); + } + } } } } diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index abe65f9d37..8a8577abdc 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -13,6 +13,7 @@ use super::hash::Hash; use super::key::common::{self, Signature}; use super::key::SigScheme; use super::storage::Epoch; +use super::token::SCALE; use super::transaction::governance::InitProposalData; /// Type alias for vote power @@ -104,8 +105,8 @@ impl Display for ProposalResult { f, "{} with {} yay votes over {} ({:.2}%)", self.result, - self.total_yay_power, - self.total_voting_power, + self.total_yay_power / SCALE as u128, + self.total_voting_power / SCALE as u128, (self.total_yay_power / self.total_voting_power) * 100 ) } From c9fb413fc40ffbef054e7e6887ca33fad1965b90 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Mon, 13 Jun 2022 16:33:24 +0200 Subject: [PATCH 029/373] [misc]: remove logs --- apps/src/lib/client/rpc.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 91bca3ce65..e5d2438ec0 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -249,8 +249,6 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Status: on-going", ""); } else { let votes = get_proposal_votes(client, start_epoch, id).await; - println!("{:?}", votes.yay_delegators); - println!("{:?}", votes.yay_validators); let proposal_result = compute_tally(client, start_epoch, votes).await; println!("{:4}Status: done", ""); From a03fca887d7299f3bbe5e9058336cb4b95e9ba6a Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Jun 2022 15:32:02 +0200 Subject: [PATCH 030/373] Fixes `safe_exit` call only if `force` is not set --- apps/src/lib/client/tx.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 39b3be7d5f..5515a8b7a5 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -547,7 +547,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { current_epoch, goverance_parameters.min_proposal_period ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 < goverance_parameters.min_proposal_period @@ -560,7 +562,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { goverance_parameters.min_proposal_period, goverance_parameters.min_proposal_period ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } else if proposal.grace_epoch <= proposal.voting_end_epoch || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 < goverance_parameters.min_proposal_grace_epochs @@ -570,7 +574,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { and end epoch must be at least {}", goverance_parameters.min_proposal_grace_epochs ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } if args.offline { @@ -721,7 +727,10 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { epoch {}", current_epoch, epoch ); - safe_exit(1) + + if !args.tx.force { + safe_exit(1) + } } let mut delegation_addresses = rpc::get_delegators_delegation( &client, @@ -773,11 +782,13 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } None => { eprintln!( - "Proposal start epoch is for proposal id {} is not \ + "Proposal start epoch for proposal id {} is not \ definied.", proposal_id ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } } } From 758573338827b5dbc99cb0627a722192efa2f807 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Jun 2022 18:16:16 +0200 Subject: [PATCH 031/373] Speeds up testing --- tests/src/e2e/ledger_tests.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index ff736560f1..2d7f593b95 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1027,7 +1027,19 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { - let test = setup::network(|genesis| genesis, None)?; + let test = setup::network(|genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 1, + min_duration: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; + + GenesisConfig { + parameters, + ..genesis + } + }, None)?; let anomac_help = vec!["--help"]; @@ -1090,9 +1102,9 @@ fn proposal_submission() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 6, - "voting_end_epoch": 18, - "grace_epoch": 24, + "voting_start_epoch": 12, + "voting_end_epoch": 24, + "grace_epoch": 30, "proposal_code_path": proposal_code.to_str().unwrap() } ); @@ -1246,7 +1258,7 @@ fn proposal_submission() -> Result<()> { // 9. Send a yay vote from a validator let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 7 { + while epoch.0 <= 13 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1311,7 +1323,7 @@ fn proposal_submission() -> Result<()> { // 11. Query the proposal and check the result let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 19 { + while epoch.0 <= 25 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1330,7 +1342,7 @@ fn proposal_submission() -> Result<()> { // 12. Wait proposal grace and check proposal author funds let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 < 26 { + while epoch.0 < 31 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1470,7 +1482,7 @@ fn proposal_offline() -> Result<()> { // 3. Generate an offline yay vote let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 5 { + while epoch.0 <= 2 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } From 957b900f5894956e3846497a970356445b99b67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 1 Jul 2022 15:43:47 +0200 Subject: [PATCH 032/373] fmt and fix clippy --- apps/src/lib/client/rpc.rs | 96 +++++++++++++++----- apps/src/lib/client/tx.rs | 3 +- apps/src/lib/node/ledger/shell/governance.rs | 10 +- shared/src/ledger/governance/utils.rs | 79 +++++++++++----- tests/src/e2e/ledger_tests.rs | 27 +++--- 5 files changed, 151 insertions(+), 64 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index e5d2438ec0..abcfc61408 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1551,8 +1551,10 @@ pub async fn get_proposal_votes( .await; let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1581,21 +1583,41 @@ pub async fn get_proposal_votes( if vote.is_yay() { match yay_delegators.get_mut(&voter_address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - yay_delegators.insert(voter_address, delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + yay_delegators + .insert(voter_address, delegations_map); } } } else { match nay_delegators.get_mut(&voter_address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - nay_delegators.insert(voter_address, delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + nay_delegators + .insert(voter_address, delegations_map); } } } @@ -1621,8 +1643,10 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1679,21 +1703,45 @@ pub async fn get_proposal_offline_votes( if proposal_vote.vote.is_yay() { match yay_delegators.get_mut(&proposal_vote.address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - yay_delegators.insert(proposal_vote.address.clone(), delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + yay_delegators.insert( + proposal_vote.address.clone(), + delegations_map, + ); } } } else { match nay_delegators.get_mut(&proposal_vote.address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - nay_delegators.insert(proposal_vote.address.clone(), delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + nay_delegators.insert( + proposal_vote.address.clone(), + delegations_map, + ); } } } @@ -1732,8 +1780,8 @@ pub async fn compute_tally( // YAY: Add delegator amount whose validator didn't vote / voted nay for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if !yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if !yay_validators.contains_key(validator_address) { total_yay_stacked_tokens += vote_power; } } @@ -1741,8 +1789,8 @@ pub async fn compute_tally( // NAY: Remove delegator amount whose validator validator vote yay for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if yay_validators.contains_key(validator_address) { total_yay_stacked_tokens -= vote_power; } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 5515a8b7a5..6b1110732f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -782,8 +782,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } None => { eprintln!( - "Proposal start epoch for proposal id {} is not \ - definied.", + "Proposal start epoch for proposal id {} is not definied.", proposal_id ); if !args.tx.force { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index e65ede6c07..6c88ffd591 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -115,7 +115,7 @@ where true, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.passed.push(id); proposal_author @@ -130,7 +130,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address @@ -146,7 +146,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address @@ -162,7 +162,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.passed.push(id); proposal_author @@ -178,7 +178,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 300b6311c5..f417c01789 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -98,11 +98,10 @@ where total_yay_stacked_tokens += amount; } - // YAY: Add delegator amount whose validator didn't vote / voted nay for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if !yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if !yay_validators.contains_key(validator_address) { total_yay_stacked_tokens += vote_power; } } @@ -110,8 +109,8 @@ where // NAY: Remove delegator amount whose validator validator vote yay for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if yay_validators.contains_key(validator_address) { total_yay_stacked_tokens -= vote_power; } } @@ -210,9 +209,11 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators= HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_validators = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -223,9 +224,13 @@ where match voter_address { Some(voter_address) => { if vote.is_yay() && validators.contains(voter_address) { - let amount = - get_validator_stake(storage, epoch, voter_address); - yay_validators.insert(voter_address.clone(), amount); + let amount = get_validator_stake( + storage, + epoch, + voter_address, + ); + yay_validators + .insert(voter_address.clone(), amount); } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key); @@ -239,23 +244,55 @@ where ); if let Some(amount) = amount { if vote.is_yay() { - match yay_delegators.get_mut(&voter_address) { + match yay_delegators + .get_mut(voter_address) + { Some(map) => { - map.insert(validator_address.clone(), VotePower::from(amount)); - }, + map.insert( + validator_address + .clone(), + VotePower::from(amount), + ); + } None => { - let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); - yay_delegators.insert(voter_address.clone(), delegations_map); + let delegations_map = + HashMap::from([( + validator_address + .clone(), + VotePower::from( + amount, + ), + )]); + yay_delegators.insert( + voter_address.clone(), + delegations_map, + ); } } } else { - match nay_delegators.get_mut(&voter_address.clone()) { + match nay_delegators + .get_mut(&voter_address.clone()) + { Some(map) => { - map.insert(validator_address.clone(), VotePower::from(amount)); - }, + map.insert( + validator_address + .clone(), + VotePower::from(amount), + ); + } None => { - let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); - nay_delegators.insert(voter_address.clone(), delegations_map); + let delegations_map = + HashMap::from([( + validator_address + .clone(), + VotePower::from( + amount, + ), + )]); + nay_delegators.insert( + voter_address.clone(), + delegations_map, + ); } } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 2d7f593b95..b678330823 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1027,19 +1027,22 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { - let test = setup::network(|genesis| { - let parameters = ParametersConfig { - min_num_of_blocks: 1, - min_duration: 1, - max_expected_time_per_block: 1, - ..genesis.parameters - }; + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 1, + min_duration: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; - GenesisConfig { - parameters, - ..genesis - } - }, None)?; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; let anomac_help = vec!["--help"]; From e13066d935015dba66349c9d9f0299c9208d04c2 Mon Sep 17 00:00:00 2001 From: AnomaBot Date: Fri, 1 Jul 2022 14:45:48 +0000 Subject: [PATCH 033/373] [ci]: update wasm checksums --- wasm/checksums.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 42eb0294e3..1c9fd7b7fe 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.753f415cfc1ab36b23afc113e6b988fd84e24e493b651906bda007471c6d767d.wasm", - "tx_from_intent.wasm": "tx_from_intent.9309a7f0ac8f7b57d897cd69e913cb7ce183e2172e255e2d808c471217a7486b.wasm", - "tx_ibc.wasm": "tx_ibc.5f526fcc143bc57988a3015e5c93250406a4a9ea5467f44d940eece3e0af781d.wasm", - "tx_init_account.wasm": "tx_init_account.7523feaefe42396b98728b6b8993648041b99c2c4b97faa85e1016fe0ef35ce0.wasm", - "tx_init_nft.wasm": "tx_init_nft.92c350bd1640aec55618155573f2d520a85676959fbcec548f5c6378419124f0.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.7ac4859d1ebd536d119c8936336ddd7ea5cb491b079261b2f83f03bf89255d3f.wasm", - "tx_init_validator.wasm": "tx_init_validator.83a93ba9a1c40c03ddb92012ee354743f8dff8c5796ebdc557654c2bd95a497c.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9c582bc9d4ee0f185a66c2c468290ee0668064987050779529b1a4db39a3989b.wasm", - "tx_transfer.wasm": "tx_transfer.f2773e82cd6519662f7d9dbdeb10a2aff9a5fc8ca54d144e6cff571b1fca97cd.wasm", - "tx_unbond.wasm": "tx_unbond.b4ae4df26a3501af40ab409d95b72bff27f953d2e2ebf20c3f7395a2454dad68.wasm", + "tx_bond.wasm": "tx_bond.c7b5ea0bb3dd86ed4d41bb89107e6d1e9a8d83668d2f20e7fc2f9ec8b2b87745.wasm", + "tx_from_intent.wasm": "tx_from_intent.2b5e9c223fdaf81c067c9f80dc506a3f019837b7ae3d8d95f9d6b9452dd4b173.wasm", + "tx_ibc.wasm": "tx_ibc.c37f214473d15cec37280641f5d598586569a9895f2be82a29a7866dc0e91066.wasm", + "tx_init_account.wasm": "tx_init_account.fb3ddd710679a44c36af58639e06ea0c994074175351440baa6b7b4e3dc094fb.wasm", + "tx_init_nft.wasm": "tx_init_nft.4ab7d3539dcd71f22736b28fae4fcda96fa9c3a8a168709688e1994a34f4e522.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.febd16b42f549d2bceca628738376f5c32b4e275992c6310627428fa1db524b8.wasm", + "tx_init_validator.wasm": "tx_init_validator.b0ba92a896153360206335921d06ba71c39a27a054af8b2aba6b6040e982680a.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.067bc999bf51fbc1ac337824e314ff83cc9b42b4a15026fb79bd938d3e34b517.wasm", + "tx_transfer.wasm": "tx_transfer.e46c7af52cfe7e4315dd4c3ba46092d4c4facdc49060d0e5d68f7cc787d28f22.wasm", + "tx_unbond.wasm": "tx_unbond.1289d46bbf4696b7a008d2a453cc5caa797e4ac5ac9da4b6cdb0e56e28cb200d.wasm", "tx_update_vp.wasm": "tx_update_vp.23a6a4f18e826c67708b32b98be67c7c241fd1478424196ae47f972c7a1334e0.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.466a6be90f5a5a79965a2106b8a5a385accc1cdb6f66345c9f39e6488e4e2a05.wasm", - "tx_withdraw.wasm": "tx_withdraw.aabfff97bf82723436bdddbd584e8199a18355decc2f1c10818ad84c57e92182.wasm", - "vp_nft.wasm": "vp_nft.d85a9628f4702f31f54888b704745f8e868b7945208d40d24ead134b338557d1.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.07226b30af3f6c091b5acfd0e6f7794f31e363cbbdb5db9acb8a0086fff82005.wasm", - "vp_token.wasm": "vp_token.bad2c74e2788089ec2692b1fce0549466f57769336df774c8582076cd3fad136.wasm", - "vp_user.wasm": "vp_user.9299851563f7fdb4e8bc9b0f23ae948655b0a049e402b090ddb453df37eb98ff.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.e12fbabf774ee4e6076cccaba067b884ac716a9316da0061715febf25912af13.wasm", + "tx_withdraw.wasm": "tx_withdraw.498e9463af77e21146783f22d475d028abc4cc1c931b2aa6a50b3a7fa9e065c1.wasm", + "vp_nft.wasm": "vp_nft.68afc06cbfcad0fbf3ae98e77ecaebbc45383241b648d8e51a0476d581b347d8.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.79cae7ebe32f85bfb6952d3050140237f117bf4a1c9074b9dbb7a40ac991bd5d.wasm", + "vp_token.wasm": "vp_token.3b1aaf3d250646f027fe74fefc52e73e77a70980dc4b6d858d5c2a5afc0951fa.wasm", + "vp_user.wasm": "vp_user.ea20b70085c845b6dfdf2761ac865382b975a035be6564dfec5e349934e232a3.wasm" } \ No newline at end of file From 952d302d004f768ff7f3dfde7e8d5be540f9e526 Mon Sep 17 00:00:00 2001 From: Unkowit Date: Wed, 8 Jun 2022 11:39:01 +0100 Subject: [PATCH 034/373] Changed hash-map to BiHashMap in the wallet in order to be able to retrieve alias from address --- Cargo.lock | 10 ++++++++++ apps/Cargo.toml | 1 + apps/src/lib/wallet/store.rs | 9 +++++---- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eb732583ef..3172380cfe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,6 +142,7 @@ dependencies = [ "async-trait", "base64 0.13.0", "bech32", + "bimap", "bit-set", "blake2b-rs", "borsh", @@ -796,6 +797,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bimap" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" +dependencies = [ + "serde 1.0.137", +] + [[package]] name = "bincode" version = "1.3.3" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 1ed0366a7d..0fd163d3ac 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -145,6 +145,7 @@ tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} websocket = "0.26.2" winapi = "0.3.9" +bimap = {version = "0.6.2", features = ["serde"]} [dev-dependencies] anoma = {path = "../shared", default-features = false, features = ["testing", "wasm-runtime"]} diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index b5a60b533d..56ea1ba6c4 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -12,6 +12,7 @@ use anoma::types::key::*; use anoma::types::transaction::EllipticCurve; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; +use bimap::BiHashMap; use file_lock::{FileLock, FileOptions}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -53,7 +54,7 @@ pub struct Store { /// Cryptographic keypairs keys: HashMap, /// Anoma address book - addresses: HashMap, + addresses: BiHashMap, /// Known mappings of public key hashes to their aliases in the `keys` /// field. Used for look-up by a public key. pkhs: HashMap, @@ -224,7 +225,7 @@ impl Store { /// Find the stored address by an alias. pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { - self.addresses.get(&alias.into()) + self.addresses.get_by_left(&alias.into()) } /// Get all known keys by their alias, paired with PKH, if known. @@ -248,7 +249,7 @@ impl Store { } /// Get all known addresses by their alias, paired with PKH, if known. - pub fn get_addresses(&self) -> &HashMap { + pub fn get_addresses(&self) -> &BiHashMap { &self.addresses } @@ -362,7 +363,7 @@ impl Store { alias = address.encode() ); } - if self.addresses.contains_key(&alias) { + if self.addresses.contains_left(&alias) { match show_overwrite_confirmation(&alias, "an address") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { From 03ca93e5c5dc8db45e1327e0beda5d73a1780bfe Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 8 Jun 2022 12:52:54 +0200 Subject: [PATCH 035/373] Make anomac balance show alias if possible instead of address --- apps/src/lib/client/rpc.rs | 4 ++++ apps/src/lib/wallet/mod.rs | 5 +++++ apps/src/lib/wallet/store.rs | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d85ff0b7c0..6db6877c01 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -188,6 +188,10 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { for (key, balance) in balances { let owner = token::is_any_token_balance_key(&key).unwrap(); + let owner = match ctx.wallet.find_alias(owner) { + Some(alias) => format!("{}", alias), + None => format!("{}", owner), + }; writeln!(w, " {}, owned by {}", balance, owner) .unwrap(); } diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 71702c9b8e..4a7be99d86 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -285,6 +285,11 @@ impl Wallet { self.store.find_address(alias) } + /// Find an alias by the address if it's in the wallet. + pub fn find_alias(&self, address: &Address) -> Option<&Alias> { + self.store.find_alias(address) + } + /// Get all known addresses by their alias, paired with PKH, if known. pub fn get_addresses(&self) -> HashMap { self.store diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 56ea1ba6c4..4a1479c56d 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -228,6 +228,11 @@ impl Store { self.addresses.get_by_left(&alias.into()) } + /// Find an alias by the address if it's in the wallet. + pub fn find_alias(&self, address: &Address) -> Option<&Alias> { + self.addresses.get_by_right(address) + } + /// Get all known keys by their alias, paired with PKH, if known. pub fn get_keys( &self, From 3eb7f6859413a07192729b4deed33481019d4770 Mon Sep 17 00:00:00 2001 From: Unkowit Date: Thu, 30 Jun 2022 10:06:59 +0100 Subject: [PATCH 036/373] added changelog --- .../unreleased/improvements/1138-change-wallet-bihashmap.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/unreleased/improvements/1138-change-wallet-bihashmap.md diff --git a/.changelog/unreleased/improvements/1138-change-wallet-bihashmap.md b/.changelog/unreleased/improvements/1138-change-wallet-bihashmap.md new file mode 100644 index 0000000000..d13b82e697 --- /dev/null +++ b/.changelog/unreleased/improvements/1138-change-wallet-bihashmap.md @@ -0,0 +1,4 @@ +- Allows simple retrival of aliases from addresses in the wallet without + the need for multiple hashmaps. This is the first step to improving the + UI if one wants to show aliases when fetching addresses from anoma wallet + ([#1138](https://github.com/anoma/anoma/pull/1138)) \ No newline at end of file From bcd6449438ac6fcf39256d277f570ba9ca8a042b Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 7 Jul 2022 17:56:47 +0100 Subject: [PATCH 037/373] Add changelog --- .changelog/unreleased/miscellaneous/1096-wasm-workspace.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/1096-wasm-workspace.md diff --git a/.changelog/unreleased/miscellaneous/1096-wasm-workspace.md b/.changelog/unreleased/miscellaneous/1096-wasm-workspace.md new file mode 100644 index 0000000000..a15f343025 --- /dev/null +++ b/.changelog/unreleased/miscellaneous/1096-wasm-workspace.md @@ -0,0 +1,2 @@ +- Use a cargo workspace for some of our wasm crates + ([#1096](https://github.com/anoma/anoma/pull/1096)) \ No newline at end of file From 4f0b5dd036b31071a113daff4031e8d86cdcc36f Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 16:29:56 +0100 Subject: [PATCH 038/373] Remove absolute wasm dir check from join-network --- apps/src/lib/client/utils.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 123eb036f4..482e62647b 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -131,15 +131,6 @@ pub async fn join_network( None } }); - if let Some(wasm_dir) = wasm_dir.as_ref() { - if wasm_dir.is_absolute() { - eprintln!( - "The arg `--wasm-dir` cannot be an absolute path. It is \ - nested inside the chain directory." - ); - cli::safe_exit(1); - } - } let release_filename = format!("{}.tar.gz", chain_id); let release_url = format!( From 44725f6ccc7137fd1fabdc2e9f59cee0381e3287 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 16:34:21 +0100 Subject: [PATCH 039/373] init-network should use default wasm dir for validators --- apps/src/lib/client/utils.rs | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 482e62647b..86ab4753ce 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -740,26 +740,6 @@ pub fn init_network( let genesis_path = global_args .base_dir .join(format!("{}.toml", chain_id.as_str())); - let wasm_dir = global_args - .wasm_dir - .as_ref() - .cloned() - .or_else(|| { - if let Ok(wasm_dir) = env::var(ENV_VAR_WASM_DIR) { - let wasm_dir: PathBuf = wasm_dir.into(); - Some(wasm_dir) - } else { - None - } - }) - .unwrap_or_else(|| config::DEFAULT_WASM_DIR.into()); - if wasm_dir.is_absolute() { - eprintln!( - "The arg `--wasm-dir` cannot be an absolute path. It is nested \ - inside the chain directory." - ); - cli::safe_exit(1); - } // Write the genesis file genesis_config::write_genesis_config(&config_clean, &genesis_path); @@ -776,7 +756,7 @@ pub fn init_network( fs::rename(&temp_dir, &chain_dir).unwrap(); // Copy the WASM checksums - let wasm_dir_full = chain_dir.join(&wasm_dir); + let wasm_dir_full = chain_dir.join(&config::DEFAULT_WASM_DIR); fs::create_dir_all(&wasm_dir_full).unwrap(); fs::copy( &wasm_checksums_path, @@ -801,15 +781,6 @@ pub fn init_network( std::fs::rename(&temp_validator_chain_dir, &validator_chain_dir) .unwrap(); - // Copy the WASM checksums - let wasm_dir_full = validator_chain_dir.join(&wasm_dir); - fs::create_dir_all(&wasm_dir_full).unwrap(); - fs::copy( - &wasm_checksums_path, - wasm_dir_full.join(config::DEFAULT_WASM_CHECKSUMS_FILE), - ) - .unwrap(); - // Write the genesis and global config into validator sub-dirs genesis_config::write_genesis_config( &config, From 6942583252aa745d0af43eaeaff12588bb49f8d9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 18:15:33 +0100 Subject: [PATCH 040/373] Improve error message when copying wasm to chain dir --- tests/src/e2e/setup.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 1342029ec0..8e90acad52 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -16,7 +16,7 @@ use color_eyre::owo_colors::OwoColorize; use escargot::CargoBuild; use expectrl::session::Session; use expectrl::{Eof, WaitStatus}; -use eyre::eyre; +use eyre::{eyre, Context}; use tempfile::{tempdir, TempDir}; /// For `color_eyre::install`, which fails if called more than once in the same @@ -814,11 +814,17 @@ pub fn copy_wasm_to_chain_dir<'a>( .join(chain_id.as_str()) .join(config::DEFAULT_WASM_DIR); for file in &wasm_files { - std::fs::copy( - working_dir.join("wasm").join(&file), - target_wasm_dir.join(&file), - ) - .unwrap(); + let src = working_dir.join("wasm").join(&file); + let dst = target_wasm_dir.join(&file); + std::fs::copy(&src, &dst) + .wrap_err_with(|| { + format!( + "copying {} to {}", + &src.to_string_lossy(), + &dst.to_string_lossy(), + ) + }) + .unwrap(); } } } From e749e4e4354656f6b863328d2b21a719d084e774 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 18:24:38 +0100 Subject: [PATCH 041/373] Add back copying wasm checksums for validators --- apps/src/lib/client/utils.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 86ab4753ce..483107bf25 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -781,6 +781,15 @@ pub fn init_network( std::fs::rename(&temp_validator_chain_dir, &validator_chain_dir) .unwrap(); + // Copy the WASM checksums + let wasm_dir_full = validator_chain_dir.join(&config::DEFAULT_WASM_DIR); + fs::create_dir_all(&wasm_dir_full).unwrap(); + fs::copy( + &wasm_checksums_path, + wasm_dir_full.join(config::DEFAULT_WASM_CHECKSUMS_FILE), + ) + .unwrap(); + // Write the genesis and global config into validator sub-dirs genesis_config::write_genesis_config( &config, From ca176b6c4367a94222a20c86d890ed9afa1ae7ea Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 09:31:26 +0100 Subject: [PATCH 042/373] Move Aborter to its own mod --- apps/src/lib/node/ledger/abortable.rs | 13 +++++++++++++ apps/src/lib/node/ledger/mod.rs | 16 ++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 apps/src/lib/node/ledger/abortable.rs diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs new file mode 100644 index 0000000000..cdf8ae3e32 --- /dev/null +++ b/apps/src/lib/node/ledger/abortable.rs @@ -0,0 +1,13 @@ +/// A panic-proof handle for aborting a future. Will abort during stack +/// unwinding and its drop method sends abort message with `who` inside it. +pub struct Aborter { + pub(super) sender: tokio::sync::mpsc::UnboundedSender<&'static str>, + pub(super) who: &'static str, +} + +impl Drop for Aborter { + fn drop(&mut self) { + // Send abort message, ignore result + let _ = self.sender.send(self.who); + } +} diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index c2aa89e504..3bb240f7d5 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -1,3 +1,4 @@ +mod abortable; mod broadcaster; pub mod events; pub mod protocol; @@ -28,6 +29,7 @@ use tower_abci::{response, split, Server}; #[cfg(feature = "ABCI")] use tower_abci_old::{response, split, Server}; +use self::abortable::Aborter; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; use crate::config::TendermintMode; @@ -507,20 +509,6 @@ async fn run_abci( .map_err(|err| Error::TowerServer(err.to_string())) } -/// A panic-proof handle for aborting a future. Will abort during stack -/// unwinding and its drop method sends abort message with `who` inside it. -struct Aborter { - sender: tokio::sync::mpsc::UnboundedSender<&'static str>, - who: &'static str, -} - -impl Drop for Aborter { - fn drop(&mut self) { - // Send abort message, ignore result - let _ = self.sender.send(self.who); - } -} - /// Function that blocks until either /// 1. User sends a shutdown signal /// 2. One of the child processes terminates, sending a message on `drop` From ea65dc07c03d94bfc88d6f103a2dcc001e9efa76 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 11:07:03 +0100 Subject: [PATCH 043/373] Moving more abort logic to its own mod --- apps/src/lib/node/ledger/abortable.rs | 161 +++++++++++++++++++++++++- apps/src/lib/node/ledger/mod.rs | 113 ------------------ 2 files changed, 158 insertions(+), 116 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index cdf8ae3e32..b55ae3794c 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,13 +1,168 @@ +use tokio::task::JoinHandle; +use tokio::sync::mpsc::{self, UnboundedSender, UnboundedReceiver}; + +/// Serves to identify an aborting async task, which is spawned +/// with an [`Aborter`]. +pub type AbortingTask = &'static str; + /// A panic-proof handle for aborting a future. Will abort during stack /// unwinding and its drop method sends abort message with `who` inside it. pub struct Aborter { - pub(super) sender: tokio::sync::mpsc::UnboundedSender<&'static str>, - pub(super) who: &'static str, + abort_send: UnboundedSender, + abort_recv: UnboundedReceiver, +} + +impl Aborter { + fn spawn_abortable(&self, who: AbortingTask, abortable: A) -> JoinHandle<()> + where + A: FnOnce(AbortGuard) -> F, + F: Future, + { + let abort = AbortGuard { + who, + sender: self.abort_send.clone(), + }; + tokio::spawn(abortable(abort)) + } + + /// This future will resolve when: + /// 1. User sends a shutdown signal + /// 2. One of the child processes terminates, sending a message on `drop` + pub async fn wait(self) -> AborterStatus { + wait_for_abort(self.abort_recv).await + } +} + +/// A panic-proof handle for aborting a future. Will abort during stack +/// unwinding and its drop method sends abort message with `who` inside it. +pub struct AbortGuard { + sender: mpsc::UnboundedSender<&'static str>, + who: &'static str, } -impl Drop for Aborter { +impl Drop for AbortGuard { fn drop(&mut self) { // Send abort message, ignore result let _ = self.sender.send(self.who); } } + +/// Function that blocks until either +/// 1. User sends a shutdown signal +/// 2. One of the child processes terminates, sending a message on `drop` +/// Returns a boolean to indicate which scenario occurred. +/// `true` means that the latter happened +/// +/// It is used by the [`Aborter`]. +#[cfg(unix)] +async fn wait_for_abort( + mut abort_recv: UnboundedReceiver, +) -> AborterStatus { + use tokio::signal::unix::{signal, SignalKind}; + let mut sigterm = signal(SignalKind::terminate()).unwrap(); + let mut sighup = signal(SignalKind::hangup()).unwrap(); + let mut sigpipe = signal(SignalKind::pipe()).unwrap(); + tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), + } + }, + signal = sigterm.recv() => { + match signal { + Some(()) => tracing::info!("Received termination signal, exiting..."), + None => tracing::error!("Termination signal cannot be caught anymore, exiting..."), + } + }, + signal = sighup.recv() => { + match signal { + Some(()) => tracing::info!("Received hangup signal, exiting..."), + None => tracing::error!("Hangup signal cannot be caught anymore, exiting..."), + } + }, + signal = sigpipe.recv() => { + match signal { + Some(()) => tracing::info!("Received pipe signal, exiting..."), + None => tracing::error!("Pipe signal cannot be caught anymore, exiting..."), + } + }, + msg = abort_recv.recv() => { + // When the msg is `None`, there are no more abort senders, so both + // Tendermint and the shell must have already exited + if let Some(who) = msg { + tracing::info!("{} has exited, shutting down...", who); + } + return AborterStatus::ChildProcessTerminated; + } + }; + AborterStatus::UserShutdownLedger +} + +#[cfg(windows)] +async fn wait_for_abort( + mut abort_recv: UnboundedReceiver, +) -> AborterStatus { + let mut sigbreak = tokio::signal::windows::ctrl_break().unwrap(); + let _ = tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), + } + }, + signal = sigbreak.recv() => { + match signal { + Some(()) => tracing::info!("Received break signal, exiting..."), + None => tracing::error!("Break signal cannot be caught anymore, exiting..."), + } + }, + msg = abort_recv.recv() => { + // When the msg is `None`, there are no more abort senders, so both + // Tendermint and the shell must have already exited + if let Some(who) = msg { + tracing::info!("{} has exited, shutting down...", who); + } + return AborterStatus::ChildProcessTerminated; + } + }; + AborterStatus::UserShutdownLedger +} + +#[cfg(not(any(unix, windows)))] +async fn wait_for_abort( + mut abort_recv: UnboundedReceiver, +) -> AborterStatus { + let _ = tokio::select! { + signal = tokio::signal::ctrl_c() => { + match signal { + Ok(()) => tracing::info!("Received interrupt signal, exiting..."), + Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), + } + }, + msg = abort_recv.recv() => { + // When the msg is `None`, there are no more abort senders, so both + // Tendermint and the shell must have already exited + if let Some(who) = msg { + tracing::info!("{} has exited, shutting down...", who); + } + return AborterStatus::ChildProcessTerminated; + } + }; + AborterStatus::UserShutdownLedger +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum AborterStatus { + /// The ledger process received a shutdown signal. + UserShutdownLedger, + /// One of the child processes terminates, signaling the [`Aborter`]. + ChildProcessTerminated, +} + +impl AborterStatus { + /// Checks if the reason for aborting was a child process terminating. + pub fn child_terminated(self) -> bool { + matches!(self, AborterStatus::ChildProcessTerminated) + } +} diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 3bb240f7d5..817d5c2e7b 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -508,116 +508,3 @@ async fn run_abci( .await .map_err(|err| Error::TowerServer(err.to_string())) } - -/// Function that blocks until either -/// 1. User sends a shutdown signal -/// 2. One of the child processes terminates, sending a message on `drop` -/// Returns a boolean to indicate which scenario occurred. -/// `true` means that the latter happened -#[cfg(unix)] -async fn wait_for_abort( - mut abort_recv: tokio::sync::mpsc::UnboundedReceiver<&'static str>, -) -> bool { - use tokio::signal::unix::{signal, SignalKind}; - let mut sigterm = signal(SignalKind::terminate()).unwrap(); - let mut sighup = signal(SignalKind::hangup()).unwrap(); - let mut sigpipe = signal(SignalKind::pipe()).unwrap(); - tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), - } - }, - signal = sigterm.recv() => { - match signal { - Some(()) => tracing::info!("Received termination signal, exiting..."), - None => tracing::error!("Termination signal cannot be caught anymore, exiting..."), - } - }, - signal = sighup.recv() => { - match signal { - Some(()) => tracing::info!("Received hangup signal, exiting..."), - None => tracing::error!("Hangup signal cannot be caught anymore, exiting..."), - } - }, - signal = sigpipe.recv() => { - match signal { - Some(()) => tracing::info!("Received pipe signal, exiting..."), - None => tracing::error!("Pipe signal cannot be caught anymore, exiting..."), - } - }, - msg = abort_recv.recv() => { - // When the msg is `None`, there are no more abort senders, so both - // Tendermint and the shell must have already exited - if let Some(who) = msg { - tracing::info!("{} has exited, shutting down...", who); - } - return true; - } - }; - false -} - -/// Function that blocks until either -/// 1. User sends a shutdown signal -/// 2. One of the child processes terminates, sending a message on `drop` -/// Returns a boolean to indicate which scenario occurred. -/// `true` means that the latter happened -#[cfg(windows)] -async fn wait_for_abort( - mut abort_recv: tokio::sync::mpsc::UnboundedReceiver<&'static str>, -) -> bool { - let mut sigbreak = tokio::signal::windows::ctrl_break().unwrap(); - let _ = tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), - } - }, - signal = sigbreak.recv() => { - match signal { - Some(()) => tracing::info!("Received break signal, exiting..."), - None => tracing::error!("Break signal cannot be caught anymore, exiting..."), - } - }, - msg = abort_recv.recv() => { - // When the msg is `None`, there are no more abort senders, so both - // Tendermint and the shell must have already exited - if let Some(who) = msg { - tracing::info!("{} has exited, shutting down...", who); - } - return true; - } - }; - false -} - -/// Function that blocks until either -/// 1. User sends a shutdown signal -/// 2. One of the child processes terminates, sending a message on `drop` -/// Returns a boolean to indicate which scenario occurred. -/// `true` means that the latter happened -#[cfg(not(any(unix, windows)))] -async fn wait_for_abort( - mut abort_recv: tokio::sync::mpsc::UnboundedReceiver<&'static str>, -) -> bool { - let _ = tokio::select! { - signal = tokio::signal::ctrl_c() => { - match signal { - Ok(()) => tracing::info!("Received interrupt signal, exiting..."), - Err(err) => tracing::error!("Failed to listen for CTRL+C signal: {}", err), - } - }, - msg = abort_recv.recv() => { - // When the msg is `None`, there are no more abort senders, so both - // Tendermint and the shell must have already exited - if let Some(who) = msg { - tracing::info!("{} has exited, shutting down...", who); - } - return true; - } - }; - false -} From 5b0a8c797e64aecd6a9409bbb48ef80f8c84a5c5 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 11:12:26 +0100 Subject: [PATCH 044/373] Moved db_cache closer to the abci service def --- apps/src/lib/node/ledger/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 817d5c2e7b..97e815418c 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -287,8 +287,6 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { Byte::from_bytes(block_cache_size_bytes as u128) .get_appropriate_unit(true) ); - let db_cache = - rocksdb::Cache::new_lru_cache(block_cache_size_bytes as usize).unwrap(); let tendermint_dir = config.tendermint_dir(); let ledger_address = config.shell.ledger_address.to_string(); @@ -373,6 +371,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { }; // Construct our ABCI application. + let db_cache = + rocksdb::Cache::new_lru_cache(block_cache_size_bytes as usize).unwrap(); let ledger_address = config.shell.ledger_address; let (shell, abci_service) = AbcippShim::new( config, From 9555c50c425616fd8c74ac2110c9569b25b9b0c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 11:39:39 +0100 Subject: [PATCH 045/373] Fix spawn_abortable --- apps/src/lib/node/ledger/abortable.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index b55ae3794c..faca7f3c5b 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,3 +1,5 @@ +use std::future::Future; + use tokio::task::JoinHandle; use tokio::sync::mpsc::{self, UnboundedSender, UnboundedReceiver}; @@ -13,10 +15,11 @@ pub struct Aborter { } impl Aborter { - fn spawn_abortable(&self, who: AbortingTask, abortable: A) -> JoinHandle<()> + pub fn spawn_abortable(&self, who: AbortingTask, abortable: A) -> JoinHandle where A: FnOnce(AbortGuard) -> F, - F: Future, + F: Future + Send + 'static, + R: Send + 'static, { let abort = AbortGuard { who, From 5b617f65b2a80187dde0ca83a9a3e4b96a2cf368 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 11:40:37 +0100 Subject: [PATCH 046/373] Add an associated functiong to create an Aborter --- apps/src/lib/node/ledger/abortable.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index faca7f3c5b..370ea5cadf 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -15,6 +15,15 @@ pub struct Aborter { } impl Aborter { + /// Creates a new [`Aborter`]. + pub fn new() -> Self { + let (abort_send, abort_recv) = mpsc::unbounded_channel(); + Self { + abort_send, + abort_recv, + } + } + pub fn spawn_abortable(&self, who: AbortingTask, abortable: A) -> JoinHandle where A: FnOnce(AbortGuard) -> F, From b4ed0755a63d4eece6e85985f2ce603903bb5133 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 11:41:13 +0100 Subject: [PATCH 047/373] Use spawn_abortable in the ledger startup --- apps/src/lib/node/ledger/mod.rs | 37 ++++++++------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 97e815418c..8a93afc5ba 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -299,9 +299,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { .expect("expected RFC3339 genesis_time"); let tendermint_config = config.tendermint.clone(); - // Channel for signalling shut down from the shell or from Tendermint - let (abort_send, abort_recv) = - tokio::sync::mpsc::unbounded_channel::<&'static str>(); + // Create an `Aborter` for signalling shut down from the shell or from Tendermint + let aborter = Aborter::new(); + // Channels for validators to send protocol txs to be broadcast to the // broadcaster service let (broadcaster_sender, broadcaster_receiver) = @@ -312,14 +312,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tokio::sync::oneshot::channel::>(); // Start Tendermint node - let abort_send_for_tm = abort_send.clone(); - let tendermint_node = tokio::spawn(async move { - // On panic or exit, the `Drop` of `AbortSender` will send abort message - let aborter = Aborter { - sender: abort_send_for_tm, - who: "Tendermint", - }; - + let tendermint_node = aborter.spawn_abortable("Tendermint", move |aborter| async move { let res = tendermint_node::run( tendermint_dir, chain_id, @@ -346,19 +339,12 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Channel for signalling shut down to broadcaster let (bc_abort_send, bc_abort_recv) = tokio::sync::oneshot::channel::<()>(); - let abort_send_for_broadcaster = abort_send.clone(); Some(( - tokio::spawn(async move { + aborter.spawn_abortable("Broadcaster", move |aborter| async move { // Construct a service for broadcasting protocol txs from the // ledger let mut broadcaster = Broadcaster::new(&rpc_address, broadcaster_receiver); - // On panic or exit, the `Drop` of `AbortSender` will send abort - // message - let aborter = Aborter { - sender: abort_send_for_broadcaster, - who: "Broadcaster", - }; broadcaster.run(bc_abort_recv).await; tracing::info!("Broadcaster is no longer running."); @@ -384,14 +370,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { ); // Start the ABCI server - let abci = tokio::spawn(async move { - // On panic or exit, the `Drop` of `AbortSender` will send abort - // message - let aborter = Aborter { - sender: abort_send, - who: "ABCI", - }; - + let abci = aborter.spawn_abortable("ABCI", move |aborter| async move { let res = run_abci(abci_service, ledger_address).await; drop(aborter); @@ -409,7 +388,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { .expect("Must be able to start a thread for the shell"); // Wait for interrupt signal or abort message - let aborted = wait_for_abort(abort_recv).await; + let aborted = aborter.wait() + .await + .child_terminated(); // Abort the ABCI service task abci.abort(); From b58f59f5d08b8f3536ef688f4890d21e4c407a31 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 12:35:12 +0100 Subject: [PATCH 048/373] Rename types Use names that make more sense, such as `AbortableSpawner` instead of `Aborter`. --- apps/src/lib/node/ledger/abortable.rs | 20 ++++++++++---------- apps/src/lib/node/ledger/mod.rs | 18 +++++++++++------- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 370ea5cadf..56384dedb6 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -4,18 +4,18 @@ use tokio::task::JoinHandle; use tokio::sync::mpsc::{self, UnboundedSender, UnboundedReceiver}; /// Serves to identify an aborting async task, which is spawned -/// with an [`Aborter`]. +/// with an [`AbortableSpawner`]. pub type AbortingTask = &'static str; /// A panic-proof handle for aborting a future. Will abort during stack /// unwinding and its drop method sends abort message with `who` inside it. -pub struct Aborter { +pub struct AbortableSpawner { abort_send: UnboundedSender, abort_recv: UnboundedReceiver, } -impl Aborter { - /// Creates a new [`Aborter`]. +impl AbortableSpawner { + /// Creates a new [`AbortableSpawner`]. pub fn new() -> Self { let (abort_send, abort_recv) = mpsc::unbounded_channel(); Self { @@ -26,11 +26,11 @@ impl Aborter { pub fn spawn_abortable(&self, who: AbortingTask, abortable: A) -> JoinHandle where - A: FnOnce(AbortGuard) -> F, + A: FnOnce(Aborter) -> F, F: Future + Send + 'static, R: Send + 'static, { - let abort = AbortGuard { + let abort = Aborter { who, sender: self.abort_send.clone(), }; @@ -47,12 +47,12 @@ impl Aborter { /// A panic-proof handle for aborting a future. Will abort during stack /// unwinding and its drop method sends abort message with `who` inside it. -pub struct AbortGuard { +pub struct Aborter { sender: mpsc::UnboundedSender<&'static str>, who: &'static str, } -impl Drop for AbortGuard { +impl Drop for Aborter { fn drop(&mut self) { // Send abort message, ignore result let _ = self.sender.send(self.who); @@ -65,7 +65,7 @@ impl Drop for AbortGuard { /// Returns a boolean to indicate which scenario occurred. /// `true` means that the latter happened /// -/// It is used by the [`Aborter`]. +/// It is used by the [`AbortableSpawner`]. #[cfg(unix)] async fn wait_for_abort( mut abort_recv: UnboundedReceiver, @@ -168,7 +168,7 @@ async fn wait_for_abort( pub enum AborterStatus { /// The ledger process received a shutdown signal. UserShutdownLedger, - /// One of the child processes terminates, signaling the [`Aborter`]. + /// One of the child processes terminates, signaling the [`AbortableSpawner`]. ChildProcessTerminated, } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 8a93afc5ba..71ea28f0a8 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -29,7 +29,7 @@ use tower_abci::{response, split, Server}; #[cfg(feature = "ABCI")] use tower_abci_old::{response, split, Server}; -use self::abortable::Aborter; +use self::abortable::AbortableSpawner; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; use crate::config::TendermintMode; @@ -299,8 +299,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { .expect("expected RFC3339 genesis_time"); let tendermint_config = config.tendermint.clone(); - // Create an `Aborter` for signalling shut down from the shell or from Tendermint - let aborter = Aborter::new(); + // Create an `AbortableSpawner` for signalling shut down from the shell or from Tendermint + let spawner = AbortableSpawner::new(); // Channels for validators to send protocol txs to be broadcast to the // broadcaster service @@ -312,7 +312,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tokio::sync::oneshot::channel::>(); // Start Tendermint node - let tendermint_node = aborter.spawn_abortable("Tendermint", move |aborter| async move { + let tendermint_node = spawner.spawn_abortable("Tendermint", move |aborter| async move { let res = tendermint_node::run( tendermint_dir, chain_id, @@ -340,7 +340,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let (bc_abort_send, bc_abort_recv) = tokio::sync::oneshot::channel::<()>(); Some(( - aborter.spawn_abortable("Broadcaster", move |aborter| async move { + spawner.spawn_abortable("Broadcaster", move |aborter| async move { // Construct a service for broadcasting protocol txs from the // ledger let mut broadcaster = @@ -370,7 +370,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { ); // Start the ABCI server - let abci = aborter.spawn_abortable("ABCI", move |aborter| async move { + let abci = spawner.spawn_abortable("ABCI", move |aborter| async move { let res = run_abci(abci_service, ledger_address).await; drop(aborter); @@ -388,7 +388,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { .expect("Must be able to start a thread for the shell"); // Wait for interrupt signal or abort message - let aborted = aborter.wait() + let aborted = spawner.wait() .await .child_terminated(); @@ -489,3 +489,7 @@ async fn run_abci( .await .map_err(|err| Error::TowerServer(err.to_string())) } + +//async fn run_tendermint(config: &config::Ledger) -> JoinHandle<()> { +// todo!() +//} From 89ae1181bc56dec67053876370a434439541efe7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 12:45:44 +0100 Subject: [PATCH 049/373] Document spawn_abortable --- apps/src/lib/node/ledger/abortable.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 56384dedb6..f20db6a3a6 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -24,6 +24,18 @@ impl AbortableSpawner { } } + /// Spawns a new task into the asynchronous runtime, with an [`Aborter`] that shall + /// be dropped when it is no longer running. + /// + /// For instance: + /// + /// ```rust + /// let spawner = AbortableSpawner::new(); + /// spawner.spawn_abortable("ExampleTask", |aborter| async { + /// drop(aborter); + /// println!("I have signaled a control task that I am no longer running!"); + /// }); + /// ``` pub fn spawn_abortable(&self, who: AbortingTask, abortable: A) -> JoinHandle where A: FnOnce(Aborter) -> F, From 8eee08b14b5d99c369e4f898c3af9054eb9eb8e1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 12:53:57 +0100 Subject: [PATCH 050/373] Document some missing stuff --- apps/src/lib/node/ledger/abortable.rs | 16 +++++++--------- apps/src/lib/node/ledger/mod.rs | 3 ++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index f20db6a3a6..53af49abed 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -50,9 +50,12 @@ impl AbortableSpawner { } /// This future will resolve when: + /// /// 1. User sends a shutdown signal /// 2. One of the child processes terminates, sending a message on `drop` - pub async fn wait(self) -> AborterStatus { + /// + /// These two scenarios are represented by the [`AborterStatus`] enum. + pub async fn wait_for_abort(self) -> AborterStatus { wait_for_abort(self.abort_recv).await } } @@ -71,13 +74,6 @@ impl Drop for Aborter { } } -/// Function that blocks until either -/// 1. User sends a shutdown signal -/// 2. One of the child processes terminates, sending a message on `drop` -/// Returns a boolean to indicate which scenario occurred. -/// `true` means that the latter happened -/// -/// It is used by the [`AbortableSpawner`]. #[cfg(unix)] async fn wait_for_abort( mut abort_recv: UnboundedReceiver, @@ -176,11 +172,13 @@ async fn wait_for_abort( AborterStatus::UserShutdownLedger } +/// An [`AborterStatus`] represents one of two possible causes that resulted +/// in shutting down the ledger. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum AborterStatus { /// The ledger process received a shutdown signal. UserShutdownLedger, - /// One of the child processes terminates, signaling the [`AbortableSpawner`]. + /// One of the ledger's child processes terminated, signaling the [`AbortableSpawner`]. ChildProcessTerminated, } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 71ea28f0a8..60694b4c80 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -388,7 +388,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { .expect("Must be able to start a thread for the shell"); // Wait for interrupt signal or abort message - let aborted = spawner.wait() + let aborted = spawner + .wait_for_abort() .await .child_terminated(); From 15a9c7a05b7d3a2b131dc4c74e45c8e5724074e4 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 13:03:47 +0100 Subject: [PATCH 051/373] Move setup to a separate async function --- apps/src/lib/node/ledger/mod.rs | 183 ++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 80 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 60694b4c80..e2ed3ac159 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -207,86 +207,11 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { /// the ledger may submit txs to the chain. All must be alive for correct /// functioning. async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { - // Prefetch needed wasm artifacts - wasm_loader::pre_fetch_wasm(&wasm_dir).await; - - // Find the system available memory - let available_memory_bytes = Lazy::new(|| { - let sys = System::new_with_specifics(RefreshKind::new().with_memory()); - let available_memory_bytes = sys.available_memory() * 1024; - tracing::info!( - "Available memory: {}", - Byte::from_bytes(available_memory_bytes as u128) - .get_appropriate_unit(true) - ); - available_memory_bytes - }); - - // Find the VP WASM compilation cache size - let vp_wasm_compilation_cache = - match config.shell.vp_wasm_compilation_cache_bytes { - Some(vp_wasm_compilation_cache) => { - tracing::info!( - "VP WASM compilation cache size set from the configuration" - ); - vp_wasm_compilation_cache - } - None => { - tracing::info!( - "VP WASM compilation cache size not configured, using 1/6 \ - of available memory." - ); - *available_memory_bytes / 6 - } - }; - tracing::info!( - "VP WASM compilation cache size: {}", - Byte::from_bytes(vp_wasm_compilation_cache as u128) - .get_appropriate_unit(true) - ); - - // Find the tx WASM compilation cache size - let tx_wasm_compilation_cache = - match config.shell.tx_wasm_compilation_cache_bytes { - Some(tx_wasm_compilation_cache) => { - tracing::info!( - "Tx WASM compilation cache size set from the configuration" - ); - tx_wasm_compilation_cache - } - None => { - tracing::info!( - "Tx WASM compilation cache size not configured, using 1/6 \ - of available memory." - ); - *available_memory_bytes / 6 - } - }; - tracing::info!( - "Tx WASM compilation cache size: {}", - Byte::from_bytes(tx_wasm_compilation_cache as u128) - .get_appropriate_unit(true) - ); - - // Setup DB cache, it must outlive the DB instance that's in the shell - let block_cache_size_bytes = match config.shell.block_cache_bytes { - Some(block_cache_bytes) => { - tracing::info!("Block cache set from the configuration.",); - block_cache_bytes - } - None => { - tracing::info!( - "Block cache size not configured, using 1/3 of available \ - memory." - ); - *available_memory_bytes / 3 - } - }; - tracing::info!( - "RocksDB block cache size: {}", - Byte::from_bytes(block_cache_size_bytes as u128) - .get_appropriate_unit(true) - ); + let RunAuxSetup { + vp_wasm_compilation_cache, + tx_wasm_compilation_cache, + block_cache_size_bytes, + } = run_aux_setup(&config, &wasm_dir).await; let tendermint_dir = config.tendermint_dir(); let ledger_address = config.shell.ledger_address.to_string(); @@ -494,3 +419,101 @@ async fn run_abci( //async fn run_tendermint(config: &config::Ledger) -> JoinHandle<()> { // todo!() //} + +/// A [`RunAuxSetup`] stores some variables used to start child +/// processes of the ledger. +struct RunAuxSetup { + vp_wasm_compilation_cache: u64, + tx_wasm_compilation_cache: u64, + block_cache_size_bytes: u64, +} + +/// Return some variables used to start child processes of the ledger. +async fn run_aux_setup(config: &config::Ledger, wasm_dir: &PathBuf) -> RunAuxSetup { + // Prefetch needed wasm artifacts + wasm_loader::pre_fetch_wasm(wasm_dir).await; + + // Find the system available memory + let available_memory_bytes = Lazy::new(|| { + let sys = System::new_with_specifics(RefreshKind::new().with_memory()); + let available_memory_bytes = sys.available_memory() * 1024; + tracing::info!( + "Available memory: {}", + Byte::from_bytes(available_memory_bytes as u128) + .get_appropriate_unit(true) + ); + available_memory_bytes + }); + + // Find the VP WASM compilation cache size + let vp_wasm_compilation_cache = + match config.shell.vp_wasm_compilation_cache_bytes { + Some(vp_wasm_compilation_cache) => { + tracing::info!( + "VP WASM compilation cache size set from the configuration" + ); + vp_wasm_compilation_cache + } + None => { + tracing::info!( + "VP WASM compilation cache size not configured, using 1/6 \ + of available memory." + ); + *available_memory_bytes / 6 + } + }; + tracing::info!( + "VP WASM compilation cache size: {}", + Byte::from_bytes(vp_wasm_compilation_cache as u128) + .get_appropriate_unit(true) + ); + + // Find the tx WASM compilation cache size + let tx_wasm_compilation_cache = + match config.shell.tx_wasm_compilation_cache_bytes { + Some(tx_wasm_compilation_cache) => { + tracing::info!( + "Tx WASM compilation cache size set from the configuration" + ); + tx_wasm_compilation_cache + } + None => { + tracing::info!( + "Tx WASM compilation cache size not configured, using 1/6 \ + of available memory." + ); + *available_memory_bytes / 6 + } + }; + tracing::info!( + "Tx WASM compilation cache size: {}", + Byte::from_bytes(tx_wasm_compilation_cache as u128) + .get_appropriate_unit(true) + ); + + // Setup DB cache, it must outlive the DB instance that's in the shell + let block_cache_size_bytes = match config.shell.block_cache_bytes { + Some(block_cache_bytes) => { + tracing::info!("Block cache set from the configuration.",); + block_cache_bytes + } + None => { + tracing::info!( + "Block cache size not configured, using 1/3 of available \ + memory." + ); + *available_memory_bytes / 3 + } + }; + tracing::info!( + "RocksDB block cache size: {}", + Byte::from_bytes(block_cache_size_bytes as u128) + .get_appropriate_unit(true) + ); + + RunAuxSetup { + vp_wasm_compilation_cache, + tx_wasm_compilation_cache, + block_cache_size_bytes, + } +} From 808ac8b9e4a545bdb92f5660590a9372016832c0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 13:08:39 +0100 Subject: [PATCH 052/373] Document RocksDB setup --- apps/src/lib/node/ledger/mod.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index e2ed3ac159..93f36222fc 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -210,7 +210,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let RunAuxSetup { vp_wasm_compilation_cache, tx_wasm_compilation_cache, - block_cache_size_bytes, + db_block_cache_size_bytes, } = run_aux_setup(&config, &wasm_dir).await; let tendermint_dir = config.tendermint_dir(); @@ -281,9 +281,11 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { None }; - // Construct our ABCI application. + // Setup DB cache, it must outlive the DB instance that's in the shell let db_cache = - rocksdb::Cache::new_lru_cache(block_cache_size_bytes as usize).unwrap(); + rocksdb::Cache::new_lru_cache(db_block_cache_size_bytes as usize).unwrap(); + + // Construct our ABCI application. let ledger_address = config.shell.ledger_address; let (shell, abci_service) = AbcippShim::new( config, @@ -425,7 +427,7 @@ async fn run_abci( struct RunAuxSetup { vp_wasm_compilation_cache: u64, tx_wasm_compilation_cache: u64, - block_cache_size_bytes: u64, + db_block_cache_size_bytes: u64, } /// Return some variables used to start child processes of the ledger. @@ -491,8 +493,8 @@ async fn run_aux_setup(config: &config::Ledger, wasm_dir: &PathBuf) -> RunAuxSet .get_appropriate_unit(true) ); - // Setup DB cache, it must outlive the DB instance that's in the shell - let block_cache_size_bytes = match config.shell.block_cache_bytes { + // Find the RocksDB block cache size + let db_block_cache_size_bytes = match config.shell.block_cache_bytes { Some(block_cache_bytes) => { tracing::info!("Block cache set from the configuration.",); block_cache_bytes @@ -507,13 +509,13 @@ async fn run_aux_setup(config: &config::Ledger, wasm_dir: &PathBuf) -> RunAuxSet }; tracing::info!( "RocksDB block cache size: {}", - Byte::from_bytes(block_cache_size_bytes as u128) + Byte::from_bytes(db_block_cache_size_bytes as u128) .get_appropriate_unit(true) ); RunAuxSetup { vp_wasm_compilation_cache, tx_wasm_compilation_cache, - block_cache_size_bytes, + db_block_cache_size_bytes, } } From e7e63c5b90313385be95600c5fd8032f6febcbad Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 13:18:09 +0100 Subject: [PATCH 053/373] Document AbortableSpawner --- apps/src/lib/node/ledger/abortable.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 53af49abed..5b47dad340 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -7,8 +7,7 @@ use tokio::sync::mpsc::{self, UnboundedSender, UnboundedReceiver}; /// with an [`AbortableSpawner`]. pub type AbortingTask = &'static str; -/// A panic-proof handle for aborting a future. Will abort during stack -/// unwinding and its drop method sends abort message with `who` inside it. +/// An [`AbortableSpawner`] will spawn abortable tasks into the asynchronous runtime. pub struct AbortableSpawner { abort_send: UnboundedSender, abort_recv: UnboundedReceiver, From bdddc27641c9c1d1ba9445c98c4b3d562b743dcd Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 14:49:13 +0100 Subject: [PATCH 054/373] Annotate source code with cleanup beginning and end --- apps/src/lib/node/ledger/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 93f36222fc..b6b1d37276 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -320,6 +320,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { .await .child_terminated(); + // NOTE: cleanup started + // Abort the ABCI service task abci.abort(); @@ -354,6 +356,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { .map(|results| (results.0, results.1, ())) } }; + + // NOTE: cleanup ended + match res { Ok((tendermint_res, abci_res, _)) => { // we ignore errors on user-initiated shutdown From 24c7b9b00124a4931702f45bcbf04f67b6db35d1 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 14:56:26 +0100 Subject: [PATCH 055/373] Move run_abci further down the file --- apps/src/lib/node/ledger/mod.rs | 82 ++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index b6b1d37276..480e5a7cb9 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -386,47 +386,6 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { } } -/// Runs the an asynchronous ABCI server with four sub-components for consensus, -/// mempool, snapshot, and info. -async fn run_abci( - abci_service: AbciService, - ledger_address: SocketAddr, -) -> shell::Result<()> { - // Split it into components. - let (consensus, mempool, snapshot, info) = split::service(abci_service, 5); - - // Hand those components to the ABCI server, but customize request behavior - // for each category - let server = Server::builder() - .consensus(consensus) - .snapshot(snapshot) - .mempool( - ServiceBuilder::new() - .load_shed() - .buffer(1024) - .service(mempool), - ) - .info( - ServiceBuilder::new() - .load_shed() - .buffer(100) - .rate_limit(50, std::time::Duration::from_secs(1)) - .service(info), - ) - .finish() - .unwrap(); - - // Run the server with the ABCI service - server - .listen(ledger_address) - .await - .map_err(|err| Error::TowerServer(err.to_string())) -} - -//async fn run_tendermint(config: &config::Ledger) -> JoinHandle<()> { -// todo!() -//} - /// A [`RunAuxSetup`] stores some variables used to start child /// processes of the ledger. struct RunAuxSetup { @@ -524,3 +483,44 @@ async fn run_aux_setup(config: &config::Ledger, wasm_dir: &PathBuf) -> RunAuxSet db_block_cache_size_bytes, } } + +/// Runs the an asynchronous ABCI server with four sub-components for consensus, +/// mempool, snapshot, and info. +async fn run_abci( + abci_service: AbciService, + ledger_address: SocketAddr, +) -> shell::Result<()> { + // Split it into components. + let (consensus, mempool, snapshot, info) = split::service(abci_service, 5); + + // Hand those components to the ABCI server, but customize request behavior + // for each category + let server = Server::builder() + .consensus(consensus) + .snapshot(snapshot) + .mempool( + ServiceBuilder::new() + .load_shed() + .buffer(1024) + .service(mempool), + ) + .info( + ServiceBuilder::new() + .load_shed() + .buffer(100) + .rate_limit(50, std::time::Duration::from_secs(1)) + .service(info), + ) + .finish() + .unwrap(); + + // Run the server with the ABCI service + server + .listen(ledger_address) + .await + .map_err(|err| Error::TowerServer(err.to_string())) +} + +//async fn run_tendermint(config: &config::Ledger) -> JoinHandle<()> { +// todo!() +//} From 54f4c79f4c633a45c4d8bb6854804b856dee3b92 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 14:56:40 +0100 Subject: [PATCH 056/373] Begin cleanup abstraction --- apps/src/lib/node/ledger/abortable.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 5b47dad340..e953764ab4 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -11,6 +11,7 @@ pub type AbortingTask = &'static str; pub struct AbortableSpawner { abort_send: UnboundedSender, abort_recv: UnboundedReceiver, + _cleanup: Vec>, } impl AbortableSpawner { @@ -20,6 +21,7 @@ impl AbortableSpawner { Self { abort_send, abort_recv, + _cleanup: Vec::new(), } } From b414e9aa0fee7e60391dc10acebad38405aa41c8 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 16:16:31 +0100 Subject: [PATCH 057/373] Start working on cleanup abstractions --- apps/src/lib/node/ledger/abortable.rs | 91 ++++++++++++++++++++++----- apps/src/lib/node/ledger/mod.rs | 8 +-- 2 files changed, 80 insertions(+), 19 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index e953764ab4..6a2587dbaa 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -11,7 +11,14 @@ pub type AbortingTask = &'static str; pub struct AbortableSpawner { abort_send: UnboundedSender, abort_recv: UnboundedReceiver, - _cleanup: Vec>, + cleanup_jobs: Vec>, +} + +/// Contains the state of an on-going [`AbortableSpawner`] task spawn. +pub struct WithCleanup<'a, A> { + who: AbortingTask, + abortable: A, + spawner: &'a mut AbortableSpawner, } impl AbortableSpawner { @@ -21,7 +28,7 @@ impl AbortableSpawner { Self { abort_send, abort_recv, - _cleanup: Vec::new(), + cleanup_jobs: Vec::new(), } } @@ -32,12 +39,42 @@ impl AbortableSpawner { /// /// ```rust /// let spawner = AbortableSpawner::new(); - /// spawner.spawn_abortable("ExampleTask", |aborter| async { - /// drop(aborter); - /// println!("I have signaled a control task that I am no longer running!"); - /// }); + /// spawner + /// .spawn_abortable("ExampleTask", |aborter| async { + /// drop(aborter); + /// println!("I have signaled a control task that I am no longer running!"); + /// }) + /// .with_no_cleanup(); /// ``` - pub fn spawn_abortable(&self, who: AbortingTask, abortable: A) -> JoinHandle + /// + /// The return type of this method is [`WithCleanup`], such that a cleanup routine, after the + /// abort is received, can be executed. + pub fn spawn_abortable<'a, A>(&'a mut self, who: AbortingTask, abortable: A) -> WithCleanup<'a, A> { + WithCleanup { + who, + abortable, + spawner: self, + } + } + + /// This future will resolve when: + /// + /// 1. User sends a shutdown signal + /// 2. One of the child processes terminates, sending a message on `drop` + /// + /// These two scenarios are represented by the [`AborterStatus`] enum. + pub async fn wait_for_abort(self) -> AborterStatus { + let status = wait_for_abort(self.abort_recv).await; + + for job in self.cleanup_jobs { + todo!() + } + + status + } + + /// This method is responsible for actually spawning the async task into the runtime. + fn spawn_abortable_task(&self, who: AbortingTask, abortable: A) -> JoinHandle where A: FnOnce(Aborter) -> F, F: Future + Send + 'static, @@ -49,15 +86,39 @@ impl AbortableSpawner { }; tokio::spawn(abortable(abort)) } +} - /// This future will resolve when: - /// - /// 1. User sends a shutdown signal - /// 2. One of the child processes terminates, sending a message on `drop` - /// - /// These two scenarios are represented by the [`AborterStatus`] enum. - pub async fn wait_for_abort(self) -> AborterStatus { - wait_for_abort(self.abort_recv).await +impl<'a, A> WithCleanup<'a, A> { + pub fn with_no_cleanup(self) -> JoinHandle + where + A: FnOnce(Aborter) -> F, + F: Future + Send + 'static, + R: Send + 'static, + { + self.spawner.spawn_abortable_task(self.who, self.abortable) + } + + pub fn with_conditional_cleanup(self, cond: bool, cleanup: C) -> JoinHandle + where + A: FnOnce(Aborter) -> F, + F: Future + Send + 'static, + R: Send + 'static, + C: FnOnce() + 'static, + { + if cond { + self.spawner.cleanup_jobs.push(Box::new(cleanup)); + } + self.with_no_cleanup() + } + + pub fn with_cleanup(self, cleanup: C) -> JoinHandle + where + A: FnOnce(Aborter) -> F, + F: Future + Send + 'static, + R: Send + 'static, + C: FnOnce() + 'static, + { + self.with_conditional_cleanup(true, cleanup) } } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 480e5a7cb9..3a52f3bdb1 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -225,7 +225,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let tendermint_config = config.tendermint.clone(); // Create an `AbortableSpawner` for signalling shut down from the shell or from Tendermint - let spawner = AbortableSpawner::new(); + let mut spawner = AbortableSpawner::new(); // Channels for validators to send protocol txs to be broadcast to the // broadcaster service @@ -255,7 +255,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tracing::error!("{:?}", &res); } res - }); + }).with_no_cleanup(); let broadcaster = if matches!( config.tendermint.tendermint_mode, @@ -274,7 +274,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tracing::info!("Broadcaster is no longer running."); drop(aborter); - }), + }).with_no_cleanup(), bc_abort_send, )) } else { @@ -302,7 +302,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { drop(aborter); res - }); + }).with_no_cleanup(); // Run the shell in the main thread let thread_builder = From b11989ba5469e92cf1388df7f5d527966a66a008 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 16:30:41 +0100 Subject: [PATCH 058/373] Document new cleanup stuff --- apps/src/lib/node/ledger/abortable.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 6a2587dbaa..c9e0e0209c 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -48,7 +48,7 @@ impl AbortableSpawner { /// ``` /// /// The return type of this method is [`WithCleanup`], such that a cleanup routine, after the - /// abort is received, can be executed. + /// abort is received, can be configured to execute. pub fn spawn_abortable<'a, A>(&'a mut self, who: AbortingTask, abortable: A) -> WithCleanup<'a, A> { WithCleanup { who, @@ -89,6 +89,8 @@ impl AbortableSpawner { } impl<'a, A> WithCleanup<'a, A> { + /// No cleanup routine will be executed for the associated task. + #[inline] pub fn with_no_cleanup(self) -> JoinHandle where A: FnOnce(Aborter) -> F, @@ -98,6 +100,9 @@ impl<'a, A> WithCleanup<'a, A> { self.spawner.spawn_abortable_task(self.who, self.abortable) } + /// A cleanup routine `cleanup` may be executed for the associated task, + /// if `cond` evaluates to `true`. + #[inline] pub fn with_conditional_cleanup(self, cond: bool, cleanup: C) -> JoinHandle where A: FnOnce(Aborter) -> F, @@ -111,6 +116,8 @@ impl<'a, A> WithCleanup<'a, A> { self.with_no_cleanup() } + /// A cleanup routine `cleanup` will be executed for the associated task. + #[inline] pub fn with_cleanup(self, cleanup: C) -> JoinHandle where A: FnOnce(Aborter) -> F, From 40f1c1e1a382547fb3613217108240833921ca37 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 16:34:53 +0100 Subject: [PATCH 059/373] Improve docs on wait_for_abort --- apps/src/lib/node/ledger/abortable.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index c9e0e0209c..96de93af60 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -59,8 +59,9 @@ impl AbortableSpawner { /// This future will resolve when: /// - /// 1. User sends a shutdown signal - /// 2. One of the child processes terminates, sending a message on `drop` + /// 1. A user sends a shutdown signal (e.g. SIGINT), or... + /// 2. One of the child processes of the ledger terminates, + /// which generates a notification upon dropping an [`Aborter`]. /// /// These two scenarios are represented by the [`AborterStatus`] enum. pub async fn wait_for_abort(self) -> AborterStatus { From e48c3fead1c2dc18d04e1700c92a128e1a820110 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Fri, 8 Jul 2022 16:41:25 +0100 Subject: [PATCH 060/373] Use AbortingTask instead of a static str --- apps/src/lib/node/ledger/abortable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 96de93af60..1e0114d04e 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -133,8 +133,8 @@ impl<'a, A> WithCleanup<'a, A> { /// A panic-proof handle for aborting a future. Will abort during stack /// unwinding and its drop method sends abort message with `who` inside it. pub struct Aborter { - sender: mpsc::UnboundedSender<&'static str>, - who: &'static str, + sender: mpsc::UnboundedSender, + who: AbortingTask, } impl Drop for Aborter { From 15f22a7399adab6e63cb2472cea6e289d88a4370 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 10 Jul 2022 19:03:05 +0100 Subject: [PATCH 061/373] Run cleanup jobs --- apps/src/lib/node/ledger/abortable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 1e0114d04e..36cd05d1be 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -68,7 +68,7 @@ impl AbortableSpawner { let status = wait_for_abort(self.abort_recv).await; for job in self.cleanup_jobs { - todo!() + job(); } status From 8fc64bde3671bf96be43278b19e5251d436179a7 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 10 Jul 2022 19:03:55 +0100 Subject: [PATCH 062/373] Add a prototype for the abci service startup routine --- apps/src/lib/node/ledger/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 3a52f3bdb1..64084cc728 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -524,3 +524,8 @@ async fn run_abci( //async fn run_tendermint(config: &config::Ledger) -> JoinHandle<()> { // todo!() //} + +// NOTE: thread join handle for shell +//async fn create_abci_broadcaster_shell(...) -> (abci, broadcaster, shell) { +// todo!() +//} From 4b3dfa3734ecb70a03aa34303107ba1fed74d100 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 10:13:22 +0100 Subject: [PATCH 063/373] Use futures instead of closures for cleanup jobs --- apps/src/lib/node/ledger/abortable.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 36cd05d1be..e73e4c74de 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,3 +1,4 @@ +use std::pin::Pin; use std::future::Future; use tokio::task::JoinHandle; @@ -11,7 +12,7 @@ pub type AbortingTask = &'static str; pub struct AbortableSpawner { abort_send: UnboundedSender, abort_recv: UnboundedReceiver, - cleanup_jobs: Vec>, + cleanup_jobs: Vec>>>, } /// Contains the state of an on-going [`AbortableSpawner`] task spawn. @@ -68,7 +69,7 @@ impl AbortableSpawner { let status = wait_for_abort(self.abort_recv).await; for job in self.cleanup_jobs { - job(); + job.await; } status @@ -109,10 +110,10 @@ impl<'a, A> WithCleanup<'a, A> { A: FnOnce(Aborter) -> F, F: Future + Send + 'static, R: Send + 'static, - C: FnOnce() + 'static, + C: Future + Send + 'static, { if cond { - self.spawner.cleanup_jobs.push(Box::new(cleanup)); + self.spawner.cleanup_jobs.push(Box::pin(cleanup)); } self.with_no_cleanup() } @@ -124,7 +125,7 @@ impl<'a, A> WithCleanup<'a, A> { A: FnOnce(Aborter) -> F, F: Future + Send + 'static, R: Send + 'static, - C: FnOnce() + 'static, + C: Future + Send + 'static, { self.with_conditional_cleanup(true, cleanup) } From 87bdc6985082479bfca48d066fcb9fbdc374b3eb Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 10:16:39 +0100 Subject: [PATCH 064/373] Refactor Tendermint node startup --- apps/src/lib/node/ledger/mod.rs | 116 +++++++++++++++++--------------- 1 file changed, 61 insertions(+), 55 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 64084cc728..a5815a9b98 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -213,50 +213,19 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { db_block_cache_size_bytes, } = run_aux_setup(&config, &wasm_dir).await; - let tendermint_dir = config.tendermint_dir(); - let ledger_address = config.shell.ledger_address.to_string(); let rpc_address = config.tendermint.rpc_address.to_string(); - let chain_id = config.chain_id.clone(); - let genesis_time = config - .genesis_time - .clone() - .try_into() - .expect("expected RFC3339 genesis_time"); - let tendermint_config = config.tendermint.clone(); // Create an `AbortableSpawner` for signalling shut down from the shell or from Tendermint let mut spawner = AbortableSpawner::new(); + // Start Tendermint node + let tendermint_node = start_tendermint(&mut spawner, &config); + // Channels for validators to send protocol txs to be broadcast to the // broadcaster service let (broadcaster_sender, broadcaster_receiver) = tokio::sync::mpsc::unbounded_channel(); - // Channel for signalling shut down to Tendermint process - let (tm_abort_send, tm_abort_recv) = - tokio::sync::oneshot::channel::>(); - - // Start Tendermint node - let tendermint_node = spawner.spawn_abortable("Tendermint", move |aborter| async move { - let res = tendermint_node::run( - tendermint_dir, - chain_id, - genesis_time, - ledger_address, - tendermint_config, - tm_abort_recv, - ) - .map_err(Error::Tendermint) - .await; - tracing::info!("Tendermint node is no longer running."); - - drop(aborter); - if res.is_err() { - tracing::error!("{:?}", &res); - } - res - }).with_no_cleanup(); - let broadcaster = if matches!( config.tendermint.tendermint_mode, TendermintMode::Validator @@ -325,24 +294,6 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Abort the ABCI service task abci.abort(); - // Shutdown tendermint_node via a message to ensure that the child process - // is properly cleaned-up. - let (tm_abort_resp_send, tm_abort_resp_recv) = - tokio::sync::oneshot::channel::<()>(); - // Ask to shutdown tendermint node cleanly. Ignore error, which can happen - // if the tendermint_node task has already finished. - if let Ok(()) = tm_abort_send.send(tm_abort_resp_send) { - match tm_abort_resp_recv.await { - Ok(()) => {} - Err(err) => { - tracing::error!( - "Failed to receive a response from tendermint: {}", - err - ); - } - } - } - let res = match broadcaster { Some((broadcaster, bc_abort_send)) => { // request the broadcaster shutdown @@ -521,9 +472,64 @@ async fn run_abci( .map_err(|err| Error::TowerServer(err.to_string())) } -//async fn run_tendermint(config: &config::Ledger) -> JoinHandle<()> { -// todo!() -//} +fn start_tendermint( + spawner: &mut AbortableSpawner, + config: &config::Ledger, +) -> tokio::task::JoinHandle> { + let tendermint_dir = config.tendermint_dir(); + let chain_id = config.chain_id.clone(); + let ledger_address = config.shell.ledger_address.to_string(); + let tendermint_config = config.tendermint.clone(); + let genesis_time = config + .genesis_time + .clone() + .try_into() + .expect("expected RFC3339 genesis_time"); + + // Channel for signalling shut down to Tendermint process + let (tm_abort_send, tm_abort_recv) = + tokio::sync::oneshot::channel::>(); + + spawner + .spawn_abortable("Tendermint", move |aborter| async move { + let res = tendermint_node::run( + tendermint_dir, + chain_id, + genesis_time, + ledger_address, + tendermint_config, + tm_abort_recv, + ) + .map_err(Error::Tendermint) + .await; + tracing::info!("Tendermint node is no longer running."); + + drop(aborter); + if res.is_err() { + tracing::error!("{:?}", &res); + } + res + }) + .with_cleanup(async move { + // Shutdown tendermint_node via a message to ensure that the child process + // is properly cleaned-up. + let (tm_abort_resp_send, tm_abort_resp_recv) = + tokio::sync::oneshot::channel::<()>(); + // Ask to shutdown tendermint node cleanly. Ignore error, which can happen + // if the tendermint_node task has already finished. + if let Ok(()) = tm_abort_send.send(tm_abort_resp_send) { + match tm_abort_resp_recv.await { + Ok(()) => {} + Err(err) => { + tracing::error!( + "Failed to receive a response from tendermint: {}", + err + ); + } + } + } + }) +} // NOTE: thread join handle for shell //async fn create_abci_broadcaster_shell(...) -> (abci, broadcaster, shell) { From 0621a3776e0dcf0054fda665e43e6ae2fe19ba13 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 10:23:18 +0100 Subject: [PATCH 065/373] Change conditional cleanup API --- apps/src/lib/node/ledger/abortable.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index e73e4c74de..2af3c11f05 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -105,14 +105,14 @@ impl<'a, A> WithCleanup<'a, A> { /// A cleanup routine `cleanup` may be executed for the associated task, /// if `cond` evaluates to `true`. #[inline] - pub fn with_conditional_cleanup(self, cond: bool, cleanup: C) -> JoinHandle + pub fn with_conditional_cleanup(self, cleanup: Option) -> JoinHandle where A: FnOnce(Aborter) -> F, F: Future + Send + 'static, R: Send + 'static, C: Future + Send + 'static, { - if cond { + if let Some(cleanup) = cleanup { self.spawner.cleanup_jobs.push(Box::pin(cleanup)); } self.with_no_cleanup() @@ -127,7 +127,7 @@ impl<'a, A> WithCleanup<'a, A> { R: Send + 'static, C: Future + Send + 'static, { - self.with_conditional_cleanup(true, cleanup) + self.with_conditional_cleanup(Some(cleanup)) } } From a349f01b3b7bd77022e5e6253561d65b3d643303 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 10:38:54 +0100 Subject: [PATCH 066/373] Fix doctest --- apps/src/lib/node/ledger/abortable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 2af3c11f05..0bd3c5ef7e 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -38,8 +38,8 @@ impl AbortableSpawner { /// /// For instance: /// - /// ```rust - /// let spawner = AbortableSpawner::new(); + /// ```no_run + /// let mut spawner = AbortableSpawner::new(); /// spawner /// .spawn_abortable("ExampleTask", |aborter| async { /// drop(aborter); From 81aa43f0b7a1e034e5d6813393b54ffe08aa91d0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 10:47:42 +0100 Subject: [PATCH 067/373] Change docs example from no_run to ignore --- apps/src/lib/node/ledger/abortable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 0bd3c5ef7e..bea3f9e968 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -38,7 +38,7 @@ impl AbortableSpawner { /// /// For instance: /// - /// ```no_run + /// ```ignore /// let mut spawner = AbortableSpawner::new(); /// spawner /// .spawn_abortable("ExampleTask", |aborter| async { From ad9d6e5cf9d1008e1c5733c92c363f5c62337e4b Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 12:29:23 +0100 Subject: [PATCH 068/373] Remove with_conditional_cleanup and add another cleanup meth --- apps/src/lib/node/ledger/abortable.rs | 28 +++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index bea3f9e968..f10a2d06fe 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,4 +1,5 @@ use std::pin::Pin; +use std::sync::Arc; use std::future::Future; use tokio::task::JoinHandle; @@ -102,32 +103,39 @@ impl<'a, A> WithCleanup<'a, A> { self.spawner.spawn_abortable_task(self.who, self.abortable) } - /// A cleanup routine `cleanup` may be executed for the associated task, - /// if `cond` evaluates to `true`. + /// A cleanup routine `cleanup` will be executed for the associated task. #[inline] - pub fn with_conditional_cleanup(self, cleanup: Option) -> JoinHandle + pub fn with_cleanup(self, cleanup: C) -> JoinHandle where A: FnOnce(Aborter) -> F, F: Future + Send + 'static, R: Send + 'static, C: Future + Send + 'static, { - if let Some(cleanup) = cleanup { - self.spawner.cleanup_jobs.push(Box::pin(cleanup)); - } + self.spawner.cleanup_jobs.push(Box::pin(cleanup)); self.with_no_cleanup() } - /// A cleanup routine `cleanup` will be executed for the associated task. + /// A cleanup routine shall be executed, which aborts a `JoinHandle` from + /// the asynchronous runtime. #[inline] - pub fn with_cleanup(self, cleanup: C) -> JoinHandle + pub fn with_join_handle_abort_cleanup(self) -> Arc> where A: FnOnce(Aborter) -> F, F: Future + Send + 'static, R: Send + 'static, - C: Future + Send + 'static, { - self.with_conditional_cleanup(Some(cleanup)) + let handle = self.spawner + .spawn_abortable_task(self.who, self.abortable); + + let handle = Arc::new(handle); + let cleanup_handle = Arc::clone(&handle); + + self.spawner.cleanup_jobs.push(Box::pin(async move { + cleanup_handle.abort(); + })); + + handle } } From 03fed9a1067112058245c3d7fbf16823161070b0 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 12:30:17 +0100 Subject: [PATCH 069/373] WIP: Refactoring abci and broadcaster startup --- apps/src/lib/node/ledger/mod.rs | 199 ++++++++++++++++++-------------- 1 file changed, 112 insertions(+), 87 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index a5815a9b98..42c43a2906 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -12,10 +12,12 @@ use std::convert::TryInto; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; +use std::thread; use anoma::ledger::governance::storage as gov_storage; use anoma::types::storage::Key; use byte_unit::Byte; +use tokio::task; use futures::future::TryFutureExt; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; @@ -203,7 +205,7 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { } /// Runs three concurrent tasks: A tendermint node, a shell which contains an -/// ABCI, server for talking to the tendermint node, and a broadcaster so that +/// ABCI server for talking to the tendermint node, and a broadcaster so that /// the ledger may submit txs to the chain. All must be alive for correct /// functioning. async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { @@ -213,75 +215,14 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { db_block_cache_size_bytes, } = run_aux_setup(&config, &wasm_dir).await; - let rpc_address = config.tendermint.rpc_address.to_string(); - // Create an `AbortableSpawner` for signalling shut down from the shell or from Tendermint let mut spawner = AbortableSpawner::new(); // Start Tendermint node let tendermint_node = start_tendermint(&mut spawner, &config); - // Channels for validators to send protocol txs to be broadcast to the - // broadcaster service - let (broadcaster_sender, broadcaster_receiver) = - tokio::sync::mpsc::unbounded_channel(); - - let broadcaster = if matches!( - config.tendermint.tendermint_mode, - TendermintMode::Validator - ) { - // Channel for signalling shut down to broadcaster - let (bc_abort_send, bc_abort_recv) = - tokio::sync::oneshot::channel::<()>(); - Some(( - spawner.spawn_abortable("Broadcaster", move |aborter| async move { - // Construct a service for broadcasting protocol txs from the - // ledger - let mut broadcaster = - Broadcaster::new(&rpc_address, broadcaster_receiver); - broadcaster.run(bc_abort_recv).await; - tracing::info!("Broadcaster is no longer running."); - - drop(aborter); - }).with_no_cleanup(), - bc_abort_send, - )) - } else { - None - }; - - // Setup DB cache, it must outlive the DB instance that's in the shell - let db_cache = - rocksdb::Cache::new_lru_cache(db_block_cache_size_bytes as usize).unwrap(); - - // Construct our ABCI application. - let ledger_address = config.shell.ledger_address; - let (shell, abci_service) = AbcippShim::new( - config, - wasm_dir, - broadcaster_sender, - &db_cache, - vp_wasm_compilation_cache, - tx_wasm_compilation_cache, - ); - - // Start the ABCI server - let abci = spawner.spawn_abortable("ABCI", move |aborter| async move { - let res = run_abci(abci_service, ledger_address).await; - - drop(aborter); - res - }).with_no_cleanup(); - - // Run the shell in the main thread - let thread_builder = - std::thread::Builder::new().name("ledger-shell".into()); - let shell_handler = thread_builder - .spawn(move || { - tracing::info!("Anoma ledger node started."); - shell.run() - }) - .expect("Must be able to start a thread for the shell"); + // Start ABCI server and broadcaster (the latter only if we are a validator node) + let (abci, broadcaster, shell_handler) = start_abci_broadcaster_shell(&mut spawner, config); // Wait for interrupt signal or abort message let aborted = spawner @@ -289,26 +230,33 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { .await .child_terminated(); - // NOTE: cleanup started - - // Abort the ABCI service task - abci.abort(); - - let res = match broadcaster { - Some((broadcaster, bc_abort_send)) => { - // request the broadcaster shutdown - let _ = bc_abort_send.send(()); - tokio::try_join!(tendermint_node, abci, broadcaster) - } + // Regain ownership of the ABCI `JoinHandle` + // + // TODO(tiago): this is only here because of a temporary hack. + // the method we are using to cancel the ABCI server task is calling + // `.abort()` on the `JoinHandle` of the returned tokio task. the handle + // therefore needs to be stored in the `AbortableSpawner`, which requires + // it to be an `Arc`, such that we may retain shared ownership of the + // `JoinHandle`, after we abort the ABCI server. + // + // tl;dr: make it such that we can cancel the ABCI server without calling + // `.abort()` on the Tokio `JoinHandle` + // + let abci = match Arc::try_unwrap(abci) { + Some(handle) => handle, None => { - // if the broadcaster service is not active, we fill in its return - // value with () - tokio::try_join!(tendermint_node, abci) - .map(|results| (results.0, results.1, ())) - } + // NOTE: this operation is infallible, since the only + // other live instance of the `Arc` was consumed after the + // cleanup job for the ABCI server ran + unreachable!() + }, }; - // NOTE: cleanup ended + let res = tokio::try_join!( + tendermint_node, + abci, + broadcaster, + ); match res { Ok((tendermint_res, abci_res, _)) => { @@ -435,6 +383,88 @@ async fn run_aux_setup(config: &config::Ledger, wasm_dir: &PathBuf) -> RunAuxSet } } +fn start_abci_broadcaster_shell( + spawner: &mut AbortableSpawner, + config: config::Ledger, +) -> (Arc>>, task::JoinHandle<()>, thread::JoinHandle<()>) { + let rpc_address = config.tendermint.rpc_address.to_string(); + + // Channels for validators to send protocol txs to be broadcast to the + // broadcaster service + let (broadcaster_sender, broadcaster_receiver) = + tokio::sync::mpsc::unbounded_channel(); + + let broadcaster_is_validator = if matches!( + config.tendermint.tendermint_mode, + TendermintMode::Validator, + ); + + // Start broadcaster + let broadcaster = broadcaster_is_validator + .then(move || + let (bc_abort_send, bc_abort_recv) = + tokio::sync::oneshot::channel::<()>(); + + spawner + .spawn_abortable("Broadcaster", move |aborter| async move { + // Construct a service for broadcasting protocol txs from the + // ledger + let mut broadcaster = + Broadcaster::new(&rpc_address, broadcaster_receiver); + broadcaster.run(bc_abort_recv).await; + tracing::info!("Broadcaster is no longer running."); + + drop(aborter); + }) + .with_cleanup(async move { + let _ = bc_abort_send.send(()); + }) + + ) + .unwrap_or_else(|| { + tokio::spawn(async { + std::future::ready(()).await + }) + }); + + // Setup DB cache, it must outlive the DB instance that's in the shell + let db_cache = + rocksdb::Cache::new_lru_cache(db_block_cache_size_bytes as usize).unwrap(); + + // Construct our ABCI application. + let ledger_address = config.shell.ledger_address; + let (shell, abci_service) = AbcippShim::new( + config, + wasm_dir, + broadcaster_sender, + &db_cache, + vp_wasm_compilation_cache, + tx_wasm_compilation_cache, + ); + + // Start the ABCI server + let abci = spawner + .spawn_abortable("ABCI", move |aborter| async move { + let res = run_abci(abci_service, ledger_address).await; + + drop(aborter); + res + }) + .with_join_handle_abort_cleanup(); + + // Run the shell in the main thread + let thread_builder = + thread::Builder::new().name("ledger-shell".into()); + let shell_handler = thread_builder + .spawn(move || { + tracing::info!("Anoma ledger node started."); + shell.run() + }) + .expect("Must be able to start a thread for the shell"); + + (abci, broadcaster, shell_handler) +} + /// Runs the an asynchronous ABCI server with four sub-components for consensus, /// mempool, snapshot, and info. async fn run_abci( @@ -475,7 +505,7 @@ async fn run_abci( fn start_tendermint( spawner: &mut AbortableSpawner, config: &config::Ledger, -) -> tokio::task::JoinHandle> { +) -> task::JoinHandle> { let tendermint_dir = config.tendermint_dir(); let chain_id = config.chain_id.clone(); let ledger_address = config.shell.ledger_address.to_string(); @@ -530,8 +560,3 @@ fn start_tendermint( } }) } - -// NOTE: thread join handle for shell -//async fn create_abci_broadcaster_shell(...) -> (abci, broadcaster, shell) { -// todo!() -//} From 23f4a0a8786a84f3c21d827b428a73a021f1cf3e Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 13:27:43 +0100 Subject: [PATCH 070/373] Refactor abci and broadcaster startup --- apps/src/lib/node/ledger/abortable.rs | 2 +- apps/src/lib/node/ledger/mod.rs | 81 ++++++++++++++------------- 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index f10a2d06fe..8b3776458e 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -119,7 +119,7 @@ impl<'a, A> WithCleanup<'a, A> { /// A cleanup routine shall be executed, which aborts a `JoinHandle` from /// the asynchronous runtime. #[inline] - pub fn with_join_handle_abort_cleanup(self) -> Arc> + pub fn with_join_handle_abort_cleanup(self) -> Arc> where A: FnOnce(Aborter) -> F, F: Future + Send + 'static, diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 42c43a2906..e4cdbd491c 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -12,6 +12,7 @@ use std::convert::TryInto; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; +use std::sync::Arc; use std::thread; use anoma::ledger::governance::storage as gov_storage; @@ -209,11 +210,7 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { /// the ledger may submit txs to the chain. All must be alive for correct /// functioning. async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { - let RunAuxSetup { - vp_wasm_compilation_cache, - tx_wasm_compilation_cache, - db_block_cache_size_bytes, - } = run_aux_setup(&config, &wasm_dir).await; + let setup_data = run_aux_setup(&config, &wasm_dir).await; // Create an `AbortableSpawner` for signalling shut down from the shell or from Tendermint let mut spawner = AbortableSpawner::new(); @@ -222,7 +219,12 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let tendermint_node = start_tendermint(&mut spawner, &config); // Start ABCI server and broadcaster (the latter only if we are a validator node) - let (abci, broadcaster, shell_handler) = start_abci_broadcaster_shell(&mut spawner, config); + let (abci, broadcaster, shell_handler) = start_abci_broadcaster_shell( + &mut spawner, + wasm_dir, + setup_data, + config, + ); // Wait for interrupt signal or abort message let aborted = spawner @@ -243,8 +245,8 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // `.abort()` on the Tokio `JoinHandle` // let abci = match Arc::try_unwrap(abci) { - Some(handle) => handle, - None => { + Ok(handle) => handle, + _ => { // NOTE: this operation is infallible, since the only // other live instance of the `Arc` was consumed after the // cleanup job for the ABCI server ran @@ -385,47 +387,50 @@ async fn run_aux_setup(config: &config::Ledger, wasm_dir: &PathBuf) -> RunAuxSet fn start_abci_broadcaster_shell( spawner: &mut AbortableSpawner, + wasm_dir: PathBuf, + setup_data: RunAuxSetup, config: config::Ledger, ) -> (Arc>>, task::JoinHandle<()>, thread::JoinHandle<()>) { let rpc_address = config.tendermint.rpc_address.to_string(); + let RunAuxSetup { + vp_wasm_compilation_cache, + tx_wasm_compilation_cache, + db_block_cache_size_bytes, + } = setup_data; // Channels for validators to send protocol txs to be broadcast to the // broadcaster service let (broadcaster_sender, broadcaster_receiver) = tokio::sync::mpsc::unbounded_channel(); - let broadcaster_is_validator = if matches!( - config.tendermint.tendermint_mode, - TendermintMode::Validator, - ); - // Start broadcaster - let broadcaster = broadcaster_is_validator - .then(move || - let (bc_abort_send, bc_abort_recv) = - tokio::sync::oneshot::channel::<()>(); - - spawner - .spawn_abortable("Broadcaster", move |aborter| async move { - // Construct a service for broadcasting protocol txs from the - // ledger - let mut broadcaster = - Broadcaster::new(&rpc_address, broadcaster_receiver); - broadcaster.run(bc_abort_recv).await; - tracing::info!("Broadcaster is no longer running."); - - drop(aborter); - }) - .with_cleanup(async move { - let _ = bc_abort_send.send(()); - }) - - ) - .unwrap_or_else(|| { - tokio::spawn(async { - std::future::ready(()).await + let broadcaster = if matches!( + config.tendermint.tendermint_mode, + TendermintMode::Validator + ) { + let (bc_abort_send, bc_abort_recv) = + tokio::sync::oneshot::channel::<()>(); + + spawner + .spawn_abortable("Broadcaster", move |aborter| async move { + // Construct a service for broadcasting protocol txs from the + // ledger + let mut broadcaster = + Broadcaster::new(&rpc_address, broadcaster_receiver); + broadcaster.run(bc_abort_recv).await; + tracing::info!("Broadcaster is no longer running."); + + drop(aborter); + }) + .with_cleanup(async move { + let _ = bc_abort_send.send(()); }) - }); + } else { + // dummy async task, which will resolve instantly + tokio::spawn(async { + std::future::ready(()).await + }) + }; // Setup DB cache, it must outlive the DB instance that's in the shell let db_cache = From b6adb0ca15deb8833e87a3b24a5ab1968f4b4603 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 13:33:08 +0100 Subject: [PATCH 071/373] Tokio block in place while joining thread --- apps/src/lib/node/ledger/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index e4cdbd491c..a58bf5df95 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -282,7 +282,9 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { tracing::info!("Anoma ledger node has shut down."); - if let Err(err) = shell_handler.join() { + let res = task::block_in_place(move || shell_handler.join()); + + if let Err(err) = res { std::panic::resume_unwind(err) } } From a5928b3b4bdd2556a5831a3df54eedf1a5963b7a Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 13:40:40 +0100 Subject: [PATCH 072/373] Run clippy and fmt --- apps/src/lib/node/ledger/abortable.rs | 37 ++++++++++++------- apps/src/lib/node/ledger/mod.rs | 51 ++++++++++++++------------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 8b3776458e..1ea7a6dc8a 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,15 +1,16 @@ +use std::future::Future; use std::pin::Pin; use std::sync::Arc; -use std::future::Future; +use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; -use tokio::sync::mpsc::{self, UnboundedSender, UnboundedReceiver}; /// Serves to identify an aborting async task, which is spawned /// with an [`AbortableSpawner`]. pub type AbortingTask = &'static str; -/// An [`AbortableSpawner`] will spawn abortable tasks into the asynchronous runtime. +/// An [`AbortableSpawner`] will spawn abortable tasks into the asynchronous +/// runtime. pub struct AbortableSpawner { abort_send: UnboundedSender, abort_recv: UnboundedReceiver, @@ -34,8 +35,8 @@ impl AbortableSpawner { } } - /// Spawns a new task into the asynchronous runtime, with an [`Aborter`] that shall - /// be dropped when it is no longer running. + /// Spawns a new task into the asynchronous runtime, with an [`Aborter`] + /// that shall be dropped when it is no longer running. /// /// For instance: /// @@ -49,9 +50,13 @@ impl AbortableSpawner { /// .with_no_cleanup(); /// ``` /// - /// The return type of this method is [`WithCleanup`], such that a cleanup routine, after the - /// abort is received, can be configured to execute. - pub fn spawn_abortable<'a, A>(&'a mut self, who: AbortingTask, abortable: A) -> WithCleanup<'a, A> { + /// The return type of this method is [`WithCleanup`], such that a cleanup + /// routine, after the abort is received, can be configured to execute. + pub fn spawn_abortable( + &mut self, + who: AbortingTask, + abortable: A, + ) -> WithCleanup<'_, A> { WithCleanup { who, abortable, @@ -76,8 +81,13 @@ impl AbortableSpawner { status } - /// This method is responsible for actually spawning the async task into the runtime. - fn spawn_abortable_task(&self, who: AbortingTask, abortable: A) -> JoinHandle + /// This method is responsible for actually spawning the async task into the + /// runtime. + fn spawn_abortable_task( + &self, + who: AbortingTask, + abortable: A, + ) -> JoinHandle where A: FnOnce(Aborter) -> F, F: Future + Send + 'static, @@ -125,8 +135,8 @@ impl<'a, A> WithCleanup<'a, A> { F: Future + Send + 'static, R: Send + 'static, { - let handle = self.spawner - .spawn_abortable_task(self.who, self.abortable); + let handle = + self.spawner.spawn_abortable_task(self.who, self.abortable); let handle = Arc::new(handle); let cleanup_handle = Arc::clone(&handle); @@ -257,7 +267,8 @@ async fn wait_for_abort( pub enum AborterStatus { /// The ledger process received a shutdown signal. UserShutdownLedger, - /// One of the ledger's child processes terminated, signaling the [`AbortableSpawner`]. + /// One of the ledger's child processes terminated, signaling the + /// [`AbortableSpawner`]. ChildProcessTerminated, } diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index a58bf5df95..8c770b4c9e 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -18,7 +18,6 @@ use std::thread; use anoma::ledger::governance::storage as gov_storage; use anoma::types::storage::Key; use byte_unit::Byte; -use tokio::task; use futures::future::TryFutureExt; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; @@ -26,6 +25,7 @@ use sysinfo::{RefreshKind, System, SystemExt}; use tendermint_proto::abci::CheckTxType; #[cfg(feature = "ABCI")] use tendermint_proto_abci::abci::CheckTxType; +use tokio::task; use tower::ServiceBuilder; #[cfg(not(feature = "ABCI"))] use tower_abci::{response, split, Server}; @@ -212,13 +212,15 @@ pub fn reset(config: config::Ledger) -> Result<(), shell::Error> { async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { let setup_data = run_aux_setup(&config, &wasm_dir).await; - // Create an `AbortableSpawner` for signalling shut down from the shell or from Tendermint + // Create an `AbortableSpawner` for signalling shut down from the shell or + // from Tendermint let mut spawner = AbortableSpawner::new(); // Start Tendermint node let tendermint_node = start_tendermint(&mut spawner, &config); - // Start ABCI server and broadcaster (the latter only if we are a validator node) + // Start ABCI server and broadcaster (the latter only if we are a validator + // node) let (abci, broadcaster, shell_handler) = start_abci_broadcaster_shell( &mut spawner, wasm_dir, @@ -227,10 +229,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { ); // Wait for interrupt signal or abort message - let aborted = spawner - .wait_for_abort() - .await - .child_terminated(); + let aborted = spawner.wait_for_abort().await.child_terminated(); // Regain ownership of the ABCI `JoinHandle` // @@ -251,14 +250,10 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // other live instance of the `Arc` was consumed after the // cleanup job for the ABCI server ran unreachable!() - }, + } }; - let res = tokio::try_join!( - tendermint_node, - abci, - broadcaster, - ); + let res = tokio::try_join!(tendermint_node, abci, broadcaster,); match res { Ok((tendermint_res, abci_res, _)) => { @@ -298,7 +293,10 @@ struct RunAuxSetup { } /// Return some variables used to start child processes of the ledger. -async fn run_aux_setup(config: &config::Ledger, wasm_dir: &PathBuf) -> RunAuxSetup { +async fn run_aux_setup( + config: &config::Ledger, + wasm_dir: &PathBuf, +) -> RunAuxSetup { // Prefetch needed wasm artifacts wasm_loader::pre_fetch_wasm(wasm_dir).await; @@ -392,7 +390,11 @@ fn start_abci_broadcaster_shell( wasm_dir: PathBuf, setup_data: RunAuxSetup, config: config::Ledger, -) -> (Arc>>, task::JoinHandle<()>, thread::JoinHandle<()>) { +) -> ( + Arc>>, + task::JoinHandle<()>, + thread::JoinHandle<()>, +) { let rpc_address = config.tendermint.rpc_address.to_string(); let RunAuxSetup { vp_wasm_compilation_cache, @@ -429,14 +431,13 @@ fn start_abci_broadcaster_shell( }) } else { // dummy async task, which will resolve instantly - tokio::spawn(async { - std::future::ready(()).await - }) + tokio::spawn(async { std::future::ready(()).await }) }; // Setup DB cache, it must outlive the DB instance that's in the shell let db_cache = - rocksdb::Cache::new_lru_cache(db_block_cache_size_bytes as usize).unwrap(); + rocksdb::Cache::new_lru_cache(db_block_cache_size_bytes as usize) + .unwrap(); // Construct our ABCI application. let ledger_address = config.shell.ledger_address; @@ -460,8 +461,7 @@ fn start_abci_broadcaster_shell( .with_join_handle_abort_cleanup(); // Run the shell in the main thread - let thread_builder = - thread::Builder::new().name("ledger-shell".into()); + let thread_builder = thread::Builder::new().name("ledger-shell".into()); let shell_handler = thread_builder .spawn(move || { tracing::info!("Anoma ledger node started."); @@ -548,12 +548,13 @@ fn start_tendermint( res }) .with_cleanup(async move { - // Shutdown tendermint_node via a message to ensure that the child process - // is properly cleaned-up. + // Shutdown tendermint_node via a message to ensure that the child + // process is properly cleaned-up. let (tm_abort_resp_send, tm_abort_resp_recv) = tokio::sync::oneshot::channel::<()>(); - // Ask to shutdown tendermint node cleanly. Ignore error, which can happen - // if the tendermint_node task has already finished. + // Ask to shutdown tendermint node cleanly. Ignore error, which can + // happen if the tendermint_node task has already + // finished. if let Ok(()) = tm_abort_send.send(tm_abort_resp_send) { match tm_abort_resp_recv.await { Ok(()) => {} From 9d1a8b1671ec8a08be33d472d3d401803a60d087 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Mon, 11 Jul 2022 13:44:29 +0100 Subject: [PATCH 073/373] Remove extra commas --- apps/src/lib/node/ledger/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 8c770b4c9e..09c7872103 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -253,7 +253,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { } }; - let res = tokio::try_join!(tendermint_node, abci, broadcaster,); + let res = tokio::try_join!(tendermint_node, abci, broadcaster); match res { Ok((tendermint_res, abci_res, _)) => { @@ -361,7 +361,7 @@ async fn run_aux_setup( // Find the RocksDB block cache size let db_block_cache_size_bytes = match config.shell.block_cache_bytes { Some(block_cache_bytes) => { - tracing::info!("Block cache set from the configuration.",); + tracing::info!("Block cache set from the configuration."); block_cache_bytes } None => { From 79ac96543e1e1c8cc5ee5e00a13483e0fe5ae433 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 12 Jul 2022 09:05:15 +0100 Subject: [PATCH 074/373] Add missing docstrings --- apps/src/lib/node/ledger/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 09c7872103..039a28c185 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -385,6 +385,12 @@ async fn run_aux_setup( } } +/// Launches two tasks into the asynchronous runtime: +/// +/// 1. An ABCI server. +/// 2. A service for broadcasting transactions via an HTTP client. +/// +/// Lastly, this function executes an ABCI shell on a new OS thread. fn start_abci_broadcaster_shell( spawner: &mut AbortableSpawner, wasm_dir: PathBuf, @@ -509,6 +515,8 @@ async fn run_abci( .map_err(|err| Error::TowerServer(err.to_string())) } +/// Launches a new task managing a Tendermint process into the asynchronous +/// runtime, and returns its `JoinHandle`. fn start_tendermint( spawner: &mut AbortableSpawner, config: &config::Ledger, From 538244186006d310215553b3e2495cc6d0cc7e7d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 12 Jul 2022 09:27:17 +0100 Subject: [PATCH 075/373] Remove join handle hack --- apps/src/lib/node/ledger/abortable.rs | 23 ---------- apps/src/lib/node/ledger/mod.rs | 61 +++++++++++++-------------- 2 files changed, 29 insertions(+), 55 deletions(-) diff --git a/apps/src/lib/node/ledger/abortable.rs b/apps/src/lib/node/ledger/abortable.rs index 1ea7a6dc8a..57ee92ff5b 100644 --- a/apps/src/lib/node/ledger/abortable.rs +++ b/apps/src/lib/node/ledger/abortable.rs @@ -1,6 +1,5 @@ use std::future::Future; use std::pin::Pin; -use std::sync::Arc; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::task::JoinHandle; @@ -125,28 +124,6 @@ impl<'a, A> WithCleanup<'a, A> { self.spawner.cleanup_jobs.push(Box::pin(cleanup)); self.with_no_cleanup() } - - /// A cleanup routine shall be executed, which aborts a `JoinHandle` from - /// the asynchronous runtime. - #[inline] - pub fn with_join_handle_abort_cleanup(self) -> Arc> - where - A: FnOnce(Aborter) -> F, - F: Future + Send + 'static, - R: Send + 'static, - { - let handle = - self.spawner.spawn_abortable_task(self.who, self.abortable); - - let handle = Arc::new(handle); - let cleanup_handle = Arc::clone(&handle); - - self.spawner.cleanup_jobs.push(Box::pin(async move { - cleanup_handle.abort(); - })); - - handle - } } /// A panic-proof handle for aborting a future. Will abort during stack diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 039a28c185..fa0c4f9ab8 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -12,7 +12,6 @@ use std::convert::TryInto; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; -use std::sync::Arc; use std::thread; use anoma::ledger::governance::storage as gov_storage; @@ -231,28 +230,7 @@ async fn run_aux(config: config::Ledger, wasm_dir: PathBuf) { // Wait for interrupt signal or abort message let aborted = spawner.wait_for_abort().await.child_terminated(); - // Regain ownership of the ABCI `JoinHandle` - // - // TODO(tiago): this is only here because of a temporary hack. - // the method we are using to cancel the ABCI server task is calling - // `.abort()` on the `JoinHandle` of the returned tokio task. the handle - // therefore needs to be stored in the `AbortableSpawner`, which requires - // it to be an `Arc`, such that we may retain shared ownership of the - // `JoinHandle`, after we abort the ABCI server. - // - // tl;dr: make it such that we can cancel the ABCI server without calling - // `.abort()` on the Tokio `JoinHandle` - // - let abci = match Arc::try_unwrap(abci) { - Ok(handle) => handle, - _ => { - // NOTE: this operation is infallible, since the only - // other live instance of the `Arc` was consumed after the - // cleanup job for the ABCI server ran - unreachable!() - } - }; - + // Wait for all managed tasks to finish. let res = tokio::try_join!(tendermint_node, abci, broadcaster); match res { @@ -397,7 +375,7 @@ fn start_abci_broadcaster_shell( setup_data: RunAuxSetup, config: config::Ledger, ) -> ( - Arc>>, + task::JoinHandle>, task::JoinHandle<()>, thread::JoinHandle<()>, ) { @@ -456,17 +434,22 @@ fn start_abci_broadcaster_shell( tx_wasm_compilation_cache, ); + // Channel for signalling shut down to ABCI server + let (abci_abort_send, abci_abort_recv) = tokio::sync::oneshot::channel(); + // Start the ABCI server let abci = spawner .spawn_abortable("ABCI", move |aborter| async move { - let res = run_abci(abci_service, ledger_address).await; + let res = run_abci(abci_service, ledger_address, abci_abort_recv).await; drop(aborter); res }) - .with_join_handle_abort_cleanup(); + .with_cleanup(async move { + let _ = abci_abort_send.send(()); + }); - // Run the shell in the main thread + // Start the shell in a new OS thread let thread_builder = thread::Builder::new().name("ledger-shell".into()); let shell_handler = thread_builder .spawn(move || { @@ -483,6 +466,7 @@ fn start_abci_broadcaster_shell( async fn run_abci( abci_service: AbciService, ledger_address: SocketAddr, + abort_recv: tokio::sync::oneshot::Receiver<()>, ) -> shell::Result<()> { // Split it into components. let (consensus, mempool, snapshot, info) = split::service(abci_service, 5); @@ -508,11 +492,24 @@ async fn run_abci( .finish() .unwrap(); - // Run the server with the ABCI service - server - .listen(ledger_address) - .await - .map_err(|err| Error::TowerServer(err.to_string())) + tokio::select! { + // Run the server with the ABCI service + status = server.listen(ledger_address) => { + status.map_err(|err| Error::TowerServer(err.to_string())) + }, + resp_sender = abort_recv => { + match resp_sender { + Ok(()) => { + tracing::info!("Shutting down ABCI server..."); + }, + Err(err) => { + tracing::error!("The ABCI server abort sender has unexpectedly dropped: {}", err); + tracing::info!("Shutting down ABCI server..."); + } + } + Ok(()) + } + } } /// Launches a new task managing a Tendermint process into the asynchronous From d1d90007f320e38fa0452d949deb6947f05da44c Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Tue, 12 Jul 2022 09:29:46 +0100 Subject: [PATCH 076/373] Run make fmt --- apps/src/lib/node/ledger/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index fa0c4f9ab8..d39ad5b99f 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -440,7 +440,8 @@ fn start_abci_broadcaster_shell( // Start the ABCI server let abci = spawner .spawn_abortable("ABCI", move |aborter| async move { - let res = run_abci(abci_service, ledger_address, abci_abort_recv).await; + let res = + run_abci(abci_service, ledger_address, abci_abort_recv).await; drop(aborter); res From dd550a2da0e33f4e631e3b945f6499ab842cfd62 Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Wed, 13 Jul 2022 12:49:22 +0100 Subject: [PATCH 077/373] Add changelog --- .../improvements/1231-refactor-ledger-run-with-cleanup.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md diff --git a/.changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md b/.changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md new file mode 100644 index 0000000000..6d0ee99747 --- /dev/null +++ b/.changelog/unreleased/improvements/1231-refactor-ledger-run-with-cleanup.md @@ -0,0 +1,2 @@ +- Refactored ledger startup code + ([#1231](https://github.com/anoma/anoma/pull/1231)) \ No newline at end of file From a358511c53dadfbdb9b0213059273cf151f54c55 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 3 Aug 2022 14:12:41 +0200 Subject: [PATCH 078/373] [ci] improve e2e log upload to add validator logs --- .github/workflows/build-and-test.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index be86985717..467defb3a3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -205,7 +205,9 @@ jobs: uses: actions/upload-artifact@v3 with: name: logs-e2e${{ matrix.make.suffix }}-${{ github.sha }} - path: /tmp/.*/logs/ + path: | + /tmp/.*/logs/ + /tmp/.*/e2e-test.*/setup/validator-*/.anoma/logs/*.log retention-days: 5 - name: Print sccache stats if: always() From 92d52d8cdb0d0f1e09b54c7f4d39ae18160f9976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 26 Jul 2022 14:21:36 +0200 Subject: [PATCH 079/373] test/e2e: update assert_success/failure to first consume output --- tests/src/e2e/ledger_tests.rs | 4 ++-- tests/src/e2e/setup.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f150e6eac0..a83d3f54ca 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1566,7 +1566,7 @@ fn test_genesis_validators() -> Result<()> { let validator_0_alias = "validator-0"; let validator_1_alias = "validator-1"; - let init_genesis_validator_0 = setup::run_cmd( + let mut init_genesis_validator_0 = setup::run_cmd( Bin::Client, [ "utils", @@ -1602,7 +1602,7 @@ fn test_genesis_validators() -> Result<()> { .remove(validator_0_alias) .unwrap(); - let init_genesis_validator_1 = setup::run_cmd( + let mut init_genesis_validator_1 = setup::run_cmd( Bin::Client, [ "utils", diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 2b0aba0696..28f1cf1825 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -503,14 +503,20 @@ impl AnomaCmd { } /// Assert that the process exited with success - pub fn assert_success(&self) { + pub fn assert_success(&mut self) { + // Make sure that there is no unread output first + let _ = self.exp_eof().unwrap(); + let status = self.session.wait().unwrap(); assert_eq!(WaitStatus::Exited(self.session.pid(), 0), status); } /// Assert that the process exited with failure #[allow(dead_code)] - pub fn assert_failure(&self) { + pub fn assert_failure(&mut self) { + // Make sure that there is no unread output first + let _ = self.exp_eof().unwrap(); + let status = self.session.wait().unwrap(); assert_ne!(WaitStatus::Exited(self.session.pid(), 0), status); } From 0bacc9506258bbf484a09f9d51963d18f036f473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 1 Aug 2022 17:19:14 +0200 Subject: [PATCH 080/373] changelog: add #247 --- .changelog/unreleased/testing/247-e2e-fix-cmd-assert.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/testing/247-e2e-fix-cmd-assert.md diff --git a/.changelog/unreleased/testing/247-e2e-fix-cmd-assert.md b/.changelog/unreleased/testing/247-e2e-fix-cmd-assert.md new file mode 100644 index 0000000000..6696c5946a --- /dev/null +++ b/.changelog/unreleased/testing/247-e2e-fix-cmd-assert.md @@ -0,0 +1,2 @@ +- E2E: Consume unread output before checking exit status. + ([#247](https://github.com/anoma/namada/pull/247)) From 0dd32e6953c016a169691e1b187a88310bc7d3c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Jul 2022 18:33:05 +0200 Subject: [PATCH 081/373] deps: remove ABCI dependencies, use ABCI++ as default --- Cargo.lock | 285 +++----------------------- apps/Cargo.toml | 39 +--- encoding_spec/Cargo.toml | 14 +- shared/Cargo.toml | 34 +-- tests/Cargo.toml | 23 +-- tx_prelude/Cargo.toml | 12 +- vm_env/Cargo.toml | 14 +- vp_prelude/Cargo.toml | 12 +- wasm/tx_template/Cargo.lock | 18 +- wasm/tx_template/Cargo.toml | 2 - wasm/vp_template/Cargo.lock | 18 +- wasm/vp_template/Cargo.toml | 2 - wasm/wasm_source/Cargo.lock | 18 +- wasm/wasm_source/Cargo.toml | 5 - wasm_for_tests/wasm_source/Cargo.lock | 18 +- 15 files changed, 80 insertions(+), 434 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31d67389e0..8eab58b3a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2189,10 +2189,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -2595,33 +2593,6 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "ibc" -version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" -dependencies = [ - "bytes 1.1.0", - "derive_more", - "flex-error", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5)", - "ics23", - "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", - "safe-regex", - "serde 1.0.137", - "serde_derive", - "serde_json", - "sha2 0.10.2", - "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "time 0.3.9", - "tracing 0.1.35", -] - [[package]] name = "ibc" version = "0.12.0" @@ -2630,7 +2601,7 @@ dependencies = [ "bytes 1.1.0", "derive_more", "flex-error", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", + "ibc-proto", "ics23", "num-traits 0.2.15", "prost 0.9.0", @@ -2641,27 +2612,14 @@ dependencies = [ "serde_json", "sha2 0.10.2", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-light-client-verifier 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-testgen 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", "time 0.3.9", "tracing 0.1.35", ] -[[package]] -name = "ibc-proto" -version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" -dependencies = [ - "bytes 1.1.0", - "prost 0.9.0", - "prost-types 0.9.0", - "serde 1.0.137", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tonic", -] - [[package]] name = "ibc-proto" version = "0.16.0" @@ -2671,7 +2629,7 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "serde 1.0.137", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-proto", "tonic", ] @@ -3890,10 +3848,8 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5)", - "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5)", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", + "ibc", + "ibc-proto", "ics23", "itertools 0.10.3", "loupe", @@ -3912,10 +3868,8 @@ dependencies = [ "sha2 0.9.9", "sparse-merkle-tree", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint", + "tendermint-proto", "test-log", "thiserror", "tonic-build", @@ -3995,14 +3949,10 @@ dependencies = [ "sysinfo", "tar", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint", + "tendermint-config", + "tendermint-proto", + "tendermint-rpc", "test-log", "thiserror", "tokio", @@ -4011,8 +3961,7 @@ dependencies = [ "tonic", "tonic-build", "tower", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing)", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", + "tower-abci", "tracing 0.1.35", "tracing-log", "tracing-subscriber 0.3.11", @@ -4076,10 +4025,6 @@ dependencies = [ "serde_json", "sha2 0.9.9", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", "test-log", "toml", "tracing 0.1.35", @@ -6305,62 +6250,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" -dependencies = [ - "async-trait", - "bytes 1.1.0", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures 0.3.21", - "num-traits 0.2.15", - "once_cell", - "prost 0.9.0", - "prost-types 0.9.0", - "serde 1.0.137", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle 2.4.1", - "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", - "time 0.3.9", - "zeroize", -] - -[[package]] -name = "tendermint" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" -dependencies = [ - "async-trait", - "bytes 1.1.0", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures 0.3.21", - "num-traits 0.2.15", - "once_cell", - "prost 0.9.0", - "prost-types 0.9.0", - "serde 1.0.137", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle 2.4.1", - "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "time 0.3.9", - "zeroize", -] - [[package]] name = "tendermint" version = "0.23.5" @@ -6384,24 +6273,11 @@ dependencies = [ "signature", "subtle 2.4.1", "subtle-encoding", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-proto", "time 0.3.9", "zeroize", ] -[[package]] -name = "tendermint-config" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" -dependencies = [ - "flex-error", - "serde 1.0.137", - "serde_json", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "toml", - "url 2.2.2", -] - [[package]] name = "tendermint-config" version = "0.23.5" @@ -6410,24 +6286,11 @@ dependencies = [ "flex-error", "serde 1.0.137", "serde_json", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint", "toml", "url 2.2.2", ] -[[package]] -name = "tendermint-light-client-verifier" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" -dependencies = [ - "derive_more", - "flex-error", - "serde 1.0.137", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "time 0.3.9", -] - [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" @@ -6436,42 +6299,8 @@ dependencies = [ "derive_more", "flex-error", "serde 1.0.137", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "time 0.3.9", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5#e43b68719e70e8e1c002e7f6fa557e0bafa01e0d" -dependencies = [ - "bytes 1.1.0", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", - "serde 1.0.137", - "serde_bytes", - "subtle-encoding", - "time 0.3.9", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" -dependencies = [ - "bytes 1.1.0", - "flex-error", - "num-derive", - "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", - "serde 1.0.137", - "serde_bytes", - "subtle-encoding", + "tendermint", + "tendermint-rpc", "time 0.3.9", ] @@ -6492,39 +6321,6 @@ dependencies = [ "time 0.3.9", ] -[[package]] -name = "tendermint-rpc" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" -dependencies = [ - "async-trait", - "async-tungstenite", - "bytes 1.1.0", - "flex-error", - "futures 0.3.21", - "getrandom 0.2.6", - "http", - "hyper 0.14.19", - "hyper-proxy", - "hyper-rustls", - "peg", - "pin-project 1.0.10", - "serde 1.0.137", - "serde_bytes", - "serde_json", - "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "thiserror", - "time 0.3.9", - "tokio", - "tracing 0.1.35", - "url 2.2.2", - "uuid", - "walkdir", -] - [[package]] name = "tendermint-rpc" version = "0.23.5" @@ -6546,9 +6342,9 @@ dependencies = [ "serde_bytes", "serde_json", "subtle-encoding", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint", + "tendermint-config", + "tendermint-proto", "thiserror", "time 0.3.9", "tokio", @@ -6558,21 +6354,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "tendermint-testgen" -version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" -dependencies = [ - "ed25519-dalek", - "gumdrop", - "serde 1.0.137", - "serde_json", - "simple-error", - "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "time 0.3.9", -] - [[package]] name = "tendermint-testgen" version = "0.23.5" @@ -6584,7 +6365,7 @@ dependencies = [ "serde_json", "simple-error", "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint", "time 0.3.9", ] @@ -7024,24 +6805,6 @@ dependencies = [ "tracing 0.1.35", ] -[[package]] -name = "tower-abci" -version = "0.1.0" -source = "git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing#73e43bf79fb21b4969cc09f79a0a40ce4cc7bb52" -dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "pin-project 1.0.10", - "prost 0.9.0", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tokio", - "tokio-stream", - "tokio-util 0.6.10", - "tower", - "tracing 0.1.30", - "tracing-tower", -] - [[package]] name = "tower-abci" version = "0.1.0" @@ -7051,7 +6814,7 @@ dependencies = [ "futures 0.3.21", "pin-project 1.0.10", "prost 0.9.0", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-proto", "tokio", "tokio-stream", "tokio-util 0.6.10", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 1464b22c8b..7a4dffac76 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -39,32 +39,14 @@ name = "namadaw" path = "src/bin/anoma-wallet/main.rs" [features] -default = ["std", "ABCI"] +default = ["std"] dev = ["namada/dev"] std = ["ed25519-consensus/std", "rand/std", "rand_core/std"] # for integration tests and test utilies -ABCI = [ - "tendermint-stable", - "tendermint-config-abci", - "tendermint-proto-abci", - "tendermint-rpc-abci", - "tower-abci-old", - "namada/ABCI", - "namada/ibc-vp-abci", -] -ABCI-plus-plus = [ - "tendermint", - "tendermint-config", - "tendermint-proto", - "tendermint-rpc", - "tower-abci", - "namada/ABCI-plus-plus", - "namada/ibc-vp", -] testing = ["dev"] [dependencies] -namada = {path = "../shared", default-features = false, features = ["wasm-runtime", "ferveo-tpke", "rand"]} +namada = {path = "../shared", features = ["wasm-runtime", "ferveo-tpke", "rand"]} ark-serialize = "0.3.0" ark-std = "0.3.0" async-std = {version = "1.9.0", features = ["unstable"]} @@ -123,14 +105,10 @@ sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", b sysinfo = {version = "=0.21.1", default-features = false} tar = "0.4.37" # temporarily using fork work-around -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-config-abci = {package = "tendermint-config", git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-proto-abci = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5", optional = true} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true, features = ["http-client", "websocket-client"]} -tendermint-rpc-abci = {package = "tendermint-rpc", git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5", optional = true, features = ["http-client", "websocket-client"]} -tendermint-stable = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5", optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} +tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} +tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client", "websocket-client"]} thiserror = "1.0.30" tokio = {version = "1.8.2", features = ["full"]} toml = "0.5.8" @@ -138,8 +116,7 @@ tonic = "0.6.1" tower = "0.4" # Also, using the same version of tendermint-rs as we do here. # with a patch for https://github.com/penumbra-zone/tower-abci/issues/7. -tower-abci = {git = "https://github.com/heliaxdev/tower-abci", rev = "f6463388fc319b6e210503b43b3aecf6faf6b200", optional = true} -tower-abci-old = {package = "tower-abci", git = "https://github.com/heliaxdev/tower-abci", branch = "yuji/rebase_v0.23.5_tracing", optional = true} +tower-abci = {git = "https://github.com/heliaxdev/tower-abci", rev = "f6463388fc319b6e210503b43b3aecf6faf6b200"} tracing = "0.1.30" tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} @@ -147,7 +124,7 @@ websocket = "0.26.2" winapi = "0.3.9" [dev-dependencies] -namada = {path = "../shared", default-features = false, features = ["testing", "wasm-runtime"]} +namada = {path = "../shared", features = ["testing", "wasm-runtime"]} cargo-watch = "7.5.0" bit-set = "0.5.2" # A fork with state machime testing diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index e4e61a098a..d875bfc1a7 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -9,20 +9,10 @@ resolver = "2" version = "0.7.0" [features] -default = ["ABCI"] - -ABCI = [ - "namada/ABCI", - "namada/ibc-vp-abci", -] - -ABCI-plus-plus = [ - "namada/ABCI-plus-plus", - "namada/ibc-vp", -] +default = [] [dependencies] -namada = {path = "../shared", default-features = false} +namada = {path = "../shared"} borsh = "0.9.0" itertools = "0.10.3" lazy_static = "1.4.0" diff --git a/shared/Cargo.toml b/shared/Cargo.toml index d63acc035f..241fd034da 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -9,7 +9,7 @@ version = "0.7.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["ABCI", "ibc-vp-abci"] +default = [] # NOTE "dev" features that shouldn't be used in live networks are enabled by default for now dev = [] ferveo-tpke = [ @@ -19,30 +19,10 @@ ferveo-tpke = [ "rand_core", "rand", ] -# for integration tests and test utilies -ibc-vp = [ - "ibc", -] -ibc-vp-abci = [ - "ibc-abci", -] ibc-mocks = [ "ibc/mocks", ] -ibc-mocks-abci = [ - "ibc-abci/mocks", -] # for integration tests and test utilies -ABCI = [ - "ibc-proto-abci", - "tendermint-stable", - "tendermint-proto-abci", -] -ABCI-plus-plus = [ - "ibc-proto", - "tendermint", - "tendermint-proto", -] testing = [ "proptest", "rand", @@ -79,10 +59,8 @@ ferveo-common = {git = "https://github.com/anoma/ferveo"} hex = "0.4.3" tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} # TODO using the same version of tendermint-rs as we do here. -ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false, optional = true} -ibc-abci = {package = "ibc", git = "https://github.com/heliaxdev/ibc-rs", branch = "yuji/v0.12.0_tm_v0.23.5", default-features = false, optional = true} -ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false, optional = true} -ibc-proto-abci = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc-rs", branch = "yuji/v0.12.0_tm_v0.23.5", default-features = false, optional = true} +ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false} +ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false} ics23 = "0.6.7" itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} @@ -103,10 +81,8 @@ sha2 = "0.9.3" sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/prost-0.9", default-features = false, features = ["std", "borsh"]} tempfile = {version = "3.2.0", optional = true} # temporarily using fork work-around for https://github.com/informalsystems/tendermint-rs/issues/971 -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", optional = true} -tendermint-proto-abci = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5", optional = true} -tendermint-stable = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5", optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} +tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} thiserror = "1.0.30" tracing = "0.1.30" wasmer = {version = "=2.2.0", optional = true} diff --git a/tests/Cargo.toml b/tests/Cargo.toml index a4ea87e21e..d513da44ef 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -8,24 +8,12 @@ resolver = "2" version = "0.7.0" [features] -default = ["wasm-runtime", "ABCI"] +default = ["wasm-runtime"] wasm-runtime = ["namada/wasm-runtime"] -ABCI = [ - "namada/ABCI", - "namada/ibc-mocks-abci", - "namada_vm_env/ABCI", -] - -ABCI-plus-plus = [ - "namada/ABCI-plus-plus", - "namada/ibc-mocks", - "namada_vm_env/ABCI-plus-plus", -] - [dependencies] -namada = {path = "../shared", default-features = false, features = ["testing"]} -namada_vm_env = {path = "../vm_env", default-features = false} +namada = {path = "../shared", features = ["testing", "ibc-mocks"]} +namada_vm_env = {path = "../vm_env"} chrono = "0.4.19" concat-idents = "1.1.2" prost = "0.9.0" @@ -33,11 +21,6 @@ serde_json = {version = "1.0.65"} sha2 = "0.9.3" test-log = {version = "0.2.7", default-features = false, features = ["trace"]} tempfile = "3.2.0" -# temporarily using fork work-around -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/abcipp-v0.23.5", optional = true} -tendermint-stable = {package = "tendermint", git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5", optional = true} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/abcipp-v0.23.5", optional = true} -tendermint-proto-abci = {package = "tendermint-proto", git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5", optional = true} tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} derivative = "2.2.0" diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 2f82e6fd8c..a35324da05 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -7,16 +7,8 @@ resolver = "2" version = "0.7.0" [features] -default = ["ABCI"] - -ABCI = [ - "namada_vm_env/ABCI", -] - -ABCI-plus-plus = [ - "namada_vm_env/ABCI-plus-plus", -] +default = [] [dependencies] -namada_vm_env = {path = "../vm_env", default-features = false} +namada_vm_env = {path = "../vm_env"} sha2 = "0.10.1" diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index ac37e33b89..f11d57d1b0 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -7,20 +7,10 @@ resolver = "2" version = "0.7.0" [features] -default = ["ABCI"] - -ABCI = [ - "namada/ABCI", - "namada/ibc-vp-abci", -] - -ABCI-plus-plus = [ - "namada/ABCI-plus-plus", - "namada/ibc-vp", -] +default = [] [dependencies] -namada = {path = "../shared", default-features = false} +namada = {path = "../shared"} namada_macros = {path = "../macros"} borsh = "0.9.0" hex = "0.4.3" diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index c21e2bdd0e..f59c5ed032 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -7,16 +7,8 @@ resolver = "2" version = "0.7.0" [features] -default = ["ABCI"] - -ABCI = [ - "namada_vm_env/ABCI", -] - -ABCI-plus-plus = [ - "namada_vm_env/ABCI-plus-plus", -] +default = [] [dependencies] -namada_vm_env = {path = "../vm_env", default-features = false} +namada_vm_env = {path = "../vm_env"} sha2 = "0.10.1" diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index fcda82a6a5..bf9d222920 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -901,10 +901,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -1069,7 +1067,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes", "derive_more", @@ -1096,7 +1094,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes", "prost", @@ -2371,7 +2369,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "bytes", @@ -2399,7 +2397,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", "serde", @@ -2412,7 +2410,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "derive_more", "flex-error", @@ -2425,7 +2423,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes", "flex-error", @@ -2442,7 +2440,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes", "flex-error", @@ -2466,7 +2464,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index a0febc6baf..469104ce65 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -19,8 +19,6 @@ getrandom = { version = "0.2", features = ["custom"] } namada_tests = {path = "../../tests"} [patch.crates-io] -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} # TODO temp patch for , and more tba. borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 3c82e7293e..98f6c7f71c 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -901,10 +901,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -1069,7 +1067,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes", "derive_more", @@ -1096,7 +1094,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes", "prost", @@ -2371,7 +2369,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "bytes", @@ -2399,7 +2397,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", "serde", @@ -2412,7 +2410,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "derive_more", "flex-error", @@ -2425,7 +2423,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes", "flex-error", @@ -2442,7 +2440,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes", "flex-error", @@ -2466,7 +2464,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 5513f52089..125b50d07a 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -19,8 +19,6 @@ getrandom = { version = "0.2", features = ["custom"] } namada_tests = {path = "../../tests"} [patch.crates-io] -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} # TODO temp patch for , and more tba. borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 6b59401588..b7185cc110 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -901,10 +901,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] [[package]] @@ -1069,7 +1067,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes", "derive_more", @@ -1096,7 +1094,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes", "prost", @@ -2397,7 +2395,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "bytes", @@ -2425,7 +2423,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", "serde", @@ -2438,7 +2436,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "derive_more", "flex-error", @@ -2451,7 +2449,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes", "flex-error", @@ -2468,7 +2466,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes", "flex-error", @@ -2492,7 +2490,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "ed25519-dalek", "gumdrop", diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index a7202d20c1..ee2234928a 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -50,11 +50,6 @@ tracing = "0.1.30" tracing-subscriber = {version = "0.3.7", default-features = false, features = ["env-filter", "fmt"]} [patch.crates-io] -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-light-client-verifier = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} -tendermint-testgen = {git = "https://github.com/heliaxdev/tendermint-rs", branch = "yuji/rebase_v0.23.5"} # TODO temp patch for , and more tba. borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 2507c11460..39f70c1787 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -902,10 +902,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -1079,7 +1077,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.12.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes", "derive_more", @@ -1106,7 +1104,7 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.16.0" -source = "git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5#e14560ecfc3f275a63b5702e038cbabd2797b2b6" +source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes", "prost", @@ -2403,7 +2401,7 @@ dependencies = [ [[package]] name = "tendermint" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", "bytes", @@ -2431,7 +2429,7 @@ dependencies = [ [[package]] name = "tendermint-config" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", "serde", @@ -2444,7 +2442,7 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "derive_more", "flex-error", @@ -2457,7 +2455,7 @@ dependencies = [ [[package]] name = "tendermint-proto" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes", "flex-error", @@ -2474,7 +2472,7 @@ dependencies = [ [[package]] name = "tendermint-rpc" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "bytes", "flex-error", @@ -2498,7 +2496,7 @@ dependencies = [ [[package]] name = "tendermint-testgen" version = "0.23.5" -source = "git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5#a1439b37ac64fbcaf508021fc1e1aff07c18147e" +source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "ed25519-dalek", "gumdrop", From f5b8ba9ae1fe9303195a53747742ed5765ab4a59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Jul 2022 18:33:48 +0200 Subject: [PATCH 082/373] all: remove "ABCI" conditional compilation --- apps/build.rs | 5 - apps/src/lib/cli.rs | 6 - apps/src/lib/client/gossip.rs | 3 - apps/src/lib/client/rpc.rs | 23 +- apps/src/lib/client/signing.rs | 15 +- apps/src/lib/client/tendermint_rpc_types.rs | 10 +- .../lib/client/tendermint_websocket_client.rs | 440 ----------------- apps/src/lib/client/tm_jsonrpc_client.rs | 402 ++++++++------- apps/src/lib/client/tx.rs | 98 +--- apps/src/lib/client/utils.rs | 6 - apps/src/lib/config/mod.rs | 6 - apps/src/lib/node/ledger/broadcaster.rs | 3 - apps/src/lib/node/ledger/events.rs | 53 +- apps/src/lib/node/ledger/mod.rs | 15 - apps/src/lib/node/ledger/rpc.rs | 3 - .../lib/node/ledger/shell/finalize_block.rs | 205 +------- apps/src/lib/node/ledger/shell/init_chain.rs | 9 - apps/src/lib/node/ledger/shell/mod.rs | 80 +-- .../lib/node/ledger/shell/prepare_proposal.rs | 465 +++++++++--------- .../lib/node/ledger/shell/process_proposal.rs | 187 +------ apps/src/lib/node/ledger/shell/queries.rs | 9 - apps/src/lib/node/ledger/shims/abcipp_shim.rs | 70 --- .../node/ledger/shims/abcipp_shim_types.rs | 92 +--- apps/src/lib/node/ledger/tendermint_node.rs | 101 +--- apps/src/lib/node/matchmaker.rs | 18 +- shared/build.rs | 4 - shared/src/ledger/storage/merkle_tree.rs | 3 - shared/src/ledger/storage/mod.rs | 3 - shared/src/lib.rs | 17 +- shared/src/types/hash.rs | 6 - shared/src/types/time.rs | 3 - tests/src/e2e/eth_bridge_tests.rs | 4 +- tests/src/e2e/gossip_tests.rs | 11 +- tests/src/e2e/helpers.rs | 27 +- tests/src/e2e/ledger_tests.rs | 62 +-- tests/src/e2e/setup.rs | 50 +- 36 files changed, 545 insertions(+), 1969 deletions(-) diff --git a/apps/build.rs b/apps/build.rs index 514785c3e0..ae49503e78 100644 --- a/apps/build.rs +++ b/apps/build.rs @@ -12,11 +12,6 @@ const PROTO_SRC: &str = "./proto"; const RUSTFMT_TOOLCHAIN_SRC: &str = "../rust-nightly-version"; fn main() { - #[cfg(all(feature = "ABCI", feature = "ABCI-plus-plus"))] - compile_error!( - "`ABCI` and `ABCI-plus-plus` may not be used at the same time" - ); - // Discover the repository version, if it exists println!("cargo:rerun-if-changed=../.git"); let describe_opts = DescribeOptions::new(); diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 95f7368668..5eef95b34d 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1372,14 +1372,8 @@ pub mod args { use namada::types::token; use namada::types::transaction::GasLimit; use serde::Deserialize; - #[cfg(not(feature = "ABCI"))] use tendermint::Timeout; - #[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; - #[cfg(feature = "ABCI")] - use tendermint_config_abci::net::Address as TendermintAddress; - #[cfg(feature = "ABCI")] - use tendermint_stable::Timeout; use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; diff --git a/apps/src/lib/client/gossip.rs b/apps/src/lib/client/gossip.rs index a3f55518d1..2225898ff4 100644 --- a/apps/src/lib/client/gossip.rs +++ b/apps/src/lib/client/gossip.rs @@ -4,10 +4,7 @@ use std::io::Write; use borsh::BorshSerialize; use namada::proto::Signed; use namada::types::intent::{Exchange, FungibleTokenIntent}; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address as TendermintAddress; use super::signing; use crate::cli::{self, args, Context}; diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 21ccb65433..34652ac825 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -30,30 +30,13 @@ use namada::types::key::*; use namada::types::storage::{Epoch, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; -#[cfg(not(feature = "ABCI"))] use tendermint::abci::Code; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address as TendermintAddress; -#[cfg(not(feature = "ABCI"))] use tendermint_rpc::error::Error as TError; -#[cfg(not(feature = "ABCI"))] use tendermint_rpc::query::Query; -#[cfg(not(feature = "ABCI"))] -use tendermint_rpc::{Client, HttpClient}; -#[cfg(not(feature = "ABCI"))] -use tendermint_rpc::{Order, SubscriptionClient, WebSocketClient}; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::error::Error as TError; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::query::Query; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::{Client, HttpClient}; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::{Order, SubscriptionClient, WebSocketClient}; -#[cfg(feature = "ABCI")] -use tendermint_stable::abci::Code; +use tendermint_rpc::{ + Client, HttpClient, Order, SubscriptionClient, WebSocketClient, +}; use crate::cli::{self, args, Context}; use crate::client::tendermint_rpc_types::TxResponse; diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index 08ba80a6d2..dd86470403 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -9,10 +9,7 @@ use namada::types::address::{Address, ImplicitAddress}; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::transaction::{hash_tx, Fee, WrapperTx}; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address as TendermintAddress; use super::rpc; use crate::cli::context::WalletAddress; @@ -139,18 +136,10 @@ pub async fn sign_wrapper( }; // We use this to determine when the wrapper tx makes it on-chain - let wrapper_hash = if !cfg!(feature = "ABCI") { - hash_tx(&tx.try_to_vec().unwrap()).to_string() - } else { - tx.tx_hash.to_string() - }; + let wrapper_hash = hash_tx(&tx.try_to_vec().unwrap()).to_string(); // We use this to determine when the decrypted inner tx makes it // on-chain - let decrypted_hash = if !cfg!(feature = "ABCI") { - Some(tx.tx_hash.to_string()) - } else { - None - }; + let decrypted_hash = tx.tx_hash.to_string(); TxBroadcastData::Wrapper { tx: tx .sign(keypair) diff --git a/apps/src/lib/client/tendermint_rpc_types.rs b/apps/src/lib/client/tendermint_rpc_types.rs index d910af7bb8..6575c74082 100644 --- a/apps/src/lib/client/tendermint_rpc_types.rs +++ b/apps/src/lib/client/tendermint_rpc_types.rs @@ -5,7 +5,6 @@ use serde::Serialize; use thiserror::Error; use crate::cli::safe_exit; -#[cfg(not(feature = "ABCI"))] use crate::node::ledger::events::Attributes; /// Errors from interacting with Tendermint's jsonrpc endpoint @@ -15,7 +14,6 @@ pub enum Error { Address(String), #[error("Error in sending JSON RPC request to Tendermint")] Send, - #[cfg(not(feature = "ABCI"))] #[error("Received an error response from Tendermint: {0:?}")] Rpc(tendermint_rpc::response_error::ResponseError), #[error("Received malformed JSON response from Tendermint")] @@ -40,7 +38,7 @@ pub enum TxBroadcastData { Wrapper { tx: Tx, wrapper_hash: String, - decrypted_hash: Option, + decrypted_hash: String, }, } @@ -63,9 +61,6 @@ impl TxResponse { let tx_hash_json = serde_json::Value::String(tx_hash.to_string()); let mut selector = jsonpath::selector(&json); let mut index = 0; - #[cfg(feature = "ABCI")] - let evt_key = "applied"; - #[cfg(not(feature = "ABCI"))] let evt_key = "accepted"; // Find the tx with a matching hash let hash = loop { @@ -134,7 +129,6 @@ impl TxResponse { } } -#[cfg(not(feature = "ABCI"))] mod params { use std::convert::TryFrom; use std::time::Duration; @@ -259,7 +253,6 @@ mod params { /// Searches for custom events emitted from the ledger and converts /// them back to thin wrapper around a hashmap for further parsing. /// Returns none if the event is not found. - #[cfg(not(feature = "ABCI"))] pub fn parse(reply: EventReply, tx_hash: &str) -> Option { let mut event = reply .items @@ -339,5 +332,4 @@ mod params { } } -#[cfg(not(feature = "ABCI"))] pub use params::*; diff --git a/apps/src/lib/client/tendermint_websocket_client.rs b/apps/src/lib/client/tendermint_websocket_client.rs index 370c6d51f8..d5af3bcee1 100644 --- a/apps/src/lib/client/tendermint_websocket_client.rs +++ b/apps/src/lib/client/tendermint_websocket_client.rs @@ -6,20 +6,10 @@ use std::sync::{Arc, Mutex}; use std::time::Duration; use async_trait::async_trait; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address; -#[cfg(not(feature = "ABCI"))] use tendermint_rpc::{ Client, Error as RpcError, Request, Response, SimpleRequest, }; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::query::Query; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::{ - Client, Error as RpcError, Request, Response, SimpleRequest, -}; use thiserror::Error; use tokio::time::Instant; use websocket::result::WebSocketError; @@ -59,18 +49,9 @@ mod rpc_types { use std::str::FromStr; use serde::{de, Deserialize, Serialize, Serializer}; - #[cfg(not(feature = "ABCI"))] use tendermint_rpc::method::Method; - #[cfg(not(feature = "ABCI"))] use tendermint_rpc::query::{EventType, Query}; - #[cfg(not(feature = "ABCI"))] use tendermint_rpc::{request, response}; - #[cfg(feature = "ABCI")] - use tendermint_rpc_abci::method::Method; - #[cfg(feature = "ABCI")] - use tendermint_rpc_abci::query::{EventType, Query}; - #[cfg(feature = "ABCI")] - use tendermint_rpc_abci::{request, response}; use super::Json; @@ -187,8 +168,6 @@ impl Display for WebSocketAddress { write!(f, "ws://{}:{}/websocket", self.host, self.port) } } -#[cfg(feature = "ABCI")] -use rpc_types::{RpcResponse, RpcSubscription, SubscribeType}; /// We need interior mutability since the `perform` method of the `Client` /// trait from `tendermint_rpc` only takes `&self` as an argument @@ -197,16 +176,8 @@ use rpc_types::{RpcResponse, RpcSubscription, SubscribeType}; type Websocket = Arc>>; type ResponseQueue = Arc>>; -#[cfg(feature = "ABCI")] -struct Subscription { - id: String, - query: Query, -} - pub struct TendermintWebsocketClient { websocket: Websocket, - #[cfg(feature = "ABCI")] - subscribed: Option, received_responses: ResponseQueue, connection_timeout: Duration, } @@ -224,8 +195,6 @@ impl TendermintWebsocketClient { { Ok(websocket) => Ok(Self { websocket: Arc::new(Mutex::new(websocket)), - #[cfg(feature = "ABCI")] - subscribed: None, received_responses: Arc::new(Mutex::new(HashMap::new())), connection_timeout: connection_timeout .unwrap_or_else(|| Duration::new(300, 0)), @@ -238,149 +207,8 @@ impl TendermintWebsocketClient { pub fn close(&mut self) { // Even in the case of errors, this will be shutdown let _ = self.websocket.lock().unwrap().shutdown(); - #[cfg(feature = "ABCI")] - { - self.subscribed = None; - } self.received_responses.lock().unwrap().clear(); } - - /// Subscribes to an event specified by the query argument. - #[cfg(feature = "ABCI")] - pub fn subscribe(&mut self, query: Query) -> Result<(), Error> { - // We do not support more than one subscription currently - // This can be fixed by correlating on ids later - if self.subscribed.is_some() { - return Err(Error::AlreadySubscribed); - } - // send the subscription request - let message = RpcSubscription(SubscribeType::Subscribe, query.clone()) - .into_json(); - let msg_id = get_id(&message).unwrap(); - - self.websocket - .lock() - .unwrap() - .send_message(&Message::text(&message)) - .map_err(Error::Websocket)?; - - // check that the request was received and a success message returned - match self.process_response(|_| Error::Subscribe(message), None) { - Ok(_) => { - self.subscribed = Some(Subscription { id: msg_id, query }); - Ok(()) - } - Err(err) => Err(err), - } - } - - /// Receive a response from the subscribed event or - /// process the response if it has already been received - #[cfg(feature = "ABCI")] - pub fn receive_response(&self) -> Result { - if let Some(Subscription { id, .. }) = &self.subscribed { - let response = self.process_response( - Error::Response, - self.received_responses.lock().unwrap().remove(id), - )?; - Ok(response) - } else { - Err(Error::NotSubscribed) - } - } - - /// Unsubscribe from the currently subscribed event - /// Note that even if an error is returned, the client - /// will return to an unsubscribed state - #[cfg(feature = "ABCI")] - pub fn unsubscribe(&mut self) -> Result<(), Error> { - match self.subscribed.take() { - Some(Subscription { query, .. }) => { - // send the subscription request - let message = - RpcSubscription(SubscribeType::Unsubscribe, query) - .into_json(); - - self.websocket - .lock() - .unwrap() - .send_message(&Message::text(&message)) - .map_err(Error::Websocket)?; - // empty out the message queue. Should be empty already - self.received_responses.lock().unwrap().clear(); - // check that the request was received and a success message - // returned - match self - .process_response(|_| Error::Unsubscribe(message), None) - { - Ok(_) => Ok(()), - Err(err) => Err(err), - } - } - _ => Err(Error::NotSubscribed), - } - } - - /// Process the next response received and handle any exceptions that - /// may have occurred. Takes a function to map response to an error - /// as a parameter. - /// - /// Optionally, the response may have been received earlier while - /// handling a different request. In that case, we process it - /// now. - #[cfg(feature = "ABCI")] - fn process_response( - &self, - f: F, - received: Option, - ) -> Result - where - F: FnOnce(String) -> Error, - { - let resp = match received { - Some(resp) => OwnedMessage::Text(resp), - None => { - let mut websocket = self.websocket.lock().unwrap(); - let start = Instant::now(); - loop { - if Instant::now().duration_since(start) - > self.connection_timeout - { - tracing::error!( - "Websocket connection timed out while waiting for \ - response" - ); - return Err(Error::ConnectionTimeout); - } - match websocket.recv_message().map_err(Error::Websocket)? { - text @ OwnedMessage::Text(_) => break text, - OwnedMessage::Ping(data) => { - tracing::debug!( - "Received websocket Ping, sending Pong" - ); - websocket - .send_message(&OwnedMessage::Pong(data)) - .unwrap(); - continue; - } - OwnedMessage::Pong(_) => { - tracing::debug!( - "Received websocket Pong, ignoring" - ); - continue; - } - other => return Err(Error::UnexpectedResponse(other)), - } - } - } - }; - match resp { - OwnedMessage::Text(raw) => RpcResponse::from_string(raw) - .map(|v| v.0) - .map_err(|e| f(e.to_string())), - other => Err(Error::UnexpectedResponse(other)), - } - } } #[async_trait] @@ -469,271 +297,3 @@ fn get_id(req_json: &str) -> Result { Err(Error::MissingId) } } - -/// The TendermintWebsocketClient has a basic state machine for ensuring -/// at most one subscription at a time. These tests cover that it -/// works as intended. -/// -/// Furthermore, since a client can handle a subscription and a -/// simple request simultaneously, we must test that the correct -/// responses are give for each of the corresponding requests -#[cfg(all(test, feature = "ABCI"))] -mod test_tendermint_websocket_client { - use std::time::Duration; - - use namada::types::transaction::hash_tx as hash_tx_bytes; - use serde::{Deserialize, Serialize}; - #[cfg(feature = "ABCI")] - use tendermint_rpc_abci::endpoint::abci_info::AbciInfo; - #[cfg(feature = "ABCI")] - use tendermint_rpc_abci::query::{EventType, Query}; - #[cfg(feature = "ABCI")] - use tendermint_rpc_abci::Client; - #[cfg(feature = "ABCI")] - use tendermint_stable::abci::transaction; - use websocket::sync::Server; - use websocket::{Message, OwnedMessage}; - - use crate::client::tendermint_websocket_client::{ - TendermintWebsocketClient, WebSocketAddress, - }; - - #[derive(Debug, Deserialize, Serialize)] - #[serde(rename_all = "snake_case")] - pub enum ReqType { - Subscribe, - Unsubscribe, - AbciInfo, - } - - #[derive(Debug, Deserialize, Serialize)] - pub struct RpcRequest { - pub jsonrpc: String, - pub id: String, - pub method: ReqType, - pub params: Option>, - } - - fn address() -> WebSocketAddress { - WebSocketAddress { - host: "localhost".into(), - port: 26657, - } - } - - #[derive(Default)] - struct Handle { - subscription_id: Option, - } - - impl Handle { - /// Mocks responses to queries. Fairly arbitrary with just enough - /// variety to test the TendermintWebsocketClient state machine and - /// message synchronization - fn handle(&mut self, msg: String) -> Vec { - let id = super::get_id(&msg).unwrap(); - let request: RpcRequest = serde_json::from_str(&msg).unwrap(); - match request.method { - ReqType::Unsubscribe => { - self.subscription_id = None; - vec![format!( - r#"{{"jsonrpc": "2.0", "id": {}, "error": "error"}}"#, - id - )] - } - ReqType::Subscribe => { - self.subscription_id = Some(id); - let id = self.subscription_id.as_ref().unwrap(); - if request.params.unwrap()[0] - == Query::from(EventType::NewBlock).to_string() - { - vec![format!( - r#"{{"jsonrpc": "2.0", "id": {}, "error": "error"}}"#, - id - )] - } else { - vec![format!( - r#"{{"jsonrpc": "2.0", "id": {}, "result": {{}}}}"#, - id - )] - } - } - ReqType::AbciInfo => { - // Mock a subscription result returning on the wire before - // the simple request result - let info = AbciInfo { - last_block_app_hash: transaction::Hash::new( - hash_tx_bytes("Testing".as_bytes()).0, - ) - .as_ref() - .into(), - ..AbciInfo::default() - }; - let resp = serde_json::to_string(&info).unwrap(); - if let Some(prev_id) = self.subscription_id.take() { - vec![ - format!( - r#"{{"jsonrpc": "2.0", "id": {}, "result": {{"subscription": "result!"}}}}"#, - prev_id - ), - format!( - r#"{{"jsonrpc": "2.0", "id": {}, "result": {{"response": {}}}}}"#, - id, resp - ), - ] - } else { - vec![format!( - r#"{{"jsonrpc": "2.0", "id": {}, "result": {{"response": {}}}}}"#, - id, resp - )] - } - } - } - } - } - - /// A mock tendermint node. This is just a basic websocket server - /// TODO: When the thread drops from scope, we may get an ignorable - /// panic as we did not shut the loop down. But we should. - fn start() { - let node = Server::bind("localhost:26657").unwrap(); - for connection in node.filter_map(Result::ok) { - std::thread::spawn(move || { - let mut handler = Handle::default(); - let mut client = connection.accept().unwrap(); - loop { - for resp in match client.recv_message().unwrap() { - OwnedMessage::Text(msg) => handler.handle(msg), - _ => panic!("Unexpected request"), - } { - let msg = Message::text(resp); - let _ = client.send_message(&msg); - } - } - }); - } - } - - /// Test that we cannot subscribe to a new event - /// if we have an active subscription - #[test] - fn test_subscribe_twice() { - std::thread::spawn(start); - // need to make sure that the mock tendermint node has time to boot up - std::thread::sleep(std::time::Duration::from_secs(1)); - let mut rpc_client = TendermintWebsocketClient::open( - address(), - Some(Duration::new(10, 0)), - ) - .expect("Client could not start"); - // Check that subscription was successful - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.as_ref().expect("Test failed").query, - Query::from(EventType::Tx) - ); - // Check that we cannot subscribe while we still have an active - // subscription - assert!(rpc_client.subscribe(Query::from(EventType::Tx)).is_err()); - } - - /// Test that even if there is an error on the protocol layer, - /// the client still unsubscribes and returns control - #[test] - fn test_unsubscribe_even_on_protocol_error() { - std::thread::spawn(start); - // need to make sure that the mock tendermint node has time to boot up - std::thread::sleep(std::time::Duration::from_secs(1)); - let mut rpc_client = TendermintWebsocketClient::open( - address(), - Some(Duration::new(10, 0)), - ) - .expect("Client could not start"); - // Check that subscription was successful - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.as_ref().expect("Test failed").query, - Query::from(EventType::Tx) - ); - // Check that unsubscribe was successful even though it returned an - // error - assert!(rpc_client.unsubscribe().is_err()); - assert!(rpc_client.subscribed.is_none()); - } - - /// Test that if we unsubscribe from an event, we can - /// reuse the client to subscribe to a new event - #[test] - fn test_subscribe_after_unsubscribe() { - std::thread::spawn(start); - // need to make sure that the mock tendermint node has time to boot up - std::thread::sleep(std::time::Duration::from_secs(1)); - let mut rpc_client = TendermintWebsocketClient::open( - address(), - Some(Duration::new(10, 0)), - ) - .expect("Client could not start"); - // Check that subscription was successful - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.as_ref().expect("Test failed").query, - Query::from(EventType::Tx) - ); - // Check that unsubscribe was successful - let _ = rpc_client.unsubscribe(); - assert!(rpc_client.subscribed.as_ref().is_none()); - // Check that we can now subscribe to new event - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.expect("Test failed").query, - Query::from(EventType::Tx) - ); - } - - /// In this test we first subscribe to an event and then - /// make a simple request. - /// - /// The mock node is set up so that while the request is waiting - /// for its response, it receives the response for the subscription. - /// - /// This test checks that methods correctly return the correct - /// responses. - #[test] - fn test_subscription_returns_before_request_handled() { - std::thread::spawn(start); - // need to make sure that the mock tendermint node has time to boot up - std::thread::sleep(std::time::Duration::from_secs(1)); - let mut rpc_client = TendermintWebsocketClient::open( - address(), - Some(Duration::new(10, 0)), - ) - .expect("Client could not start"); - // Check that subscription was successful - rpc_client.subscribe(Query::from(EventType::Tx)).unwrap(); - assert_eq!( - rpc_client.subscribed.as_ref().expect("Test failed").query, - Query::from(EventType::Tx) - ); - // Check that there are no pending subscription responses - assert!(rpc_client.received_responses.lock().unwrap().is_empty()); - // If the wrong response is returned, json deserialization will fail the - // test - let _ = - tokio_test::block_on(rpc_client.abci_info()).expect("Test failed"); - // Check that we received the subscription response and it has been - // stored - assert!( - rpc_client - .received_responses - .lock() - .unwrap() - .contains_key(&rpc_client.subscribed.as_ref().unwrap().id) - ); - - // check that we receive the expected response to the subscription - let response = rpc_client.receive_response().expect("Test failed"); - assert_eq!(response.to_string(), r#"{"subscription":"result!"}"#); - // Check that there are no pending subscription responses - assert!(rpc_client.received_responses.lock().unwrap().is_empty()); - } -} diff --git a/apps/src/lib/client/tm_jsonrpc_client.rs b/apps/src/lib/client/tm_jsonrpc_client.rs index 12bd6d45b3..7372012ff5 100644 --- a/apps/src/lib/client/tm_jsonrpc_client.rs +++ b/apps/src/lib/client/tm_jsonrpc_client.rs @@ -1,225 +1,223 @@ -#[cfg(not(feature = "ABCI"))] -mod tm_jsonrpc { - use std::convert::TryFrom; - use std::fmt::{Display, Formatter}; - use std::ops::{Deref, DerefMut}; +use std::convert::TryFrom; +use std::fmt::{Display, Formatter}; +use std::ops::{Deref, DerefMut}; - use curl::easy::{Easy2, Handler, WriteError}; - use serde::{Deserialize, Serialize}; - use tendermint_config::net::Address as TendermintAddress; - use tendermint_rpc::query::Query; +use curl::easy::{Easy2, Handler, WriteError}; +use serde::{Deserialize, Serialize}; +use tendermint_config::net::Address as TendermintAddress; +use tendermint_rpc::query::Query; - use crate::client::tendermint_rpc_types::{ - parse, Error, EventParams, EventReply, TxResponse, - }; +use crate::client::tendermint_rpc_types::{ + parse, Error, EventParams, EventReply, TxResponse, +}; - /// Maximum number of times we try to send a curl request - const MAX_SEND_ATTEMPTS: u8 = 10; - /// Number of events we request from the events log - const NUM_EVENTS: u64 = 10; +/// Maximum number of times we try to send a curl request +const MAX_SEND_ATTEMPTS: u8 = 10; +/// Number of events we request from the events log +const NUM_EVENTS: u64 = 10; - pub struct JsonRpcAddress<'a> { - host: &'a str, - port: u16, - } +pub struct JsonRpcAddress<'a> { + host: &'a str, + port: u16, +} - impl<'a> TryFrom<&'a TendermintAddress> for JsonRpcAddress<'a> { - type Error = Error; +impl<'a> TryFrom<&'a TendermintAddress> for JsonRpcAddress<'a> { + type Error = Error; - fn try_from(value: &'a TendermintAddress) -> Result { - match value { - TendermintAddress::Tcp { host, port, .. } => Ok(Self { - host: host.as_str(), - port: *port, - }), - _ => Err(Error::Address(value.to_string())), - } + fn try_from(value: &'a TendermintAddress) -> Result { + match value { + TendermintAddress::Tcp { host, port, .. } => Ok(Self { + host: host.as_str(), + port: *port, + }), + _ => Err(Error::Address(value.to_string())), } } +} - impl<'a> Display for JsonRpcAddress<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}", self.host, self.port) - } +impl<'a> Display for JsonRpcAddress<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.host, self.port) } +} - /// The body of a json rpc request - #[derive(Serialize)] - pub struct Request { - /// Method name - pub method: String, - /// parameters to give the method - params: EventParams, - /// ID of the request - id: u8, - } +/// The body of a json rpc request +#[derive(Serialize)] +pub struct Request { + /// Method name + pub method: String, + /// parameters to give the method + params: EventParams, + /// ID of the request + id: u8, +} - impl From for Request { - fn from(params: EventParams) -> Self { - Request { - method: "events".into(), - params, - id: 1, - } +impl From for Request { + fn from(params: EventParams) -> Self { + Request { + method: "events".into(), + params, + id: 1, } } +} - /// The response we get back from Tendermint - #[derive(Serialize, Deserialize)] - pub struct Response { - /// JSON-RPC version - jsonrpc: String, - /// Identifier included in request - id: u8, - /// Results of request (if successful) - result: Option, - /// Error message if unsuccessful - error: Option, - } +/// The response we get back from Tendermint +#[derive(Serialize, Deserialize)] +pub struct Response { + /// JSON-RPC version + jsonrpc: String, + /// Identifier included in request + id: u8, + /// Results of request (if successful) + result: Option, + /// Error message if unsuccessful + error: Option, +} - impl Response { - /// Convert the response into a result type - pub fn into_result(self) -> Result { - if let Some(e) = self.error { - Err(Error::Rpc(e)) - } else if let Some(result) = self.result { - Ok(result) - } else { - Err(Error::MalformedJson) - } +impl Response { + /// Convert the response into a result type + pub fn into_result(self) -> Result { + if let Some(e) = self.error { + Err(Error::Rpc(e)) + } else if let Some(result) = self.result { + Ok(result) + } else { + Err(Error::MalformedJson) } } +} - /// Holds bytes returned in response to curl request - #[derive(Default)] - pub struct Collector(Vec); +/// Holds bytes returned in response to curl request +#[derive(Default)] +pub struct Collector(Vec); - impl Handler for Collector { - fn write(&mut self, data: &[u8]) -> Result { - self.0.extend_from_slice(data); - Ok(data.len()) - } +impl Handler for Collector { + fn write(&mut self, data: &[u8]) -> Result { + self.0.extend_from_slice(data); + Ok(data.len()) } +} - /// The RPC client - pub struct Client<'a> { - /// The actual curl client - inner: Easy2, - /// Url to send requests to - url: &'a str, - /// The request body - request: Request, - /// The hash of the tx whose corresponding event is being searched for. - hash: &'a str, - } +/// The RPC client +pub struct Client<'a> { + /// The actual curl client + inner: Easy2, + /// Url to send requests to + url: &'a str, + /// The request body + request: Request, + /// The hash of the tx whose corresponding event is being searched for. + hash: &'a str, +} - impl<'a> Deref for Client<'a> { - type Target = Easy2; +impl<'a> Deref for Client<'a> { + type Target = Easy2; - fn deref(&self) -> &Self::Target { - &self.inner - } + fn deref(&self) -> &Self::Target { + &self.inner } +} - impl<'a> DerefMut for Client<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } +impl<'a> DerefMut for Client<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner } +} - impl<'a> Client<'a> { - /// Create a new client - pub fn new(url: &'a str, request: Request, hash: &'a str) -> Self { - let mut client = Self { - inner: Easy2::new(Collector::default()), - url, - request, - hash, - }; - client.initialize(); - client - } +impl<'a> Client<'a> { + /// Create a new client + pub fn new(url: &'a str, request: Request, hash: &'a str) -> Self { + let mut client = Self { + inner: Easy2::new(Collector::default()), + url, + request, + hash, + }; + client.initialize(); + client + } - /// Send a request to Tendermint - /// - /// Takes the 10 newest block header events and searches for - /// the relevant event among them. - pub fn send(&mut self) -> Result { - // send off the request - // this loop is here because if commit timeouts - // become too long, sometimes we get back empty responses. - for attempt in 0..MAX_SEND_ATTEMPTS { - match self.perform() { - Ok(()) => break, - Err(err) => { - tracing::debug!(?attempt, response = ?err, "attempting request") - } + /// Send a request to Tendermint + /// + /// Takes the 10 newest block header events and searches for + /// the relevant event among them. + pub fn send(&mut self) -> Result { + // send off the request + // this loop is here because if commit timeouts + // become too long, sometimes we get back empty responses. + for attempt in 0..MAX_SEND_ATTEMPTS { + match self.perform() { + Ok(()) => break, + Err(err) => { + tracing::debug!(?attempt, response = ?err, "attempting request") } } - if self.get_ref().0.is_empty() { - return Err(Error::Send); - } - - // deserialize response - let response: Response = - serde_json::from_slice(self.get_ref().0.as_slice()) - .map_err(Error::Deserialize)?; - let response = response.into_result()?; - // search for the event in the response and return - // it if found. Else request the next chunk of results - parse(response, self.hash) - .ok_or_else(|| Error::NotFound(self.hash.to_string())) } - - /// Initialize the curl client from the fields of `Client` - fn initialize(&mut self) { - self.inner.reset(); - let url = self.url; - self.url(url).unwrap(); - self.post(true).unwrap(); - - // craft the body of the request - let request_body = serde_json::to_string(&self.request).unwrap(); - self.post_field_size(request_body.as_bytes().len() as u64) - .unwrap(); - // update the request and serialize to bytes - let data = serde_json::to_string(&self.request).unwrap(); - let data = data.as_bytes(); - self.post_fields_copy(data).unwrap(); + if self.get_ref().0.is_empty() { + return Err(Error::Send); } + + // deserialize response + let response: Response = + serde_json::from_slice(self.get_ref().0.as_slice()) + .map_err(Error::Deserialize)?; + let response = response.into_result()?; + // search for the event in the response and return + // it if found. Else request the next chunk of results + parse(response, self.hash) + .ok_or_else(|| Error::NotFound(self.hash.to_string())) } - /// Given a query looking for a particular Anoma event, - /// query the Tendermint's jsonrpc endpoint for the events - /// log. Returns the appropriate event if found in the log. - pub async fn fetch_event( - address: &str, - filter: Query, - tx_hash: &str, - ) -> Result { + /// Initialize the curl client from the fields of `Client` + fn initialize(&mut self) { + self.inner.reset(); + let url = self.url; + self.url(url).unwrap(); + self.post(true).unwrap(); + // craft the body of the request - let request = Request::from(EventParams::new( - filter, - NUM_EVENTS, - std::time::Duration::from_secs(60), - )); - // construct a curl client - let mut client = Client::new(address, request, tx_hash); - // perform the request - client.send() + let request_body = serde_json::to_string(&self.request).unwrap(); + self.post_field_size(request_body.as_bytes().len() as u64) + .unwrap(); + // update the request and serialize to bytes + let data = serde_json::to_string(&self.request).unwrap(); + let data = data.as_bytes(); + self.post_fields_copy(data).unwrap(); } +} - #[cfg(test)] - mod test_rpc_types { - use serde_json::json; +/// Given a query looking for a particular Anoma event, +/// query the Tendermint's jsonrpc endpoint for the events +/// log. Returns the appropriate event if found in the log. +pub async fn fetch_event( + address: &str, + filter: Query, + tx_hash: &str, +) -> Result { + // craft the body of the request + let request = Request::from(EventParams::new( + filter, + NUM_EVENTS, + std::time::Duration::from_secs(60), + )); + // construct a curl client + let mut client = Client::new(address, request, tx_hash); + // perform the request + client.send() +} - use super::*; - use crate::client::tendermint_rpc_types::{EventData, EventItem}; +#[cfg(test)] +mod test_rpc_types { + use serde_json::json; - /// Test that we correctly parse the response from Tendermint - #[test] - fn test_parse_response() { - let resp = r#" + use super::*; + use crate::client::tendermint_rpc_types::{EventData, EventItem}; + + /// Test that we correctly parse the response from Tendermint + #[test] + fn test_parse_response() { + let resp = r#" { "jsonrpc":"2.0", "id":1, @@ -241,27 +239,23 @@ mod tm_jsonrpc { "newest":"16f1b066717b4261-0060" } }"#; - let response: Response = - serde_json::from_str(resp).expect("Test failed"); - let items = response.into_result().expect("Test failed").items; - assert_eq!( - items, - vec![EventItem { - cursor: String::from("16f1b066717b4261-0060").into(), - event: "NewRoundStep".to_string(), - data: EventData { - r#type: "tendermint/event/RoundState".to_string(), - value: json!({ - "height":"17416", - "round":0, - "step":"RoundStepCommit" - }), - } - }] - ) - } + let response: Response = + serde_json::from_str(resp).expect("Test failed"); + let items = response.into_result().expect("Test failed").items; + assert_eq!( + items, + vec![EventItem { + cursor: String::from("16f1b066717b4261-0060").into(), + event: "NewRoundStep".to_string(), + data: EventData { + r#type: "tendermint/event/RoundState".to_string(), + value: json!({ + "height":"17416", + "round":0, + "step":"RoundStepCommit" + }), + } + }] + ) } } - -#[cfg(not(feature = "ABCI"))] -pub use tm_jsonrpc::*; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 5335f7d450..6f1fd96707 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -25,38 +25,22 @@ use namada::types::transaction::nft::{CreateNft, MintNft}; use namada::types::transaction::{pos, InitAccount, InitValidator, UpdateVp}; use namada::types::{address, token}; use namada::{ledger, vm}; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address as TendermintAddress; -#[cfg(not(feature = "ABCI"))] use tendermint_rpc::endpoint::broadcast::tx_sync::Response; -#[cfg(not(feature = "ABCI"))] use tendermint_rpc::query::{EventType, Query}; -#[cfg(not(feature = "ABCI"))] use tendermint_rpc::{Client, HttpClient}; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::endpoint::broadcast::tx_sync::Response; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::query::{EventType, Query}; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::{Client, HttpClient}; use super::rpc; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::signing::{find_keypair, sign_tx}; -#[cfg(not(feature = "ABCI"))] -use crate::client::tendermint_rpc_types::Error; -use crate::client::tendermint_rpc_types::{TxBroadcastData, TxResponse}; +use crate::client::tendermint_rpc_types::{Error, TxBroadcastData, TxResponse}; use crate::client::tendermint_websocket_client::{ Error as WsError, TendermintWebsocketClient, WebSocketAddress, }; -#[cfg(not(feature = "ABCI"))] use crate::client::tm_jsonrpc_client::{fetch_event, JsonRpcAddress}; use crate::node::ledger::tendermint_node; -#[cfg(not(feature = "ABCI"))] const ACCEPTED_QUERY_KEY: &str = "accepted.hash"; const APPLIED_QUERY_KEY: &str = "applied.hash"; const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; @@ -1149,13 +1133,10 @@ pub async fn broadcast_tx( println!("Transaction added to mempool: {:?}", response); // Print the transaction identifiers to enable the extraction of // acceptance/application results later - #[cfg(not(feature = "ABCI"))] { println!("Wrapper transaction hash: {:?}", wrapper_tx_hash); println!("Inner transaction hash: {:?}", _decrypted_tx_hash); } - #[cfg(feature = "ABCI")] - println!("Transaction hash: {:?}", wrapper_tx_hash); Ok(response) } else { Err(WsError::Response(response.log.to_string())) @@ -1170,7 +1151,6 @@ pub async fn broadcast_tx( /// 3. The decrypted payload of the tx has been included on the blockchain. /// /// In the case of errors in any of those stages, an error message is returned -#[cfg(not(feature = "ABCI"))] pub async fn submit_tx( address: TendermintAddress, to_broadcast: TxBroadcastData, @@ -1192,7 +1172,7 @@ pub async fn submit_tx( let wrapper_query = Query::from(EventType::NewBlockHeader) .and_eq(ACCEPTED_QUERY_KEY, wrapper_hash.as_str()); let tx_query = Query::from(EventType::NewBlockHeader) - .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_ref().unwrap().as_str()); + .and_eq(APPLIED_QUERY_KEY, decrypted_hash.as_str()); // broadcast the tx if let Err(err) = broadcast_tx(address, &to_broadcast).await { @@ -1212,12 +1192,8 @@ pub async fn submit_tx( // and applied if response.code == 0.to_string() { // get the event for the inner tx - let response = fetch_event( - &url, - tx_query, - decrypted_hash.as_ref().unwrap().as_str(), - ) - .await?; + let response = + fetch_event(&url, tx_query, decrypted_hash.as_str()).await?; println!( "Transaction applied with result: {}", serde_json::to_string_pretty(&response).unwrap() @@ -1231,69 +1207,3 @@ pub async fn submit_tx( Ok(response) } } - -/// Broadcast a transaction to be included in the blockchain. -/// -/// Checks that -/// 1. The tx has been successfully included into the mempool of a validator -/// 2. The tx with encrypted payload has been included on the blockchain -/// 3. The decrypted payload of the tx has been included on the blockchain. -/// -/// In the case of errors in any of those stages, an error message is returned -#[cfg(feature = "ABCI")] -pub async fn submit_tx( - address: TendermintAddress, - to_broadcast: TxBroadcastData, -) -> Result { - let (_, wrapper_hash, _decrypted_hash) = match &to_broadcast { - TxBroadcastData::Wrapper { - tx, - wrapper_hash, - decrypted_hash, - } => (tx, wrapper_hash, decrypted_hash), - _ => panic!("Cannot broadcast a dry-run transaction"), - }; - - let websocket_timeout = - if let Ok(val) = env::var(ENV_VAR_ANOMA_TENDERMINT_WEBSOCKET_TIMEOUT) { - if let Ok(timeout) = val.parse::() { - Duration::new(timeout, 0) - } else { - Duration::new(300, 0) - } - } else { - Duration::new(300, 0) - }; - - let mut wrapper_tx_subscription = TendermintWebsocketClient::open( - WebSocketAddress::try_from(address.clone())?, - Some(websocket_timeout), - )?; - - // It is better to subscribe to the transaction before it is broadcast - // - // Note that the `APPLIED_QUERY_KEY` key comes from a custom event - // created by the shell - let query = Query::from(EventType::NewBlock) - .and_eq(APPLIED_QUERY_KEY, wrapper_hash.as_str()); - wrapper_tx_subscription.subscribe(query)?; - - // Broadcast the supplied transaction - broadcast_tx(address, &to_broadcast).await?; - - let parsed = { - let parsed = TxResponse::find_tx( - wrapper_tx_subscription.receive_response()?, - wrapper_hash, - ); - println!( - "Transaction applied with result: {}", - serde_json::to_string_pretty(&parsed).unwrap() - ); - Ok(parsed) - }; - - wrapper_tx_subscription.unsubscribe()?; - wrapper_tx_subscription.close(); - parsed -} diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index d16e80e8d3..c377648b65 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -18,14 +18,8 @@ use rand::prelude::ThreadRng; use rand::thread_rng; use serde_json::json; use sha2::{Digest, Sha256}; -#[cfg(not(feature = "ABCI"))] use tendermint::node::Id as TendermintNodeId; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_stable::node::Id as TendermintNodeId; use crate::cli::context::ENV_VAR_WASM_DIR; use crate::cli::{self, args}; diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index b81430fc3b..987077d5cd 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -19,14 +19,8 @@ use namada::types::chain::ChainId; use namada::types::time::Rfc3339String; use regex::Regex; use serde::{de, Deserialize, Serialize}; -#[cfg(not(feature = "ABCI"))] use tendermint::Timeout; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_stable::Timeout; use thiserror::Error; use crate::cli; diff --git a/apps/src/lib/node/ledger/broadcaster.rs b/apps/src/lib/node/ledger/broadcaster.rs index 662291b1d5..94aec1a00c 100644 --- a/apps/src/lib/node/ledger/broadcaster.rs +++ b/apps/src/lib/node/ledger/broadcaster.rs @@ -1,7 +1,4 @@ -#[cfg(not(feature = "ABCI"))] use tendermint_rpc::{Client, HttpClient}; -#[cfg(feature = "ABCI")] -use tendermint_rpc_abci::{Client, HttpClient}; use tokio::sync::mpsc::UnboundedReceiver; /// A service for broadcasting txs via an HTTP client. diff --git a/apps/src/lib/node/ledger/events.rs b/apps/src/lib/node/ledger/events.rs index 9751d3b8b6..493c4855da 100644 --- a/apps/src/lib/node/ledger/events.rs +++ b/apps/src/lib/node/ledger/events.rs @@ -7,10 +7,7 @@ use borsh::BorshSerialize; use namada::ledger::governance::utils::ProposalEvent; use namada::types::ibc::IbcEvent; use namada::types::transaction::{hash_tx, TxType}; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::EventAttribute; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::EventAttribute; use thiserror::Error; /// Indicates if an event is emitted do to @@ -43,7 +40,6 @@ pub enum EventType { Proposal, } -#[cfg(not(feature = "ABCI"))] impl Display for EventType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -56,19 +52,6 @@ impl Display for EventType { } } -#[cfg(feature = "ABCI")] -impl Display for EventType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - EventType::Accepted => write!(f, "applied"), - EventType::Applied => write!(f, "applied"), - EventType::Ibc(t) => write!(f, "{}", t), - EventType::Proposal => write!(f, "proposal"), - }?; - Ok(()) - } -} - impl Event { /// Creates a new event with the hash and height of the transaction /// already filled in @@ -80,16 +63,12 @@ impl Event { level: EventLevel::Tx, attributes: HashMap::new(), }; - event["hash"] = if !cfg!(feature = "ABCI") { - hash_tx( - &wrapper - .try_to_vec() - .expect("Serializing wrapper should not fail"), - ) - .to_string() - } else { - wrapper.tx_hash.to_string() - }; + event["hash"] = hash_tx( + &wrapper + .try_to_vec() + .expect("Serializing wrapper should not fail"), + ) + .to_string(); event } TxType::Decrypted(decrypted) => { @@ -170,7 +149,6 @@ impl From for Event { } } -#[cfg(not(feature = "ABCI"))] /// Convert our custom event into the necessary tendermint proto type impl From for tendermint_proto::abci::Event { fn from(event: Event) -> Self { @@ -189,25 +167,6 @@ impl From for tendermint_proto::abci::Event { } } -#[cfg(feature = "ABCI")] -/// Convert our custom event into the necessary tendermint proto type -impl From for tendermint_proto_abci::abci::Event { - fn from(event: Event) -> Self { - Self { - r#type: event.event_type.to_string(), - attributes: event - .attributes - .into_iter() - .map(|(key, value)| EventAttribute { - key: key.into_bytes(), - value: value.into_bytes(), - index: true, - }) - .collect(), - } - } -} - /// A thin wrapper around a HashMap for parsing event JSONs /// returned in tendermint subscription responses. #[derive(Debug)] diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index 03f5aa36c7..e84674b059 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -18,15 +18,9 @@ use namada::ledger::governance::storage as gov_storage; use namada::types::storage::Key; use once_cell::unsync::Lazy; use sysinfo::{RefreshKind, System, SystemExt}; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::CheckTxType; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::CheckTxType; use tower::ServiceBuilder; -#[cfg(not(feature = "ABCI"))] use tower_abci::{response, split, Server}; -#[cfg(feature = "ABCI")] -use tower_abci_old::{response, split, Server}; use self::shims::abcipp_shim::AbciService; use crate::config::utils::num_of_threads; @@ -96,30 +90,21 @@ impl Shell { } Request::Info(_) => Ok(Response::Info(self.last_state())), Request::Query(query) => Ok(Response::Query(self.query(query))), - #[cfg(not(feature = "ABCI"))] Request::PrepareProposal(block) => { Ok(Response::PrepareProposal(self.prepare_proposal(block))) } Request::VerifyHeader(_req) => { Ok(Response::VerifyHeader(self.verify_header(_req))) } - #[cfg(not(feature = "ABCI"))] Request::ProcessProposal(block) => { Ok(Response::ProcessProposal(self.process_proposal(block))) } - #[cfg(feature = "ABCI")] - Request::DeliverTx(deliver_tx) => Ok(Response::DeliverTx( - self.process_and_decode_proposal(deliver_tx), - )), - #[cfg(not(feature = "ABCI"))] Request::RevertProposal(_req) => { Ok(Response::RevertProposal(self.revert_proposal(_req))) } - #[cfg(not(feature = "ABCI"))] Request::ExtendVote(_req) => { Ok(Response::ExtendVote(self.extend_vote(_req))) } - #[cfg(not(feature = "ABCI"))] Request::VerifyVoteExtension(_req) => Ok( Response::VerifyVoteExtension(self.verify_vote_extension(_req)), ), diff --git a/apps/src/lib/node/ledger/rpc.rs b/apps/src/lib/node/ledger/rpc.rs index 3836a11f97..ce3cbd592d 100644 --- a/apps/src/lib/node/ledger/rpc.rs +++ b/apps/src/lib/node/ledger/rpc.rs @@ -5,10 +5,7 @@ use std::str::FromStr; use namada::types::address::Address; use namada::types::storage; -#[cfg(not(feature = "ABCI"))] use tendermint::abci::Path as AbciPath; -#[cfg(feature = "ABCI")] -use tendermint_stable::abci::Path as AbciPath; use thiserror::Error; /// RPC query path diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index c7ba3177b4..6afba892e1 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -10,14 +10,8 @@ use namada::ledger::treasury::ADDRESS as treasury_address; use namada::types::address::{xan as m1t, Address}; use namada::types::governance::TallyResult; use namada::types::storage::{BlockHash, Epoch, Header}; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::Misbehavior as Evidence; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::PublicKey as TendermintPublicKey; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::Evidence; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::crypto::PublicKey as TendermintPublicKey; use super::*; use crate::node::ledger::events::EventType; @@ -302,16 +296,12 @@ where let mut tx_event = match &tx_type { TxType::Wrapper(_wrapper) => { - if !cfg!(feature = "ABCI") { - self.storage.tx_queue.push(_wrapper.clone()); - } + self.storage.tx_queue.push(_wrapper.clone()); Event::new_tx_event(&tx_type, height.0) } TxType::Decrypted(inner) => { // We remove the corresponding wrapper tx from the queue - if !cfg!(feature = "ABCI") { - self.storage.tx_queue.pop(); - } + self.storage.tx_queue.pop(); let mut event = Event::new_tx_event(&tx_type, height.0); if let DecryptedTx::Undecryptable(_) = inner { event["log"] = @@ -525,7 +515,6 @@ mod test_finalize_block { FinalizeBlock, ProcessedTx, }; - #[cfg(not(feature = "ABCI"))] /// Check that if a wrapper tx was rejected by [`process_proposal`], /// check that the correct event is returned. Check that it does /// not appear in the queue of txs to be decrypted @@ -600,62 +589,6 @@ mod test_finalize_block { assert_eq!(counter, 3); } - #[cfg(feature = "ABCI")] - /// Check that if a wrapper tx was rejected by [`process_proposal`], - /// check that the correct event is returned. - #[test] - fn test_process_proposal_rejected_wrapper_tx() { - let (mut shell, _) = setup(); - let keypair = gen_keypair(); - let mut processed_txs = vec![]; - // create some wrapper txs - for i in 1..5 { - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(format!("transaction data: {}", i).as_bytes().to_owned()), - ); - let wrapper = WrapperTx::new( - Fee { - amount: i.into(), - token: xan(), - }, - &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), - ); - let tx = wrapper.sign(&keypair).expect("Test failed"); - if i > 1 { - processed_txs.push(ProcessedTx { - tx: tx.to_bytes(), - result: TxResult { - code: u32::try_from(i.rem_euclid(2)) - .expect("Test failed"), - info: "".into(), - }, - }); - } - } - - // check that the correct events were created - for (index, event) in shell - .finalize_block(FinalizeBlock { - txs: processed_txs.clone(), - ..Default::default() - }) - .expect("Test failed") - .iter() - .enumerate() - { - assert_eq!(event.event_type.to_string(), String::from("applied")); - let code = - event.attributes.get("code").expect("Test failed").clone(); - assert_eq!(code, index.rem_euclid(2).to_string()); - } - } - - #[cfg(not(feature = "ABCI"))] /// Check that if a decrypted tx was rejected by [`process_proposal`], /// check that the correct event is returned. Check that it is still /// removed from the queue of txs to be included in the next block @@ -706,43 +639,6 @@ mod test_finalize_block { assert!(shell.next_wrapper().is_none()); } - #[cfg(feature = "ABCI")] - /// Check that if a decrypted tx was rejected by [`process_proposal`], - /// check that the correct event is returned. - #[test] - fn test_process_proposal_rejected_decrypted_tx() { - let (mut shell, _) = setup(); - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(String::from("transaction data").as_bytes().to_owned()), - ); - let processed_tx = ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted(raw_tx))) - .to_bytes(), - result: TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "".into(), - }, - }; - - // check that the decrypted tx was not applied - for event in shell - .finalize_block(FinalizeBlock { - txs: vec![processed_tx], - ..Default::default() - }) - .expect("Test failed") - { - assert_eq!(event.event_type.to_string(), String::from("applied")); - let code = - event.attributes.get("code").expect("Test failed").as_str(); - assert_eq!(code, String::from(ErrorCodes::InvalidTx).as_str()); - } - // check that the corresponding wrapper tx was removed from the queue - assert!(shell.next_wrapper().is_none()); - } - - #[cfg(not(feature = "ABCI"))] /// Test that if a tx is undecryptable, it is applied /// but the tx result contains the appropriate error code. #[test] @@ -799,63 +695,6 @@ mod test_finalize_block { assert!(shell.next_wrapper().is_none()); } - #[cfg(feature = "ABCI")] - /// Test that if a tx is undecryptable, it is applied - /// but the tx result contains the appropriate error code. - #[test] - fn test_undecryptable_returns_error_code() { - let (mut shell, _) = setup(); - - let keypair = crate::wallet::defaults::daewon_keypair(); - let pubkey = EncryptionKey::default(); - // not valid tx bytes - let tx = "garbage data".as_bytes().to_owned(); - let inner_tx = - namada::types::transaction::encrypted::EncryptedTx::encrypt( - &tx, pubkey, - ); - let wrapper = WrapperTx { - fee: Fee { - amount: 0.into(), - token: xan(), - }, - pk: keypair.ref_to(), - epoch: Epoch(0), - gas_limit: 0.into(), - inner_tx, - tx_hash: hash_tx(&tx), - }; - let processed_tx = ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - wrapper, - ))) - .to_bytes(), - result: TxResult { - code: ErrorCodes::Ok.into(), - info: "".into(), - }, - }; - - // check that correct error message is returned - for event in shell - .finalize_block(FinalizeBlock { - txs: vec![processed_tx], - ..Default::default() - }) - .expect("Test failed") - { - assert_eq!(event.event_type.to_string(), String::from("applied")); - let code = - event.attributes.get("code").expect("Test failed").as_str(); - assert_eq!(code, String::from(ErrorCodes::Undecryptable).as_str()); - - let log = event.attributes.get("log").expect("Test failed").clone(); - assert!(log.contains("Transaction could not be decrypted.")) - } - // check that the corresponding wrapper tx was removed from the queue - assert!(shell.next_wrapper().is_none()); - } - /// Test that the wrapper txs are queued in the order they /// are received from the block. Tests that the previously /// decrypted txs are de-queued. @@ -946,17 +785,10 @@ mod test_finalize_block { { if index < 2 { // these should be accepted wrapper txs - if !cfg!(feature = "ABCI") { - assert_eq!( - event.event_type.to_string(), - String::from("accepted") - ); - } else { - assert_eq!( - event.event_type.to_string(), - String::from("applied") - ); - } + assert_eq!( + event.event_type.to_string(), + String::from("accepted") + ); let code = event.attributes.get("code").expect("Test failed").as_str(); assert_eq!(code, String::from(ErrorCodes::Ok).as_str()); @@ -972,21 +804,18 @@ mod test_finalize_block { } } - #[cfg(not(feature = "ABCI"))] - { - // check that the applied decrypted txs were dequeued and the - // accepted wrappers were enqueued in correct order - let mut txs = valid_txs.iter(); + // check that the applied decrypted txs were dequeued and the + // accepted wrappers were enqueued in correct order + let mut txs = valid_txs.iter(); - let mut counter = 0; - while let Some(wrapper) = shell.next_wrapper() { - assert_eq!( - wrapper.tx_hash, - txs.next().expect("Test failed").tx_hash - ); - counter += 1; - } - assert_eq!(counter, 2); + let mut counter = 0; + while let Some(wrapper) = shell.next_wrapper() { + assert_eq!( + wrapper.tx_hash, + txs.next().expect("Test failed").tx_hash + ); + counter += 1; } + assert_eq!(counter, 2); } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 347c3bff53..a989338751 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -5,18 +5,9 @@ use std::hash::Hash; use namada::types::key::*; #[cfg(not(feature = "dev"))] use sha2::{Digest, Sha256}; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::abci; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::PublicKey as TendermintPublicKey; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::google::protobuf; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::crypto::PublicKey as TendermintPublicKey; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::google::protobuf; use super::*; use crate::wasm_loader; diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index f72dcf615a..2d6134dbb1 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -7,7 +7,6 @@ //! More info in . mod finalize_block; mod init_chain; -#[cfg(not(feature = "ABCI"))] mod prepare_proposal; mod process_proposal; mod queries; @@ -45,29 +44,16 @@ use namada::vm::wasm::{TxCache, VpCache}; use namada::vm::WasmCacheRwAccess; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive}; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_verify_vote_extension::VerifyStatus; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ Misbehavior as Evidence, MisbehaviorType as EvidenceType, RequestPrepareProposal, ValidatorUpdate, }; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::public_key; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::types::ConsensusParams; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::ConsensusParams; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::{Evidence, EvidenceType, ValidatorUpdate}; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::crypto::public_key; use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; -#[cfg(not(feature = "ABCI"))] use tower_abci::{request, response}; -#[cfg(feature = "ABCI")] -use tower_abci_old::{request, response}; use super::rpc; use crate::config::{genesis, TendermintMode}; @@ -331,17 +317,10 @@ where } /// Iterate lazily over the wrapper txs in order - #[cfg(not(feature = "ABCI"))] fn next_wrapper(&mut self) -> Option<&WrapperTx> { self.storage.tx_queue.lazy_next() } - /// Iterate lazily over the wrapper txs in order - #[cfg(feature = "ABCI")] - fn next_wrapper(&mut self) -> Option { - self.storage.tx_queue.pop() - } - /// If we reject the decrypted txs because they were out of /// order, reset the iterator. pub fn reset_tx_queue_iter(&mut self) { @@ -517,7 +496,6 @@ where } } - #[cfg(not(feature = "ABCI"))] /// INVARIANT: This method must be stateless. pub fn extend_vote( &self, @@ -526,7 +504,6 @@ where Default::default() } - #[cfg(not(feature = "ABCI"))] /// INVARIANT: This method must be stateless. pub fn verify_vote_extension( &self, @@ -621,7 +598,6 @@ where /// Lookup a validator's keypair for their established account from their /// wallet. If the node is not validator, this function returns None - #[cfg(not(feature = "ABCI"))] #[allow(dead_code)] fn get_account_keypair(&self) -> Option> { let wallet_path = &self.base_dir.join(self.chain_id.as_str()); @@ -672,14 +648,8 @@ mod test_utils { use namada::types::storage::{BlockHash, Epoch, Header}; use namada::types::transaction::Fee; use tempfile::tempdir; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{RequestInitChain, RequestProcessProposal}; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::google::protobuf::Timestamp; - #[cfg(feature = "ABCI")] - use tendermint_proto_abci::abci::{RequestDeliverTx, RequestInitChain}; - #[cfg(feature = "ABCI")] - use tendermint_proto_abci::google::protobuf::Timestamp; use tokio::sync::mpsc::UnboundedReceiver; use super::*; @@ -786,39 +756,23 @@ mod test_utils { &mut self, req: ProcessProposal, ) -> std::result::Result, TestError> { - #[cfg(not(feature = "ABCI"))] - { - let resp = - self.shell.process_proposal(RequestProcessProposal { - txs: req.txs.clone(), - ..Default::default() - }); - let results = resp - .tx_results - .iter() - .zip(req.txs.into_iter()) - .map(|(res, tx_bytes)| ProcessedTx { - result: res.into(), - tx: tx_bytes, - }) - .collect(); - if resp.status != 1 { - Err(TestError::RejectProposal(results)) - } else { - Ok(results) - } - } - #[cfg(feature = "ABCI")] - { - Ok(req - .txs - .into_iter() - .map(|tx_bytes| { - self.process_and_decode_proposal(RequestDeliverTx { - tx: tx_bytes, - }) - }) - .collect()) + let resp = self.shell.process_proposal(RequestProcessProposal { + txs: req.txs.clone(), + ..Default::default() + }); + let results = resp + .tx_results + .iter() + .zip(req.txs.into_iter()) + .map(|(res, tx_bytes)| ProcessedTx { + result: res.into(), + tx: tx_bytes, + }) + .collect(); + if resp.status != 1 { + Err(TestError::RejectProposal(results)) + } else { + Ok(results) } } diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index c175a49a44..001d2eb6b3 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,221 +1,164 @@ //! Implementation of the [`PrepareProposal`] ABCI++ method for the Shell -#[cfg(not(feature = "ABCI"))] -mod prepare_block { - use tendermint_proto::abci::TxRecord; +use tendermint_proto::abci::TxRecord; - use super::super::*; - use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; +use super::*; +use crate::node::ledger::shims::abcipp_shim_types::shim::TxBytes; - impl Shell - where - D: DB + for<'iter> DBIter<'iter> + Sync + 'static, - H: StorageHasher + Sync + 'static, - { - /// Begin a new block. - /// - /// We include half of the new wrapper txs given to us from the mempool - /// by tendermint. The rest of the block is filled with decryptions - /// of the wrapper txs from the previously committed block. - /// - /// INVARIANT: Any changes applied in this method must be reverted if - /// the proposal is rejected (unless we can simply overwrite - /// them in the next block). - pub fn prepare_proposal( - &mut self, - req: RequestPrepareProposal, - ) -> response::PrepareProposal { - // We can safely reset meter, because if the block is rejected, - // we'll reset again on the next proposal, until the - // proposal is accepted - self.gas_meter.reset(); - let txs = if let ShellMode::Validator { .. } = self.mode { - // TODO: This should not be hardcoded - let privkey = ::G2Affine::prime_subgroup_generator(); +impl Shell +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + /// Begin a new block. + /// + /// We include half of the new wrapper txs given to us from the mempool + /// by tendermint. The rest of the block is filled with decryptions + /// of the wrapper txs from the previously committed block. + /// + /// INVARIANT: Any changes applied in this method must be reverted if + /// the proposal is rejected (unless we can simply overwrite + /// them in the next block). + pub fn prepare_proposal( + &mut self, + req: RequestPrepareProposal, + ) -> response::PrepareProposal { + // We can safely reset meter, because if the block is rejected, + // we'll reset again on the next proposal, until the + // proposal is accepted + self.gas_meter.reset(); + let txs = if let ShellMode::Validator { .. } = self.mode { + // TODO: This should not be hardcoded + let privkey = ::G2Affine::prime_subgroup_generator(); - // TODO: Craft the Ethereum state update tx - // filter in half of the new txs from Tendermint, only keeping - // wrappers - let number_of_new_txs = 1 + req.txs.len() / 2; - let mut txs: Vec = req - .txs - .into_iter() - .take(number_of_new_txs) - .map(|tx_bytes| { - if let Ok(Ok(TxType::Wrapper(_))) = - Tx::try_from(tx_bytes.as_slice()).map(process_tx) - { - record::keep(tx_bytes) - } else { - record::remove(tx_bytes) - } - }) - .collect(); + // TODO: Craft the Ethereum state update tx + // filter in half of the new txs from Tendermint, only keeping + // wrappers + let number_of_new_txs = 1 + req.txs.len() / 2; + let mut txs: Vec = req + .txs + .into_iter() + .take(number_of_new_txs) + .map(|tx_bytes| { + if let Ok(Ok(TxType::Wrapper(_))) = + Tx::try_from(tx_bytes.as_slice()).map(process_tx) + { + record::keep(tx_bytes) + } else { + record::remove(tx_bytes) + } + }) + .collect(); - // decrypt the wrapper txs included in the previous block - let mut decrypted_txs = self - .storage - .tx_queue - .iter() - .map(|tx| { - Tx::from(match tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted(tx), - _ => DecryptedTx::Undecryptable(tx.clone()), - }) - .to_bytes() + // decrypt the wrapper txs included in the previous block + let mut decrypted_txs = self + .storage + .tx_queue + .iter() + .map(|tx| { + Tx::from(match tx.decrypt(privkey) { + Ok(tx) => DecryptedTx::Decrypted(tx), + _ => DecryptedTx::Undecryptable(tx.clone()), }) - .map(record::add) - .collect(); + .to_bytes() + }) + .map(record::add) + .collect(); - txs.append(&mut decrypted_txs); - txs - } else { - vec![] - }; + txs.append(&mut decrypted_txs); + txs + } else { + vec![] + }; - response::PrepareProposal { - tx_records: txs, - ..Default::default() - } + response::PrepareProposal { + tx_records: txs, + ..Default::default() } } +} - /// Functions for creating the appropriate TxRecord given the - /// numeric code - pub(super) mod record { - use tendermint_proto::abci::tx_record::TxAction; +/// Functions for creating the appropriate TxRecord given the +/// numeric code +pub(super) mod record { + use tendermint_proto::abci::tx_record::TxAction; - use super::*; + use super::*; - /// Keep this transaction in the proposal - pub fn keep(tx: TxBytes) -> TxRecord { - TxRecord { - action: TxAction::Unmodified as i32, - tx, - } + /// Keep this transaction in the proposal + pub fn keep(tx: TxBytes) -> TxRecord { + TxRecord { + action: TxAction::Unmodified as i32, + tx, } + } - /// A transaction added to the proposal not provided by - /// Tendermint from the mempool - pub fn add(tx: TxBytes) -> TxRecord { - TxRecord { - action: TxAction::Added as i32, - tx, - } + /// A transaction added to the proposal not provided by + /// Tendermint from the mempool + pub fn add(tx: TxBytes) -> TxRecord { + TxRecord { + action: TxAction::Added as i32, + tx, } + } - /// Remove this transaction from the set provided - /// by Tendermint from the mempool - pub fn remove(tx: TxBytes) -> TxRecord { - TxRecord { - action: TxAction::Removed as i32, - tx, - } + /// Remove this transaction from the set provided + /// by Tendermint from the mempool + pub fn remove(tx: TxBytes) -> TxRecord { + TxRecord { + action: TxAction::Removed as i32, + tx, } } +} - #[cfg(test)] - mod test_prepare_proposal { - use namada::types::address::xan; - use namada::types::storage::Epoch; - use namada::types::transaction::Fee; - use tendermint_proto::abci::tx_record::TxAction; - - use super::*; - use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; - - /// Test that if a tx from the mempool is not a - /// WrapperTx type, it is not included in the - /// proposed block. - #[test] - fn test_prepare_proposal_rejects_non_wrapper_tx() { - let (mut shell, _) = TestShell::new(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction_data".as_bytes().to_owned()), - ); - let req = RequestPrepareProposal { - txs: vec![tx.to_bytes()], - max_tx_bytes: 0, - ..Default::default() - }; - assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(tx.to_bytes())] - ); - } +#[cfg(test)] +mod test_prepare_proposal { + use namada::types::address::xan; + use namada::types::storage::Epoch; + use namada::types::transaction::Fee; + use tendermint_proto::abci::tx_record::TxAction; - /// Test that if an error is encountered while - /// trying to process a tx from the mempool, - /// we simply exclude it from the proposal - #[test] - fn test_error_in_processing_tx() { - let (mut shell, _) = TestShell::new(); - let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction_data".as_bytes().to_owned()), - ); - // an unsigned wrapper will cause an error in processing - let wrapper = Tx::new( - "".as_bytes().to_owned(), - Some( - WrapperTx::new( - Fee { - amount: 0.into(), - token: xan(), - }, - &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - ) - .try_to_vec() - .expect("Test failed"), - ), - ) - .to_bytes(); - let req = RequestPrepareProposal { - txs: vec![wrapper.clone()], - max_tx_bytes: 0, - ..Default::default() - }; - assert_eq!( - shell.prepare_proposal(req).tx_records, - vec![record::remove(wrapper)] - ); - } + use super::*; + use crate::node::ledger::shell::test_utils::{gen_keypair, TestShell}; - /// Test that the decrypted txs are included - /// in the proposal in the same order as their - /// corresponding wrappers - #[test] - fn test_decrypted_txs_in_correct_order() { - let (mut shell, _) = TestShell::new(); - let keypair = gen_keypair(); - let mut expected_wrapper = vec![]; - let mut expected_decrypted = vec![]; + /// Test that if a tx from the mempool is not a + /// WrapperTx type, it is not included in the + /// proposed block. + #[test] + fn test_prepare_proposal_rejects_non_wrapper_tx() { + let (mut shell, _) = TestShell::new(); + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction_data".as_bytes().to_owned()), + ); + let req = RequestPrepareProposal { + txs: vec![tx.to_bytes()], + max_tx_bytes: 0, + ..Default::default() + }; + assert_eq!( + shell.prepare_proposal(req).tx_records, + vec![record::remove(tx.to_bytes())] + ); + } - let mut req = RequestPrepareProposal { - txs: vec![], - max_tx_bytes: 0, - ..Default::default() - }; - // create a request with two new wrappers from mempool and - // two wrappers from the previous block to be decrypted - for i in 0..2 { - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some( - format!("transaction data: {}", i) - .as_bytes() - .to_owned(), - ), - ); - expected_decrypted - .push(Tx::from(DecryptedTx::Decrypted(tx.clone()))); - let wrapper_tx = WrapperTx::new( + /// Test that if an error is encountered while + /// trying to process a tx from the mempool, + /// we simply exclude it from the proposal + #[test] + fn test_error_in_processing_tx() { + let (mut shell, _) = TestShell::new(); + let keypair = gen_keypair(); + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some("transaction_data".as_bytes().to_owned()), + ); + // an unsigned wrapper will cause an error in processing + let wrapper = Tx::new( + "".as_bytes().to_owned(), + Some( + WrapperTx::new( Fee { amount: 0.into(), token: xan(), @@ -225,51 +168,97 @@ mod prepare_block { 0.into(), tx, Default::default(), - ); - let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); - shell.enqueue_tx(wrapper_tx); - expected_wrapper.push(wrapper.clone()); - req.txs.push(wrapper.to_bytes()); - } - // we extract the inner data from the txs for testing - // equality since otherwise changes in timestamps would - // fail the test - expected_wrapper.append(&mut expected_decrypted); - let expected_txs: Vec> = expected_wrapper - .iter() - .map(|tx| tx.data.clone().expect("Test failed")) - .collect(); - - let received: Vec> = shell - .prepare_proposal(req) - .tx_records - .iter() - .filter_map( - |TxRecord { - tx: tx_bytes, - action, - }| { - if *action == (TxAction::Unmodified as i32) - || *action == (TxAction::Added as i32) - { - Some( - Tx::try_from(tx_bytes.as_slice()) - .expect("Test failed") - .data - .expect("Test failed"), - ) - } else { - None - } - }, ) - .collect(); - // check that the order of the txs is correct - assert_eq!(received, expected_txs); + .try_to_vec() + .expect("Test failed"), + ), + ) + .to_bytes(); + let req = RequestPrepareProposal { + txs: vec![wrapper.clone()], + max_tx_bytes: 0, + ..Default::default() + }; + assert_eq!( + shell.prepare_proposal(req).tx_records, + vec![record::remove(wrapper)] + ); + } + + /// Test that the decrypted txs are included + /// in the proposal in the same order as their + /// corresponding wrappers + #[test] + fn test_decrypted_txs_in_correct_order() { + let (mut shell, _) = TestShell::new(); + let keypair = gen_keypair(); + let mut expected_wrapper = vec![]; + let mut expected_decrypted = vec![]; + + let mut req = RequestPrepareProposal { + txs: vec![], + max_tx_bytes: 0, + ..Default::default() + }; + // create a request with two new wrappers from mempool and + // two wrappers from the previous block to be decrypted + for i in 0..2 { + let tx = Tx::new( + "wasm_code".as_bytes().to_owned(), + Some(format!("transaction data: {}", i).as_bytes().to_owned()), + ); + expected_decrypted + .push(Tx::from(DecryptedTx::Decrypted(tx.clone()))); + let wrapper_tx = WrapperTx::new( + Fee { + amount: 0.into(), + token: xan(), + }, + &keypair, + Epoch(0), + 0.into(), + tx, + Default::default(), + ); + let wrapper = wrapper_tx.sign(&keypair).expect("Test failed"); + shell.enqueue_tx(wrapper_tx); + expected_wrapper.push(wrapper.clone()); + req.txs.push(wrapper.to_bytes()); } + // we extract the inner data from the txs for testing + // equality since otherwise changes in timestamps would + // fail the test + expected_wrapper.append(&mut expected_decrypted); + let expected_txs: Vec> = expected_wrapper + .iter() + .map(|tx| tx.data.clone().expect("Test failed")) + .collect(); + + let received: Vec> = shell + .prepare_proposal(req) + .tx_records + .iter() + .filter_map( + |TxRecord { + tx: tx_bytes, + action, + }| { + if *action == (TxAction::Unmodified as i32) + || *action == (TxAction::Added as i32) + { + Some( + Tx::try_from(tx_bytes.as_slice()) + .expect("Test failed") + .data + .expect("Test failed"), + ) + } else { + None + } + }, + ) + .collect(); + // check that the order of the txs is correct + assert_eq!(received, expected_txs); } } - -#[allow(unused_imports)] -#[cfg(not(feature = "ABCI"))] -pub use prepare_block::*; diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 9e41db598b..1108663372 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -1,13 +1,9 @@ //! Implementation of the ['VerifyHeader`], [`ProcessProposal`], //! and [`RevertProposal`] ABCI++ methods for the Shell -#[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::response_process_proposal::ProposalStatus; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ ExecTxResult, RequestProcessProposal, ResponseProcessProposal, }; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::RequestDeliverTx; use super::*; @@ -28,7 +24,6 @@ where /// but we only reject the entire block if the order of the /// included txs violates the order decided upon in the previous /// block. - #[cfg(not(feature = "ABCI"))] pub fn process_proposal( &mut self, req: RequestProcessProposal, @@ -174,89 +169,6 @@ where } } - /// If we are not using ABCI++, we check the wrapper, - /// decode it, and check the decoded payload all at once - #[cfg(feature = "ABCI")] - pub fn process_and_decode_proposal( - &mut self, - req: RequestDeliverTx, - ) -> shim::request::ProcessedTx { - // check the wrapper tx - let req_tx = match Tx::try_from(req.tx.as_ref()) { - Ok(tx) => tx, - Err(_) => { - return shim::request::ProcessedTx { - result: TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "The submitted transaction was not \ - deserializable" - .into(), - }, - // this ensures that emitted events are of the correct type - tx: req.tx, - }; - } - }; - match process_tx(req_tx.clone()) { - Ok(TxType::Wrapper(_)) => {} - Ok(TxType::Protocol(_)) => { - let result = self.process_single_tx(&req.tx); - return shim::request::ProcessedTx { tx: req.tx, result }; - } - Ok(_) => { - return shim::request::ProcessedTx { - result: TxResult { - code: ErrorCodes::InvalidTx.into(), - info: "Transaction rejected: Non-encrypted \ - transactions are not supported" - .into(), - }, - // this ensures that emitted events are of the correct type - tx: req.tx, - }; - } - Err(_) => { - // This will be caught later - } - } - - let wrapper_resp = self.process_single_tx(&req.tx); - let privkey = ::G2Affine::prime_subgroup_generator(); - - if wrapper_resp.code == 0 { - // if the wrapper passed, decode it - if let Ok(TxType::Wrapper(wrapper)) = process_tx(req_tx) { - let decoded = Tx::from(match wrapper.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted(tx), - _ => DecryptedTx::Undecryptable(wrapper.clone()), - }) - .to_bytes(); - // we are not checking that txs are out of order - self.storage.tx_queue.push(wrapper); - // check the decoded tx - let decoded_resp = self.process_single_tx(&decoded); - // this ensures that the tx queue is empty even if an error - // happened in [`process_proposal`]. - self.storage.tx_queue.pop(); - shim::request::ProcessedTx { - // this ensures that emitted events are of the correct type - tx: decoded, - result: decoded_resp, - } - } else { - // This was checked above - unreachable!() - } - } else { - shim::request::ProcessedTx { - // this ensures that emitted events are of the correct type - tx: req.tx, - result: wrapper_resp, - } - } - } - - #[cfg(not(feature = "ABCI"))] pub fn revert_proposal( &mut self, _req: shim::request::RevertProposal, @@ -278,20 +190,12 @@ mod test_process_proposal { use namada::types::token::Amount; use namada::types::transaction::encrypted::EncryptedTx; use namada::types::transaction::{EncryptionKey, Fee}; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::RequestInitChain; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::google::protobuf::Timestamp; - #[cfg(feature = "ABCI")] - use tendermint_proto_abci::abci::RequestInitChain; - #[cfg(feature = "ABCI")] - use tendermint_proto_abci::google::protobuf::Timestamp; use super::*; - #[cfg(not(feature = "ABCI"))] - use crate::node::ledger::shell::test_utils::TestError; use crate::node::ledger::shell::test_utils::{ - gen_keypair, ProcessProposal, TestShell, + gen_keypair, ProcessProposal, TestError, TestShell, }; /// Test that if a wrapper tx is not signed, it is rejected @@ -339,11 +243,6 @@ mod test_process_proposal { response.result.info, String::from("Wrapper transactions must be signed") ); - #[cfg(feature = "ABCI")] - { - assert_eq!(response.tx, tx); - assert!(shell.shell.storage.tx_queue.is_empty()) - } } /// Test that a wrapper tx with invalid signature is rejected @@ -426,11 +325,6 @@ mod test_process_proposal { response.result.info, expected_error ); - #[cfg(feature = "ABCI")] - { - assert_eq!(response.tx, new_tx.to_bytes()); - assert!(shell.shell.storage.tx_queue.is_empty()) - } } /// Test that if the account submitting the tx is not known and the fee is @@ -474,11 +368,6 @@ mod test_process_proposal { "The address given does not have sufficient balance to pay fee" .to_string(), ); - #[cfg(feature = "ABCI")] - { - assert_eq!(response.tx, wrapper.to_bytes()); - assert!(shell.shell.storage.tx_queue.is_empty()) - } } /// Test that if the account submitting the tx does @@ -535,14 +424,8 @@ mod test_process_proposal { "The address given does not have sufficient balance to pay fee" ) ); - #[cfg(feature = "ABCI")] - { - assert_eq!(response.tx, wrapper.to_bytes()); - assert!(shell.shell.storage.tx_queue.is_empty()) - } } - #[cfg(not(feature = "ABCI"))] /// Test that if the expected order of decrypted txs is /// validated, [`process_proposal`] rejects it #[test] @@ -608,7 +491,6 @@ mod test_process_proposal { ); } - #[cfg(not(feature = "ABCI"))] /// Test that a tx incorrectly labelled as undecryptable /// is rejected by [`process_proposal`] #[test] @@ -692,15 +574,11 @@ mod test_process_proposal { ); wrapper.tx_hash = Hash([0; 32]); - let tx = if !cfg!(feature = "ABCI") { - shell.enqueue_tx(wrapper.clone()); - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - #[allow(clippy::redundant_clone)] - wrapper.clone(), - ))) - } else { - wrapper.sign(&keypair).expect("Test failed") - }; + shell.enqueue_tx(wrapper.clone()); + let tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( + #[allow(clippy::redundant_clone)] + wrapper.clone(), + ))); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -715,23 +593,6 @@ mod test_process_proposal { panic!("Test failed") }; assert_eq!(response.result.code, u32::from(ErrorCodes::Ok)); - #[cfg(feature = "ABCI")] - { - match process_tx( - Tx::try_from(response.tx.as_ref()).expect("Test failed"), - ) - .expect("Test failed") - { - TxType::Decrypted(DecryptedTx::Undecryptable(inner)) => { - assert_eq!( - hash_tx(inner.try_to_vec().unwrap().as_ref()), - hash_tx(wrapper.try_to_vec().unwrap().as_ref()) - ); - assert!(shell.shell.storage.tx_queue.is_empty()) - } - _ => panic!("Test failed"), - } - } } /// Test that if a wrapper tx contains garbage bytes @@ -765,15 +626,11 @@ mod test_process_proposal { tx_hash: hash_tx(&tx), }; - let signed = if !cfg!(feature = "ABCI") { - shell.enqueue_tx(wrapper.clone()); - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - #[allow(clippy::redundant_clone)] - wrapper.clone(), - ))) - } else { - wrapper.sign(&keypair).expect("Test failed") - }; + shell.enqueue_tx(wrapper.clone()); + let signed = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( + #[allow(clippy::redundant_clone)] + wrapper.clone(), + ))); let request = ProcessProposal { txs: vec![signed.to_bytes()], }; @@ -787,26 +644,8 @@ mod test_process_proposal { panic!("Test failed") }; assert_eq!(response.result.code, u32::from(ErrorCodes::Ok)); - #[cfg(feature = "ABCI")] - { - match process_tx( - Tx::try_from(response.tx.as_ref()).expect("Test failed"), - ) - .expect("Test failed") - { - TxType::Decrypted(DecryptedTx::Undecryptable(inner)) => { - assert_eq!( - hash_tx(inner.try_to_vec().unwrap().as_ref()), - hash_tx(wrapper.try_to_vec().unwrap().as_ref()) - ); - assert!(shell.shell.storage.tx_queue.is_empty()); - } - _ => panic!("Test failed"), - } - } } - #[cfg(not(feature = "ABCI"))] /// Test that if more decrypted txs are submitted to /// [`process_proposal`] than expected, they are rejected #[test] @@ -871,9 +710,5 @@ mod test_process_proposal { supported" ), ); - #[cfg(feature = "ABCI")] - { - assert_eq!(response.tx, tx.to_bytes()); - } } } diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index 71ad018a2f..a643501d9e 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -10,18 +10,9 @@ use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Key, PrefixValue}; use namada::types::token::{self, Amount}; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::crypto::{ProofOp, ProofOps}; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::google::protobuf; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::types::EvidenceParams; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::crypto::{ProofOp, ProofOps}; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::google::protobuf; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::types::EvidenceParams; use super::*; use crate::node::ledger::response; diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 99ed7e1ad8..59abc3d1a9 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -5,24 +5,12 @@ use std::pin::Pin; use std::task::{Context, Poll}; use futures::future::FutureExt; -#[cfg(feature = "ABCI")] -use namada::types::hash::Hash; -#[cfg(feature = "ABCI")] -use namada::types::storage::BlockHash; -#[cfg(feature = "ABCI")] -use namada::types::transaction::hash_tx; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::abci::RequestBeginBlock; use tokio::sync::mpsc::UnboundedSender; use tower::Service; -#[cfg(not(feature = "ABCI"))] use tower_abci::{BoxError, Request as Req, Response as Resp}; -#[cfg(feature = "ABCI")] -use tower_abci_old::{BoxError, Request as Req, Response as Resp}; use super::super::Shell; use super::abcipp_shim_types::shim::request::{FinalizeBlock, ProcessedTx}; -#[cfg(not(feature = "ABCI"))] use super::abcipp_shim_types::shim::response::TxResult; use super::abcipp_shim_types::shim::{Error, Request, Response}; use crate::config; @@ -33,8 +21,6 @@ use crate::config; #[derive(Debug)] pub struct AbcippShim { service: Shell, - #[cfg(feature = "ABCI")] - begin_block_request: Option, processed_txs: Vec, shell_recv: std::sync::mpsc::Receiver<( Req, @@ -66,8 +52,6 @@ impl AbcippShim { vp_wasm_compilation_cache, tx_wasm_compilation_cache, ), - #[cfg(feature = "ABCI")] - begin_block_request: None, processed_txs: vec![], shell_recv, }, @@ -75,23 +59,11 @@ impl AbcippShim { ) } - #[cfg(feature = "ABCI")] - /// Get the hash of the txs in the block - pub fn get_hash(&self) -> Hash { - let bytes: Vec = self - .processed_txs - .iter() - .flat_map(|processed| processed.tx.clone()) - .collect(); - hash_tx(bytes.as_slice()) - } - /// Run the shell's blocking loop that receives messages from the /// [`AbciService`]. pub fn run(mut self) { while let Ok((req, resp_sender)) = self.shell_recv.recv() { let resp = match req { - #[cfg(not(feature = "ABCI"))] Req::ProcessProposal(proposal) => { let txs = proposal.txs.clone(); self.service @@ -113,7 +85,6 @@ impl AbcippShim { _ => unreachable!(), }) } - #[cfg(not(feature = "ABCI"))] Req::FinalizeBlock(block) => { let mut txs = vec![]; std::mem::swap(&mut txs, &mut self.processed_txs); @@ -129,47 +100,6 @@ impl AbcippShim { _ => Err(Error::ConvertResp(res)), }) } - #[cfg(feature = "ABCI")] - Req::BeginBlock(block) => { - // we save this data to be forwarded to finalize later - self.begin_block_request = Some(block); - Ok(Resp::BeginBlock(Default::default())) - } - #[cfg(feature = "ABCI")] - Req::DeliverTx(deliver_tx) => { - // We call [`process_single_tx`] to report back the validity - // of the tx to tendermint. - self.service - .call(Request::DeliverTx(deliver_tx)) - .map_err(Error::from) - .and_then(|res| match res { - Response::DeliverTx(resp) => { - self.processed_txs.push(resp); - Ok(Resp::DeliverTx(Default::default())) - } - _ => unreachable!(), - }) - } - #[cfg(feature = "ABCI")] - Req::EndBlock(_) => { - let mut txs = vec![]; - std::mem::swap(&mut txs, &mut self.processed_txs); - let mut end_block_request: FinalizeBlock = - self.begin_block_request.take().unwrap().into(); - let hash = self.get_hash(); - end_block_request.hash = BlockHash::from(hash.clone()); - end_block_request.header.hash = hash; - end_block_request.txs = txs; - self.service - .call(Request::FinalizeBlock(end_block_request)) - .map_err(Error::from) - .and_then(|res| match res { - Response::FinalizeBlock(resp) => { - Ok(Resp::EndBlock(resp.into())) - } - _ => Err(Error::ConvertResp(res)), - }) - } _ => match Request::try_from(req.clone()) { Ok(request) => self .service diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs index 7e7395fd78..bea7ba56af 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim_types.rs @@ -1,12 +1,8 @@ -#[cfg(not(feature = "ABCI"))] use tower_abci::{Request, Response}; -#[cfg(feature = "ABCI")] -use tower_abci_old::{Request, Response}; pub mod shim { use std::convert::TryFrom; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, RequestEcho, RequestExtendVote, RequestFlush, RequestInfo, RequestInitChain, @@ -19,16 +15,6 @@ pub mod shim { ResponsePrepareProposal, ResponseProcessProposal, ResponseQuery, ResponseVerifyVoteExtension, }; - #[cfg(feature = "ABCI")] - use tendermint_proto_abci::abci::{ - RequestApplySnapshotChunk, RequestCheckTx, RequestCommit, - RequestDeliverTx, RequestEcho, RequestFlush, RequestInfo, - RequestInitChain, RequestListSnapshots, RequestLoadSnapshotChunk, - RequestOfferSnapshot, RequestQuery, ResponseApplySnapshotChunk, - ResponseCheckTx, ResponseCommit, ResponseEcho, ResponseEndBlock, - ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, - ResponseLoadSnapshotChunk, ResponseOfferSnapshot, ResponseQuery, - }; use thiserror::Error; use super::{Request as Req, Response as Resp}; @@ -65,20 +51,13 @@ pub mod shim { InitChain(RequestInitChain), Info(RequestInfo), Query(RequestQuery), - #[cfg(not(feature = "ABCI"))] PrepareProposal(RequestPrepareProposal), #[allow(dead_code)] VerifyHeader(request::VerifyHeader), - #[cfg(not(feature = "ABCI"))] ProcessProposal(RequestProcessProposal), - #[cfg(feature = "ABCI")] - DeliverTx(RequestDeliverTx), #[allow(dead_code)] - #[cfg(not(feature = "ABCI"))] RevertProposal(request::RevertProposal), - #[cfg(not(feature = "ABCI"))] ExtendVote(RequestExtendVote), - #[cfg(not(feature = "ABCI"))] VerifyVoteExtension(RequestVerifyVoteExtension), FinalizeBlock(request::FinalizeBlock), Commit(RequestCommit), @@ -103,9 +82,7 @@ pub mod shim { Req::Commit(inner) => Ok(Request::Commit(inner)), Req::Flush(inner) => Ok(Request::Flush(inner)), Req::Echo(inner) => Ok(Request::Echo(inner)), - #[cfg(not(feature = "ABCI"))] Req::ExtendVote(inner) => Ok(Request::ExtendVote(inner)), - #[cfg(not(feature = "ABCI"))] Req::VerifyVoteExtension(inner) => { Ok(Request::VerifyVoteExtension(inner)) } @@ -118,7 +95,6 @@ pub mod shim { Req::ApplySnapshotChunk(inner) => { Ok(Request::ApplySnapshotChunk(inner)) } - #[cfg(not(feature = "ABCI"))] Req::PrepareProposal(inner) => { Ok(Request::PrepareProposal(inner)) } @@ -135,22 +111,13 @@ pub mod shim { InitChain(ResponseInitChain), Info(ResponseInfo), Query(ResponseQuery), - #[cfg(not(feature = "ABCI"))] PrepareProposal(ResponsePrepareProposal), VerifyHeader(response::VerifyHeader), - #[cfg(not(feature = "ABCI"))] ProcessProposal(ResponseProcessProposal), - #[cfg(feature = "ABCI")] - DeliverTx(request::ProcessedTx), - #[cfg(not(feature = "ABCI"))] RevertProposal(response::RevertProposal), - #[cfg(not(feature = "ABCI"))] ExtendVote(ResponseExtendVote), - #[cfg(not(feature = "ABCI"))] VerifyVoteExtension(ResponseVerifyVoteExtension), FinalizeBlock(response::FinalizeBlock), - #[cfg(feature = "ABCI")] - EndBlock(ResponseEndBlock), Commit(ResponseCommit), Flush(ResponseFlush), Echo(ResponseEcho), @@ -186,13 +153,10 @@ pub mod shim { Response::ApplySnapshotChunk(inner) => { Ok(Resp::ApplySnapshotChunk(inner)) } - #[cfg(not(feature = "ABCI"))] Response::PrepareProposal(inner) => { Ok(Resp::PrepareProposal(inner)) } - #[cfg(not(feature = "ABCI"))] Response::ExtendVote(inner) => Ok(Resp::ExtendVote(inner)), - #[cfg(not(feature = "ABCI"))] Response::VerifyVoteExtension(inner) => { Ok(Resp::VerifyVoteExtension(inner)) } @@ -208,16 +172,12 @@ pub mod shim { use namada::types::hash::Hash; use namada::types::storage::{BlockHash, Header}; use namada::types::time::DateTimeUtc; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ Misbehavior as Evidence, RequestFinalizeBlock, }; - #[cfg(feature = "ABCI")] - use tendermint_proto_abci::abci::{Evidence, RequestBeginBlock}; pub struct VerifyHeader; - #[cfg(not(feature = "ABCI"))] pub struct RevertProposal; /// A Tx and the result of calling Process Proposal on it @@ -234,7 +194,6 @@ pub mod shim { pub txs: Vec, } - #[cfg(not(feature = "ABCI"))] impl From for FinalizeBlock { fn from(req: RequestFinalizeBlock) -> FinalizeBlock { FinalizeBlock { @@ -252,48 +211,17 @@ pub mod shim { } } } - - #[cfg(feature = "ABCI")] - impl From for FinalizeBlock { - fn from(req: RequestBeginBlock) -> FinalizeBlock { - let header = req.header.unwrap(); - FinalizeBlock { - hash: BlockHash::default(), - header: Header { - hash: Hash::default(), - time: DateTimeUtc::try_from(header.time.unwrap()) - .unwrap(), - next_validators_hash: Hash::try_from( - header.next_validators_hash.as_slice(), - ) - .unwrap(), - }, - byzantine_validators: req.byzantine_validators, - txs: vec![], - } - } - } } /// Custom types for response payloads pub mod response { - #[cfg(not(feature = "ABCI"))] use tendermint_proto::abci::{ Event as TmEvent, ExecTxResult, ResponseFinalizeBlock, ValidatorUpdate, }; - #[cfg(not(feature = "ABCI"))] use tendermint_proto::types::ConsensusParams; - #[cfg(feature = "ABCI")] - use tendermint_proto_abci::abci::ConsensusParams; - #[cfg(feature = "ABCI")] - use tendermint_proto_abci::abci::{Event as TmEvent, ValidatorUpdate}; - #[cfg(feature = "ABCI")] - use tower_abci_old::response; - use crate::node::ledger::events::Event; - #[cfg(not(feature = "ABCI"))] - use crate::node::ledger::events::EventLevel; + use crate::node::ledger::events::{Event, EventLevel}; #[derive(Debug, Default)] pub struct VerifyHeader; @@ -304,7 +232,6 @@ pub mod shim { pub info: String, } - #[cfg(not(feature = "ABCI"))] impl From for ExecTxResult { fn from(TxResult { code, info }: TxResult) -> Self { ExecTxResult { @@ -315,7 +242,6 @@ pub mod shim { } } - #[cfg(not(feature = "ABCI"))] impl From<&ExecTxResult> for TxResult { fn from(ExecTxResult { code, info, .. }: &ExecTxResult) -> Self { TxResult { @@ -335,7 +261,6 @@ pub mod shim { pub consensus_param_updates: Option, } - #[cfg(not(feature = "ABCI"))] impl From for ResponseFinalizeBlock { fn from(resp: FinalizeBlock) -> Self { ResponseFinalizeBlock { @@ -374,20 +299,5 @@ pub mod shim { } } } - - #[cfg(feature = "ABCI")] - impl From for response::EndBlock { - fn from(resp: FinalizeBlock) -> Self { - Self { - events: resp - .events - .into_iter() - .map(TmEvent::from) - .collect(), - validator_updates: resp.validator_updates, - consensus_param_updates: resp.consensus_param_updates, - } - } - } } } diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index c38146cd35..136f925df4 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -9,22 +9,9 @@ use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::time::DateTimeUtc; use serde_json::json; -#[cfg(not(feature = "ABCI"))] use tendermint::Genesis; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; -#[cfg(not(feature = "ABCI"))] -use tendermint_config::Error as TendermintError; -#[cfg(not(feature = "ABCI"))] -use tendermint_config::TendermintConfig; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::Error as TendermintError; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::TendermintConfig; -#[cfg(feature = "ABCI")] -use tendermint_stable::Genesis; +use tendermint_config::{Error as TendermintError, TendermintConfig}; use thiserror::Error; use tokio::fs::{self, File, OpenOptions}; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -97,19 +84,11 @@ pub async fn run( }; // init and run a tendermint node child process - let output = if !cfg!(feature = "ABCI") { - Command::new(&tendermint_path) - .args(&["init", &mode, "--home", &home_dir_string]) - .output() - .await - .map_err(Error::Init)? - } else { - Command::new(&tendermint_path) - .args(&["init", "--home", &home_dir_string]) - .output() - .await - .map_err(Error::Init)? - }; + let output = Command::new(&tendermint_path) + .args(&["init", &mode, "--home", &home_dir_string]) + .output() + .await + .map_err(Error::Init)?; if !output.status.success() { panic!("Tendermint failed to initialize with {:#?}", output); } @@ -135,37 +114,20 @@ pub async fn run( .await; } } - #[cfg(not(feature = "ABCI"))] - { - write_tm_genesis(&home_dir, chain_id, genesis_time, &config).await; - } - #[cfg(feature = "ABCI")] - { - write_tm_genesis(&home_dir, chain_id, genesis_time).await; - } + write_tm_genesis(&home_dir, chain_id, genesis_time, &config).await; update_tendermint_config(&home_dir, config).await?; let mut tendermint_node = Command::new(&tendermint_path); - if !cfg!(feature = "ABCI") { - tendermint_node.args(&[ - "start", - "--mode", - &mode, - "--proxy-app", - &ledger_address, - "--home", - &home_dir_string, - ]) - } else { - tendermint_node.args(&[ - "start", - "--proxy_app", - &ledger_address, - "--home", - &home_dir_string, - ]) - }; + tendermint_node.args(&[ + "start", + "--mode", + &mode, + "--proxy-app", + &ledger_address, + "--home", + &home_dir_string, + ]); let log_stdout = match env::var(ENV_VAR_TM_STDOUT) { Ok(val) => val.to_ascii_lowercase().trim() == "true", @@ -218,7 +180,6 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { let tendermint_path = from_env_or_default()?; let tendermint_dir = tendermint_dir.as_ref().to_string_lossy(); // reset all the Tendermint state, if any - #[cfg(not(feature = "ABCI"))] std::process::Command::new(tendermint_path) .args(&[ "reset", @@ -230,17 +191,6 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { ]) .output() .expect("Failed to reset tendermint node's data"); - #[cfg(feature = "ABCI")] - std::process::Command::new(tendermint_path) - .args(&[ - "unsafe-reset-all", - // NOTE: log config: https://docs.tendermint.com/master/nodes/logging.html#configuring-log-levels - // "--log-level=\"*debug\"", - "--home", - &tendermint_dir, - ]) - .output() - .expect("Failed to reset tendermint node's data"); std::fs::remove_dir_all(format!("{}/config", tendermint_dir,)) .expect("Failed to reset tendermint node's config"); Ok(()) @@ -360,19 +310,10 @@ async fn update_tendermint_config( config.p2p.persistent_peers = tendermint_config.p2p_persistent_peers; config.p2p.pex = tendermint_config.p2p_pex; config.p2p.allow_duplicate_ip = tendermint_config.p2p_allow_duplicate_ip; - #[cfg(feature = "ABCI")] - { - config.p2p.addr_book_strict = tendermint_config.p2p_addr_book_strict; - } // In "dev", only produce blocks when there are txs or when the AppHash // changes config.consensus.create_empty_blocks = true; // !cfg!(feature = "dev"); - #[cfg(feature = "ABCI")] - { - config.consensus.timeout_commit = - tendermint_config.consensus_timeout_commit; - } // We set this to true as we don't want any invalid tx be re-applied. This // also implies that it's not possible for an invalid tx to become valid @@ -395,7 +336,6 @@ async fn update_tendermint_config( tendermint_config.instrumentation_namespace; // setup the events log - #[cfg(not(feature = "ABCI"))] { // keep events for one minute config.rpc.event_log_window_size = @@ -421,7 +361,7 @@ async fn write_tm_genesis( home_dir: impl AsRef, chain_id: ChainId, genesis_time: DateTimeUtc, - #[cfg(not(feature = "ABCI"))] config: &config::Tendermint, + config: &config::Tendermint, ) { let home_dir = home_dir.as_ref(); let path = home_dir.join("config").join("genesis.json"); @@ -442,11 +382,8 @@ async fn write_tm_genesis( genesis.genesis_time = genesis_time .try_into() .expect("Couldn't convert DateTimeUtc to Tendermint Time"); - #[cfg(not(feature = "ABCI"))] - { - genesis.consensus_params.timeout.commit = - config.consensus_timeout_commit.into() - } + genesis.consensus_params.timeout.commit = + config.consensus_timeout_commit.into(); let mut file = OpenOptions::new() .write(true) diff --git a/apps/src/lib/node/matchmaker.rs b/apps/src/lib/node/matchmaker.rs index 3813569391..0a01528b00 100644 --- a/apps/src/lib/node/matchmaker.rs +++ b/apps/src/lib/node/matchmaker.rs @@ -14,14 +14,8 @@ use namada::types::intent::{IntentTransfers, MatchedExchanges}; use namada::types::key::*; use namada::types::matchmaker::AddIntentResult; use namada::types::transaction::{hash_tx, Fee, WrapperTx}; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net; -#[cfg(not(feature = "ABCI"))] use tendermint_config::net::Address as TendermintAddress; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net; -#[cfg(feature = "ABCI")] -use tendermint_config_abci::net::Address as TendermintAddress; use super::gossip::rpc::matchmakers::{ ClientDialer, ClientListener, MsgFromClient, MsgFromServer, @@ -331,17 +325,9 @@ impl ResultHandler { // TODO: Actually use the fetched encryption key Default::default(), ); - let wrapper_hash = if !cfg!(feature = "ABCI") { - hash_tx(&tx.try_to_vec().unwrap()).to_string() - } else { - tx.tx_hash.to_string() - }; + let wrapper_hash = hash_tx(&tx.try_to_vec().unwrap()).to_string(); - let decrypted_hash = if !cfg!(feature = "ABCI") { - Some(tx.tx_hash.to_string()) - } else { - None - }; + let decrypted_hash = tx.tx_hash.to_string(); TxBroadcastData::Wrapper { tx: tx .sign(&self.tx_signing_key) diff --git a/shared/build.rs b/shared/build.rs index d87f7a918f..3c5ffb41f1 100644 --- a/shared/build.rs +++ b/shared/build.rs @@ -9,10 +9,6 @@ const PROTO_SRC: &str = "./proto"; const RUSTFMT_TOOLCHAIN_SRC: &str = "../rust-nightly-version"; fn main() { - #[cfg(all(feature = "ABCI", feature = "ABCI-plus-plus"))] - compile_error!( - "`ABCI` and `ABCI-plus-plus` may not be used at the same time" - ); if let Ok(val) = env::var("COMPILE_PROTO") { if val.to_ascii_lowercase() == "false" { // Skip compiling proto files diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 183fa457b0..fa0f4a011f 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -16,10 +16,7 @@ use sparse_merkle_tree::default_store::DefaultStore; use sparse_merkle_tree::error::Error as SmtError; use sparse_merkle_tree::traits::Hasher; use sparse_merkle_tree::{SparseMerkleTree, H256}; -#[cfg(not(feature = "ABCI"))] use tendermint::merkle::proof::{Proof, ProofOp}; -#[cfg(feature = "ABCI")] -use tendermint_stable::merkle::proof::{Proof, ProofOp}; use thiserror::Error; use crate::bytes::ByteBuf; diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 56e9862eb9..0b3d19a742 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -8,10 +8,7 @@ pub mod write_log; use core::fmt::Debug; -#[cfg(not(feature = "ABCI"))] use tendermint::merkle::proof::Proof; -#[cfg(feature = "ABCI")] -use tendermint_stable::merkle::proof::Proof; use thiserror::Error; use super::parameters; diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 5199e120a6..5c2d2fe4d7 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -6,22 +6,7 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -#[cfg(not(feature = "ABCI"))] -pub use ibc; -#[cfg(feature = "ABCI")] -pub use ibc_abci as ibc; -#[cfg(not(feature = "ABCI"))] -pub use ibc_proto; -#[cfg(feature = "ABCI")] -pub use ibc_proto_abci as ibc_proto; -#[cfg(not(feature = "ABCI"))] -pub use tendermint; -#[cfg(not(feature = "ABCI"))] -pub use tendermint_proto; -#[cfg(feature = "ABCI")] -pub use tendermint_proto_abci as tendermint_proto; -#[cfg(feature = "ABCI")] -pub use tendermint_stable as tendermint; +pub use {ibc, ibc_proto, tendermint, tendermint_proto}; pub mod bytes; pub mod ledger; diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 6e2198ceb6..e2fed30379 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -6,14 +6,8 @@ use std::ops::Deref; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -#[cfg(not(feature = "ABCI"))] use tendermint::abci::transaction; -#[cfg(not(feature = "ABCI"))] use tendermint::Hash as TmHash; -#[cfg(feature = "ABCI")] -use tendermint_stable::abci::transaction; -#[cfg(feature = "ABCI")] -use tendermint_stable::Hash as TmHash; use thiserror::Error; /// The length of the transaction hash string diff --git a/shared/src/types/time.rs b/shared/src/types/time.rs index 47cdbb36ff..ec91ff5b14 100644 --- a/shared/src/types/time.rs +++ b/shared/src/types/time.rs @@ -6,10 +6,7 @@ use std::ops::{Add, Sub}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; pub use chrono::{DateTime, Duration, TimeZone, Utc}; -#[cfg(not(feature = "ABCI"))] use tendermint_proto::google::protobuf; -#[cfg(feature = "ABCI")] -use tendermint_proto_abci::google::protobuf; use crate::tendermint::time::Time; use crate::tendermint::Error as TendermintError; diff --git a/tests/src/e2e/eth_bridge_tests.rs b/tests/src/e2e/eth_bridge_tests.rs index 70e753e813..b276dd409e 100644 --- a/tests/src/e2e/eth_bridge_tests.rs +++ b/tests/src/e2e/eth_bridge_tests.rs @@ -76,9 +76,7 @@ fn everything() { .unwrap(); if !dry_run { - if !cfg!(feature = "ABCI") { - anomac_tx.exp_string("Transaction accepted").unwrap(); - } + anomac_tx.exp_string("Transaction accepted").unwrap(); anomac_tx.exp_string("Transaction applied").unwrap(); } // TODO: we should check here explicitly with the ledger via a diff --git a/tests/src/e2e/gossip_tests.rs b/tests/src/e2e/gossip_tests.rs index feb0e88324..5da19c0758 100644 --- a/tests/src/e2e/gossip_tests.rs +++ b/tests/src/e2e/gossip_tests.rs @@ -182,15 +182,8 @@ fn match_intents() -> Result<()> { let validator_one_gossiper = get_gossiper_mm_server(&test, &Who::Validator(0)); - // The RPC port is either 27660 for ABCI or 28660 for ABCI++ (see - // `setup::network`) - let rpc_port = (27660 - + if cfg!(feature = "ABCI") { - 0 - } else { - setup::ABCI_PLUS_PLUS_PORT_OFFSET - }) - .to_string(); + // The RPC port starts at 27660 (see `setup::network`) + let rpc_port = 27660; let rpc_address = format!("127.0.0.1:{}", rpc_port); // Start intent gossiper node diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index f72044b80a..cc0c45cf8c 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -173,26 +173,13 @@ pub fn generate_bin_command(bin_name: &str, manifest_path: &Path) -> Command { }; if !use_prebuilt_binaries { - let build_cmd = if !cfg!(feature = "ABCI") { - CargoBuild::new() - .package(APPS_PACKAGE) - .manifest_path(manifest_path) - .no_default_features() - .features("ABCI-plus-plus") - // Explicitly disable dev, in case it's enabled when a test is - // invoked - .env("ANOMA_DEV", "false") - .bin(bin_name) - } else { - CargoBuild::new() - .package(APPS_PACKAGE) - .manifest_path(manifest_path) - .features("ABCI") - // Explicitly disable dev, in case it's enabled when a test is - // invoked - .env("ANOMA_DEV", "false") - .bin(bin_name) - }; + let build_cmd = CargoBuild::new() + .package(APPS_PACKAGE) + .manifest_path(manifest_path) + // Explicitly disable dev, in case it's enabled when a test is + // invoked + .env("ANOMA_DEV", "false") + .bin(bin_name); let build_cmd = if run_debug { build_cmd diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index a83d3f54ca..c453c13cf4 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -52,11 +52,7 @@ fn run_ledger() -> Result<()> { let mut ledger = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; ledger.exp_string("Anoma ledger node started")?; - if !cfg!(feature = "ABCI") { - ledger.exp_string("This node is a fullnode")?; - } else { - ledger.exp_string("This node is not a validator")?; - } + ledger.exp_string("This node is a fullnode")?; } Ok(()) @@ -66,8 +62,8 @@ fn run_ledger() -> Result<()> { /// 1. Run 2 genesis validator ledger nodes and 1 non-validator node /// 2. Submit a valid token transfer tx /// 3. Check that all the nodes processed the tx with the same result -/// TODO: run this test for ABCI-plus-plus once https://github.com/tendermint/tendermint/issues/8840 is fixed -#[cfg(not(feature = "ABCI-plus-plus"))] +/// TODO: run this test once https://github.com/tendermint/tendermint/issues/8840 is fixed +#[ignore] #[test] fn test_node_connectivity() -> Result<()> { // Setup 2 genesis validator nodes @@ -87,11 +83,7 @@ fn test_node_connectivity() -> Result<()> { let mut non_validator = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; non_validator.exp_string("Anoma ledger node started")?; - if !cfg!(feature = "ABCI") { - non_validator.exp_string("This node is a fullnode")?; - } else { - non_validator.exp_string("This node is not a validator")?; - } + non_validator.exp_string("This node is a fullnode")?; // 2. Submit a valid token transfer tx let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); @@ -342,9 +334,7 @@ fn ledger_txs_and_queries() -> Result<()> { let mut client = run!(test, Bin::Client, tx_args, Some(40))?; if !dry_run { - if !cfg!(feature = "ABCI") { - client.exp_string("Transaction accepted")?; - } + client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; } client.exp_string("Transaction is valid.")?; @@ -460,9 +450,7 @@ fn invalid_transactions() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - if !cfg!(feature = "ABCI") { - client.exp_string("Transaction accepted")?; - } + client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; client.exp_string("Transaction is invalid")?; client.exp_string(r#""code": "1"#)?; @@ -519,9 +507,7 @@ fn invalid_transactions() -> Result<()> { ]; let mut client = run!(test, Bin::Client, tx_args, Some(40))?; - if !cfg!(feature = "ABCI") { - client.exp_string("Transaction accepted")?; - } + client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; client.exp_string("Error trying to apply a transaction")?; @@ -967,9 +953,7 @@ fn ledger_many_txs_in_a_block() -> Result<()> { let mut args = (*tx_args).clone(); args.push(&*validator_one_rpc); let mut client = run!(*test, Bin::Client, args, Some(40))?; - if !cfg!(feature = "ABCI") { - client.exp_string("Transaction accepted")?; - } + client.exp_string("Transaction accepted")?; client.exp_string("Transaction applied")?; client.exp_string("Transaction is valid.")?; client.assert_success(); @@ -1516,8 +1500,8 @@ fn generate_proposal_json( /// 3. Setup and start the 2 genesis validator nodes and a non-validator node /// 4. Submit a valid token transfer tx from one validator to the other /// 5. Check that all the nodes processed the tx with the same result -/// TODO: run this test for ABCI-plus-plus once https://github.com/tendermint/tendermint/issues/8840 is fixed -#[cfg(not(feature = "ABCI-plus-plus"))] +/// TODO: run this test once https://github.com/tendermint/tendermint/issues/8840 is fixed +#[ignore] #[test] fn test_genesis_validators() -> Result<()> { use std::collections::HashMap; @@ -1550,17 +1534,7 @@ fn test_genesis_validators() -> Result<()> { let net_address_port_0 = net_address_0.port(); // Find the first port (ledger P2P) that should be used for a validator at // the given index - let get_first_port = |ix: u8| { - net_address_port_0 - + 6 * (ix as u16 + 1) - + if cfg!(feature = "ABCI") { - 0 - } else { - // The ABCI++ ports at `26670 + ABCI_PLUS_PLUS_PORT_OFFSET`, - // see `network` - setup::ABCI_PLUS_PLUS_PORT_OFFSET - } - }; + let get_first_port = |ix: u8| net_address_port_0 + 6 * (ix as u16 + 1); // 1. Setup 2 genesis validators let validator_0_alias = "validator-0"; @@ -1773,13 +1747,7 @@ fn test_genesis_validators() -> Result<()> { // We have to update the ports in the configs again, because the ones from // `join-network` use the defaults let update_config = |ix: u8, mut config: Config| { - let first_port = net_address_port_0 - + 6 * (ix as u16 + 1) - + if cfg!(feature = "ABCI") { - 0 - } else { - setup::ABCI_PLUS_PLUS_PORT_OFFSET - }; + let first_port = net_address_port_0 + 6 * (ix as u16 + 1); config.ledger.tendermint.p2p_address.set_port(first_port); config .ledger @@ -1829,11 +1797,7 @@ fn test_genesis_validators() -> Result<()> { let mut non_validator = run_as!(test, Who::NonValidator, Bin::Node, args, Some(40))?; non_validator.exp_string("Anoma ledger node started")?; - if !cfg!(feature = "ABCI") { - non_validator.exp_string("This node is a fullnode")?; - } else { - non_validator.exp_string("This node is not a validator")?; - } + non_validator.exp_string("This node is a fullnode")?; // 4. Submit a valid token transfer tx let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 28f1cf1825..b7e4c60e9c 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -55,10 +55,6 @@ pub struct Network { pub chain_id: ChainId, } -/// Offset the ports used in the network configuration by 1000 for ABCI++ to -/// avoid shared resources -pub const ABCI_PLUS_PLUS_PORT_OFFSET: u16 = 1000; - /// Add `num` validators to the genesis config. Note that called from inside /// the [`network`]'s first argument's closure, there is 1 validator already /// present to begin with, so e.g. `add_validators(1, _)` will configure a @@ -86,15 +82,7 @@ pub fn add_validators(num: u8, mut genesis: GenesisConfig) -> GenesisConfig { validator.intent_gossip_seed = None; let mut net_address = net_address_0; // 6 ports for each validator - let first_port = net_address_port_0 - + 6 * (ix as u16 + 1) - + if cfg!(feature = "ABCI") { - 0 - } else { - // The ABCI++ ports at `26670 + ABCI_PLUS_PLUS_PORT_OFFSET`, - // see `network` - ABCI_PLUS_PLUS_PORT_OFFSET - }; + let first_port = net_address_port_0 + 6 * (ix as u16 + 1); net_address.set_port(first_port); validator.net_address = Some(net_address.to_string()); let name = format!("validator-{}", ix + 1); @@ -122,23 +110,10 @@ pub fn network( let test_dir = TestDir::new(); // Open the source genesis file - let mut genesis = genesis_config::open_genesis_config( + let genesis = genesis_config::open_genesis_config( working_dir.join(SINGLE_NODE_NET_GENESIS), ); - if !cfg!(feature = "ABCI") { - // The ABCI ports start at `26670`, ABCI++ at `26670 + - // ABCI_PLUS_PLUS_PORT_OFFSET`to avoid using shared resources with ABCI - // feature if running at the same time. - let validator_0 = genesis.validator.get_mut("validator-0").unwrap(); - let mut net_address_0 = - SocketAddr::from_str(validator_0.net_address.as_ref().unwrap()) - .unwrap(); - let current_port = net_address_0.port(); - net_address_0.set_port(current_port + ABCI_PLUS_PLUS_PORT_OFFSET); - validator_0.net_address = Some(net_address_0.to_string()); - }; - // Run the provided function on it let genesis = update_genesis(genesis); @@ -429,16 +404,17 @@ impl Test { pub fn working_dir() -> PathBuf { let working_dir = fs::canonicalize("..").unwrap(); - if cfg!(feature = "ABCI") { - // Check that tendermint is on $PATH - Command::new("which").arg("tendermint").assert().success(); - std::env::var("TENDERMINT") - .expect_err("The env variable TENDERMINT must **not** be set"); - } else { - std::env::var("TENDERMINT").expect( - "The env variable TENDERMINT must be set and point to a local \ - build of the tendermint abci++ branch", - ); + // Check that tendermint is either on $PATH or `TENDERMINT` env var is set + if std::env::var("TENDERMINT").is_err() { + Command::new("which") + .arg("tendermint") + .assert() + .try_success() + .expect( + "The env variable TENDERMINT must be set and point to a local \ + build of the tendermint abci++ branch, or the tendermint \ + binary must be on PATH", + ); } working_dir } From 87f162fe1ebd175433bff71db3858e6149d4202b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Jul 2022 18:37:17 +0200 Subject: [PATCH 083/373] make: remove "*-abci-plus-plus" --- Makefile | 93 ++------------------------------------------------------ 1 file changed, 2 insertions(+), 91 deletions(-) diff --git a/Makefile b/Makefile index ea42642e83..3474f6c8e6 100644 --- a/Makefile +++ b/Makefile @@ -19,20 +19,11 @@ audit-ignores += RUSTSEC-2021-0076 build: $(cargo) build -build-abci-plus-plus: - $(cargo) build --no-default-features --features "ABCI-plus-plus" - build-test: $(cargo) build --tests -build-test-abci-plus-plus: - $(cargo) build --tests --no-default-features --features "ABCI-plus-plus" - build-release: - ANOMA_DEV=false $(cargo) build --release --package namada_apps --manifest-path Cargo.toml --features "ABCI" - -build-release-abci-plus-plus: - ANOMA_DEV=false $(cargo) build --release --package namada_apps --no-default-features --features "ABCI-plus-plus" + ANOMA_DEV=false $(cargo) build --release --package namada_apps --manifest-path Cargo.toml check-release: ANOMA_DEV=false $(cargo) check --release --package namada_apps @@ -56,43 +47,14 @@ check: make -C $(wasms_for_tests) check && \ $(foreach wasm,$(wasm_templates),$(check-wasm) && ) true -check-abci-plus-plus: - $(cargo) check --no-default-features --features "ABCI-plus-plus" - clippy-wasm = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --all-targets -- -D warnings -clippy-wasm-abci-plus-plus = $(cargo) +$(nightly) clippy --manifest-path $(wasm)/Cargo.toml --all-targets --no-default-features --features "ABCI-plus-plus" -- -D warnings - clippy: ANOMA_DEV=false $(cargo) +$(nightly) clippy --all-targets -- -D warnings && \ make -C $(wasms) clippy && \ make -C $(wasms_for_tests) clippy && \ $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true -clippy-abci-plus-plus: - ANOMA_DEV=false $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./apps/Cargo.toml \ - --no-default-features \ - --features "std testing ABCI-plus-plus" && \ - $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./proof_of_stake/Cargo.toml \ - --features "testing" && \ - $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./shared/Cargo.toml \ - --no-default-features \ - --features "testing wasm-runtime ABCI-plus-plus ibc-mocks" && \ - $(cargo) +$(nightly) clippy --all-targets \ - --manifest-path ./tests/Cargo.toml \ - --no-default-features \ - --features "wasm-runtime ABCI-plus-plus namada_apps/ABCI-plus-plus" && \ - $(cargo) +$(nightly) clippy \ - --all-targets \ - --manifest-path ./vm_env/Cargo.toml \ - --no-default-features \ - --features "ABCI-plus-plus" && \ - make -C $(wasms) clippy && \ - $(foreach wasm,$(wasm_templates),$(clippy-wasm) && ) true - clippy-fix: $(cargo) +$(nightly) clippy --fix -Z unstable-options --all-targets --allow-dirty --allow-staged @@ -106,10 +68,6 @@ run-ledger: # runs the node $(cargo) run --bin namadan -- ledger run -run-ledger-abci-plus-plus: - # runs the node - $(cargo) run --bin namadan --no-default-features --features "ABCI-plus-plus" -- ledger run - run-gossip: # runs the node gossip node $(cargo) run --bin namadan -- gossip run @@ -118,10 +76,6 @@ reset-ledger: # runs the node $(cargo) run --bin namadan -- ledger reset -reset-ledger-abci-plus-plus: - # runs the node - $(cargo) run --bin namadan --no-default-features --features "ABCI-plus-plus" -- ledger reset - audit: $(cargo) audit $(foreach ignore,$(audit-ignores), --ignore $(ignore)) @@ -133,51 +87,8 @@ test-e2e: --test-threads=1 \ -Z unstable-options --report-time -test-e2e-abci-plus-plus: - RUST_BACKTRACE=1 $(cargo) test e2e \ - --manifest-path ./tests/Cargo.toml \ - --no-default-features \ - --features "wasm-runtime ABCI-plus-plus namada_apps/ABCI-plus-plus" \ - -- \ - --test-threads=1 \ - -Z unstable-options --report-time - -test-unit-abci-plus-plus: - $(cargo) test \ - --manifest-path ./apps/Cargo.toml \ - --no-default-features \ - --features "testing std ABCI-plus-plus" \ - -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path \ - ./proof_of_stake/Cargo.toml \ - --features "testing" \ - -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./shared/Cargo.toml \ - --no-default-features \ - --features "testing wasm-runtime ABCI-plus-plus ibc-mocks" \ - -- \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./tests/Cargo.toml \ - --no-default-features \ - --features "wasm-runtime ABCI-plus-plus namada_apps/ABCI-plus-plus" \ - -- \ - --skip e2e \ - -Z unstable-options --report-time && \ - $(cargo) test \ - --manifest-path ./vm_env/Cargo.toml \ - --no-default-features \ - --features "ABCI-plus-plus" \ - -- \ - -Z unstable-options --report-time - test-unit: - $(cargo) test --no-default-features \ - --features "wasm-runtime ABCI ibc-mocks-abci" \ + $(cargo) test \ -- \ --skip e2e \ -Z unstable-options --report-time From f4092ac4aa8c30eff2e99aea7f3f94ba2cf070bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 28 Jul 2022 15:44:29 +0200 Subject: [PATCH 084/373] shell: process transaction when `ProcessProposal` hasn't (non-validator) --- .../lib/node/ledger/shell/process_proposal.rs | 17 +++-- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 70 +++++++++++++++++-- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index 1108663372..f12b16d9eb 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -28,13 +28,7 @@ where &mut self, req: RequestProcessProposal, ) -> ResponseProcessProposal { - let tx_results: Vec = req - .txs - .iter() - .map(|tx_bytes| { - ExecTxResult::from(self.process_single_tx(tx_bytes)) - }) - .collect(); + let tx_results = self.process_txs(&req.txs); ResponseProcessProposal { status: if tx_results.iter().any(|res| res.code > 3) { @@ -47,6 +41,15 @@ where } } + /// Check all the given txs. + pub fn process_txs(&mut self, txs: &[Vec]) -> Vec { + txs.iter() + .map(|tx_bytes| { + ExecTxResult::from(self.process_single_tx(tx_bytes)) + }) + .collect() + } + /// Checks if the Tx can be deserialized from bytes. Checks the fees and /// signatures of the fee payer for a transaction if it is a wrapper tx. /// diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 59abc3d1a9..f52459741d 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -5,6 +5,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use futures::future::FutureExt; +use tendermint_proto::abci::ResponseFinalizeBlock; use tokio::sync::mpsc::UnboundedSender; use tower::Service; use tower_abci::{BoxError, Request as Req, Response as Resp}; @@ -21,7 +22,9 @@ use crate::config; #[derive(Debug)] pub struct AbcippShim { service: Shell, - processed_txs: Vec, + /// This is `Some` only when `ProcessProposal` request is received before + /// `FinalizeBlock`, which is optional for non-validator nodes. + processed_txs: Option>, shell_recv: std::sync::mpsc::Receiver<( Req, tokio::sync::oneshot::Sender>, @@ -52,7 +55,7 @@ impl AbcippShim { vp_wasm_compilation_cache, tx_wasm_compilation_cache, ), - processed_txs: vec![], + processed_txs: None, shell_recv, }, AbciService { shell_send }, @@ -71,13 +74,17 @@ impl AbcippShim { .map_err(Error::from) .and_then(|res| match res { Response::ProcessProposal(resp) => { + self.processed_txs = + Some(Vec::with_capacity(txs.len())); + let processed_txs = + self.processed_txs.as_mut().unwrap(); for (result, tx) in resp .tx_results .iter() .map(TxResult::from) .zip(txs.into_iter()) { - self.processed_txs + processed_txs .push(ProcessedTx { tx, result }); } Ok(Resp::ProcessProposal(resp)) @@ -86,16 +93,67 @@ impl AbcippShim { }) } Req::FinalizeBlock(block) => { - let mut txs = vec![]; - std::mem::swap(&mut txs, &mut self.processed_txs); + let (txs, processing_results) = + match self.processed_txs.take() { + Some(processed_txs) => { + // When there are processed_txs from + // `ProcessProposal`, we don't need to add + // processing + // results again + (processed_txs, None) + } + None => { + // When there are no processed txs, it means + // that `ProcessProposal` request has not been + // received and so we need to process + // transactions first in the same way as + // `ProcessProposal`. + let unprocessed_txs = block.txs.clone(); + let processing_results = + self.service.process_txs(&block.txs); + let mut txs = + Vec::with_capacity(unprocessed_txs.len()); + for (result, tx) in processing_results + .iter() + .map(TxResult::from) + .zip(unprocessed_txs.into_iter()) + { + txs.push(ProcessedTx { tx, result }); + } + // Then we also have to add the processing + // result events to the `FinalizeBlock` + // tx_results + (txs, Some(processing_results)) + } + }; + let mut finalize_req: FinalizeBlock = block.into(); finalize_req.txs = txs; + self.service .call(Request::FinalizeBlock(finalize_req)) .map_err(Error::from) .and_then(|res| match res { Response::FinalizeBlock(resp) => { - Ok(Resp::FinalizeBlock(resp.into())) + let mut resp: ResponseFinalizeBlock = + resp.into(); + + // Add processing results, if any + if let Some(processing_results) = + processing_results + { + for (tx_result, processing_result) in resp + .tx_results + .iter_mut() + .zip(processing_results) + { + tx_result + .events + .extend(processing_result.events); + } + } + + Ok(Resp::FinalizeBlock(resp)) } _ => Err(Error::ConvertResp(res)), }) From 7c32a5febdcbe2c516fea05d4b1d6b9cb1a562f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 28 Jul 2022 16:00:54 +0200 Subject: [PATCH 085/373] test/e2e/ledger: enable ignored tests for ABCI++ workaround --- tests/src/e2e/ledger_tests.rs | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index c453c13cf4..552e675e67 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -62,8 +62,6 @@ fn run_ledger() -> Result<()> { /// 1. Run 2 genesis validator ledger nodes and 1 non-validator node /// 2. Submit a valid token transfer tx /// 3. Check that all the nodes processed the tx with the same result -/// TODO: run this test once https://github.com/tendermint/tendermint/issues/8840 is fixed -#[ignore] #[test] fn test_node_connectivity() -> Result<()> { // Setup 2 genesis validator nodes @@ -85,6 +83,10 @@ fn test_node_connectivity() -> Result<()> { non_validator.exp_string("Anoma ledger node started")?; non_validator.exp_string("This node is a fullnode")?; + let bg_validator_0 = validator_0.background(); + let bg_validator_1 = validator_1.background(); + let bg_non_validator = non_validator.background(); + // 2. Submit a valid token transfer tx let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let tx_args = [ @@ -111,6 +113,10 @@ fn test_node_connectivity() -> Result<()> { client.assert_success(); // 3. Check that all the nodes processed the tx with the same result + let mut validator_0 = bg_validator_0.foreground(); + let mut validator_1 = bg_validator_1.foreground(); + let mut non_validator = bg_non_validator.foreground(); + let expected_result = "all VPs accepted transaction"; validator_0.exp_string(expected_result)?; validator_1.exp_string(expected_result)?; @@ -1500,8 +1506,6 @@ fn generate_proposal_json( /// 3. Setup and start the 2 genesis validator nodes and a non-validator node /// 4. Submit a valid token transfer tx from one validator to the other /// 5. Check that all the nodes processed the tx with the same result -/// TODO: run this test once https://github.com/tendermint/tendermint/issues/8840 is fixed -#[ignore] #[test] fn test_genesis_validators() -> Result<()> { use std::collections::HashMap; @@ -1799,6 +1803,10 @@ fn test_genesis_validators() -> Result<()> { non_validator.exp_string("Anoma ledger node started")?; non_validator.exp_string("This node is a fullnode")?; + let bg_validator_0 = validator_0.background(); + let bg_validator_1 = validator_1.background(); + let bg_non_validator = non_validator.background(); + // 4. Submit a valid token transfer tx let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let tx_args = [ @@ -1826,6 +1834,10 @@ fn test_genesis_validators() -> Result<()> { client.assert_success(); // 3. Check that all the nodes processed the tx with the same result + let mut validator_0 = bg_validator_0.foreground(); + let mut validator_1 = bg_validator_1.foreground(); + let mut non_validator = bg_non_validator.foreground(); + let expected_result = "all VPs accepted transaction"; validator_0.exp_string(expected_result)?; validator_1.exp_string(expected_result)?; From c856157e34edc41aafcbe486add85951fbda53ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 28 Jul 2022 16:14:30 +0200 Subject: [PATCH 086/373] scripts/get_tendermint: update for ABCI++ temp fork release --- scripts/get_tendermint.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/get_tendermint.sh b/scripts/get_tendermint.sh index 8ad0498304..8e74d3f851 100755 --- a/scripts/get_tendermint.sh +++ b/scripts/get_tendermint.sh @@ -4,11 +4,13 @@ set -Eo pipefail # an examplary download-url # https://github.com/tendermint/tendermint/releases/download/v0.34.13/tendermint_0.34.13_linux_amd64.tar.gz -TM_MAJORMINOR="0.34" -TM_PATCH="13" -TM_REPO="https://github.com/tendermint/tendermint" +# https://github.com/heliaxdev/tendermint/releases/download/v0.1.1-abcipp/tendermint_0.1.0-abcipp_darwin_amd64.tar.gz +TM_MAJORMINOR="0.1" +TM_PATCH="1" +TM_SUFFIX="-abcipp" +TM_REPO="https://github.com/heliaxdev/tendermint" -TM_VERSION="${TM_MAJORMINOR}.${TM_PATCH}" +TM_VERSION="${TM_MAJORMINOR}.${TM_PATCH}${TM_SUFFIX}" TARGET_PATH="/usr/local/bin" TMP_PATH="/tmp" From 67eee8c17165ead6476272b97a49149e7816c994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 29 Jul 2022 14:41:28 +0200 Subject: [PATCH 087/373] ledger: refactor tx_queue --- .../lib/node/ledger/shell/finalize_block.rs | 9 +++--- apps/src/lib/node/ledger/shell/mod.rs | 14 +++----- .../lib/node/ledger/shell/process_proposal.rs | 15 ++++++--- shared/src/types/storage.rs | 32 +++---------------- 4 files changed, 24 insertions(+), 46 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 6afba892e1..5db7e8b38d 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -399,7 +399,6 @@ where } response.events.push(tx_event); } - self.reset_tx_queue_iter(); if new_epoch { self.update_epoch(&mut response); @@ -577,7 +576,7 @@ mod test_finalize_block { // verify that the queue of wrapper txs to be processed is correct let mut valid_tx = valid_wrappers.iter(); let mut counter = 0; - while let Some(wrapper) = shell.next_wrapper() { + for wrapper in shell.iter_tx_queue() { // we cannot easily implement the PartialEq trait for WrapperTx // so we check the hashes of the inner txs for equality assert_eq!( @@ -636,7 +635,7 @@ mod test_finalize_block { assert_eq!(code, &String::from(ErrorCodes::InvalidTx)); } // check that the corresponding wrapper tx was removed from the queue - assert!(shell.next_wrapper().is_none()); + assert!(shell.storage.tx_queue.is_empty()); } /// Test that if a tx is undecryptable, it is applied @@ -692,7 +691,7 @@ mod test_finalize_block { assert!(log.contains("Transaction could not be decrypted.")) } // check that the corresponding wrapper tx was removed from the queue - assert!(shell.next_wrapper().is_none()); + assert!(shell.storage.tx_queue.is_empty()); } /// Test that the wrapper txs are queued in the order they @@ -809,7 +808,7 @@ mod test_finalize_block { let mut txs = valid_txs.iter(); let mut counter = 0; - while let Some(wrapper) = shell.next_wrapper() { + for wrapper in shell.iter_tx_queue() { assert_eq!( wrapper.tx_hash, txs.next().expect("Test failed").tx_hash diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 2d6134dbb1..d93ec23c56 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -316,15 +316,10 @@ where } } - /// Iterate lazily over the wrapper txs in order - fn next_wrapper(&mut self) -> Option<&WrapperTx> { - self.storage.tx_queue.lazy_next() - } - - /// If we reject the decrypted txs because they were out of - /// order, reset the iterator. - pub fn reset_tx_queue_iter(&mut self) { - self.storage.tx_queue.rewind() + /// Iterate over the wrapper txs in order + #[allow(dead_code)] + fn iter_tx_queue(&mut self) -> impl Iterator { + self.storage.tx_queue.iter() } /// Load the Merkle root hash and the height of the last committed block, if @@ -793,7 +788,6 @@ mod test_utils { #[cfg(test)] pub fn enqueue_tx(&mut self, wrapper: WrapperTx) { self.shell.storage.tx_queue.push(wrapper); - self.shell.reset_tx_queue_iter(); } } diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index f12b16d9eb..ac09f8863a 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -42,10 +42,13 @@ where } /// Check all the given txs. - pub fn process_txs(&mut self, txs: &[Vec]) -> Vec { + pub fn process_txs(&self, txs: &[Vec]) -> Vec { + let mut tx_queue_iter = self.storage.tx_queue.iter(); txs.iter() .map(|tx_bytes| { - ExecTxResult::from(self.process_single_tx(tx_bytes)) + ExecTxResult::from( + self.process_single_tx(tx_bytes, &mut tx_queue_iter), + ) }) .collect() } @@ -68,7 +71,11 @@ where /// INVARIANT: Any changes applied in this method must be reverted if the /// proposal is rejected (unless we can simply overwrite them in the /// next block). - pub(crate) fn process_single_tx(&mut self, tx_bytes: &[u8]) -> TxResult { + pub(crate) fn process_single_tx<'a>( + &self, + tx_bytes: &[u8], + tx_queue_iter: &mut impl Iterator, + ) -> TxResult { let tx = match Tx::try_from(tx_bytes) { Ok(tx) => tx, Err(_) => { @@ -102,7 +109,7 @@ where is coming soon to a blockchain near you. Patience." .into(), }, - TxType::Decrypted(tx) => match self.next_wrapper() { + TxType::Decrypted(tx) => match tx_queue_iter.next() { Some(wrapper) => { if wrapper.tx_hash != tx.hash_commitment() { TxResult { diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 028d46470c..fc87bc8d51 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -681,51 +681,29 @@ impl Epochs { #[cfg(feature = "ferveo-tpke")] #[derive(Default, Debug, Clone, BorshDeserialize, BorshSerialize)] /// Wrapper txs to be decrypted in the next block proposal -pub struct TxQueue { - /// Index of next wrapper_tx to fetch from storage - next_wrapper: usize, - /// The actual wrappers - queue: std::collections::VecDeque, -} +pub struct TxQueue(std::collections::VecDeque); #[cfg(feature = "ferveo-tpke")] impl TxQueue { /// Add a new wrapper at the back of the queue pub fn push(&mut self, wrapper: WrapperTx) { - self.queue.push_back(wrapper); + self.0.push_back(wrapper); } /// Remove the wrapper at the head of the queue pub fn pop(&mut self) -> Option { - self.queue.pop_front() - } - - /// Iterate lazily over the queue. Finds the next value and advances the - /// lazy iterator. - #[allow(dead_code)] - pub fn lazy_next(&mut self) -> Option<&WrapperTx> { - let next = self.queue.get(self.next_wrapper); - if self.next_wrapper < self.queue.len() { - self.next_wrapper += 1; - } - next - } - - /// Reset the iterator to the head of the queue - pub fn rewind(&mut self) { - self.next_wrapper = 0; + self.0.pop_front() } /// Get an iterator over the queue - #[allow(dead_code)] pub fn iter(&self) -> impl std::iter::Iterator { - self.queue.iter() + self.0.iter() } /// Check if there are any txs in the queue #[allow(dead_code)] pub fn is_empty(&self) -> bool { - self.queue.is_empty() + self.0.is_empty() } } From 43aa815b84a2a29b0ef62e994f7acf26455ed9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 29 Jul 2022 14:42:24 +0200 Subject: [PATCH 088/373] ledger: make prepare_proposal and process_proposal stateless --- .../lib/node/ledger/shell/finalize_block.rs | 3 + .../lib/node/ledger/shell/prepare_proposal.rs | 10 +- .../lib/node/ledger/shell/process_proposal.rs | 2 +- apps/src/lib/node/ledger/shims/abcipp_shim.rs | 105 ++++++------------ 4 files changed, 38 insertions(+), 82 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 5db7e8b38d..1f758a2f2e 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -45,6 +45,9 @@ where &mut self, req: shim::request::FinalizeBlock, ) -> Result { + // reset gas meter before we start + self.gas_meter.reset(); + let mut response = shim::response::FinalizeBlock::default(); // begin the next block and check if a new epoch began let (height, new_epoch) = diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 001d2eb6b3..7c15180d2c 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -20,13 +20,9 @@ where /// the proposal is rejected (unless we can simply overwrite /// them in the next block). pub fn prepare_proposal( - &mut self, + &self, req: RequestPrepareProposal, ) -> response::PrepareProposal { - // We can safely reset meter, because if the block is rejected, - // we'll reset again on the next proposal, until the - // proposal is accepted - self.gas_meter.reset(); let txs = if let ShellMode::Validator { .. } = self.mode { // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); @@ -127,7 +123,7 @@ mod test_prepare_proposal { /// proposed block. #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { - let (mut shell, _) = TestShell::new(); + let (shell, _) = TestShell::new(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), Some("transaction_data".as_bytes().to_owned()), @@ -148,7 +144,7 @@ mod test_prepare_proposal { /// we simply exclude it from the proposal #[test] fn test_error_in_processing_tx() { - let (mut shell, _) = TestShell::new(); + let (shell, _) = TestShell::new(); let keypair = gen_keypair(); let tx = Tx::new( "wasm_code".as_bytes().to_owned(), diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index ac09f8863a..25375244ba 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -25,7 +25,7 @@ where /// included txs violates the order decided upon in the previous /// block. pub fn process_proposal( - &mut self, + &self, req: RequestProcessProposal, ) -> ResponseProcessProposal { let tx_results = self.process_txs(&req.txs); diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index f52459741d..ffe90c1d61 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -22,9 +22,6 @@ use crate::config; #[derive(Debug)] pub struct AbcippShim { service: Shell, - /// This is `Some` only when `ProcessProposal` request is received before - /// `FinalizeBlock`, which is optional for non-validator nodes. - processed_txs: Option>, shell_recv: std::sync::mpsc::Receiver<( Req, tokio::sync::oneshot::Sender>, @@ -55,7 +52,6 @@ impl AbcippShim { vp_wasm_compilation_cache, tx_wasm_compilation_cache, ), - processed_txs: None, shell_recv, }, AbciService { shell_send }, @@ -67,65 +63,30 @@ impl AbcippShim { pub fn run(mut self) { while let Ok((req, resp_sender)) = self.shell_recv.recv() { let resp = match req { - Req::ProcessProposal(proposal) => { - let txs = proposal.txs.clone(); - self.service - .call(Request::ProcessProposal(proposal)) - .map_err(Error::from) - .and_then(|res| match res { - Response::ProcessProposal(resp) => { - self.processed_txs = - Some(Vec::with_capacity(txs.len())); - let processed_txs = - self.processed_txs.as_mut().unwrap(); - for (result, tx) in resp - .tx_results - .iter() - .map(TxResult::from) - .zip(txs.into_iter()) - { - processed_txs - .push(ProcessedTx { tx, result }); - } - Ok(Resp::ProcessProposal(resp)) - } - _ => unreachable!(), - }) - } + Req::ProcessProposal(proposal) => self + .service + .call(Request::ProcessProposal(proposal)) + .map_err(Error::from) + .and_then(|res| match res { + Response::ProcessProposal(resp) => { + Ok(Resp::ProcessProposal(resp)) + } + _ => unreachable!(), + }), Req::FinalizeBlock(block) => { - let (txs, processing_results) = - match self.processed_txs.take() { - Some(processed_txs) => { - // When there are processed_txs from - // `ProcessProposal`, we don't need to add - // processing - // results again - (processed_txs, None) - } - None => { - // When there are no processed txs, it means - // that `ProcessProposal` request has not been - // received and so we need to process - // transactions first in the same way as - // `ProcessProposal`. - let unprocessed_txs = block.txs.clone(); - let processing_results = - self.service.process_txs(&block.txs); - let mut txs = - Vec::with_capacity(unprocessed_txs.len()); - for (result, tx) in processing_results - .iter() - .map(TxResult::from) - .zip(unprocessed_txs.into_iter()) - { - txs.push(ProcessedTx { tx, result }); - } - // Then we also have to add the processing - // result events to the `FinalizeBlock` - // tx_results - (txs, Some(processing_results)) - } - }; + // Process transactions first in the same way as + // `ProcessProposal`. + let unprocessed_txs = block.txs.clone(); + let processing_results = + self.service.process_txs(&block.txs); + let mut txs = Vec::with_capacity(unprocessed_txs.len()); + for (result, tx) in processing_results + .iter() + .map(TxResult::from) + .zip(unprocessed_txs.into_iter()) + { + txs.push(ProcessedTx { tx, result }); + } let mut finalize_req: FinalizeBlock = block.into(); finalize_req.txs = txs; @@ -138,19 +99,15 @@ impl AbcippShim { let mut resp: ResponseFinalizeBlock = resp.into(); - // Add processing results, if any - if let Some(processing_results) = - processing_results + // Add processing results + for (tx_result, processing_result) in resp + .tx_results + .iter_mut() + .zip(processing_results) { - for (tx_result, processing_result) in resp - .tx_results - .iter_mut() - .zip(processing_results) - { - tx_result - .events - .extend(processing_result.events); - } + tx_result + .events + .extend(processing_result.events); } Ok(Resp::FinalizeBlock(resp)) From 84ab5441ae8f89f1abffb69f1d0bf7281b5b6251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 29 Jul 2022 14:42:49 +0200 Subject: [PATCH 089/373] ledger: debug log some ABCI++ requests --- apps/src/lib/node/ledger/mod.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/mod.rs b/apps/src/lib/node/ledger/mod.rs index e84674b059..f71e73ea74 100644 --- a/apps/src/lib/node/ledger/mod.rs +++ b/apps/src/lib/node/ledger/mod.rs @@ -86,17 +86,20 @@ impl Shell { fn call(&mut self, req: Request) -> Result { match req { Request::InitChain(init) => { + tracing::debug!("Request InitChain"); self.init_chain(init).map(Response::InitChain) } Request::Info(_) => Ok(Response::Info(self.last_state())), Request::Query(query) => Ok(Response::Query(self.query(query))), Request::PrepareProposal(block) => { + tracing::debug!("Request PrepareProposal"); Ok(Response::PrepareProposal(self.prepare_proposal(block))) } Request::VerifyHeader(_req) => { Ok(Response::VerifyHeader(self.verify_header(_req))) } Request::ProcessProposal(block) => { + tracing::debug!("Request ProcessProposal"); Ok(Response::ProcessProposal(self.process_proposal(block))) } Request::RevertProposal(_req) => { @@ -105,14 +108,21 @@ impl Shell { Request::ExtendVote(_req) => { Ok(Response::ExtendVote(self.extend_vote(_req))) } - Request::VerifyVoteExtension(_req) => Ok( - Response::VerifyVoteExtension(self.verify_vote_extension(_req)), - ), + Request::VerifyVoteExtension(_req) => { + tracing::debug!("Request VerifyVoteExtension"); + Ok(Response::VerifyVoteExtension( + self.verify_vote_extension(_req), + )) + } Request::FinalizeBlock(finalize) => { + tracing::debug!("Request FinalizeBlock"); self.load_proposals(); self.finalize_block(finalize).map(Response::FinalizeBlock) } - Request::Commit(_) => Ok(Response::Commit(self.commit())), + Request::Commit(_) => { + tracing::debug!("Request Commit"); + Ok(Response::Commit(self.commit())) + } Request::Flush(_) => Ok(Response::Flush(Default::default())), Request::Echo(msg) => Ok(Response::Echo(response::Echo { message: msg.message, From 855d0db1fc1e36e97b35135b05c0beba74e8c79b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 29 Jul 2022 13:12:40 +0000 Subject: [PATCH 090/373] [ci] wasm checksums update --- wasm/checksums.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 1495d6f6f5..5d670022b7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.302172cc7d0a7e2278ce58299809875f354925364b25c9b64e92461995c51950.wasm", - "tx_from_intent.wasm": "tx_from_intent.19099ad11a5f59272bd5dbd8b7bc7093ae66ae7a2b25034300f1b34d3e37ffd1.wasm", - "tx_ibc.wasm": "tx_ibc.9aec1969a37705f9ae5d6e95d13126e0f37e78171ef37c2f0fdd0eb16ac49270.wasm", - "tx_init_account.wasm": "tx_init_account.7a45233a98978c67ff005bf8a1fb8f3f7689a75f98684c1735617776f98fabad.wasm", - "tx_init_nft.wasm": "tx_init_nft.b0dd29e0e982c3bd04c7a8c4dcd0184d9d827df6a4211794dd74fbdced1e7430.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.45be75dc2c22048dce23ae346c895b2be19737a39844416436aac62914847388.wasm", - "tx_init_validator.wasm": "tx_init_validator.5a7c9a3a115883359246a4145af11f748ded043b5b36d1cb71e54fb3ef169183.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.fd8932515db71638263193930f60c14cec54c11e72e6ab40d8201d0247db0c1a.wasm", - "tx_transfer.wasm": "tx_transfer.9e51e5b48ba3ddee699fed334e14fe9a81b7e299b0cfcbf10482b9f784c092c2.wasm", - "tx_unbond.wasm": "tx_unbond.020d22fa306850b0a4aade96f711087d03568ed45276fff60226a80de47cc455.wasm", + "tx_bond.wasm": "tx_bond.71acaf1ff3a4ac1ce6550c4ce594cf70d684213769f45641be5cdb4b7321184e.wasm", + "tx_from_intent.wasm": "tx_from_intent.e18907b1fb0e1c4e4946228c0ec57838a783a5d88c6b3129d44935386693cee8.wasm", + "tx_ibc.wasm": "tx_ibc.38ee97db63b765f1639ee24dd3e7f264139f146a56f7c06edafde63eaecedade.wasm", + "tx_init_account.wasm": "tx_init_account.6be05f8ca240d9e978dfe6a1566b3860e07fa3aee334003fc5d85a5a1ae99cf7.wasm", + "tx_init_nft.wasm": "tx_init_nft.5f710b641c38cbe7822c2359bf7a764e864e952f4660ccff9b58b97fbaa4b3a2.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.25666fc8fbf251bd4ad478cb968f1d50a8100a0a7aae0e3ee5e50e28304e8e3e.wasm", + "tx_init_validator.wasm": "tx_init_validator.2b1a4c947242ddddf94a491b8c571185f649913606bfa7d37f668ddc86fe1049.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.9af8eea9219db09161b98405c18ea3c82db03b982f69a66ce1d117e64a715c3c.wasm", + "tx_transfer.wasm": "tx_transfer.87e4fba9d2476388e0f2b1d4bc00c396ac7b8b7b38d5079735fe53bc5984b583.wasm", + "tx_unbond.wasm": "tx_unbond.61f2ac46e7680ec35aba367ec908ac63aa43d6765cd07da932aa9ea45d77be46.wasm", "tx_update_vp.wasm": "tx_update_vp.00d828bdf510d92d971ca59015945c8c00f285a802a655451cc5ee87c5ee99bc.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.b8aa22d6d22c31fa6c1f4619a81eaa43aa40c8865b91f71449a5f3c65b84eacf.wasm", - "tx_withdraw.wasm": "tx_withdraw.b8538e5acfc2945e98b76cc17eb11a03c545021e8f70cf6e5b436219e749b559.wasm", - "vp_nft.wasm": "vp_nft.d272e0c50f17c020fe806e03278e83377ec45b81e88432316ce836ee24605f6e.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.79c1da702d67f464453af0b145872bba28913b029508f1257b4a22f69123ec1e.wasm", - "vp_token.wasm": "vp_token.04482a8132e91ab726b4a9027f44a84c885c36e3d608e9c4e153d0cfe4f88449.wasm", - "vp_user.wasm": "vp_user.e390d55fc2e797fcc4c43bd40b93ea1c3d58544d5f086301a0dbc38bd93388ba.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.92ed628aca7856e2b0fe461c0a0a65c6f7c1f585ac542c10feceef0e4b9ce611.wasm", + "tx_withdraw.wasm": "tx_withdraw.fb54d108a6fe7c6db16e573110c4a9f52158ec993e04e5a06e1425b8dd676c54.wasm", + "vp_nft.wasm": "vp_nft.3421840146f8d2e6c3ce3a0d94bbb0dad6ef296c50e9d00744179d290f32745b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.1b37ab6939280fbb54512918053362fae34e3db74f029ef31c9f08e767539afb.wasm", + "vp_token.wasm": "vp_token.08dcec2d3ecb0942d0a244bbd176f9a42424ec09544120db1b30c0d97ed7dbb1.wasm", + "vp_user.wasm": "vp_user.404f089f0c73fcf906f7d6059d74c6bab3e2e3d3a108394503bbc22588ff95b9.wasm" } \ No newline at end of file From 818a36a6c10d858b3fcde09d76e6e42243fa0770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 1 Aug 2022 17:28:01 +0200 Subject: [PATCH 091/373] update the changelog config to namada repo --- .changelog/config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changelog/config.toml b/.changelog/config.toml index fdc4671ed2..551bae0e61 100644 --- a/.changelog/config.toml +++ b/.changelog/config.toml @@ -1,4 +1,4 @@ -project_url = 'https://github.com/anoma/anoma' +project_url = 'https://github.com/anoma/namada' # Settings related to components/sub-modules. Only relevant if you make use of # components/sub-modules. From 5bf1210971c5285b75016774122ea9d2c773c422 Mon Sep 17 00:00:00 2001 From: Unkowit Date: Wed, 8 Jun 2022 11:39:01 +0100 Subject: [PATCH 092/373] Changed hash-map to BiHashMap in the wallet in order to be able to retrieve alias from address --- Cargo.lock | 250 +++++++++++++++++++++++++++++++++++ apps/Cargo.toml | 1 + apps/src/lib/wallet/store.rs | 9 +- 3 files changed, 256 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9c1414272..6db34248ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,247 @@ dependencies = [ "memchr", ] +[[package]] +name = "anoma" +version = "0.6.1" +dependencies = [ + "anoma_proof_of_stake", + "ark-bls12-381", + "ark-ec", + "ark-serialize", + "assert_matches", + "bech32", + "borsh", + "byte-unit", + "chrono", + "clru", + "derivative", + "ed25519-consensus", + "ferveo", + "ferveo-common", + "group-threshold-cryptography", + "hex", + "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5)", + "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5)", + "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", + "ics23", + "itertools 0.10.3", + "loupe", + "parity-wasm", + "pretty_assertions", + "proptest", + "prost 0.9.0", + "prost-types 0.9.0", + "pwasm-utils", + "rand 0.8.5", + "rand_core 0.6.3", + "rust_decimal", + "serde 1.0.137", + "serde_json", + "sha2 0.9.9", + "sparse-merkle-tree", + "tempfile", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "test-log", + "thiserror", + "tonic-build", + "tracing 0.1.35", + "tracing-subscriber 0.3.11", + "wasmer", + "wasmer-cache", + "wasmer-compiler-singlepass", + "wasmer-engine-dylib", + "wasmer-engine-universal", + "wasmer-vm", + "wasmparser 0.83.0", +] + +[[package]] +name = "anoma_apps" +version = "0.6.1" +dependencies = [ + "anoma", + "ark-serialize", + "ark-std", + "async-std", + "async-trait", + "base64 0.13.0", + "bech32", + "bimap", + "bit-set", + "blake2b-rs", + "borsh", + "byte-unit", + "byteorder", + "cargo-watch", + "clap 3.0.0-beta.2", + "color-eyre", + "config", + "curl", + "derivative", + "directories", + "ed25519-consensus", + "eyre", + "ferveo", + "ferveo-common", + "file-lock", + "flate2", + "futures 0.3.21", + "git2", + "hex", + "itertools 0.10.3", + "jsonpath_lib", + "libc", + "libloading", + "libp2p", + "message-io", + "num-derive", + "num-traits 0.2.15", + "num_cpus", + "once_cell", + "orion", + "pathdiff", + "proptest", + "prost 0.9.0", + "prost-types 0.9.0", + "rand 0.8.5", + "rand_core 0.6.3", + "rayon", + "regex", + "reqwest", + "rlimit", + "rocksdb", + "rpassword", + "serde 1.0.137", + "serde_bytes", + "serde_json", + "serde_regex", + "sha2 0.9.9", + "signal-hook", + "sparse-merkle-tree", + "sysinfo", + "tar", + "tempfile", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", + "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", + "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", + "test-log", + "thiserror", + "tokio", + "tokio-test", + "toml", + "tonic", + "tonic-build", + "tower", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing)", + "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", + "tracing 0.1.35", + "tracing-log", + "tracing-subscriber 0.3.11", + "websocket", + "winapi 0.3.9", +] + +[[package]] +name = "anoma_encoding_spec" +version = "0.6.1" +dependencies = [ + "anoma", + "borsh", + "itertools 0.10.3", + "lazy_static 1.4.0", + "madato", +] + +[[package]] +name = "anoma_macros" +version = "0.6.1" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "anoma_proof_of_stake" +version = "0.6.1" +dependencies = [ + "borsh", + "proptest", + "thiserror", +] + +[[package]] +name = "anoma_tests" +version = "0.6.1" +dependencies = [ + "anoma", + "anoma_apps", + "anoma_vm_env", + "assert_cmd", + "borsh", + "chrono", + "color-eyre", + "concat-idents", + "derivative", + "escargot", + "expectrl", + "eyre", + "file-serve", + "fs_extra", + "hex", + "itertools 0.10.3", + "libp2p", + "pretty_assertions", + "proptest", + "prost 0.9.0", + "serde_json", + "sha2 0.9.9", + "tempfile", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", + "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", + "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", + "test-log", + "toml", + "tracing 0.1.35", + "tracing-subscriber 0.3.11", +] + +[[package]] +name = "anoma_tx_prelude" +version = "0.6.1" +dependencies = [ + "anoma_vm_env", + "sha2 0.10.2", +] + +[[package]] +name = "anoma_vm_env" +version = "0.6.1" +dependencies = [ + "anoma", + "anoma_macros", + "borsh", + "hex", +] + +[[package]] +name = "anoma_vp_prelude" +version = "0.6.1" +dependencies = [ + "anoma_vm_env", + "sha2 0.10.2", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -556,6 +797,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bimap" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" +dependencies = [ + "serde 1.0.137", +] + [[package]] name = "bincode" version = "1.3.3" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 7a4dffac76..71de773446 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -122,6 +122,7 @@ tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter"]} websocket = "0.26.2" winapi = "0.3.9" +bimap = {version = "0.6.2", features = ["serde"]} [dev-dependencies] namada = {path = "../shared", features = ["testing", "wasm-runtime"]} diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 95454d3d2a..2a1b52e238 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -8,6 +8,7 @@ use std::str::FromStr; use ark_std::rand::prelude::*; use ark_std::rand::SeedableRng; +use bimap::BiHashMap; use file_lock::{FileLock, FileOptions}; use namada::types::address::{Address, ImplicitAddress}; use namada::types::key::dkg_session_keys::DkgKeypair; @@ -53,7 +54,7 @@ pub struct Store { /// Cryptographic keypairs keys: HashMap, /// Anoma address book - addresses: HashMap, + addresses: BiHashMap, /// Known mappings of public key hashes to their aliases in the `keys` /// field. Used for look-up by a public key. pkhs: HashMap, @@ -224,7 +225,7 @@ impl Store { /// Find the stored address by an alias. pub fn find_address(&self, alias: impl AsRef) -> Option<&Address> { - self.addresses.get(&alias.into()) + self.addresses.get_by_left(&alias.into()) } /// Get all known keys by their alias, paired with PKH, if known. @@ -248,7 +249,7 @@ impl Store { } /// Get all known addresses by their alias, paired with PKH, if known. - pub fn get_addresses(&self) -> &HashMap { + pub fn get_addresses(&self) -> &BiHashMap { &self.addresses } @@ -362,7 +363,7 @@ impl Store { alias = address.encode() ); } - if self.addresses.contains_key(&alias) { + if self.addresses.contains_left(&alias) { match show_overwrite_confirmation(&alias, "an address") { ConfirmationResponse::Replace => {} ConfirmationResponse::Reselect(new_alias) => { From f8322d38dd7d70135f15466a7b9bbc13ee13749e Mon Sep 17 00:00:00 2001 From: James Hiew Date: Wed, 8 Jun 2022 12:52:54 +0200 Subject: [PATCH 093/373] Make anomac balance show alias if possible instead of address --- apps/src/lib/client/rpc.rs | 4 ++++ apps/src/lib/wallet/mod.rs | 5 +++++ apps/src/lib/wallet/store.rs | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..44dcd81b78 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -171,6 +171,10 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { for (key, balance) in balances { let owner = token::is_any_token_balance_key(&key).unwrap(); + let owner = match ctx.wallet.find_alias(owner) { + Some(alias) => format!("{}", alias), + None => format!("{}", owner), + }; writeln!(w, " {}, owned by {}", balance, owner) .unwrap(); } diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 5fec7dca98..a79450139e 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -285,6 +285,11 @@ impl Wallet { self.store.find_address(alias) } + /// Find an alias by the address if it's in the wallet. + pub fn find_alias(&self, address: &Address) -> Option<&Alias> { + self.store.find_alias(address) + } + /// Get all known addresses by their alias, paired with PKH, if known. pub fn get_addresses(&self) -> HashMap { self.store diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 2a1b52e238..70536e0a29 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -228,6 +228,11 @@ impl Store { self.addresses.get_by_left(&alias.into()) } + /// Find an alias by the address if it's in the wallet. + pub fn find_alias(&self, address: &Address) -> Option<&Alias> { + self.addresses.get_by_right(address) + } + /// Get all known keys by their alias, paired with PKH, if known. pub fn get_keys( &self, From ebabe9a9f4d9f8c7d7b372c7bbb58a03ffa6859b Mon Sep 17 00:00:00 2001 From: Unkowit Date: Thu, 30 Jun 2022 10:06:59 +0100 Subject: [PATCH 094/373] added changelog --- .../unreleased/improvements/1138-change-wallet-bihashmap.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .changelog/unreleased/improvements/1138-change-wallet-bihashmap.md diff --git a/.changelog/unreleased/improvements/1138-change-wallet-bihashmap.md b/.changelog/unreleased/improvements/1138-change-wallet-bihashmap.md new file mode 100644 index 0000000000..d13b82e697 --- /dev/null +++ b/.changelog/unreleased/improvements/1138-change-wallet-bihashmap.md @@ -0,0 +1,4 @@ +- Allows simple retrival of aliases from addresses in the wallet without + the need for multiple hashmaps. This is the first step to improving the + UI if one wants to show aliases when fetching addresses from anoma wallet + ([#1138](https://github.com/anoma/anoma/pull/1138)) \ No newline at end of file From d93259f53704a4d029cf58c6ba9c97dfdb71e05f Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 12 Jun 2022 21:44:40 +0200 Subject: [PATCH 095/373] [cli]: fix output of 'anoma wallet addres find' - #993 --- apps/src/lib/cli.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5eef95b34d..ba503b9a95 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2862,7 +2862,7 @@ pub mod args { fn def(app: App) -> App { app.arg( - ALIAS_OPT + ALIAS .def() .about("An alias associated with the address."), ) From 1898aa8c07cd6ae753e0273547cad960aad775e5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 20 Jun 2022 10:54:42 +0200 Subject: [PATCH 096/373] require just one of either alias or address as CL args, functionality for address to come --- apps/src/lib/cli.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index ba503b9a95..68b1c65050 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -9,7 +9,7 @@ pub mod context; mod utils; -use clap::{crate_authors, AppSettings, ArgMatches}; +use clap::{crate_authors, AppSettings, ArgMatches, ArgGroup}; pub use utils::safe_exit; use utils::*; @@ -1377,7 +1377,7 @@ pub mod args { use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; - use super::ArgMatches; + use super::{ArgMatches,ArgGroup}; use crate::config; use crate::config::TendermintMode; @@ -2866,6 +2866,14 @@ pub mod args { .def() .about("An alias associated with the address."), ) + .arg(ADDRESS + .def() + .about("The actual address ") + ) + .group(ArgGroup::new("find") + .args(&["alias", "address"]) + .required(true) + ) } } From 55de205b2e0b345313f0d9fc86d33e860b372d19 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 22 Jun 2022 13:06:58 +0200 Subject: [PATCH 097/373] upgrading the parsing, includes printouts for current debugging (to be removed later) --- apps/src/lib/cli.rs | 13 +++++++++---- apps/src/lib/cli/utils.rs | 9 +++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 68b1c65050..8149817d64 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -2852,12 +2852,14 @@ pub mod args { #[derive(Clone, Debug)] pub struct AddressFind { pub alias: String, + pub address: Address, } impl Args for AddressFind { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS.parse(matches); - Self { alias } + let address = RAW_ADDRESS.parse(matches); + Self { alias, address } } fn def(app: App) -> App { @@ -2866,11 +2868,11 @@ pub mod args { .def() .about("An alias associated with the address."), ) - .arg(ADDRESS + .arg(RAW_ADDRESS .def() - .about("The actual address ") + .about("The bech32m encoded address string.") ) - .group(ArgGroup::new("find") + .group(ArgGroup::new("find_flags") .args(&["alias", "address"]) .required(true) ) @@ -3097,6 +3099,7 @@ pub fn anoma_client_cli() -> AnomaClient { pub fn anoma_wallet_cli() -> (cmds::AnomaWallet, Context) { let app = anoma_wallet_app(); + println!("created app from anoma_wallet_app()"); cmds::AnomaWallet::parse_or_print_help(app) } @@ -3128,10 +3131,12 @@ fn anoma_client_app() -> App { } fn anoma_wallet_app() -> App { + println!("Inside anoma wallet"); let app = App::new(APP_NAME) .version(anoma_version()) .author(crate_authors!("\n")) .about("Anoma wallet command line interface.") .setting(AppSettings::SubcommandRequiredElseHelp); + println!("Set up wallet app, now adding subcommands"); cmds::AnomaWallet::add_sub(args::Global::def(app)) } diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index f47ec42696..22b95b7c30 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -17,8 +17,16 @@ pub trait Cmd: Sized { fn parse(matches: &ArgMatches) -> Option; fn parse_or_print_help(app: App) -> (Self, Context) { + println!("Inside parse_or_print_help(app)"); let mut app = Self::add_sub(app); + println!("Added subs again"); + + println!("{:?}",app); + + dbg!(app.clone().get_matches()); + let matches = app.clone().get_matches(); + println!("Parse or print help"); match Self::parse(&matches) { Some(cmd) => { let global_args = args::Global::parse(&matches); @@ -170,6 +178,7 @@ where ::Err: Debug, { pub fn parse(&self, matches: &ArgMatches) -> T { + println!("I'm parsing!"); parse_opt(matches, self.name).unwrap() } } From 103cc8ebeea65435f491cc51acc8dfaf78e1e527 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 24 Jun 2022 16:56:06 +0200 Subject: [PATCH 098/373] remove printouts used for debugging clap issues --- apps/src/lib/cli/utils.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index 22b95b7c30..e873db19ed 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -17,16 +17,8 @@ pub trait Cmd: Sized { fn parse(matches: &ArgMatches) -> Option; fn parse_or_print_help(app: App) -> (Self, Context) { - println!("Inside parse_or_print_help(app)"); let mut app = Self::add_sub(app); - println!("Added subs again"); - - println!("{:?}",app); - - dbg!(app.clone().get_matches()); - let matches = app.clone().get_matches(); - println!("Parse or print help"); match Self::parse(&matches) { Some(cmd) => { let global_args = args::Global::parse(&matches); From 83767c2cab4035d55cf2ad719838aed8d8542cc4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 24 Jun 2022 17:00:27 +0200 Subject: [PATCH 099/373] change AddressFind -> AddressOrAliasFind, wrap fields of this struct in Option types, combine find address/alias find fns into one (solves a clap::ArgGroup issue) --- apps/src/bin/anoma-wallet/cli.rs | 38 ++++++++++++++++++++++---------- apps/src/lib/cli.rs | 36 ++++++++++++++---------------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 9807516a93..2ee8ba27ec 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -28,8 +28,8 @@ pub fn main() -> Result<()> { cmds::WalletAddress::Gen(cmds::AddressGen(args)) => { key_and_address_gen(ctx, args) } - cmds::WalletAddress::Find(cmds::AddressFind(args)) => { - address_find(ctx, args) + cmds::WalletAddress::Find(cmds::AddressOrAliasFind(args)) => { + address_or_alias_find(ctx, args) } cmds::WalletAddress::List(cmds::AddressList) => address_list(ctx), cmds::WalletAddress::Add(cmds::AddressAdd(args)) => { @@ -190,17 +190,31 @@ fn address_list(ctx: Context) { } } -/// Find address by its alias. -fn address_find(ctx: Context, args: args::AddressFind) { +/// Find address (alias) by its alias (address). +fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { let wallet = ctx.wallet; - if let Some(address) = wallet.find_address(&args.alias) { - println!("Found address {}", address.to_pretty_string()); - } else { - println!( - "No address with alias {} found. Use the command `address list` \ - to see all the known addresses.", - args.alias.to_lowercase() - ); + if args.address.is_some() && args.alias.is_some() { + println!("This should not be happening, as clap should emit its own error message."); + } else if args.alias.is_some() { + if let Some(address) = wallet.find_address(&args.alias.as_ref().unwrap()) { + println!("Found address {}", address.to_pretty_string()); + } else { + println!( + "No address with alias {} found. Use the command `address list` \ + to see all the known addresses.", + args.alias.unwrap().to_lowercase() + ); + } + } else if args.address.is_some() { + if let Some(alias) = wallet.find_alias(&args.address.as_ref().unwrap()) { + println!("Found alias {}", alias.to_string()); + } else { + println!( + "No alias with address {} found. Use the command `address list` \ + to see all the known addresses.", + args.address.unwrap() + ); + } } } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 8149817d64..8703abedd4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -480,7 +480,7 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum WalletAddress { Gen(AddressGen), - Find(AddressFind), + Find(AddressOrAliasFind), List(AddressList), Add(AddressAdd), } @@ -506,7 +506,7 @@ pub mod cmds { ) .setting(AppSettings::SubcommandRequiredElseHelp) .subcommand(AddressGen::def()) - .subcommand(AddressFind::def()) + .subcommand(AddressOrAliasFind::def()) .subcommand(AddressList::def()) .subcommand(AddressAdd::def()) } @@ -538,21 +538,21 @@ pub mod cmds { /// Find an address by its alias #[derive(Clone, Debug)] - pub struct AddressFind(pub args::AddressFind); + pub struct AddressOrAliasFind(pub args::AddressOrAliasFind); - impl SubCmd for AddressFind { + impl SubCmd for AddressOrAliasFind { const CMD: &'static str = "find"; fn parse(matches: &ArgMatches) -> Option { matches .subcommand_matches(Self::CMD) - .map(|matches| AddressFind(args::AddressFind::parse(matches))) + .map(|matches| AddressOrAliasFind(args::AddressOrAliasFind::parse(matches))) } fn def() -> App { App::new(Self::CMD) .about("Find an address by its alias.") - .add_args::() + .add_args::() } } @@ -1453,6 +1453,7 @@ pub mod args { const PROPOSAL_ID_OPT: ArgOpt = arg_opt("proposal-id"); const PROPOSAL_VOTE: Arg = arg("vote"); const RAW_ADDRESS: Arg
= arg("address"); + const RAW_ADDRESS_OPT: ArgOpt
= RAW_ADDRESS.opt(); const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); @@ -2850,30 +2851,30 @@ pub mod args { /// Wallet address lookup arguments #[derive(Clone, Debug)] - pub struct AddressFind { - pub alias: String, - pub address: Address, + pub struct AddressOrAliasFind { + pub alias: Option, + pub address: Option
, } - impl Args for AddressFind { + impl Args for AddressOrAliasFind { fn parse(matches: &ArgMatches) -> Self { - let alias = ALIAS.parse(matches); - let address = RAW_ADDRESS.parse(matches); - Self { alias, address } + let alias = ALIAS_OPT.parse(matches); + let address = RAW_ADDRESS_OPT.parse(matches); + Self { alias , address } } fn def(app: App) -> App { app.arg( - ALIAS + ALIAS_OPT .def() .about("An alias associated with the address."), ) - .arg(RAW_ADDRESS + .arg(RAW_ADDRESS_OPT .def() .about("The bech32m encoded address string.") ) .group(ArgGroup::new("find_flags") - .args(&["alias", "address"]) + .args(&[ALIAS_OPT.name, RAW_ADDRESS_OPT.name]) .required(true) ) } @@ -3099,7 +3100,6 @@ pub fn anoma_client_cli() -> AnomaClient { pub fn anoma_wallet_cli() -> (cmds::AnomaWallet, Context) { let app = anoma_wallet_app(); - println!("created app from anoma_wallet_app()"); cmds::AnomaWallet::parse_or_print_help(app) } @@ -3131,12 +3131,10 @@ fn anoma_client_app() -> App { } fn anoma_wallet_app() -> App { - println!("Inside anoma wallet"); let app = App::new(APP_NAME) .version(anoma_version()) .author(crate_authors!("\n")) .about("Anoma wallet command line interface.") .setting(AppSettings::SubcommandRequiredElseHelp); - println!("Set up wallet app, now adding subcommands"); cmds::AnomaWallet::add_sub(args::Global::def(app)) } From c620e0dc8615f5207afe360c72bb62c992021207 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 30 Jun 2022 15:24:41 +0200 Subject: [PATCH 100/373] changes from make fmt and make clippy --- apps/src/bin/anoma-wallet/cli.rs | 21 +++++++++++++-------- apps/src/lib/cli.rs | 26 ++++++++++++++------------ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 2ee8ba27ec..c080f23cd8 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -194,24 +194,29 @@ fn address_list(ctx: Context) { fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { let wallet = ctx.wallet; if args.address.is_some() && args.alias.is_some() { - println!("This should not be happening, as clap should emit its own error message."); + println!( + "This should not be happening, as clap should emit its own error \ + message." + ); } else if args.alias.is_some() { - if let Some(address) = wallet.find_address(&args.alias.as_ref().unwrap()) { + if let Some(address) = + wallet.find_address(&args.alias.as_ref().unwrap()) + { println!("Found address {}", address.to_pretty_string()); } else { println!( - "No address with alias {} found. Use the command `address list` \ - to see all the known addresses.", + "No address with alias {} found. Use the command `address \ + list` to see all the known addresses.", args.alias.unwrap().to_lowercase() ); } } else if args.address.is_some() { - if let Some(alias) = wallet.find_alias(&args.address.as_ref().unwrap()) { - println!("Found alias {}", alias.to_string()); + if let Some(alias) = wallet.find_alias(args.address.as_ref().unwrap()) { + println!("Found alias {}", alias); } else { println!( - "No alias with address {} found. Use the command `address list` \ - to see all the known addresses.", + "No alias with address {} found. Use the command `address \ + list` to see all the known addresses.", args.address.unwrap() ); } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 8703abedd4..07d7083440 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -9,7 +9,7 @@ pub mod context; mod utils; -use clap::{crate_authors, AppSettings, ArgMatches, ArgGroup}; +use clap::{crate_authors, AppSettings, ArgGroup, ArgMatches}; pub use utils::safe_exit; use utils::*; @@ -544,9 +544,9 @@ pub mod cmds { const CMD: &'static str = "find"; fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| AddressOrAliasFind(args::AddressOrAliasFind::parse(matches))) + matches.subcommand_matches(Self::CMD).map(|matches| { + AddressOrAliasFind(args::AddressOrAliasFind::parse(matches)) + }) } fn def() -> App { @@ -1377,7 +1377,7 @@ pub mod args { use super::context::{WalletAddress, WalletKeypair, WalletPublicKey}; use super::utils::*; - use super::{ArgMatches,ArgGroup}; + use super::{ArgGroup, ArgMatches}; use crate::config; use crate::config::TendermintMode; @@ -2860,7 +2860,7 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let alias = ALIAS_OPT.parse(matches); let address = RAW_ADDRESS_OPT.parse(matches); - Self { alias , address } + Self { alias, address } } fn def(app: App) -> App { @@ -2869,13 +2869,15 @@ pub mod args { .def() .about("An alias associated with the address."), ) - .arg(RAW_ADDRESS_OPT - .def() - .about("The bech32m encoded address string.") + .arg( + RAW_ADDRESS_OPT + .def() + .about("The bech32m encoded address string."), ) - .group(ArgGroup::new("find_flags") - .args(&[ALIAS_OPT.name, RAW_ADDRESS_OPT.name]) - .required(true) + .group( + ArgGroup::new("find_flags") + .args(&[ALIAS_OPT.name, RAW_ADDRESS_OPT.name]) + .required(true), ) } } From 339c1a2b2f1cc0ffe932c81baac697bd45cff0f7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 1 Jul 2022 02:00:03 +0200 Subject: [PATCH 101/373] remove overlooked print-out --- apps/src/lib/cli/utils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index e873db19ed..f47ec42696 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -170,7 +170,6 @@ where ::Err: Debug, { pub fn parse(&self, matches: &ArgMatches) -> T { - println!("I'm parsing!"); parse_opt(matches, self.name).unwrap() } } From 02e80d0da0825ca1e5f91e70b9817073680c3d4f Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 1 Jul 2022 13:17:27 +0200 Subject: [PATCH 102/373] changelog --- .changelog/unreleased/improvements/1161-anomaw-address-find.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1161-anomaw-address-find.md diff --git a/.changelog/unreleased/improvements/1161-anomaw-address-find.md b/.changelog/unreleased/improvements/1161-anomaw-address-find.md new file mode 100644 index 0000000000..4a2903f6f9 --- /dev/null +++ b/.changelog/unreleased/improvements/1161-anomaw-address-find.md @@ -0,0 +1,2 @@ +- Improved CLI experience for 'anomaw address find' + ([#1161](https://github.com/anoma/anoma/pull/1161)) \ No newline at end of file From 529c08d98e69795cbbea74d14679651da9d378bd Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 4 Aug 2022 19:27:09 -0700 Subject: [PATCH 103/373] new Cargo.lock after rebase --- Cargo.lock | 242 +---------------------------------------------------- 1 file changed, 1 insertion(+), 241 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6db34248ca..dd5934d171 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,247 +72,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "anoma" -version = "0.6.1" -dependencies = [ - "anoma_proof_of_stake", - "ark-bls12-381", - "ark-ec", - "ark-serialize", - "assert_matches", - "bech32", - "borsh", - "byte-unit", - "chrono", - "clru", - "derivative", - "ed25519-consensus", - "ferveo", - "ferveo-common", - "group-threshold-cryptography", - "hex", - "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5)", - "ibc 0.12.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?branch=yuji/v0.12.0_tm_v0.23.5)", - "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", - "ics23", - "itertools 0.10.3", - "loupe", - "parity-wasm", - "pretty_assertions", - "proptest", - "prost 0.9.0", - "prost-types 0.9.0", - "pwasm-utils", - "rand 0.8.5", - "rand_core 0.6.3", - "rust_decimal", - "serde 1.0.137", - "serde_json", - "sha2 0.9.9", - "sparse-merkle-tree", - "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "test-log", - "thiserror", - "tonic-build", - "tracing 0.1.35", - "tracing-subscriber 0.3.11", - "wasmer", - "wasmer-cache", - "wasmer-compiler-singlepass", - "wasmer-engine-dylib", - "wasmer-engine-universal", - "wasmer-vm", - "wasmparser 0.83.0", -] - -[[package]] -name = "anoma_apps" -version = "0.6.1" -dependencies = [ - "anoma", - "ark-serialize", - "ark-std", - "async-std", - "async-trait", - "base64 0.13.0", - "bech32", - "bimap", - "bit-set", - "blake2b-rs", - "borsh", - "byte-unit", - "byteorder", - "cargo-watch", - "clap 3.0.0-beta.2", - "color-eyre", - "config", - "curl", - "derivative", - "directories", - "ed25519-consensus", - "eyre", - "ferveo", - "ferveo-common", - "file-lock", - "flate2", - "futures 0.3.21", - "git2", - "hex", - "itertools 0.10.3", - "jsonpath_lib", - "libc", - "libloading", - "libp2p", - "message-io", - "num-derive", - "num-traits 0.2.15", - "num_cpus", - "once_cell", - "orion", - "pathdiff", - "proptest", - "prost 0.9.0", - "prost-types 0.9.0", - "rand 0.8.5", - "rand_core 0.6.3", - "rayon", - "regex", - "reqwest", - "rlimit", - "rocksdb", - "rpassword", - "serde 1.0.137", - "serde_bytes", - "serde_json", - "serde_regex", - "sha2 0.9.9", - "signal-hook", - "sparse-merkle-tree", - "sysinfo", - "tar", - "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-config 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-rpc 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9)", - "test-log", - "thiserror", - "tokio", - "tokio-test", - "toml", - "tonic", - "tonic-build", - "tower", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?branch=yuji/rebase_v0.23.5_tracing)", - "tower-abci 0.1.0 (git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200)", - "tracing 0.1.35", - "tracing-log", - "tracing-subscriber 0.3.11", - "websocket", - "winapi 0.3.9", -] - -[[package]] -name = "anoma_encoding_spec" -version = "0.6.1" -dependencies = [ - "anoma", - "borsh", - "itertools 0.10.3", - "lazy_static 1.4.0", - "madato", -] - -[[package]] -name = "anoma_macros" -version = "0.6.1" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "anoma_proof_of_stake" -version = "0.6.1" -dependencies = [ - "borsh", - "proptest", - "thiserror", -] - -[[package]] -name = "anoma_tests" -version = "0.6.1" -dependencies = [ - "anoma", - "anoma_apps", - "anoma_vm_env", - "assert_cmd", - "borsh", - "chrono", - "color-eyre", - "concat-idents", - "derivative", - "escargot", - "expectrl", - "eyre", - "file-serve", - "fs_extra", - "hex", - "itertools 0.10.3", - "libp2p", - "pretty_assertions", - "proptest", - "prost 0.9.0", - "serde_json", - "sha2 0.9.9", - "tempfile", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", - "tendermint 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/abcipp-v0.23.5)", - "tendermint-proto 0.23.5 (git+https://github.com/heliaxdev/tendermint-rs?branch=yuji/rebase_v0.23.5)", - "test-log", - "toml", - "tracing 0.1.35", - "tracing-subscriber 0.3.11", -] - -[[package]] -name = "anoma_tx_prelude" -version = "0.6.1" -dependencies = [ - "anoma_vm_env", - "sha2 0.10.2", -] - -[[package]] -name = "anoma_vm_env" -version = "0.6.1" -dependencies = [ - "anoma", - "anoma_macros", - "borsh", - "hex", -] - -[[package]] -name = "anoma_vp_prelude" -version = "0.6.1" -dependencies = [ - "anoma_vm_env", - "sha2 0.10.2", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -4145,6 +3904,7 @@ dependencies = [ "async-trait", "base64 0.13.0", "bech32", + "bimap", "bit-set", "blake2b-rs", "borsh", From db05b47f4e3878a8cf06a6d25daaff4fffedfc0e Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 4 Aug 2022 19:51:12 -0700 Subject: [PATCH 104/373] fix a CL message, change a println! to a panic statement --- apps/src/bin/anoma-wallet/cli.rs | 2 +- apps/src/lib/cli.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index c080f23cd8..8354332629 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -194,7 +194,7 @@ fn address_list(ctx: Context) { fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { let wallet = ctx.wallet; if args.address.is_some() && args.alias.is_some() { - println!( + assert!(false, "This should not be happening, as clap should emit its own error \ message." ); diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 07d7083440..bc990380a7 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -551,7 +551,7 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about("Find an address by its alias.") + .about("Find an address by its alias or an alias by its address.") .add_args::() } } From 632ed3b688b410929357533bfe802007e92eda80 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 4 Aug 2022 19:52:04 -0700 Subject: [PATCH 105/373] make fmt --- apps/src/bin/anoma-wallet/cli.rs | 3 ++- apps/src/lib/cli.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 8354332629..e32564db7d 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -194,7 +194,8 @@ fn address_list(ctx: Context) { fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { let wallet = ctx.wallet; if args.address.is_some() && args.alias.is_some() { - assert!(false, + assert!( + false, "This should not be happening, as clap should emit its own error \ message." ); diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index bc990380a7..a67d00e6c4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -551,7 +551,9 @@ pub mod cmds { fn def() -> App { App::new(Self::CMD) - .about("Find an address by its alias or an alias by its address.") + .about( + "Find an address by its alias or an alias by its address.", + ) .add_args::() } } From 73f13d4990c33aa1d2e71e2bd444009090703695 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 23 Jun 2022 12:33:26 +0200 Subject: [PATCH 106/373] changes to Cargo.toml and Cargo.lock from adding latest version of zeroize crate --- Cargo.lock | 50 ++++++++++++++++----------- shared/Cargo.toml | 1 + wasm/tx_template/Cargo.lock | 5 +-- wasm/vp_template/Cargo.lock | 5 +-- wasm/wasm_source/Cargo.lock | 5 +-- wasm_for_tests/wasm_source/Cargo.lock | 5 +-- 6 files changed, 43 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9c1414272..cdcd3e6132 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -34,7 +34,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures", + "cpufeatures 0.2.2", "opaque-debug 0.3.0", ] @@ -910,13 +910,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.7.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08493fa7707effc63254c66c6ea908675912493cd67952eda23c09fae2610b1" +checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures", + "cpufeatures 0.1.5", "zeroize", ] @@ -928,17 +928,17 @@ checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures", + "cpufeatures 0.2.2", ] [[package]] name = "chacha20poly1305" -version = "0.8.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6547abe025f4027edacd9edaa357aded014eecec42a5070d9b885c3c334aba2" +checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" dependencies = [ "aead", - "chacha20 0.7.3", + "chacha20 0.7.1", "cipher", "poly1305", "zeroize", @@ -1123,6 +1123,15 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "cpufeatures" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.2" @@ -1376,9 +1385,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest 0.9.0", @@ -3883,6 +3892,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -4698,7 +4708,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.2", "opaque-debug 0.3.0", "universal-hash", ] @@ -4710,7 +4720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.2", "opaque-debug 0.3.0", "universal-hash", ] @@ -5868,7 +5878,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.2", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -5880,7 +5890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.2", "digest 0.10.3", ] @@ -5904,7 +5914,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.2", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -5916,7 +5926,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.2", "digest 0.10.3", ] @@ -8000,9 +8010,9 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "1.2.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ "curve25519-dalek", "rand_core 0.5.1", @@ -8043,9 +8053,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" dependencies = [ "zeroize_derive", ] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 241fd034da..86a96aa743 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -92,6 +92,7 @@ wasmer-engine-dylib = {version = "=2.2.0", optional = true} wasmer-engine-universal = {version = "=2.2.0", optional = true} wasmer-vm = {version = "2.2.0", optional = true} wasmparser = "0.83.0" +zeroize = "1.5.5" [dev-dependencies] assert_matches = "1.5.0" diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index bf9d222920..56d188bd09 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1394,6 +1394,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -3272,9 +3273,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zeroize" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 98f6c7f71c..6132ba7736 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1394,6 +1394,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -3272,9 +3273,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zeroize" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index b7185cc110..e6f814b267 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1394,6 +1394,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -3287,9 +3288,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zeroize" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 39f70c1787..162ff40653 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1405,6 +1405,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -3299,9 +3300,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "zeroize" -version = "1.5.4" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] From 64b3558abbd0233225078e1aea68dd100db4b601 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 23 Jun 2022 12:38:14 +0200 Subject: [PATCH 107/373] wrap SigningKey in a Box pointer when placing into SecretKey struct, test that memory is actually zeroized after dropping SecretKey --- shared/src/types/key/ed25519.rs | 19 +++++++++++++------ shared/src/types/key/mod.rs | 16 ++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index 12e5093bd2..2bc796f10c 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -9,6 +9,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; +use zeroize::{Zeroize}; use super::{ ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, @@ -122,8 +123,8 @@ impl FromStr for PublicKey { } /// Ed25519 secret key -#[derive(Debug, Serialize, Deserialize)] -pub struct SecretKey(pub ed25519_consensus::SigningKey); +#[derive(Debug, Serialize, Deserialize, Zeroize)] +pub struct SecretKey(pub Box); impl super::SecretKey for SecretKey { type PublicKey = PublicKey; @@ -157,13 +158,13 @@ impl RefTo for SecretKey { impl Clone for SecretKey { fn clone(&self) -> SecretKey { - SecretKey(ed25519_consensus::SigningKey::from(self.0.to_bytes())) + SecretKey(Box::new(ed25519_consensus::SigningKey::from(self.0.to_bytes()))) } } impl BorshDeserialize for SecretKey { fn deserialize(buf: &mut &[u8]) -> std::io::Result { - Ok(SecretKey( + Ok(SecretKey(Box::new( ed25519_consensus::SigningKey::try_from( <[u8; SECRET_KEY_LENGTH] as BorshDeserialize>::deserialize( buf, @@ -173,7 +174,7 @@ impl BorshDeserialize for SecretKey { .map_err(|e| { std::io::Error::new(std::io::ErrorKind::InvalidInput, e) })?, - )) + ))) } } @@ -218,6 +219,12 @@ impl FromStr for SecretKey { } } +impl Drop for SecretKey { + fn drop(&mut self) { + self.0.zeroize(); + } +} + /// Ed25519 signature #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct Signature(pub ed25519_consensus::Signature); @@ -325,7 +332,7 @@ impl super::SigScheme for SigScheme { where R: CryptoRng + RngCore, { - SecretKey(ed25519_consensus::SigningKey::new(csprng)) + SecretKey(Box::new(ed25519_consensus::SigningKey::new(csprng))) } fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a1343cd7e5..f27b6b36c1 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -415,6 +415,22 @@ macro_rules! sigscheme_test { println!("Public key: {}", public_key); println!("Secret key: {}", secret_key); } + + #[test] + fn zeroize_keypair() { + use rand::thread_rng; + + let sk = ed25519::SecretKey(Box::new(ed25519_consensus::SigningKey::new(thread_rng()))); + let len = sk.0.as_bytes().len(); + let ptr = sk.0.as_bytes().as_ptr(); + + drop(sk); + + assert_eq!(&[0u8; 32], unsafe { + core::slice::from_raw_parts(ptr, len) + }); + + } } }; } From 30106a288afc9fc4b735eae25e61268c431e1ed7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 30 Jun 2022 15:50:31 +0200 Subject: [PATCH 108/373] make fmt --- shared/src/types/key/ed25519.rs | 6 ++++-- shared/src/types/key/mod.rs | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index 2bc796f10c..48db89005a 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -9,7 +9,7 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; -use zeroize::{Zeroize}; +use zeroize::Zeroize; use super::{ ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, @@ -158,7 +158,9 @@ impl RefTo for SecretKey { impl Clone for SecretKey { fn clone(&self) -> SecretKey { - SecretKey(Box::new(ed25519_consensus::SigningKey::from(self.0.to_bytes()))) + SecretKey(Box::new(ed25519_consensus::SigningKey::from( + self.0.to_bytes(), + ))) } } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index f27b6b36c1..e7571bb050 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -420,7 +420,9 @@ macro_rules! sigscheme_test { fn zeroize_keypair() { use rand::thread_rng; - let sk = ed25519::SecretKey(Box::new(ed25519_consensus::SigningKey::new(thread_rng()))); + let sk = ed25519::SecretKey(Box::new( + ed25519_consensus::SigningKey::new(thread_rng()), + )); let len = sk.0.as_bytes().len(); let ptr = sk.0.as_bytes().as_ptr(); @@ -429,7 +431,6 @@ macro_rules! sigscheme_test { assert_eq!(&[0u8; 32], unsafe { core::slice::from_raw_parts(ptr, len) }); - } } }; From 9ed23b236bf3499425f2a0c1387bc75b6d6c539b Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 6 Jul 2022 00:41:27 +0200 Subject: [PATCH 109/373] move zeroize test out of macro (also in advance of incorporating secp256k1) --- shared/src/types/key/mod.rs | 39 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index e7571bb050..9deccf75a9 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -415,26 +415,31 @@ macro_rules! sigscheme_test { println!("Public key: {}", public_key); println!("Secret key: {}", secret_key); } - - #[test] - fn zeroize_keypair() { - use rand::thread_rng; - - let sk = ed25519::SecretKey(Box::new( - ed25519_consensus::SigningKey::new(thread_rng()), - )); - let len = sk.0.as_bytes().len(); - let ptr = sk.0.as_bytes().as_ptr(); - - drop(sk); - - assert_eq!(&[0u8; 32], unsafe { - core::slice::from_raw_parts(ptr, len) - }); - } } }; } #[cfg(test)] sigscheme_test! {ed25519_test, ed25519::SigScheme} + +#[cfg(test)] +mod more_tests { + use super::*; + + #[test] + fn zeroize_keypair_ed25519() { + use rand::thread_rng; + + let sk = ed25519::SecretKey(Box::new( + ed25519_consensus::SigningKey::new(thread_rng()), + )); + let len = sk.0.as_bytes().len(); + let ptr = sk.0.as_bytes().as_ptr(); + + drop(sk); + + assert_eq!(&[0u8; 32], unsafe { + core::slice::from_raw_parts(ptr, len) + }); + } +} From 4f90d43be26259ef43d3c675783bf69606e867fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 5 Aug 2022 14:29:18 +0000 Subject: [PATCH 110/373] [ci skip] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5d670022b7..91c2317ccf 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.71acaf1ff3a4ac1ce6550c4ce594cf70d684213769f45641be5cdb4b7321184e.wasm", - "tx_from_intent.wasm": "tx_from_intent.e18907b1fb0e1c4e4946228c0ec57838a783a5d88c6b3129d44935386693cee8.wasm", - "tx_ibc.wasm": "tx_ibc.38ee97db63b765f1639ee24dd3e7f264139f146a56f7c06edafde63eaecedade.wasm", - "tx_init_account.wasm": "tx_init_account.6be05f8ca240d9e978dfe6a1566b3860e07fa3aee334003fc5d85a5a1ae99cf7.wasm", - "tx_init_nft.wasm": "tx_init_nft.5f710b641c38cbe7822c2359bf7a764e864e952f4660ccff9b58b97fbaa4b3a2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.25666fc8fbf251bd4ad478cb968f1d50a8100a0a7aae0e3ee5e50e28304e8e3e.wasm", - "tx_init_validator.wasm": "tx_init_validator.2b1a4c947242ddddf94a491b8c571185f649913606bfa7d37f668ddc86fe1049.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9af8eea9219db09161b98405c18ea3c82db03b982f69a66ce1d117e64a715c3c.wasm", - "tx_transfer.wasm": "tx_transfer.87e4fba9d2476388e0f2b1d4bc00c396ac7b8b7b38d5079735fe53bc5984b583.wasm", - "tx_unbond.wasm": "tx_unbond.61f2ac46e7680ec35aba367ec908ac63aa43d6765cd07da932aa9ea45d77be46.wasm", - "tx_update_vp.wasm": "tx_update_vp.00d828bdf510d92d971ca59015945c8c00f285a802a655451cc5ee87c5ee99bc.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.92ed628aca7856e2b0fe461c0a0a65c6f7c1f585ac542c10feceef0e4b9ce611.wasm", - "tx_withdraw.wasm": "tx_withdraw.fb54d108a6fe7c6db16e573110c4a9f52158ec993e04e5a06e1425b8dd676c54.wasm", - "vp_nft.wasm": "vp_nft.3421840146f8d2e6c3ce3a0d94bbb0dad6ef296c50e9d00744179d290f32745b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1b37ab6939280fbb54512918053362fae34e3db74f029ef31c9f08e767539afb.wasm", - "vp_token.wasm": "vp_token.08dcec2d3ecb0942d0a244bbd176f9a42424ec09544120db1b30c0d97ed7dbb1.wasm", - "vp_user.wasm": "vp_user.404f089f0c73fcf906f7d6059d74c6bab3e2e3d3a108394503bbc22588ff95b9.wasm" + "tx_bond.wasm": "tx_bond.5aa466cd8ecbe9c5f9b777956052f4e0164f099c30260475f0e9cd71bbd99e0d.wasm", + "tx_from_intent.wasm": "tx_from_intent.95f421a3caa886186655c92aee1545e95d893ad6ce003e5317dc16ca5ef2076b.wasm", + "tx_ibc.wasm": "tx_ibc.c3c5dafe1a1740a848394c3264e8802184181658c12356f99ce95388922220e7.wasm", + "tx_init_account.wasm": "tx_init_account.9e70d6ca9ee4c0b9ca62a58b95f52321df145f5c84fff44f5a88bba0771a1a17.wasm", + "tx_init_nft.wasm": "tx_init_nft.7d57769d2da3d1dba1775763d6d33305335c8232220a459c847e512ef7ef1165.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.20a4bb9daa9499b39b564124a4cc07421b24ba1c36f8c8f48fda6772b295f841.wasm", + "tx_init_validator.wasm": "tx_init_validator.a7cf7bbb695a3a8c618a8811a4a7c061231b0272b06234fdcfb5264e9938895d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.24272eface53a9a4c4d927ae33c6e139c56779ecae4854095153a0300da59cbc.wasm", + "tx_transfer.wasm": "tx_transfer.9f13d688b915a150dcfd2472c5ba698fddfc4a3a080e81f06b3a061af72508d9.wasm", + "tx_unbond.wasm": "tx_unbond.e9c6c5f9fd6987afd3213d533735d2bb6349a9a002f0f6d1b8fb1b6ea1581cfd.wasm", + "tx_update_vp.wasm": "tx_update_vp.d47bfe6ef55a86bce36093feeff74fe09ec7cffebf9696dd37658756a4eb793d.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e1c327bbe49d7b6b5445ad641d9f154813ae73c98ba8df7b85c5a06dc4ede41e.wasm", + "tx_withdraw.wasm": "tx_withdraw.7f83fe1f8bb0fa1864c64d329cec54c317524715644907066ab322f5b2be0056.wasm", + "vp_nft.wasm": "vp_nft.955cbe702d2925a209529cc5d7b810768fe3e6c597907a941417bc50ca31e42e.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.35c7df353e7d4ffd986f92a81a2b49bd85f4b0265d85be9c38fe88c389d61ce3.wasm", + "vp_token.wasm": "vp_token.abbca3decf5ca2fb46b7de0589b52355c413c3281f6c8aa13868008747b41484.wasm", + "vp_user.wasm": "vp_user.e4a2076ebd3d458a2d57768007105a0e3baed597456e401b3a2787d4314e511f.wasm" } \ No newline at end of file From 32ab54c2b18ef74924131ab8e86cfa1a377ce86c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 5 Aug 2022 16:30:15 +0200 Subject: [PATCH 111/373] changelog: add #277 --- .changelog/unreleased/improvements/277-zeroize-secret-keys.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/277-zeroize-secret-keys.md diff --git a/.changelog/unreleased/improvements/277-zeroize-secret-keys.md b/.changelog/unreleased/improvements/277-zeroize-secret-keys.md new file mode 100644 index 0000000000..27cb40bf55 --- /dev/null +++ b/.changelog/unreleased/improvements/277-zeroize-secret-keys.md @@ -0,0 +1,2 @@ +- Zeroize secret keys from memory + ([#277](https://github.com/anoma/namada/pull/277)) \ No newline at end of file From 9b7143e489142ed33f4fff3ce377c881a6ac49af Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 15 Jun 2022 13:48:42 +0200 Subject: [PATCH 112/373] initial commit for supporting secp256k1 keys --- Cargo.lock | 77 +++- shared/Cargo.toml | 1 + shared/src/types/key/common.rs | 61 +++- shared/src/types/key/ed25519.rs | 6 +- shared/src/types/key/mod.rs | 39 +- shared/src/types/key/secp256k1.rs | 494 ++++++++++++++++++++++++++ wasm/tx_template/Cargo.lock | 87 +++++ wasm/vp_template/Cargo.lock | 87 +++++ wasm/wasm_source/Cargo.lock | 87 +++++ wasm_for_tests/wasm_source/Cargo.lock | 87 +++++ 10 files changed, 1006 insertions(+), 20 deletions(-) create mode 100644 shared/src/types/key/secp256k1.rs diff --git a/Cargo.lock b/Cargo.lock index cdcd3e6132..e43983c3df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2442,6 +2442,16 @@ dependencies = [ "digest 0.8.1", ] +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + [[package]] name = "hmac-drbg" version = "0.2.0" @@ -2450,7 +2460,18 @@ checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" dependencies = [ "digest 0.8.1", "generic-array 0.12.4", - "hmac", + "hmac 0.7.1", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.5", + "hmac 0.8.1", ] [[package]] @@ -3017,7 +3038,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "lazy_static 1.4.0", - "libsecp256k1", + "libsecp256k1 0.3.5", "log 0.4.17", "multihash", "multistream-select", @@ -3401,13 +3422,62 @@ dependencies = [ "arrayref", "crunchy", "digest 0.8.1", - "hmac-drbg", + "hmac-drbg 0.2.0", "rand 0.7.3", "sha2 0.8.2", "subtle 2.4.1", "typenum", ] +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.0", + "digest 0.9.0", + "hmac-drbg 0.3.0", + "lazy_static 1.4.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde 1.0.137", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle 2.4.1", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "libssh2-sys" version = "0.2.23" @@ -3862,6 +3932,7 @@ dependencies = [ "ibc-proto", "ics23", "itertools 0.10.3", + "libsecp256k1 0.7.1", "loupe", "namada_proof_of_stake", "parity-wasm", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 86a96aa743..3b9153095c 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -64,6 +64,7 @@ ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c3 ics23 = "0.6.7" itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} +libsecp256k1 = {version = "0.7.0", default-features = false, features = ["std", "hmac", "lazy-static-context"]} parity-wasm = {version = "0.42.2", optional = true} # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 27e7c29b89..1986799142 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -9,7 +9,7 @@ use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use super::{ - ed25519, ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, + ed25519, secp256k1, ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; @@ -31,6 +31,8 @@ use super::{ pub enum PublicKey { /// Encapsulate Ed25519 public keys Ed25519(ed25519::PublicKey), + /// Encapsulate Secp256k1 public keys + Secp256k1(secp256k1::PublicKey), } impl super::PublicKey for PublicKey { @@ -49,6 +51,13 @@ impl super::PublicKey for PublicKey { ) .map_err(ParsePublicKeyError::InvalidEncoding)?, )) + } else if PK::TYPE == secp256k1::PublicKey::TYPE { + Ok(Self::Secp256k1( + secp256k1::PublicKey::try_from_slice( + pk.try_to_vec().unwrap().as_slice(), + ) + .map_err(ParsePublicKeyError::InvalidEncoding)?, + )) } else { Err(ParsePublicKeyError::MismatchedScheme) } @@ -77,6 +86,8 @@ impl FromStr for PublicKey { pub enum SecretKey { /// Encapsulate Ed25519 secret keys Ed25519(ed25519::SecretKey), + /// Encapsulate Secp256k1 secret keys + Secp256k1(secp256k1::SecretKey), } impl Serialize for SecretKey { @@ -88,13 +99,12 @@ impl Serialize for SecretKey { S: serde::Serializer, { // String encoded, because toml doesn't support enums - match self { - ed25519_sk @ SecretKey::Ed25519(_) => { - let keypair_string = - format!("{}{}", "ED25519_SK_PREFIX", ed25519_sk); - Serialize::serialize(&keypair_string, serializer) - } - } + let prefix = match self { + SecretKey::Ed25519(_) => "ED25519_SK_PREFIX", + SecretKey::Secp256k1(_) => "SECP256K1_SK_PREFIX", + }; + let keypair_string = format!("{}{}",prefix,self); + Serialize::serialize(&keypair_string,serializer) } } @@ -110,6 +120,8 @@ impl<'de> Deserialize<'de> for SecretKey { .map_err(D::Error::custom)?; if let Some(raw) = keypair_string.strip_prefix("ED25519_SK_PREFIX") { SecretKey::from_str(raw).map_err(D::Error::custom) + } else if let Some(raw) = keypair_string.strip_prefix("SECP256K1_SK_PREFIX") { + SecretKey::from_str(raw).map_err(D::Error::custom) } else { Err(D::Error::custom( "Could not deserialize SecretKey do to invalid prefix", @@ -136,7 +148,13 @@ impl super::SecretKey for SecretKey { ) .map_err(ParseSecretKeyError::InvalidEncoding)?, )) - } else { + } else if PK::TYPE == secp256k1::SecretKey::TYPE { + Ok(Self::Secp256k1( + secp256k1::SecretKey::try_from_slice( + pk.try_to_vec().unwrap().as_ref(), + ) + .map_err(ParseSecretKeyError::InvalidEncoding)?, + )) } else { Err(ParseSecretKeyError::MismatchedScheme) } } @@ -146,6 +164,7 @@ impl RefTo for SecretKey { fn ref_to(&self) -> PublicKey { match self { SecretKey::Ed25519(sk) => PublicKey::Ed25519(sk.ref_to()), + SecretKey::Secp256k1(sk) => PublicKey::Secp256k1(sk.ref_to()), } } } @@ -183,6 +202,8 @@ impl FromStr for SecretKey { pub enum Signature { /// Encapsulate Ed25519 signatures Ed25519(ed25519::Signature), + /// Encapsulate Secp256k1 signatures + Secp256k1(secp256k1::Signature), } impl super::Signature for Signature { @@ -201,6 +222,13 @@ impl super::Signature for Signature { ) .map_err(ParseSignatureError::InvalidEncoding)?, )) + } else if PK::TYPE == secp256k1::Signature::TYPE { + Ok(Self::Secp256k1( + secp256k1::Signature::try_from_slice( + pk.try_to_vec().unwrap().as_slice(), + ) + .map_err(ParseSignatureError::InvalidEncoding)?, + )) } else { Err(ParseSignatureError::MismatchedScheme) } @@ -248,6 +276,9 @@ impl super::SigScheme for SigScheme { SecretKey::Ed25519(kp) => { Signature::Ed25519(ed25519::SigScheme::sign(kp, data)) } + SecretKey::Secp256k1(kp) => { + Signature::Secp256k1(secp256k1::SigScheme::sign(kp, data)) + } } } @@ -259,7 +290,11 @@ impl super::SigScheme for SigScheme { match (pk, sig) { (PublicKey::Ed25519(pk), Signature::Ed25519(sig)) => { ed25519::SigScheme::verify_signature(pk, data, sig) - } // _ => Err(VerifySigError::MismatchedScheme), + } + (PublicKey::Secp256k1(pk), Signature::Secp256k1(sig)) => { + secp256k1::SigScheme::verify_signature(pk, data, sig) + } + _ => Err(VerifySigError::MismatchedScheme), } } @@ -271,7 +306,11 @@ impl super::SigScheme for SigScheme { match (pk, sig) { (PublicKey::Ed25519(pk), Signature::Ed25519(sig)) => { ed25519::SigScheme::verify_signature_raw(pk, data, sig) - } // _ => Err(VerifySigError::MismatchedScheme), + } + (PublicKey::Secp256k1(pk), Signature::Secp256k1(sig)) => { + secp256k1::SigScheme::verify_signature_raw(pk, data, sig) + } + _ => Err(VerifySigError::MismatchedScheme), } } } diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index 48db89005a..4f3f9f02d7 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -35,7 +35,7 @@ impl super::PublicKey for PublicKey { #[allow(clippy::bind_instead_of_map)] super::common::PublicKey::try_from_pk(pk).and_then(|x| match x { super::common::PublicKey::Ed25519(epk) => Ok(epk), - // _ => Err(ParsePublicKeyError::MismatchedScheme), + _ => Err(ParsePublicKeyError::MismatchedScheme), }) } else if PK::TYPE == Self::TYPE { Self::try_from_slice(pk.try_to_vec().unwrap().as_slice()) @@ -139,7 +139,7 @@ impl super::SecretKey for SecretKey { #[allow(clippy::bind_instead_of_map)] super::common::SecretKey::try_from_sk(pk).and_then(|x| match x { super::common::SecretKey::Ed25519(epk) => Ok(epk), - // _ => Err(ParseSecretKeyError::MismatchedScheme), + _ => Err(ParseSecretKeyError::MismatchedScheme), }) } else if PK::TYPE == Self::TYPE { Self::try_from_slice(pk.try_to_vec().unwrap().as_slice()) @@ -242,7 +242,7 @@ impl super::Signature for Signature { #[allow(clippy::bind_instead_of_map)] super::common::Signature::try_from_sig(pk).and_then(|x| match x { super::common::Signature::Ed25519(epk) => Ok(epk), - // _ => Err(ParseSignatureError::MismatchedScheme), + _ => Err(ParseSignatureError::MismatchedScheme), }) } else if PK::TYPE == Self::TYPE { Self::try_from_slice(pk.try_to_vec().unwrap().as_slice()) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 9deccf75a9..6a95d2eb2d 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -19,6 +19,7 @@ use crate::types::address; pub mod common; pub mod ed25519; +pub mod secp256k1; const PK_STORAGE_KEY: &str = "public_key"; const PROTOCOL_PK_STORAGE_KEY: &str = "protocol_public_key"; @@ -126,18 +127,33 @@ pub trait TryFromRef: Sized { } /// Type capturing signature scheme IDs -#[derive(PartialEq, Eq, Copy, Clone)] +#[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum SchemeType { /// Type identifier for Ed25519-consensus Ed25519Consensus, + /// Type identifier for Secp256k1-consensus + Secp256k1Consensus, /// Type identifier for Common Common, } +impl FromStr for SchemeType { + type Err = (); + + fn from_str(input: &str) -> Result { + match input.to_lowercase().as_str() { + "ed25519" => Ok(Self::Ed25519Consensus), + "secp256k1" => Ok(Self::Secp256k1Consensus), + "common" => Ok(Self::Common), + _ => Err(()), + } + } +} + /// Represents a signature pub trait Signature: - Hash + PartialOrd + Serialize + BorshSerialize + BorshDeserialize + Hash + PartialOrd + Serialize + BorshSerialize + BorshDeserialize + BorshSchema { /// The scheme type of this implementation const TYPE: SchemeType; @@ -164,6 +180,7 @@ pub trait Signature: pub trait PublicKey: BorshSerialize + BorshDeserialize + + BorshSchema + Ord + Clone + Display @@ -199,6 +216,7 @@ pub trait PublicKey: pub trait SecretKey: BorshSerialize + BorshDeserialize + + BorshSchema + Display + Debug + RefTo @@ -415,12 +433,27 @@ macro_rules! sigscheme_test { println!("Public key: {}", public_key); println!("Secret key: {}", secret_key); } + + /// Run `cargo test gen_keypair -- --nocapture` to generate a + /// new keypair. + #[test] + fn gen_sign_verify() { + use rand::prelude::ThreadRng; + use rand::thread_rng; + + let mut rng: ThreadRng = thread_rng(); + let sk = <$type>::generate(&mut rng); + let sig = <$type>::sign(&sk, b"hello"); + assert!(<$type>::verify_signature_raw(&sk.ref_to(), b"hello", &sig).is_ok()); + } } }; } #[cfg(test)] sigscheme_test! {ed25519_test, ed25519::SigScheme} +#[cfg(test)] +sigscheme_test! {secp256k1_test, secp256k1::SigScheme} #[cfg(test)] mod more_tests { @@ -442,4 +475,4 @@ mod more_tests { core::slice::from_raw_parts(ptr, len) }); } -} +} \ No newline at end of file diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs new file mode 100644 index 0000000000..a7efa973fc --- /dev/null +++ b/shared/src/types/key/secp256k1.rs @@ -0,0 +1,494 @@ +//! secp256k1 keys and related functionality + +use std::fmt; +use std::fmt::{Debug, Display}; +use std::hash::{Hash, Hasher}; +use std::io::{ErrorKind, Write}; +use std::str::FromStr; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use serde::Serializer; + +//use libsecp256k1::util::SECRET_KEY_SIZE; +use sha2::{Digest, Sha256}; + +#[cfg(feature = "rand")] +use rand::{CryptoRng, RngCore}; +use serde::{Deserialize,Serialize}; +use serde::de::{Error, SeqAccess, Visitor}; +use serde::ser::SerializeTuple; + +use super::{ + ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, + SchemeType, SigScheme as SigSchemeTrait, VerifySigError, +}; + + +/// secp256k1 public key +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct PublicKey(libsecp256k1::PublicKey); + +impl super::PublicKey for PublicKey { + const TYPE: SchemeType = SigScheme::TYPE; + + fn try_from_pk( + pk: &PK, + ) -> Result { + if PK::TYPE == super::common::PublicKey::TYPE { + // TODO remove once the wildcard match is used below + #[allow(clippy::bind_instead_of_map)] + super::common::PublicKey::try_from_pk(pk).and_then(|x| match x { + super::common::PublicKey::Secp256k1(epk) => Ok(epk), + _ => Err(ParsePublicKeyError::MismatchedScheme), + }) + } else if PK::TYPE == Self::TYPE { + Self::try_from_slice(pk.try_to_vec().unwrap().as_slice()) + .map_err(ParsePublicKeyError::InvalidEncoding) + } else { + Err(ParsePublicKeyError::MismatchedScheme) + } + } +} + +impl BorshDeserialize for PublicKey { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + // deserialize the bytes first + let pk = libsecp256k1::PublicKey::parse_compressed( + buf.get(0..libsecp256k1::util::COMPRESSED_PUBLIC_KEY_SIZE) + .ok_or_else(|| std::io::Error::from(ErrorKind::UnexpectedEof))? + .try_into() + .unwrap(), + ) + .map_err(|e| { + std::io::Error::new( + ErrorKind::InvalidInput, + format!("Error decoding secp256k1 public key: {}", e), + ) + })?; + *buf = &buf[libsecp256k1::util::COMPRESSED_PUBLIC_KEY_SIZE..]; + Ok(PublicKey(pk)) + } +} + +impl BorshSerialize for PublicKey { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + writer.write(&self.0.serialize_compressed())?; + Ok(()) + } +} + +impl BorshSchema for PublicKey { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + // Encoded as `[u8; COMPRESSED_PUBLIC_KEY_SIZE]` + let elements = "u8".into(); + let length = libsecp256k1::util::COMPRESSED_PUBLIC_KEY_SIZE as u32; + let definition = borsh::schema::Definition::Array { elements, length }; + definitions.insert(Self::declaration(), definition); + } + + fn declaration() -> borsh::schema::Declaration { + "secp256k1::PublicKey".into() + } +} + +#[allow(clippy::derive_hash_xor_eq)] +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + self.0.serialize_compressed().hash(state); + } +} + +impl PartialOrd for PublicKey { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.serialize_compressed().partial_cmp(&other.0.serialize_compressed()) + } +} + +impl Ord for PublicKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.serialize_compressed().cmp(&other.0.serialize_compressed()) + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(&self.0.serialize_compressed())) + } +} + +impl FromStr for PublicKey { + type Err = ParsePublicKeyError; + + fn from_str(s: &str) -> Result { + let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + BorshDeserialize::try_from_slice(&vec) + .map_err(ParsePublicKeyError::InvalidEncoding) + } +} + +impl From for PublicKey { + fn from(pk: libsecp256k1::PublicKey) -> Self { + Self(pk) + } +} + +/// Secp256k1 secret key +#[derive(Debug, Clone)] +pub struct SecretKey(libsecp256k1::SecretKey); + +impl super::SecretKey for SecretKey { + type PublicKey = PublicKey; + + const TYPE: SchemeType = SigScheme::TYPE; + + fn try_from_sk( + pk: &PK, + ) -> Result { + if PK::TYPE == super::common::SecretKey::TYPE { + // TODO remove once the wildcard match is used below + #[allow(clippy::bind_instead_of_map)] + super::common::SecretKey::try_from_sk(pk).and_then(|x| match x { + super::common::SecretKey::Secp256k1(epk) => Ok(epk), + _ => Err(ParseSecretKeyError::MismatchedScheme), + }) + } else if PK::TYPE == Self::TYPE { + Self::try_from_slice(pk.try_to_vec().unwrap().as_slice()) + .map_err(ParseSecretKeyError::InvalidEncoding) + } else { + Err(ParseSecretKeyError::MismatchedScheme) + } + } +} + +impl Serialize for SecretKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // not sure if this is how I should be doing this! + // https://serde.rs/impl-serialize.html! + let arr = self.0.serialize(); + let mut seq = serializer.serialize_tuple(arr.len())?; + for elem in &arr[..] { + seq.serialize_element(elem)?; + } + seq.end() + } +} + +impl<'de> Deserialize<'de> for SecretKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> + { + struct ByteArrayVisitor; + + impl<'de> Visitor<'de> for ByteArrayVisitor { + type Value = [u8; libsecp256k1::util::SECRET_KEY_SIZE]; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(&format!("expecting an array of length {}", libsecp256k1::util::SECRET_KEY_SIZE)) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut arr = [0u8; libsecp256k1::util::SECRET_KEY_SIZE]; + #[allow(clippy::needless_range_loop)] + for i in 0..libsecp256k1::util::SECRET_KEY_SIZE { + arr[i] = seq.next_element()? + .ok_or_else(|| Error::invalid_length(i, &self))?; + } + Ok(arr) + } + } + + let arr_res = deserializer.deserialize_tuple(libsecp256k1::util::SECRET_KEY_SIZE, ByteArrayVisitor)?; + let key = libsecp256k1::SecretKey::parse_slice(&arr_res) + .map_err(D::Error::custom); + Ok(SecretKey(key.unwrap())) + + } +} + +impl BorshDeserialize for SecretKey { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + // deserialize the bytes first + Ok(SecretKey( + libsecp256k1::SecretKey::parse( + &(BorshDeserialize::deserialize(buf)?), + ) + .map_err(|e| { + std::io::Error::new( + ErrorKind::InvalidInput, + format!("Error decoding secp256k1 secret key: {}", e), + ) + })?, + )) + } +} + +impl BorshSerialize for SecretKey { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + BorshSerialize::serialize(&self.0.serialize(), writer) + } +} + +impl BorshSchema for SecretKey { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + // Encoded as `[u8; SECRET_KEY_SIZE]` + let elements = "u8".into(); + let length = libsecp256k1::util::SECRET_KEY_SIZE as u32; + let definition = borsh::schema::Definition::Array { elements, length }; + definitions.insert(Self::declaration(), definition); + } + + fn declaration() -> borsh::schema::Declaration { + "secp256k1::SecretKey".into() + } +} + +impl Display for SecretKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", hex::encode(&self.0.serialize())) + } +} + +impl FromStr for SecretKey { + type Err = ParseSecretKeyError; + + fn from_str(s: &str) -> Result { + let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + BorshDeserialize::try_from_slice(&vec) + .map_err(ParseSecretKeyError::InvalidEncoding) + } +} + +impl RefTo for SecretKey { + fn ref_to(&self) -> PublicKey { + PublicKey(libsecp256k1::PublicKey::from_secret_key(&self.0)) + } +} + +/// Secp256k1 signature +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Signature(libsecp256k1::Signature); + +impl super::Signature for Signature { + const TYPE: SchemeType = SigScheme::TYPE; + + fn try_from_sig( + pk: &PK, + ) -> Result { + if PK::TYPE == super::common::Signature::TYPE { + // TODO remove once the wildcard match is used below + #[allow(clippy::bind_instead_of_map)] + super::common::Signature::try_from_sig(pk).and_then(|x| match x { + super::common::Signature::Secp256k1(epk) => Ok(epk), + _ => Err(ParseSignatureError::MismatchedScheme), + }) + } else if PK::TYPE == Self::TYPE { + Self::try_from_slice(pk.try_to_vec().unwrap().as_slice()) + .map_err(ParseSignatureError::InvalidEncoding) + } else { + Err(ParseSignatureError::MismatchedScheme) + } + } +} + +impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let arr = self.0.serialize(); + let mut seq = serializer.serialize_tuple(arr.len())?; + for elem in &arr[..] { + seq.serialize_element(elem)?; + } + seq.end() + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de> + { + struct ByteArrayVisitor; + + impl<'de> Visitor<'de> for ByteArrayVisitor { + type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE]; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(&format!("an array of length {}", libsecp256k1::util::SIGNATURE_SIZE)) + } + + fn visit_seq(self, mut seq: A) -> Result<[u8; 64], A::Error> + where + A: SeqAccess<'de>, + { + let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE]; + #[allow(clippy::needless_range_loop)] + for i in 0..libsecp256k1::util::SIGNATURE_SIZE { + arr[i] = seq.next_element()? + .ok_or_else(|| Error::invalid_length(i, &self))?; + } + Ok(arr) + } + } + + let arr_res = deserializer.deserialize_tuple(libsecp256k1::util::SIGNATURE_SIZE, ByteArrayVisitor)?; + let sig = libsecp256k1::Signature::parse_standard(&arr_res) + .map_err(D::Error::custom); + Ok(Signature(sig.unwrap())) + + } +} + +impl BorshDeserialize for Signature { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + // deserialize the bytes first + Ok(Signature( + libsecp256k1::Signature::parse_standard( + &(BorshDeserialize::deserialize(buf)?), + ) + .map_err(|e| { + std::io::Error::new( + ErrorKind::InvalidInput, + format!("Error decoding secp256k1 signature: {}", e), + ) + })?, + )) + } +} + +impl BorshSerialize for Signature { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + BorshSerialize::serialize(&self.0.serialize(), writer) + } +} + +impl BorshSchema for Signature { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + // Encoded as `[u8; SIGNATURE_SIZE]` + let elements = "u8".into(); + let length = libsecp256k1::util::SIGNATURE_SIZE as u32; + let definition = borsh::schema::Definition::Array { elements, length }; + definitions.insert(Self::declaration(), definition); + } + + fn declaration() -> borsh::schema::Declaration { + "secp256k1::Signature".into() + } +} + +#[allow(clippy::derive_hash_xor_eq)] +impl Hash for Signature { + fn hash(&self, state: &mut H) { + self.0.serialize().hash(state); + } +} + +impl PartialOrd for Signature { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.serialize().partial_cmp(&other.0.serialize()) + } +} + +/// An implementation of the Secp256k1 signature scheme +#[derive( + Debug, + Clone, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + Default, +)] +pub struct SigScheme; + +impl super::SigScheme for SigScheme { + type PublicKey = PublicKey; + type SecretKey = SecretKey; + type Signature = Signature; + + const TYPE: SchemeType = SchemeType::Secp256k1Consensus; + + #[cfg(feature = "rand")] + fn generate(csprng: &mut R) -> SecretKey + where + R: CryptoRng + RngCore, + { + SecretKey(libsecp256k1::SecretKey::random(csprng)) + } + + /// Sign the data with a key + fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { + let hash = Sha256::digest(data.as_ref()); + let message = libsecp256k1::Message::parse_slice(hash.as_ref()) + .expect("Message encoding should not fail"); + Signature(libsecp256k1::sign(&message, &keypair.0).0) + } + + fn verify_signature( + pk: &Self::PublicKey, + data: &T, + sig: &Self::Signature, + ) -> Result<(), VerifySigError> { + let bytes = &data + .try_to_vec() + .map_err(VerifySigError::DataEncodingError)?[..]; + let hash = Sha256::digest(bytes.as_ref()); + let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) + .expect("Error parsing given data"); + let check = libsecp256k1::verify(message, &sig.0, &pk.0); + match check { + true => Ok(()), + false => Err(VerifySigError::SigVerifyError( + format!("Error verifying secp256k1 signature: {}",libsecp256k1::Error::InvalidSignature) + )), + } + + + + } + + fn verify_signature_raw( + pk: &Self::PublicKey, + data: &[u8], + sig: &Self::Signature, + ) -> Result<(), VerifySigError> { + let hash = Sha256::digest(data.as_ref()); + let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) + .expect("Error parsing raw data"); + let check = libsecp256k1::verify(message,&sig.0, &pk.0); + match check { + true => Ok(()), + false => Err(VerifySigError::SigVerifyError( + format!("Error verifying secp256k1 signature: {}",libsecp256k1::Error::InvalidSignature) + )), + } + + } +} diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 56d188bd09..c7734c3587 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -531,6 +531,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.3" @@ -541,6 +547,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -994,6 +1010,27 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac", +] + [[package]] name = "http" version = "0.2.6" @@ -1221,6 +1258,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "lazy_static", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "log" version = "0.4.14" @@ -1367,6 +1453,7 @@ dependencies = [ "ibc-proto", "ics23", "itertools", + "libsecp256k1", "loupe", "namada_proof_of_stake", "parity-wasm", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 6132ba7736..cc9146fd62 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -531,6 +531,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.3" @@ -541,6 +547,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -994,6 +1010,27 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac", +] + [[package]] name = "http" version = "0.2.6" @@ -1221,6 +1258,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "lazy_static", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "log" version = "0.4.14" @@ -1367,6 +1453,7 @@ dependencies = [ "ibc-proto", "ics23", "itertools", + "libsecp256k1", "loupe", "namada_proof_of_stake", "parity-wasm", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index e6f814b267..8f2ed44ca5 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -531,6 +531,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.3" @@ -541,6 +547,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -994,6 +1010,27 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac", +] + [[package]] name = "http" version = "0.2.6" @@ -1221,6 +1258,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "lazy_static", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "log" version = "0.4.14" @@ -1367,6 +1453,7 @@ dependencies = [ "ibc-proto", "ics23", "itertools", + "libsecp256k1", "loupe", "namada_proof_of_stake", "parity-wasm", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 162ff40653..96927ae10c 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -532,6 +532,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.3" @@ -542,6 +548,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -1004,6 +1020,27 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac", +] + [[package]] name = "http" version = "0.2.6" @@ -1231,6 +1268,55 @@ dependencies = [ "winapi", ] +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "lazy_static", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "log" version = "0.4.16" @@ -1378,6 +1464,7 @@ dependencies = [ "ibc-proto", "ics23", "itertools", + "libsecp256k1", "loupe", "namada_proof_of_stake", "parity-wasm", From bc4d3fa301c015e833c0fe4606e23f8f1f614f71 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Jun 2022 18:11:56 +0200 Subject: [PATCH 113/373] command line options for specifying key scheme --- apps/src/lib/cli.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5eef95b34d..127673b9f3 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1457,6 +1457,7 @@ pub mod args { const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); const RPC_SOCKET_ADDR: ArgOpt = arg_opt("rpc"); + const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519Consensus)); const SIGNER: ArgOpt = arg_opt("signer"); const SIGNING_KEY_OPT: ArgOpt = SIGNING_KEY.opt(); const SIGNING_KEY: Arg = arg("signing-key"); @@ -2721,6 +2722,8 @@ pub mod args { /// Wallet generate key and implicit address arguments #[derive(Clone, Debug)] pub struct KeyAndAddressGen { + /// Scheme type + pub scheme: SchemeType, /// Key alias pub alias: Option, /// Don't encrypt the keypair @@ -2729,16 +2732,23 @@ pub mod args { impl Args for KeyAndAddressGen { fn parse(matches: &ArgMatches) -> Self { + let scheme = SCHEME.parse(matches); let alias = ALIAS_OPT.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); Self { + scheme, alias, unsafe_dont_encrypt, } } fn def(app: App) -> App { - app.arg(ALIAS_OPT.def().about( + app.arg(SCHEME.def().about( + "The type of key that should be generated. Argument must be \ + either ed25519 or secp256k1. If none provided, the default key scheme \ + is ed25519.", + )) + .arg(ALIAS_OPT.def().about( "The key and address alias. If none provided, the alias will \ be the public key hash.", )) @@ -3009,6 +3019,7 @@ pub mod args { pub alias: String, pub net_address: SocketAddr, pub unsafe_dont_encrypt: bool, + pub key_scheme: SchemeType, } impl Args for InitGenesisValidator { @@ -3016,10 +3027,12 @@ pub mod args { let alias = ALIAS.parse(matches); let net_address = NET_ADDRESS.parse(matches); let unsafe_dont_encrypt = UNSAFE_DONT_ENCRYPT.parse(matches); + let key_scheme = SCHEME.parse(matches); Self { alias, net_address, unsafe_dont_encrypt, + key_scheme, } } @@ -3034,6 +3047,10 @@ pub mod args { "UNSAFE: Do not encrypt the generated keypairs. Do not \ use this for keys used in a live network.", )) + .arg(SCHEME.def().about( + "The key scheme/type used for the validator keys. Currently \ + support ed25519 and secp256k1." + )) } } } From 5be50a5542ab51901604e476ce4a2539e52c5c96 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Jun 2022 18:19:19 +0200 Subject: [PATCH 114/373] incorporate options into key generation functions --- apps/src/bin/anoma-wallet/cli.rs | 3 +- apps/src/lib/client/tx.rs | 18 ++++++-- apps/src/lib/client/utils.rs | 74 ++++++++++++++++++++---------- apps/src/lib/wallet/mod.rs | 4 +- apps/src/lib/wallet/pre_genesis.rs | 21 +++++---- apps/src/lib/wallet/store.rs | 21 ++++++--- 6 files changed, 97 insertions(+), 44 deletions(-) diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 9807516a93..3889489956 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -45,12 +45,13 @@ pub fn main() -> Result<()> { fn key_and_address_gen( ctx: Context, args::KeyAndAddressGen { + scheme, alias, unsafe_dont_encrypt, }: args::KeyAndAddressGen, ) { let mut wallet = ctx.wallet; - let (alias, _key) = wallet.gen_key(alias, unsafe_dont_encrypt); + let (alias, _key) = wallet.gen_key(scheme, alias, unsafe_dont_encrypt); wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); println!( "Successfully added a key and an address with alias: \"{}\"", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6f1fd96707..8022a4a27f 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -177,7 +177,11 @@ pub async fn submit_init_validator( let account_key = ctx.get_opt_cached(&account_key).unwrap_or_else(|| { println!("Generating validator account key..."); ctx.wallet - .gen_key(Some(validator_key_alias.clone()), unsafe_dont_encrypt) + .gen_key( + SchemeType::Ed25519Consensus, + Some(validator_key_alias.clone()), + unsafe_dont_encrypt, + ) .1 .ref_to() }); @@ -186,7 +190,11 @@ pub async fn submit_init_validator( ctx.get_opt_cached(&consensus_key).unwrap_or_else(|| { println!("Generating consensus key..."); ctx.wallet - .gen_key(Some(consensus_key_alias.clone()), unsafe_dont_encrypt) + .gen_key( + SchemeType::Ed25519Consensus, + Some(consensus_key_alias.clone()), + unsafe_dont_encrypt, + ) .1 }); @@ -194,7 +202,11 @@ pub async fn submit_init_validator( ctx.get_opt_cached(&rewards_account_key).unwrap_or_else(|| { println!("Generating staking reward account key..."); ctx.wallet - .gen_key(Some(rewards_key_alias.clone()), unsafe_dont_encrypt) + .gen_key( + SchemeType::Ed25519Consensus, + Some(rewards_key_alias.clone()), + unsafe_dont_encrypt, + ) .1 .ref_to() }); diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index c377648b65..3082b21c44 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -257,11 +257,13 @@ pub async fn join_network( if let Some((validator_alias, pre_genesis_wallet)) = validator_alias_and_pre_genesis_wallet { - let tendermint_node_key: ed25519::SecretKey = pre_genesis_wallet + let tendermint_node_key: common::SecretKey = pre_genesis_wallet .tendermint_node_key .try_to_sk() .unwrap_or_else(|_err| { - eprintln!("Tendermint node key must be ed25519"); + eprintln!( + "Tendermint node key must be common (need to change?)" + ); cli::safe_exit(1) }); @@ -333,7 +335,7 @@ pub async fn join_network( .. } = peer { - node_id != *peer_id + node_id.as_ref().unwrap() != peer_id } else { true } @@ -351,11 +353,14 @@ pub async fn join_network( const TENDERMINT_NODE_ID_LENGTH: usize = 20; /// Derive Tendermint node ID from public key -fn id_from_pk(pk: &ed25519::PublicKey) -> TendermintNodeId { - let digest = Sha256::digest(pk.try_to_vec().unwrap().as_slice()); +fn id_from_pk( + pk: &common::PublicKey, +) -> Result { + let pk_bytes = pk.try_to_vec(); + let digest = Sha256::digest(pk_bytes.unwrap().as_slice()); let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH]; bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); - TendermintNodeId::new(bytes) + Ok(TendermintNodeId::new(bytes)) } /// Initialize a new test network from the given configuration. @@ -440,10 +445,10 @@ pub fn init_network( format!("validator {name} Tendermint node key"), &config.tendermint_node_key, ) - .map(|pk| ed25519::PublicKey::try_from_pk(&pk).unwrap()) + .map(|pk| common::PublicKey::try_from_pk(&pk).unwrap()) .unwrap_or_else(|| { // Generate a node key - let node_sk = ed25519::SigScheme::generate(&mut rng); + let node_sk = common::SigScheme::generate(&mut rng); let node_pk = write_tendermint_node_key(&tm_home_dir, node_sk); @@ -453,7 +458,7 @@ pub fn init_network( }); // Derive the node ID from the node key - let node_id: TendermintNodeId = id_from_pk(&node_pk); + let node_id: TendermintNodeId = id_from_pk(&node_pk).unwrap(); // Build the list of persistent peers from the validators' node IDs let peer = TendermintAddress::from_str(&format!( @@ -512,8 +517,11 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-consensus-key", name); println!("Generating validator {} consensus key...", name); - let (_alias, keypair) = - wallet.gen_key(Some(alias), unsafe_dont_encrypt); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Common, + Some(alias), + unsafe_dont_encrypt, + ); // Write consensus key for Tendermint tendermint_node::write_validator_key( @@ -532,8 +540,11 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-account-key", name); println!("Generating validator {} account key...", name); - let (_alias, keypair) = - wallet.gen_key(Some(alias), unsafe_dont_encrypt); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Common, + Some(alias), + unsafe_dont_encrypt, + ); keypair.ref_to() }); @@ -547,8 +558,11 @@ pub fn init_network( "Generating validator {} staking reward account key...", name ); - let (_alias, keypair) = - wallet.gen_key(Some(alias), unsafe_dont_encrypt); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Common, + Some(alias), + unsafe_dont_encrypt, + ); keypair.ref_to() }); @@ -559,8 +573,11 @@ pub fn init_network( .unwrap_or_else(|| { let alias = format!("{}-protocol-key", name); println!("Generating validator {} protocol signing key...", name); - let (_alias, keypair) = - wallet.gen_key(Some(alias), unsafe_dont_encrypt); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Common, + Some(alias), + unsafe_dont_encrypt, + ); keypair.ref_to() }); @@ -715,8 +732,11 @@ pub fn init_network( "Generating implicit account {} key and address ...", name ); - let (_alias, keypair) = - wallet.gen_key(Some(name.clone()), unsafe_dont_encrypt); + let (_alias, keypair) = wallet.gen_key( + SchemeType::Common, + Some(name.clone()), + unsafe_dont_encrypt, + ); let public_key = genesis_config::HexString(keypair.ref_to().to_string()); config.public_key = Some(public_key); @@ -1005,6 +1025,7 @@ fn init_established_account( if config.public_key.is_none() { println!("Generating established account {} key...", name.as_ref()); let (_alias, keypair) = wallet.gen_key( + SchemeType::Common, Some(format!("{}-key", name.as_ref())), unsafe_dont_encrypt, ); @@ -1026,12 +1047,14 @@ pub fn init_genesis_validator( alias, net_address, unsafe_dont_encrypt, + key_scheme, }: args::InitGenesisValidator, ) { let pre_genesis_dir = validator_pre_genesis_dir(&global_args.base_dir, &alias); println!("Generating validator keys..."); let pre_genesis = pre_genesis::ValidatorWallet::gen_and_store( + key_scheme, unsafe_dont_encrypt, &pre_genesis_dir, ) @@ -1136,16 +1159,21 @@ fn network_configs_url_prefix(chain_id: &ChainId) -> String { fn write_tendermint_node_key( tm_home_dir: &Path, - node_sk: ed25519::SecretKey, -) -> ed25519::PublicKey { - let node_pk: ed25519::PublicKey = node_sk.ref_to(); + node_sk: common::SecretKey, +) -> common::PublicKey { + let node_pk: common::PublicKey = node_sk.ref_to(); // Convert and write the keypair into Tendermint // node_key.json file let node_keypair = [node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()].concat(); + + let key_str = match node_sk { + common::SecretKey::Ed25519(_) => "Ed25519", + common::SecretKey::Secp256k1(_) => "Secp256k1", + }; let tm_node_keypair_json = json!({ "priv_key": { - "type": "tendermint/PrivKeyEd25519", + "type": format!("tendermint/PrivKey{}",key_str), "value": base64::encode(node_keypair), } }); diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 5fec7dca98..7b0afd899d 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -101,11 +101,12 @@ impl Wallet { /// key. pub fn gen_key( &mut self, + scheme: SchemeType, alias: Option, unsafe_dont_encrypt: bool, ) -> (String, Rc) { let password = read_and_confirm_pwd(unsafe_dont_encrypt); - let (alias, key) = self.store.gen_key(alias, password); + let (alias, key) = self.store.gen_key(scheme, alias, password); // Cache the newly added key self.decrypted_key_cache.insert(alias.clone(), key.clone()); (alias.into(), key) @@ -134,6 +135,7 @@ impl Wallet { Some(Err(err)) => Err(err), other => Ok(Store::gen_validator_keys( other.map(|res| res.unwrap().as_ref().clone()), + SchemeType::Common )), } } diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 6ecb396004..49fd2bbeaf 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -2,9 +2,9 @@ use std::fs; use std::path::{Path, PathBuf}; use std::rc::Rc; +use anoma::types::key::{common, SchemeType}; use ark_serialize::{Read, Write}; use file_lock::{FileLock, FileOptions}; -use namada::types::key::common; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -66,10 +66,11 @@ impl ValidatorWallet { /// Generate a new [`ValidatorWallet`] with required pre-genesis keys and /// store it as TOML at the given path. pub fn gen_and_store( + scheme: SchemeType, unsafe_dont_encrypt: bool, store_dir: &Path, ) -> std::io::Result { - let validator = Self::gen(unsafe_dont_encrypt); + let validator = Self::gen(scheme, unsafe_dont_encrypt); let data = validator.store.encode(); let wallet_path = validator_file_name(store_dir); // Make sure the dir exists @@ -140,14 +141,15 @@ impl ValidatorWallet { /// Generate a new [`Validator`] with required pre-genesis keys. Will prompt /// for password when `!unsafe_dont_encrypt`. - fn gen(unsafe_dont_encrypt: bool) -> Self { + fn gen(scheme: SchemeType, unsafe_dont_encrypt: bool) -> Self { let password = wallet::read_and_confirm_pwd(unsafe_dont_encrypt); - let (account_key, account_sk) = gen_key_to_store(&password); - let (consensus_key, consensus_sk) = gen_key_to_store(&password); - let (rewards_key, rewards_sk) = gen_key_to_store(&password); + let (account_key, account_sk) = gen_key_to_store(scheme, &password); + let (consensus_key, consensus_sk) = gen_key_to_store(scheme, &password); + let (rewards_key, rewards_sk) = gen_key_to_store(scheme, &password); let (tendermint_node_key, tendermint_node_sk) = - gen_key_to_store(&password); - let validator_keys = store::Store::gen_validator_keys(None); + gen_key_to_store(scheme, &password); + let validator_keys = + store::Store::gen_validator_keys(None, SchemeType::Common); let store = ValidatorStore { account_key, consensus_key, @@ -180,9 +182,10 @@ impl ValidatorStore { } fn gen_key_to_store( + scheme: SchemeType, password: &Option, ) -> (StoredKeypair, Rc) { - let sk = store::gen_sk(); + let sk = store::gen_sk(scheme); StoredKeypair::new(sk, password.clone()) } diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 95454d3d2a..cabaa6eb49 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -259,10 +259,11 @@ impl Store { /// pointer to the key. pub fn gen_key( &mut self, + scheme: SchemeType, alias: Option, password: Option, ) -> (Alias, Rc) { - let sk = gen_sk(); + let sk = gen_sk(scheme); let pkh: PublicKeyHash = PublicKeyHash::from(&sk.ref_to()); let (keypair_to_store, raw_keypair) = StoredKeypair::new(sk, password); let address = Address::Implicit(ImplicitAddress(pkh.clone())); @@ -287,8 +288,9 @@ impl Store { /// Note that this removes the validator data. pub fn gen_validator_keys( protocol_keypair: Option, + scheme: SchemeType ) -> ValidatorKeys { - let protocol_keypair = protocol_keypair.unwrap_or_else(gen_sk); + let protocol_keypair = protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); let dkg_keypair = ferveo_common::Keypair::::new( &mut StdRng::from_entropy(), ); @@ -500,12 +502,17 @@ pub fn wallet_file(store_dir: impl AsRef) -> PathBuf { } /// Generate a new secret key. -pub fn gen_sk() -> common::SecretKey { +pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { use rand::rngs::OsRng; let mut csprng = OsRng {}; - ed25519::SigScheme::generate(&mut csprng) - .try_to_sk() - .unwrap() + match scheme { + SchemeType::Ed25519Consensus => + ed25519::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), + SchemeType::Secp256k1Consensus => + secp256k1::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), + SchemeType::Common => + common::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), + } } #[cfg(all(test, feature = "dev"))] @@ -515,7 +522,7 @@ mod test_wallet { #[test] fn test_toml_roundtrip() { let mut store = Store::new(); - let validator_keys = Store::gen_validator_keys(None); + let validator_keys = Store::gen_validator_keys(None,SchemeType::Common); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys From f03e86185dec3fab56fc48eac253acb9fd1e0dec Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 14:35:20 +0200 Subject: [PATCH 115/373] remove clippy::bind_instead_of_map now that we will use wildcard --- shared/src/types/key/ed25519.rs | 6 ------ shared/src/types/key/secp256k1.rs | 6 ------ 2 files changed, 12 deletions(-) diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index 4f3f9f02d7..d8f3765acd 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -31,8 +31,6 @@ impl super::PublicKey for PublicKey { pk: &PK, ) -> Result { if PK::TYPE == super::common::PublicKey::TYPE { - // TODO remove once the wildcard match is used below - #[allow(clippy::bind_instead_of_map)] super::common::PublicKey::try_from_pk(pk).and_then(|x| match x { super::common::PublicKey::Ed25519(epk) => Ok(epk), _ => Err(ParsePublicKeyError::MismatchedScheme), @@ -135,8 +133,6 @@ impl super::SecretKey for SecretKey { pk: &PK, ) -> Result { if PK::TYPE == super::common::SecretKey::TYPE { - // TODO remove once the wildcard match is used below - #[allow(clippy::bind_instead_of_map)] super::common::SecretKey::try_from_sk(pk).and_then(|x| match x { super::common::SecretKey::Ed25519(epk) => Ok(epk), _ => Err(ParseSecretKeyError::MismatchedScheme), @@ -238,8 +234,6 @@ impl super::Signature for Signature { pk: &PK, ) -> Result { if PK::TYPE == super::common::Signature::TYPE { - // TODO remove once the wildcard match is used below - #[allow(clippy::bind_instead_of_map)] super::common::Signature::try_from_sig(pk).and_then(|x| match x { super::common::Signature::Ed25519(epk) => Ok(epk), _ => Err(ParseSignatureError::MismatchedScheme), diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index a7efa973fc..c12d6152cc 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -34,8 +34,6 @@ impl super::PublicKey for PublicKey { pk: &PK, ) -> Result { if PK::TYPE == super::common::PublicKey::TYPE { - // TODO remove once the wildcard match is used below - #[allow(clippy::bind_instead_of_map)] super::common::PublicKey::try_from_pk(pk).and_then(|x| match x { super::common::PublicKey::Secp256k1(epk) => Ok(epk), _ => Err(ParsePublicKeyError::MismatchedScheme), @@ -149,8 +147,6 @@ impl super::SecretKey for SecretKey { pk: &PK, ) -> Result { if PK::TYPE == super::common::SecretKey::TYPE { - // TODO remove once the wildcard match is used below - #[allow(clippy::bind_instead_of_map)] super::common::SecretKey::try_from_sk(pk).and_then(|x| match x { super::common::SecretKey::Secp256k1(epk) => Ok(epk), _ => Err(ParseSecretKeyError::MismatchedScheme), @@ -291,8 +287,6 @@ impl super::Signature for Signature { pk: &PK, ) -> Result { if PK::TYPE == super::common::Signature::TYPE { - // TODO remove once the wildcard match is used below - #[allow(clippy::bind_instead_of_map)] super::common::Signature::try_from_sig(pk).and_then(|x| match x { super::common::Signature::Secp256k1(epk) => Ok(epk), _ => Err(ParseSignatureError::MismatchedScheme), From cf083e41d37d6aa1b11eaf5a4779ab227830608e Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 14:36:30 +0200 Subject: [PATCH 116/373] make libsecp256k1 objects public when wrapped within our own Key and Sig objects --- shared/src/types/key/secp256k1.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index c12d6152cc..48d4b61def 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -25,7 +25,7 @@ use super::{ /// secp256k1 public key #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct PublicKey(libsecp256k1::PublicKey); +pub struct PublicKey(pub libsecp256k1::PublicKey); impl super::PublicKey for PublicKey { const TYPE: SchemeType = SigScheme::TYPE; @@ -136,7 +136,7 @@ impl From for PublicKey { /// Secp256k1 secret key #[derive(Debug, Clone)] -pub struct SecretKey(libsecp256k1::SecretKey); +pub struct SecretKey(pub libsecp256k1::SecretKey); impl super::SecretKey for SecretKey { type PublicKey = PublicKey; @@ -278,7 +278,7 @@ impl RefTo for SecretKey { /// Secp256k1 signature #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Signature(libsecp256k1::Signature); +pub struct Signature(pub libsecp256k1::Signature); impl super::Signature for Signature { const TYPE: SchemeType = SigScheme::TYPE; From 38600f69d9737ba992dc1001907e224074848265 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 22:57:16 +0200 Subject: [PATCH 117/373] drop 'Consensus' from SchemeType enum variants --- apps/src/lib/cli.rs | 4 ++-- apps/src/lib/client/tx.rs | 6 +++--- apps/src/lib/wallet/store.rs | 4 ++-- shared/src/types/key/ed25519.rs | 2 +- shared/src/types/key/mod.rs | 12 ++++++------ shared/src/types/key/secp256k1.rs | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 127673b9f3..0c5ba692db 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1457,7 +1457,7 @@ pub mod args { const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); const RPC_SOCKET_ADDR: ArgOpt = arg_opt("rpc"); - const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519Consensus)); + const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); const SIGNER: ArgOpt = arg_opt("signer"); const SIGNING_KEY_OPT: ArgOpt = SIGNING_KEY.opt(); const SIGNING_KEY: Arg = arg("signing-key"); @@ -3049,7 +3049,7 @@ pub mod args { )) .arg(SCHEME.def().about( "The key scheme/type used for the validator keys. Currently \ - support ed25519 and secp256k1." + supports ed25519 and secp256k1." )) } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8022a4a27f..92ec1dc77d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -178,7 +178,7 @@ pub async fn submit_init_validator( println!("Generating validator account key..."); ctx.wallet .gen_key( - SchemeType::Ed25519Consensus, + SchemeType::Ed25519, Some(validator_key_alias.clone()), unsafe_dont_encrypt, ) @@ -191,7 +191,7 @@ pub async fn submit_init_validator( println!("Generating consensus key..."); ctx.wallet .gen_key( - SchemeType::Ed25519Consensus, + SchemeType::Ed25519, Some(consensus_key_alias.clone()), unsafe_dont_encrypt, ) @@ -203,7 +203,7 @@ pub async fn submit_init_validator( println!("Generating staking reward account key..."); ctx.wallet .gen_key( - SchemeType::Ed25519Consensus, + SchemeType::Ed25519, Some(rewards_key_alias.clone()), unsafe_dont_encrypt, ) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index cabaa6eb49..63e4f0d4b6 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -506,9 +506,9 @@ pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { use rand::rngs::OsRng; let mut csprng = OsRng {}; match scheme { - SchemeType::Ed25519Consensus => + SchemeType::Ed25519 => ed25519::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), - SchemeType::Secp256k1Consensus => + SchemeType::Secp256k1 => secp256k1::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), SchemeType::Common => common::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index d8f3765acd..dbcf9fe04c 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -321,7 +321,7 @@ impl super::SigScheme for SigScheme { type SecretKey = SecretKey; type Signature = Signature; - const TYPE: SchemeType = SchemeType::Ed25519Consensus; + const TYPE: SchemeType = SchemeType::Ed25519; #[cfg(feature = "rand")] fn generate(csprng: &mut R) -> SecretKey diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 6a95d2eb2d..f90cc4aa2e 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -129,10 +129,10 @@ pub trait TryFromRef: Sized { /// Type capturing signature scheme IDs #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub enum SchemeType { - /// Type identifier for Ed25519-consensus - Ed25519Consensus, - /// Type identifier for Secp256k1-consensus - Secp256k1Consensus, + /// Type identifier for Ed25519 scheme + Ed25519, + /// Type identifier for Secp256k1 scheme + Secp256k1, /// Type identifier for Common Common, } @@ -142,8 +142,8 @@ impl FromStr for SchemeType { fn from_str(input: &str) -> Result { match input.to_lowercase().as_str() { - "ed25519" => Ok(Self::Ed25519Consensus), - "secp256k1" => Ok(Self::Secp256k1Consensus), + "ed25519" => Ok(Self::Ed25519), + "secp256k1" => Ok(Self::Secp256k1), "common" => Ok(Self::Common), _ => Err(()), } diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 48d4b61def..572831d97c 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -427,7 +427,7 @@ impl super::SigScheme for SigScheme { type SecretKey = SecretKey; type Signature = Signature; - const TYPE: SchemeType = SchemeType::Secp256k1Consensus; + const TYPE: SchemeType = SchemeType::Secp256k1; #[cfg(feature = "rand")] fn generate(csprng: &mut R) -> SecretKey From 62acc40432b1718cd7549cfbf097e2070fd8f1d5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 23:49:27 +0200 Subject: [PATCH 118/373] remove Result layering for id_from_pk --- apps/src/lib/client/utils.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 3082b21c44..819243799f 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -335,7 +335,7 @@ pub async fn join_network( .. } = peer { - node_id.as_ref().unwrap() != peer_id + node_id != *peer_id } else { true } @@ -353,14 +353,11 @@ pub async fn join_network( const TENDERMINT_NODE_ID_LENGTH: usize = 20; /// Derive Tendermint node ID from public key -fn id_from_pk( - pk: &common::PublicKey, -) -> Result { - let pk_bytes = pk.try_to_vec(); - let digest = Sha256::digest(pk_bytes.unwrap().as_slice()); +fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { + let digest = Sha256::digest(pk.try_to_vec().unwrap().as_slice()); let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH]; bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); - Ok(TendermintNodeId::new(bytes)) + TendermintNodeId::new(bytes) } /// Initialize a new test network from the given configuration. @@ -458,7 +455,7 @@ pub fn init_network( }); // Derive the node ID from the node key - let node_id: TendermintNodeId = id_from_pk(&node_pk).unwrap(); + let node_id: TendermintNodeId = id_from_pk(&node_pk); // Build the list of persistent peers from the validators' node IDs let peer = TendermintAddress::from_str(&format!( From 588a84e0eb2afead657874a6372d29d1b603980d Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 28 Jun 2022 18:45:34 +0200 Subject: [PATCH 119/373] clean up code implementing Serialize/Deserialize, comment on certain implementations --- shared/src/types/key/secp256k1.rs | 40 +++++++------------------------ 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 572831d97c..a91f521bb1 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -165,14 +165,8 @@ impl Serialize for SecretKey { where S: Serializer, { - // not sure if this is how I should be doing this! - // https://serde.rs/impl-serialize.html! let arr = self.0.serialize(); - let mut seq = serializer.serialize_tuple(arr.len())?; - for elem in &arr[..] { - seq.serialize_element(elem)?; - } - seq.end() + serde::Serialize::serialize(&arr, serializer) } } @@ -181,34 +175,10 @@ impl<'de> Deserialize<'de> for SecretKey { where D: serde::Deserializer<'de> { - struct ByteArrayVisitor; - - impl<'de> Visitor<'de> for ByteArrayVisitor { - type Value = [u8; libsecp256k1::util::SECRET_KEY_SIZE]; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str(&format!("expecting an array of length {}", libsecp256k1::util::SECRET_KEY_SIZE)) - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut arr = [0u8; libsecp256k1::util::SECRET_KEY_SIZE]; - #[allow(clippy::needless_range_loop)] - for i in 0..libsecp256k1::util::SECRET_KEY_SIZE { - arr[i] = seq.next_element()? - .ok_or_else(|| Error::invalid_length(i, &self))?; - } - Ok(arr) - } - } - - let arr_res = deserializer.deserialize_tuple(libsecp256k1::util::SECRET_KEY_SIZE, ByteArrayVisitor)?; + let arr_res: [u8; libsecp256k1::util::SECRET_KEY_SIZE] = serde::Deserialize::deserialize(deserializer)?; let key = libsecp256k1::SecretKey::parse_slice(&arr_res) .map_err(D::Error::custom); Ok(SecretKey(key.unwrap())) - } } @@ -300,12 +270,18 @@ impl super::Signature for Signature { } } +// Would ideally like Serialize, Deserialize to be implemented in libsecp256k1, may try to do so and merge +// upstream in the future. + impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let arr = self.0.serialize(); + // TODO: implement the line below, currently cannot support [u8; 64] + // serde::Serialize::serialize(&arr, serializer) + let mut seq = serializer.serialize_tuple(arr.len())?; for elem in &arr[..] { seq.serialize_element(elem)?; From 7dd63cb8768af22980a84087684c4a4b37774eba Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 28 Jun 2022 22:44:23 +0200 Subject: [PATCH 120/373] change variable names in fns try_to_sk() and try_to_sig() to reduce confusion --- shared/src/types/key/common.rs | 32 ++++++++++++++++---------------- shared/src/types/key/mod.rs | 12 ++++++------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 1986799142..2543f0c056 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -135,23 +135,23 @@ impl super::SecretKey for SecretKey { const TYPE: SchemeType = SigScheme::TYPE; - fn try_from_sk( - pk: &PK, + fn try_from_sk( + sk: &SK, ) -> Result { - if PK::TYPE == Self::TYPE { - Self::try_from_slice(pk.try_to_vec().unwrap().as_ref()) + if SK::TYPE == Self::TYPE { + Self::try_from_slice(sk.try_to_vec().unwrap().as_ref()) .map_err(ParseSecretKeyError::InvalidEncoding) - } else if PK::TYPE == ed25519::SecretKey::TYPE { + } else if SK::TYPE == ed25519::SecretKey::TYPE { Ok(Self::Ed25519( ed25519::SecretKey::try_from_slice( - pk.try_to_vec().unwrap().as_ref(), + sk.try_to_vec().unwrap().as_ref(), ) .map_err(ParseSecretKeyError::InvalidEncoding)?, )) - } else if PK::TYPE == secp256k1::SecretKey::TYPE { + } else if SK::TYPE == secp256k1::SecretKey::TYPE { Ok(Self::Secp256k1( secp256k1::SecretKey::try_from_slice( - pk.try_to_vec().unwrap().as_ref(), + sk.try_to_vec().unwrap().as_ref(), ) .map_err(ParseSecretKeyError::InvalidEncoding)?, )) } else { @@ -209,23 +209,23 @@ pub enum Signature { impl super::Signature for Signature { const TYPE: SchemeType = SigScheme::TYPE; - fn try_from_sig( - pk: &PK, + fn try_from_sig( + sig: &SIG, ) -> Result { - if PK::TYPE == Self::TYPE { - Self::try_from_slice(pk.try_to_vec().unwrap().as_slice()) + if SIG::TYPE == Self::TYPE { + Self::try_from_slice(sig.try_to_vec().unwrap().as_slice()) .map_err(ParseSignatureError::InvalidEncoding) - } else if PK::TYPE == ed25519::Signature::TYPE { + } else if SIG::TYPE == ed25519::Signature::TYPE { Ok(Self::Ed25519( ed25519::Signature::try_from_slice( - pk.try_to_vec().unwrap().as_slice(), + sig.try_to_vec().unwrap().as_slice(), ) .map_err(ParseSignatureError::InvalidEncoding)?, )) - } else if PK::TYPE == secp256k1::Signature::TYPE { + } else if SIG::TYPE == secp256k1::Signature::TYPE { Ok(Self::Secp256k1( secp256k1::Signature::try_from_slice( - pk.try_to_vec().unwrap().as_slice(), + sig.try_to_vec().unwrap().as_slice(), ) .map_err(ParseSignatureError::InvalidEncoding)?, )) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index f90cc4aa2e..9ce1b03838 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -158,11 +158,11 @@ pub trait Signature: /// The scheme type of this implementation const TYPE: SchemeType; /// Convert from one Signature type to another - fn try_from_sig( - pk: &PK, + fn try_from_sig( + sig: &SIG, ) -> Result { - if PK::TYPE == Self::TYPE { - let sig_arr = pk.try_to_vec().unwrap(); + if SIG::TYPE == Self::TYPE { + let sig_arr = sig.try_to_vec().unwrap(); let res = Self::try_from_slice(sig_arr.as_ref()); res.map_err(ParseSignatureError::InvalidEncoding) } else { @@ -170,8 +170,8 @@ pub trait Signature: } } /// Convert from self to another SecretKey type - fn try_to_sig(&self) -> Result { - PK::try_from_sig(self) + fn try_to_sig(&self) -> Result { + SIG::try_from_sig(self) } } From b6fdc27c1186d661a9aa34a6fd32dff8638bde2e Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 12:57:36 +0200 Subject: [PATCH 121/373] convert from common to underlying key type in id_from_pk() when constructing the TendermintNodeId --- apps/src/lib/client/utils.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 819243799f..f4071b8d85 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -354,9 +354,20 @@ const TENDERMINT_NODE_ID_LENGTH: usize = 20; /// Derive Tendermint node ID from public key fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { - let digest = Sha256::digest(pk.try_to_vec().unwrap().as_slice()); let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH]; - bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); + + match pk { + common::PublicKey::Ed25519(_) => { + let _pk: ed25519::PublicKey = pk.try_to_pk().unwrap(); + let digest = Sha256::digest(_pk.try_to_vec().unwrap().as_slice()); + bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); + }, + common::PublicKey::Secp256k1(_) => { + let _pk: secp256k1::PublicKey = pk.try_to_pk().unwrap(); + let digest = Sha256::digest(_pk.try_to_vec().unwrap().as_slice()); + bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); + }, + } TendermintNodeId::new(bytes) } From 58dfdd158fb22bdcab3310508333151b6dd05c79 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 14:38:57 +0200 Subject: [PATCH 122/373] improve write_tendermint_node_key() to produce the proper json given the underlying key scheme --- apps/src/lib/client/utils.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index f4071b8d85..48cdfce7be 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1170,15 +1170,20 @@ fn write_tendermint_node_key( node_sk: common::SecretKey, ) -> common::PublicKey { let node_pk: common::PublicKey = node_sk.ref_to(); - // Convert and write the keypair into Tendermint - // node_key.json file - let node_keypair = - [node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()].concat(); - - let key_str = match node_sk { - common::SecretKey::Ed25519(_) => "Ed25519", - common::SecretKey::Secp256k1(_) => "Secp256k1", + + // Convert and write the keypair into Tendermint node_key.json file. + // Tendermint requires concatenating the private-public keys for ed25519 + // but does not for secp256k1. + let (node_keypair, key_str) = match node_sk { + common::SecretKey::Ed25519(_) => { + ([node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()].concat(), + "Ed25519") + }, + common::SecretKey::Secp256k1(_) => { + (node_sk.try_to_vec().unwrap(), "Secp256k1") + }, }; + let tm_node_keypair_json = json!({ "priv_key": { "type": format!("tendermint/PrivKey{}",key_str), From 71c70a4f90b2314d6cea056eb07422114ac8939b Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 22:07:21 +0200 Subject: [PATCH 123/373] allow CL specification of a specific key scheme for the TxInitValidator --- apps/src/lib/cli.rs | 7 +++++++ apps/src/lib/client/tx.rs | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 0c5ba692db..837895d8fd 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1690,6 +1690,7 @@ pub mod args { pub struct TxInitValidator { pub tx: Tx, pub source: WalletAddress, + pub scheme: SchemeType, pub account_key: Option, pub consensus_key: Option, pub rewards_account_key: Option, @@ -1703,6 +1704,7 @@ pub mod args { fn parse(matches: &ArgMatches) -> Self { let tx = Tx::parse(matches); let source = SOURCE.parse(matches); + let scheme = SCHEME.parse(matches); let account_key = VALIDATOR_ACCOUNT_KEY.parse(matches); let consensus_key = VALIDATOR_CONSENSUS_KEY.parse(matches); let rewards_account_key = REWARDS_KEY.parse(matches); @@ -1713,6 +1715,7 @@ pub mod args { Self { tx, source, + scheme, account_key, consensus_key, rewards_account_key, @@ -1728,6 +1731,10 @@ pub mod args { .arg(SOURCE.def().about( "The source account's address that signs the transaction.", )) + .arg(SCHEME.def().about( + "The key scheme/type used for the validator keys. Currently \ + supports ed25519 and secp256k1." + )) .arg(VALIDATOR_ACCOUNT_KEY.def().about( "A public key for the validator account. A new one will \ be generated if none given.", diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 92ec1dc77d..6404e09555 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -156,6 +156,7 @@ pub async fn submit_init_validator( args::TxInitValidator { tx: tx_args, source, + scheme, account_key, consensus_key, rewards_account_key, @@ -178,7 +179,7 @@ pub async fn submit_init_validator( println!("Generating validator account key..."); ctx.wallet .gen_key( - SchemeType::Ed25519, + scheme, Some(validator_key_alias.clone()), unsafe_dont_encrypt, ) @@ -191,7 +192,7 @@ pub async fn submit_init_validator( println!("Generating consensus key..."); ctx.wallet .gen_key( - SchemeType::Ed25519, + scheme, Some(consensus_key_alias.clone()), unsafe_dont_encrypt, ) @@ -203,7 +204,7 @@ pub async fn submit_init_validator( println!("Generating staking reward account key..."); ctx.wallet .gen_key( - SchemeType::Ed25519, + scheme, Some(rewards_key_alias.clone()), unsafe_dont_encrypt, ) From 3331035b52f113a5869a82bb999aaad2bde8d566 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 22:08:14 +0200 Subject: [PATCH 124/373] clean and simplify code in id_from_pk() --- apps/src/lib/client/utils.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 48cdfce7be..96967617c2 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -355,19 +355,12 @@ const TENDERMINT_NODE_ID_LENGTH: usize = 20; /// Derive Tendermint node ID from public key fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH]; - - match pk { - common::PublicKey::Ed25519(_) => { - let _pk: ed25519::PublicKey = pk.try_to_pk().unwrap(); - let digest = Sha256::digest(_pk.try_to_vec().unwrap().as_slice()); - bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); - }, - common::PublicKey::Secp256k1(_) => { - let _pk: secp256k1::PublicKey = pk.try_to_pk().unwrap(); - let digest = Sha256::digest(_pk.try_to_vec().unwrap().as_slice()); - bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); - }, - } + let pk_bytes = match pk { + common::PublicKey::Ed25519(_pk) => _pk.try_to_vec().unwrap(), + common::PublicKey::Secp256k1(_pk) => _pk.try_to_vec().unwrap(), + }; + let digest = Sha256::digest(pk_bytes.as_slice()); + bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); TendermintNodeId::new(bytes) } From e4c234491151cf7e37c38fc711c6185d161b210b Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 15 Jun 2022 13:48:42 +0200 Subject: [PATCH 125/373] initial commit for supporting secp256k1 keys --- shared/src/types/key/common.rs | 25 ++++++++++++++++++------- shared/src/types/key/mod.rs | 7 +++++-- shared/src/types/key/secp256k1.rs | 6 ------ 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 2543f0c056..e536a7171d 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -9,8 +9,9 @@ use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use super::{ - ed25519, secp256k1, ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, - RefTo, SchemeType, SigScheme as SigSchemeTrait, VerifySigError, + ed25519, secp256k1, ParsePublicKeyError, ParseSecretKeyError, + ParseSignatureError, RefTo, SchemeType, SigScheme as SigSchemeTrait, + VerifySigError, }; /// Public key @@ -100,11 +101,11 @@ impl Serialize for SecretKey { { // String encoded, because toml doesn't support enums let prefix = match self { - SecretKey::Ed25519(_) => "ED25519_SK_PREFIX", + SecretKey::Ed25519(_) => "ED25519_SK_PREFIX", SecretKey::Secp256k1(_) => "SECP256K1_SK_PREFIX", }; - let keypair_string = format!("{}{}",prefix,self); - Serialize::serialize(&keypair_string,serializer) + let keypair_string = format!("{}{}", prefix, self); + Serialize::serialize(&keypair_string, serializer) } } @@ -120,7 +121,9 @@ impl<'de> Deserialize<'de> for SecretKey { .map_err(D::Error::custom)?; if let Some(raw) = keypair_string.strip_prefix("ED25519_SK_PREFIX") { SecretKey::from_str(raw).map_err(D::Error::custom) - } else if let Some(raw) = keypair_string.strip_prefix("SECP256K1_SK_PREFIX") { + } else if let Some(raw) = + keypair_string.strip_prefix("SECP256K1_SK_PREFIX") + { SecretKey::from_str(raw).map_err(D::Error::custom) } else { Err(D::Error::custom( @@ -154,7 +157,8 @@ impl super::SecretKey for SecretKey { sk.try_to_vec().unwrap().as_ref(), ) .map_err(ParseSecretKeyError::InvalidEncoding)?, - )) } else { + )) + } else { Err(ParseSecretKeyError::MismatchedScheme) } } @@ -229,6 +233,13 @@ impl super::Signature for Signature { ) .map_err(ParseSignatureError::InvalidEncoding)?, )) + } else if SIG::TYPE == secp256k1::Signature::TYPE { + Ok(Self::Secp256k1( + secp256k1::Signature::try_from_slice( + sig.try_to_vec().unwrap().as_slice(), + ) + .map_err(ParseSignatureError::InvalidEncoding)?, + )) } else { Err(ParseSignatureError::MismatchedScheme) } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 9ce1b03838..a1323a4bc6 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -444,7 +444,10 @@ macro_rules! sigscheme_test { let mut rng: ThreadRng = thread_rng(); let sk = <$type>::generate(&mut rng); let sig = <$type>::sign(&sk, b"hello"); - assert!(<$type>::verify_signature_raw(&sk.ref_to(), b"hello", &sig).is_ok()); + assert!( + <$type>::verify_signature_raw(&sk.ref_to(), b"hello", &sig) + .is_ok() + ); } } }; @@ -475,4 +478,4 @@ mod more_tests { core::slice::from_raw_parts(ptr, len) }); } -} \ No newline at end of file +} diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index a91f521bb1..33482cafc2 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -270,18 +270,12 @@ impl super::Signature for Signature { } } -// Would ideally like Serialize, Deserialize to be implemented in libsecp256k1, may try to do so and merge -// upstream in the future. - impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let arr = self.0.serialize(); - // TODO: implement the line below, currently cannot support [u8; 64] - // serde::Serialize::serialize(&arr, serializer) - let mut seq = serializer.serialize_tuple(arr.len())?; for elem in &arr[..] { seq.serialize_element(elem)?; From a3f559e2a5acfc5ef9686477ea32240443bbb8e3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Jun 2022 18:11:56 +0200 Subject: [PATCH 126/373] command line options for specifying key scheme --- apps/src/lib/cli.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 837895d8fd..f546860cfd 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1457,7 +1457,8 @@ pub mod args { const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); const RPC_SOCKET_ADDR: ArgOpt = arg_opt("rpc"); - const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); + const SCHEME: ArgDefault = + arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); const SIGNER: ArgOpt = arg_opt("signer"); const SIGNING_KEY_OPT: ArgOpt = SIGNING_KEY.opt(); const SIGNING_KEY: Arg = arg("signing-key"); @@ -1732,8 +1733,8 @@ pub mod args { "The source account's address that signs the transaction.", )) .arg(SCHEME.def().about( - "The key scheme/type used for the validator keys. Currently \ - supports ed25519 and secp256k1." + "The key scheme/type used for the validator keys. \ + Currently supports ed25519 and secp256k1.", )) .arg(VALIDATOR_ACCOUNT_KEY.def().about( "A public key for the validator account. A new one will \ @@ -2752,8 +2753,8 @@ pub mod args { fn def(app: App) -> App { app.arg(SCHEME.def().about( "The type of key that should be generated. Argument must be \ - either ed25519 or secp256k1. If none provided, the default key scheme \ - is ed25519.", + either ed25519 or secp256k1. If none provided, the default \ + key scheme is ed25519.", )) .arg(ALIAS_OPT.def().about( "The key and address alias. If none provided, the alias will \ @@ -3055,8 +3056,8 @@ pub mod args { use this for keys used in a live network.", )) .arg(SCHEME.def().about( - "The key scheme/type used for the validator keys. Currently \ - supports ed25519 and secp256k1." + "The key scheme/type used for the validator keys. \ + Currently supports ed25519 and secp256k1.", )) } } From da3783ebeb844fa3746ad28bdf267b9e9753d073 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Jun 2022 18:19:19 +0200 Subject: [PATCH 127/373] incorporate options into key generation functions --- apps/src/lib/client/tx.rs | 6 +++--- apps/src/lib/client/utils.rs | 33 +++++++++++++++--------------- apps/src/lib/wallet/pre_genesis.rs | 5 ++--- apps/src/lib/wallet/store.rs | 27 ++++++++++++++++-------- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6404e09555..74d9d9dab0 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -179,7 +179,7 @@ pub async fn submit_init_validator( println!("Generating validator account key..."); ctx.wallet .gen_key( - scheme, + SchemeType::Ed25519Consensus, Some(validator_key_alias.clone()), unsafe_dont_encrypt, ) @@ -192,7 +192,7 @@ pub async fn submit_init_validator( println!("Generating consensus key..."); ctx.wallet .gen_key( - scheme, + SchemeType::Ed25519Consensus, Some(consensus_key_alias.clone()), unsafe_dont_encrypt, ) @@ -204,7 +204,7 @@ pub async fn submit_init_validator( println!("Generating staking reward account key..."); ctx.wallet .gen_key( - scheme, + SchemeType::Ed25519Consensus, Some(rewards_key_alias.clone()), unsafe_dont_encrypt, ) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 96967617c2..637cbd3e55 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -335,7 +335,7 @@ pub async fn join_network( .. } = peer { - node_id != *peer_id + node_id.as_ref().unwrap() != peer_id } else { true } @@ -353,7 +353,11 @@ pub async fn join_network( const TENDERMINT_NODE_ID_LENGTH: usize = 20; /// Derive Tendermint node ID from public key -fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { +fn id_from_pk( + pk: &common::PublicKey, +) -> Result { + let pk_bytes = pk.try_to_vec(); + let digest = Sha256::digest(pk_bytes.unwrap().as_slice()); let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH]; let pk_bytes = match pk { common::PublicKey::Ed25519(_pk) => _pk.try_to_vec().unwrap(), @@ -361,7 +365,7 @@ fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { }; let digest = Sha256::digest(pk_bytes.as_slice()); bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); - TendermintNodeId::new(bytes) + Ok(TendermintNodeId::new(bytes)) } /// Initialize a new test network from the given configuration. @@ -459,7 +463,7 @@ pub fn init_network( }); // Derive the node ID from the node key - let node_id: TendermintNodeId = id_from_pk(&node_pk); + let node_id: TendermintNodeId = id_from_pk(&node_pk).unwrap(); // Build the list of persistent peers from the validators' node IDs let peer = TendermintAddress::from_str(&format!( @@ -1163,20 +1167,15 @@ fn write_tendermint_node_key( node_sk: common::SecretKey, ) -> common::PublicKey { let node_pk: common::PublicKey = node_sk.ref_to(); - - // Convert and write the keypair into Tendermint node_key.json file. - // Tendermint requires concatenating the private-public keys for ed25519 - // but does not for secp256k1. - let (node_keypair, key_str) = match node_sk { - common::SecretKey::Ed25519(_) => { - ([node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()].concat(), - "Ed25519") - }, - common::SecretKey::Secp256k1(_) => { - (node_sk.try_to_vec().unwrap(), "Secp256k1") - }, + // Convert and write the keypair into Tendermint + // node_key.json file + let node_keypair = + [node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()].concat(); + + let key_str = match node_sk { + common::SecretKey::Ed25519(_) => "Ed25519", + common::SecretKey::Secp256k1(_) => "Secp256k1", }; - let tm_node_keypair_json = json!({ "priv_key": { "type": format!("tendermint/PrivKey{}",key_str), diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 49fd2bbeaf..4a6b4a4679 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -2,9 +2,9 @@ use std::fs; use std::path::{Path, PathBuf}; use std::rc::Rc; -use anoma::types::key::{common, SchemeType}; use ark_serialize::{Read, Write}; use file_lock::{FileLock, FileOptions}; +use namada::types::key::{common, SchemeType}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -148,8 +148,7 @@ impl ValidatorWallet { let (rewards_key, rewards_sk) = gen_key_to_store(scheme, &password); let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store(scheme, &password); - let validator_keys = - store::Store::gen_validator_keys(None, SchemeType::Common); + let validator_keys = store::Store::gen_validator_keys(None, scheme); let store = ValidatorStore { account_key, consensus_key, diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 63e4f0d4b6..70af381426 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -288,9 +288,10 @@ impl Store { /// Note that this removes the validator data. pub fn gen_validator_keys( protocol_keypair: Option, - scheme: SchemeType + scheme: SchemeType, ) -> ValidatorKeys { - let protocol_keypair = protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); + let protocol_keypair = + protocol_keypair.unwrap_or_else(|| gen_sk(scheme)); let dkg_keypair = ferveo_common::Keypair::::new( &mut StdRng::from_entropy(), ); @@ -506,12 +507,19 @@ pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { use rand::rngs::OsRng; let mut csprng = OsRng {}; match scheme { - SchemeType::Ed25519 => - ed25519::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), - SchemeType::Secp256k1 => - secp256k1::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), - SchemeType::Common => - common::SigScheme::generate(&mut csprng).try_to_sk().unwrap(), + SchemeType::Ed25519Consensus => { + ed25519::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap() + } + SchemeType::Secp256k1Consensus => { + secp256k1::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap() + } + SchemeType::Common => common::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(), } } @@ -522,7 +530,8 @@ mod test_wallet { #[test] fn test_toml_roundtrip() { let mut store = Store::new(); - let validator_keys = Store::gen_validator_keys(None,SchemeType::Common); + let validator_keys = + Store::gen_validator_keys(None, SchemeType::Common); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys From 58fd9670cf353ed3625377eaf7b457174429df13 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 22:57:16 +0200 Subject: [PATCH 128/373] drop 'Consensus' from SchemeType enum variants --- apps/src/lib/cli.rs | 4 ++-- apps/src/lib/client/tx.rs | 6 +++--- apps/src/lib/wallet/store.rs | 16 ++++++---------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f546860cfd..8b0360e1a0 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3056,8 +3056,8 @@ pub mod args { use this for keys used in a live network.", )) .arg(SCHEME.def().about( - "The key scheme/type used for the validator keys. \ - Currently supports ed25519 and secp256k1.", + "The key scheme/type used for the validator keys. Currently \ + supports ed25519 and secp256k1." )) } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 74d9d9dab0..f6d79d2b55 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -179,7 +179,7 @@ pub async fn submit_init_validator( println!("Generating validator account key..."); ctx.wallet .gen_key( - SchemeType::Ed25519Consensus, + SchemeType::Ed25519, Some(validator_key_alias.clone()), unsafe_dont_encrypt, ) @@ -192,7 +192,7 @@ pub async fn submit_init_validator( println!("Generating consensus key..."); ctx.wallet .gen_key( - SchemeType::Ed25519Consensus, + SchemeType::Ed25519, Some(consensus_key_alias.clone()), unsafe_dont_encrypt, ) @@ -204,7 +204,7 @@ pub async fn submit_init_validator( println!("Generating staking reward account key..."); ctx.wallet .gen_key( - SchemeType::Ed25519Consensus, + SchemeType::Ed25519, Some(rewards_key_alias.clone()), unsafe_dont_encrypt, ) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 70af381426..d60af1be96 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -507,16 +507,12 @@ pub fn gen_sk(scheme: SchemeType) -> common::SecretKey { use rand::rngs::OsRng; let mut csprng = OsRng {}; match scheme { - SchemeType::Ed25519Consensus => { - ed25519::SigScheme::generate(&mut csprng) - .try_to_sk() - .unwrap() - } - SchemeType::Secp256k1Consensus => { - secp256k1::SigScheme::generate(&mut csprng) - .try_to_sk() - .unwrap() - } + SchemeType::Ed25519 => ed25519::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(), + SchemeType::Secp256k1 => secp256k1::SigScheme::generate(&mut csprng) + .try_to_sk() + .unwrap(), SchemeType::Common => common::SigScheme::generate(&mut csprng) .try_to_sk() .unwrap(), From ea859956c3751eec585419ed954a063c51965888 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 23:49:27 +0200 Subject: [PATCH 129/373] remove Result layering for id_from_pk --- apps/src/lib/client/utils.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 637cbd3e55..5dde08ff76 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -335,7 +335,7 @@ pub async fn join_network( .. } = peer { - node_id.as_ref().unwrap() != peer_id + node_id != *peer_id } else { true } @@ -353,11 +353,8 @@ pub async fn join_network( const TENDERMINT_NODE_ID_LENGTH: usize = 20; /// Derive Tendermint node ID from public key -fn id_from_pk( - pk: &common::PublicKey, -) -> Result { - let pk_bytes = pk.try_to_vec(); - let digest = Sha256::digest(pk_bytes.unwrap().as_slice()); +fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { + let digest = Sha256::digest(pk.try_to_vec().unwrap().as_slice()); let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH]; let pk_bytes = match pk { common::PublicKey::Ed25519(_pk) => _pk.try_to_vec().unwrap(), @@ -365,7 +362,7 @@ fn id_from_pk( }; let digest = Sha256::digest(pk_bytes.as_slice()); bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); - Ok(TendermintNodeId::new(bytes)) + TendermintNodeId::new(bytes) } /// Initialize a new test network from the given configuration. @@ -463,7 +460,7 @@ pub fn init_network( }); // Derive the node ID from the node key - let node_id: TendermintNodeId = id_from_pk(&node_pk).unwrap(); + let node_id: TendermintNodeId = id_from_pk(&node_pk); // Build the list of persistent peers from the validators' node IDs let peer = TendermintAddress::from_str(&format!( From 506e0b591d026a59e4821637b246c0744a0d988d Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 28 Jun 2022 18:45:34 +0200 Subject: [PATCH 130/373] clean up code implementing Serialize/Deserialize, comment on certain implementations --- shared/src/types/key/secp256k1.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 33482cafc2..a91f521bb1 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -270,12 +270,18 @@ impl super::Signature for Signature { } } +// Would ideally like Serialize, Deserialize to be implemented in libsecp256k1, may try to do so and merge +// upstream in the future. + impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let arr = self.0.serialize(); + // TODO: implement the line below, currently cannot support [u8; 64] + // serde::Serialize::serialize(&arr, serializer) + let mut seq = serializer.serialize_tuple(arr.len())?; for elem in &arr[..] { seq.serialize_element(elem)?; From bc3b5b368b7203541a91eb59cd5caace9d0e79f4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 28 Jun 2022 22:44:23 +0200 Subject: [PATCH 131/373] change variable names in fns try_to_sk() and try_to_sig() to reduce confusion --- shared/src/types/key/common.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index e536a7171d..3cdec73bb9 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -233,13 +233,6 @@ impl super::Signature for Signature { ) .map_err(ParseSignatureError::InvalidEncoding)?, )) - } else if SIG::TYPE == secp256k1::Signature::TYPE { - Ok(Self::Secp256k1( - secp256k1::Signature::try_from_slice( - sig.try_to_vec().unwrap().as_slice(), - ) - .map_err(ParseSignatureError::InvalidEncoding)?, - )) } else { Err(ParseSignatureError::MismatchedScheme) } From bf2ef9770511e8ba7124fe959e751a973df3edd8 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 12:57:36 +0200 Subject: [PATCH 132/373] convert from common to underlying key type in id_from_pk() when constructing the TendermintNodeId --- apps/src/lib/client/utils.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 5dde08ff76..044639bd64 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -354,14 +354,20 @@ const TENDERMINT_NODE_ID_LENGTH: usize = 20; /// Derive Tendermint node ID from public key fn id_from_pk(pk: &common::PublicKey) -> TendermintNodeId { - let digest = Sha256::digest(pk.try_to_vec().unwrap().as_slice()); let mut bytes = [0u8; TENDERMINT_NODE_ID_LENGTH]; - let pk_bytes = match pk { - common::PublicKey::Ed25519(_pk) => _pk.try_to_vec().unwrap(), - common::PublicKey::Secp256k1(_pk) => _pk.try_to_vec().unwrap(), - }; - let digest = Sha256::digest(pk_bytes.as_slice()); - bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); + + match pk { + common::PublicKey::Ed25519(_) => { + let _pk: ed25519::PublicKey = pk.try_to_pk().unwrap(); + let digest = Sha256::digest(_pk.try_to_vec().unwrap().as_slice()); + bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); + } + common::PublicKey::Secp256k1(_) => { + let _pk: secp256k1::PublicKey = pk.try_to_pk().unwrap(); + let digest = Sha256::digest(_pk.try_to_vec().unwrap().as_slice()); + bytes.copy_from_slice(&digest[..TENDERMINT_NODE_ID_LENGTH]); + } + } TendermintNodeId::new(bytes) } From 029ee3a806f856e3bfcc0816e528c4a4ff4dce77 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 14:38:57 +0200 Subject: [PATCH 133/373] improve write_tendermint_node_key() to produce the proper json given the underlying key scheme --- apps/src/lib/client/utils.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 044639bd64..4bda774614 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1170,15 +1170,20 @@ fn write_tendermint_node_key( node_sk: common::SecretKey, ) -> common::PublicKey { let node_pk: common::PublicKey = node_sk.ref_to(); - // Convert and write the keypair into Tendermint - // node_key.json file - let node_keypair = - [node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()].concat(); - - let key_str = match node_sk { - common::SecretKey::Ed25519(_) => "Ed25519", - common::SecretKey::Secp256k1(_) => "Secp256k1", + + // Convert and write the keypair into Tendermint node_key.json file. + // Tendermint requires concatenating the private-public keys for ed25519 + // but does not for secp256k1. + let (node_keypair, key_str) = match node_sk { + common::SecretKey::Ed25519(_) => { + ([node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()].concat(), + "Ed25519") + }, + common::SecretKey::Secp256k1(_) => { + (node_sk.try_to_vec().unwrap(), "Secp256k1") + }, }; + let tm_node_keypair_json = json!({ "priv_key": { "type": format!("tendermint/PrivKey{}",key_str), From 06a25f36f02f94f508a4fb52df8335d9a9cdeef7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 22:07:21 +0200 Subject: [PATCH 134/373] allow CL specification of a specific key scheme for the TxInitValidator --- apps/src/lib/cli.rs | 4 ++-- apps/src/lib/client/tx.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 8b0360e1a0..f546860cfd 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3056,8 +3056,8 @@ pub mod args { use this for keys used in a live network.", )) .arg(SCHEME.def().about( - "The key scheme/type used for the validator keys. Currently \ - supports ed25519 and secp256k1." + "The key scheme/type used for the validator keys. \ + Currently supports ed25519 and secp256k1.", )) } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index f6d79d2b55..6404e09555 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -179,7 +179,7 @@ pub async fn submit_init_validator( println!("Generating validator account key..."); ctx.wallet .gen_key( - SchemeType::Ed25519, + scheme, Some(validator_key_alias.clone()), unsafe_dont_encrypt, ) @@ -192,7 +192,7 @@ pub async fn submit_init_validator( println!("Generating consensus key..."); ctx.wallet .gen_key( - SchemeType::Ed25519, + scheme, Some(consensus_key_alias.clone()), unsafe_dont_encrypt, ) @@ -204,7 +204,7 @@ pub async fn submit_init_validator( println!("Generating staking reward account key..."); ctx.wallet .gen_key( - SchemeType::Ed25519, + scheme, Some(rewards_key_alias.clone()), unsafe_dont_encrypt, ) From 50ed07719395f3ab77cf8f35d7d0cd8439bb5cb9 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 22:08:14 +0200 Subject: [PATCH 135/373] clean and simplify code in id_from_pk() --- apps/src/lib/client/utils.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 4bda774614..61bc45ad9b 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1175,13 +1175,14 @@ fn write_tendermint_node_key( // Tendermint requires concatenating the private-public keys for ed25519 // but does not for secp256k1. let (node_keypair, key_str) = match node_sk { - common::SecretKey::Ed25519(_) => { - ([node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()].concat(), - "Ed25519") - }, + common::SecretKey::Ed25519(_) => ( + [node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()] + .concat(), + "Ed25519", + ), common::SecretKey::Secp256k1(_) => { (node_sk.try_to_vec().unwrap(), "Secp256k1") - }, + } }; let tm_node_keypair_json = json!({ From 9a1f4f1e6b5304553ae9ed18405521ed84f39ae1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 4 Jul 2022 13:56:11 +0200 Subject: [PATCH 136/373] fix bug in supplying keypair to Tendermint --- apps/src/lib/client/utils.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 61bc45ad9b..4b4b5b2af0 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1175,13 +1175,13 @@ fn write_tendermint_node_key( // Tendermint requires concatenating the private-public keys for ed25519 // but does not for secp256k1. let (node_keypair, key_str) = match node_sk { - common::SecretKey::Ed25519(_) => ( - [node_sk.try_to_vec().unwrap(), node_pk.try_to_vec().unwrap()] + common::SecretKey::Ed25519(sk) => ( + [sk.try_to_vec().unwrap(), sk.ref_to().try_to_vec().unwrap()] .concat(), "Ed25519", ), - common::SecretKey::Secp256k1(_) => { - (node_sk.try_to_vec().unwrap(), "Secp256k1") + common::SecretKey::Secp256k1(sk) => { + (sk.try_to_vec().unwrap(), "Secp256k1") } }; From acaf062a2c20bef8037fd95deb755751ecd0c1d6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 4 Jul 2022 13:59:31 +0200 Subject: [PATCH 137/373] fix some comments --- shared/src/types/key/mod.rs | 5 ++--- shared/src/types/key/secp256k1.rs | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a1323a4bc6..c88fe85ffa 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -205,7 +205,7 @@ pub trait PublicKey: Err(ParsePublicKeyError::MismatchedScheme) } } - /// Convert from self to another SecretKey type + /// Convert from self to another PublicKey type fn try_to_pk(&self) -> Result { PK::try_from_pk(self) } @@ -434,8 +434,7 @@ macro_rules! sigscheme_test { println!("Secret key: {}", secret_key); } - /// Run `cargo test gen_keypair -- --nocapture` to generate a - /// new keypair. + /// Sign a simple message and verify the signature. #[test] fn gen_sign_verify() { use rand::prelude::ThreadRng; diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index a91f521bb1..af70f44b9a 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,8 +7,6 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::Serializer; - -//use libsecp256k1::util::SECRET_KEY_SIZE; use sha2::{Digest, Sha256}; #[cfg(feature = "rand")] From c6cc9a4a4529d51886a8a010b004733cf1defaca Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 4 Jul 2022 14:00:30 +0200 Subject: [PATCH 138/373] e2e test_genesis_validators(): make each validator have different key scheme --- tests/src/e2e/ledger_tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 552e675e67..edd171f6aa 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1540,7 +1540,7 @@ fn test_genesis_validators() -> Result<()> { // the given index let get_first_port = |ix: u8| net_address_port_0 + 6 * (ix as u16 + 1); - // 1. Setup 2 genesis validators + // 1. Setup 2 genesis validators, one with ed25519 keys (0) and one with secp256k1 keys (1) let validator_0_alias = "validator-0"; let validator_1_alias = "validator-1"; @@ -1552,6 +1552,7 @@ fn test_genesis_validators() -> Result<()> { "--unsafe-dont-encrypt", "--alias", validator_0_alias, + "--scheme ed25519", "--net-address", &format!("127.0.0.1:{}", get_first_port(0)), ], @@ -1588,6 +1589,7 @@ fn test_genesis_validators() -> Result<()> { "--unsafe-dont-encrypt", "--alias", validator_1_alias, + "--scheme secp256k1", "--net-address", &format!("127.0.0.1:{}", get_first_port(1)), ], From 476322ec5dd70792f3b055df86a7dd899381103b Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 5 Jul 2022 15:08:19 +0200 Subject: [PATCH 139/373] fix unit test test_toml_roundtrip to supply good validator keys --- apps/src/lib/wallet/store.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index d60af1be96..76a2053419 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -524,10 +524,23 @@ mod test_wallet { use super::*; #[test] - fn test_toml_roundtrip() { + fn test_toml_roundtrip_ed25519() { let mut store = Store::new(); let validator_keys = - Store::gen_validator_keys(None, SchemeType::Common); + Store::gen_validator_keys(None, SchemeType::Ed25519); + store.add_validator_data( + Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), + validator_keys + ); + let data = store.encode(); + let _ = Store::decode(data).expect("Test failed"); + } + + #[test] + fn test_toml_roundtrip_secp256k1() { + let mut store = Store::new(); + let validator_keys = + Store::gen_validator_keys(None, SchemeType::Secp256k1); store.add_validator_data( Address::decode("atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5").unwrap(), validator_keys From 5d6ccb39f15fb0d8ce0ba3f0679250a707cfbf6a Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 5 Jul 2022 15:09:49 +0200 Subject: [PATCH 140/373] make fmt --- apps/src/lib/wallet/mod.rs | 2 +- shared/src/types/key/secp256k1.rs | 73 +++++++++++++++++-------------- tests/src/e2e/ledger_tests.rs | 3 +- 3 files changed, 43 insertions(+), 35 deletions(-) diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 7b0afd899d..5bdf8e6261 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -135,7 +135,7 @@ impl Wallet { Some(Err(err)) => Err(err), other => Ok(Store::gen_validator_keys( other.map(|res| res.unwrap().as_ref().clone()), - SchemeType::Common + SchemeType::Common, )), } } diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index af70f44b9a..3aece8051f 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -5,22 +5,20 @@ use std::fmt::{Debug, Display}; use std::hash::{Hash, Hasher}; use std::io::{ErrorKind, Write}; use std::str::FromStr; -use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use serde::Serializer; -use sha2::{Digest, Sha256}; +use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; -use serde::{Deserialize,Serialize}; use serde::de::{Error, SeqAccess, Visitor}; use serde::ser::SerializeTuple; +use serde::{Deserialize, Serialize, Serializer}; +use sha2::{Digest, Sha256}; use super::{ ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, SchemeType, SigScheme as SigSchemeTrait, VerifySigError, }; - /// secp256k1 public key #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct PublicKey(pub libsecp256k1::PublicKey); @@ -50,9 +48,9 @@ impl BorshDeserialize for PublicKey { // deserialize the bytes first let pk = libsecp256k1::PublicKey::parse_compressed( buf.get(0..libsecp256k1::util::COMPRESSED_PUBLIC_KEY_SIZE) - .ok_or_else(|| std::io::Error::from(ErrorKind::UnexpectedEof))? - .try_into() - .unwrap(), + .ok_or_else(|| std::io::Error::from(ErrorKind::UnexpectedEof))? + .try_into() + .unwrap(), ) .map_err(|e| { std::io::Error::new( @@ -100,13 +98,17 @@ impl Hash for PublicKey { impl PartialOrd for PublicKey { fn partial_cmp(&self, other: &Self) -> Option { - self.0.serialize_compressed().partial_cmp(&other.0.serialize_compressed()) + self.0 + .serialize_compressed() + .partial_cmp(&other.0.serialize_compressed()) } } impl Ord for PublicKey { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.serialize_compressed().cmp(&other.0.serialize_compressed()) + self.0 + .serialize_compressed() + .cmp(&other.0.serialize_compressed()) } } @@ -159,7 +161,7 @@ impl super::SecretKey for SecretKey { } impl Serialize for SecretKey { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> Result where S: Serializer, { @@ -171,9 +173,10 @@ impl Serialize for SecretKey { impl<'de> Deserialize<'de> for SecretKey { fn deserialize(deserializer: D) -> Result where - D: serde::Deserializer<'de> + D: serde::Deserializer<'de>, { - let arr_res: [u8; libsecp256k1::util::SECRET_KEY_SIZE] = serde::Deserialize::deserialize(deserializer)?; + let arr_res: [u8; libsecp256k1::util::SECRET_KEY_SIZE] = + serde::Deserialize::deserialize(deserializer)?; let key = libsecp256k1::SecretKey::parse_slice(&arr_res) .map_err(D::Error::custom); Ok(SecretKey(key.unwrap())) @@ -268,11 +271,11 @@ impl super::Signature for Signature { } } -// Would ideally like Serialize, Deserialize to be implemented in libsecp256k1, may try to do so and merge -// upstream in the future. +// Would ideally like Serialize, Deserialize to be implemented in libsecp256k1, +// may try to do so and merge upstream in the future. impl Serialize for Signature { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: S) -> Result where S: Serializer, { @@ -290,8 +293,8 @@ impl Serialize for Signature { impl<'de> Deserialize<'de> for Signature { fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de> + where + D: serde::Deserializer<'de>, { struct ByteArrayVisitor; @@ -299,7 +302,10 @@ impl<'de> Deserialize<'de> for Signature { type Value = [u8; libsecp256k1::util::SIGNATURE_SIZE]; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str(&format!("an array of length {}", libsecp256k1::util::SIGNATURE_SIZE)) + formatter.write_str(&format!( + "an array of length {}", + libsecp256k1::util::SIGNATURE_SIZE + )) } fn visit_seq(self, mut seq: A) -> Result<[u8; 64], A::Error> @@ -309,18 +315,21 @@ impl<'de> Deserialize<'de> for Signature { let mut arr = [0u8; libsecp256k1::util::SIGNATURE_SIZE]; #[allow(clippy::needless_range_loop)] for i in 0..libsecp256k1::util::SIGNATURE_SIZE { - arr[i] = seq.next_element()? + arr[i] = seq + .next_element()? .ok_or_else(|| Error::invalid_length(i, &self))?; } Ok(arr) } } - let arr_res = deserializer.deserialize_tuple(libsecp256k1::util::SIGNATURE_SIZE, ByteArrayVisitor)?; + let arr_res = deserializer.deserialize_tuple( + libsecp256k1::util::SIGNATURE_SIZE, + ByteArrayVisitor, + )?; let sig = libsecp256k1::Signature::parse_standard(&arr_res) .map_err(D::Error::custom); Ok(Signature(sig.unwrap())) - } } @@ -433,13 +442,11 @@ impl super::SigScheme for SigScheme { let check = libsecp256k1::verify(message, &sig.0, &pk.0); match check { true => Ok(()), - false => Err(VerifySigError::SigVerifyError( - format!("Error verifying secp256k1 signature: {}",libsecp256k1::Error::InvalidSignature) - )), + false => Err(VerifySigError::SigVerifyError(format!( + "Error verifying secp256k1 signature: {}", + libsecp256k1::Error::InvalidSignature + ))), } - - - } fn verify_signature_raw( @@ -450,13 +457,13 @@ impl super::SigScheme for SigScheme { let hash = Sha256::digest(data.as_ref()); let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) .expect("Error parsing raw data"); - let check = libsecp256k1::verify(message,&sig.0, &pk.0); + let check = libsecp256k1::verify(message, &sig.0, &pk.0); match check { true => Ok(()), - false => Err(VerifySigError::SigVerifyError( - format!("Error verifying secp256k1 signature: {}",libsecp256k1::Error::InvalidSignature) - )), + false => Err(VerifySigError::SigVerifyError(format!( + "Error verifying secp256k1 signature: {}", + libsecp256k1::Error::InvalidSignature + ))), } - } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index edd171f6aa..62b96f70bc 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1540,7 +1540,8 @@ fn test_genesis_validators() -> Result<()> { // the given index let get_first_port = |ix: u8| net_address_port_0 + 6 * (ix as u16 + 1); - // 1. Setup 2 genesis validators, one with ed25519 keys (0) and one with secp256k1 keys (1) + // 1. Setup 2 genesis validators, one with ed25519 keys (0) and one with + // secp256k1 keys (1) let validator_0_alias = "validator-0"; let validator_1_alias = "validator-1"; From 0f92dd120b7ec232669a1ccf24d467159ac8f98f Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 5 Jul 2022 16:00:46 +0200 Subject: [PATCH 141/373] changes from Clippy suggestions --- shared/src/types/key/secp256k1.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 3aece8051f..677fc3c4b3 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -65,7 +65,7 @@ impl BorshDeserialize for PublicKey { impl BorshSerialize for PublicKey { fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - writer.write(&self.0.serialize_compressed())?; + writer.write_all(&self.0.serialize_compressed())?; Ok(()) } } @@ -436,7 +436,7 @@ impl super::SigScheme for SigScheme { let bytes = &data .try_to_vec() .map_err(VerifySigError::DataEncodingError)?[..]; - let hash = Sha256::digest(bytes.as_ref()); + let hash = Sha256::digest(bytes); let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) .expect("Error parsing given data"); let check = libsecp256k1::verify(message, &sig.0, &pk.0); @@ -454,7 +454,7 @@ impl super::SigScheme for SigScheme { data: &[u8], sig: &Self::Signature, ) -> Result<(), VerifySigError> { - let hash = Sha256::digest(data.as_ref()); + let hash = Sha256::digest(data); let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) .expect("Error parsing raw data"); let check = libsecp256k1::verify(message, &sig.0, &pk.0); From 79181c28ea35a25f4325558e633fda14b4bc4b1c Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 6 Jul 2022 17:33:01 +0200 Subject: [PATCH 142/373] fix bug where we were generating a key with common scheme --- apps/src/lib/client/utils.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 4b4b5b2af0..5aebd1d26e 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -453,10 +453,11 @@ pub fn init_network( format!("validator {name} Tendermint node key"), &config.tendermint_node_key, ) - .map(|pk| common::PublicKey::try_from_pk(&pk).unwrap()) .unwrap_or_else(|| { - // Generate a node key - let node_sk = common::SigScheme::generate(&mut rng); + // Generate a node key with ed25519 as default + let node_sk = common::SecretKey::Ed25519( + ed25519::SigScheme::generate(&mut rng), + ); let node_pk = write_tendermint_node_key(&tm_home_dir, node_sk); From 4be80fddaec968c50f809af0afd608b4d64f9f11 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 6 Jul 2022 23:45:44 +0200 Subject: [PATCH 143/373] fix bug to prevent generating keys with common SchemeType --- apps/src/lib/client/utils.rs | 12 ++++++------ apps/src/lib/wallet/mod.rs | 6 +++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 5aebd1d26e..4b8f5d6f07 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -527,7 +527,7 @@ pub fn init_network( let alias = format!("{}-consensus-key", name); println!("Generating validator {} consensus key...", name); let (_alias, keypair) = wallet.gen_key( - SchemeType::Common, + SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, ); @@ -550,7 +550,7 @@ pub fn init_network( let alias = format!("{}-account-key", name); println!("Generating validator {} account key...", name); let (_alias, keypair) = wallet.gen_key( - SchemeType::Common, + SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, ); @@ -568,7 +568,7 @@ pub fn init_network( name ); let (_alias, keypair) = wallet.gen_key( - SchemeType::Common, + SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, ); @@ -583,7 +583,7 @@ pub fn init_network( let alias = format!("{}-protocol-key", name); println!("Generating validator {} protocol signing key...", name); let (_alias, keypair) = wallet.gen_key( - SchemeType::Common, + SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, ); @@ -742,7 +742,7 @@ pub fn init_network( name ); let (_alias, keypair) = wallet.gen_key( - SchemeType::Common, + SchemeType::Ed25519, Some(name.clone()), unsafe_dont_encrypt, ); @@ -1034,7 +1034,7 @@ fn init_established_account( if config.public_key.is_none() { println!("Generating established account {} key...", name.as_ref()); let (_alias, keypair) = wallet.gen_key( - SchemeType::Common, + SchemeType::Ed25519, Some(format!("{}-key", name.as_ref())), unsafe_dont_encrypt, ); diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 5bdf8e6261..b5c68dd2c1 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -120,6 +120,10 @@ impl Wallet { &mut self, protocol_pk: Option, ) -> Result { + let scheme = match protocol_pk.as_ref().unwrap() { + common::PublicKey::Ed25519(_) => SchemeType::Ed25519, + common::PublicKey::Secp256k1(_) => SchemeType::Secp256k1, + }; let protocol_keypair = protocol_pk.map(|pk| { self.find_key_by_pkh(&PublicKeyHash::from(&pk)) .ok() @@ -135,7 +139,7 @@ impl Wallet { Some(Err(err)) => Err(err), other => Ok(Store::gen_validator_keys( other.map(|res| res.unwrap().as_ref().clone()), - SchemeType::Common, + scheme, )), } } From 4644fcd103960ee88cac36b13274e65eb94c8191 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 8 Jul 2022 11:01:57 +0200 Subject: [PATCH 144/373] make validator_key_to_json() compatible with ed25519 and secp256k1 keys --- apps/src/lib/node/ledger/tendermint_node.rs | 52 ++++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 136f925df4..25dd49ba63 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -198,27 +198,43 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { /// Convert a common signing scheme validator key into JSON for /// Tendermint -fn validator_key_to_json( +fn validator_key_to_json( address: &Address, - sk: &SK, + sk: &common::SecretKey, ) -> std::result::Result { let address = address.raw_hash().unwrap(); - ed25519::SecretKey::try_from_sk(sk).map(|sk| { - let pk: ed25519::PublicKey = sk.ref_to(); - let ck_arr = - [sk.try_to_vec().unwrap(), pk.try_to_vec().unwrap()].concat(); - json!({ - "address": address, - "pub_key": { - "type": "tendermint/PubKeyEd25519", - "value": base64::encode(pk.try_to_vec().unwrap()), - }, - "priv_key": { - "type": "tendermint/PrivKeyEd25519", - "value": base64::encode(ck_arr), - } - }) - }) + + let (id_str, pk_arr, kp_arr) = match sk { + common::SecretKey::Ed25519(_) => { + let sk_ed: ed25519::SecretKey = sk.try_to_sk().unwrap(); + let keypair = [ + sk_ed.try_to_vec().unwrap(), + sk_ed.ref_to().try_to_vec().unwrap(), + ] + .concat(); + ("Ed25519", sk_ed.ref_to().try_to_vec().unwrap(), keypair) + } + common::SecretKey::Secp256k1(_) => { + let sk_sec: secp256k1::SecretKey = sk.try_to_sk().unwrap(); + ( + "Secp256k1", + sk_sec.ref_to().try_to_vec().unwrap(), + sk_sec.try_to_vec().unwrap(), + ) + } + }; + + Ok(json!({ + "address": address, + "pub_key": { + "type": format!("tendermint/PubKey{}",id_str), + "value": base64::encode(pk_arr), + }, + "priv_key": { + "type": format!("tendermint/PrivKey{}",id_str), + "value": base64::encode(kp_arr), + } + })) } /// Initialize validator private key for Tendermint From 7f6e77495986412736fbe2196c72ea8662370b98 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 8 Jul 2022 11:14:48 +0200 Subject: [PATCH 145/373] fix bug in supplying args to test_genesis_validators() --- tests/src/e2e/ledger_tests.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 62b96f70bc..22eb4f4ac5 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1553,7 +1553,8 @@ fn test_genesis_validators() -> Result<()> { "--unsafe-dont-encrypt", "--alias", validator_0_alias, - "--scheme ed25519", + "--scheme", + "ed25519", "--net-address", &format!("127.0.0.1:{}", get_first_port(0)), ], @@ -1590,7 +1591,8 @@ fn test_genesis_validators() -> Result<()> { "--unsafe-dont-encrypt", "--alias", validator_1_alias, - "--scheme secp256k1", + "--scheme", + "secp256k1", "--net-address", &format!("127.0.0.1:{}", get_first_port(1)), ], From a43154479e287fa8d49d9fc482c1ba236b327838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 5 Aug 2022 18:16:29 +0200 Subject: [PATCH 146/373] add a test to zeroize secp256k1 --- shared/src/types/key/mod.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index c88fe85ffa..1e12667346 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -465,11 +465,26 @@ mod more_tests { fn zeroize_keypair_ed25519() { use rand::thread_rng; - let sk = ed25519::SecretKey(Box::new( - ed25519_consensus::SigningKey::new(thread_rng()), - )); - let len = sk.0.as_bytes().len(); - let ptr = sk.0.as_bytes().as_ptr(); + let sk = ed25519::SigScheme::generate(&mut thread_rng()); + let sk_bytes = sk.0.as_bytes(); + let len = sk_bytes.len(); + let ptr = sk_bytes.as_ptr(); + + drop(sk); + + assert_eq!(&[0u8; 32], unsafe { + core::slice::from_raw_parts(ptr, len) + }); + } + + #[test] + fn zeroize_keypair_seck256k1() { + use rand::thread_rng; + + let sk = secp256k1::SigScheme::generate(&mut thread_rng()); + let sk_bytes = sk.0.serialize(); + let len = sk_bytes.len(); + let ptr = sk_bytes.as_ptr(); drop(sk); From 374206c91ec407ee55f3b7c2eb46652fd0488de6 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 17 Jun 2022 09:51:09 +0100 Subject: [PATCH 147/373] Add `anomac utils fetch-wasms` command --- apps/src/bin/anoma-client/cli.rs | 3 +++ apps/src/lib/cli.rs | 43 +++++++++++++++++++++++++++++++- apps/src/lib/client/utils.rs | 17 +++++++++++++ apps/src/lib/wasm_loader/mod.rs | 5 ++-- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index d04eeff9ab..8d5d9ff234 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -94,6 +94,9 @@ pub async fn main() -> Result<()> { Utils::JoinNetwork(JoinNetwork(args)) => { utils::join_network(global_args, args).await } + Utils::FetchWasms(FetchWasms(args)) => { + utils::fetch_wasms(global_args, args).await + } Utils::InitNetwork(InitNetwork(args)) => { utils::init_network(global_args, args) } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5eef95b34d..ec716a1385 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1262,6 +1262,7 @@ pub mod cmds { #[derive(Clone, Debug)] pub enum Utils { JoinNetwork(JoinNetwork), + FetchWasms(FetchWasms), InitNetwork(InitNetwork), InitGenesisValidator(InitGenesisValidator), } @@ -1273,11 +1274,15 @@ pub mod cmds { matches.subcommand_matches(Self::CMD).and_then(|matches| { let join_network = SubCmd::parse(matches).map(Self::JoinNetwork); + let fetch_wasms = SubCmd::parse(matches).map(Self::FetchWasms); let init_network = SubCmd::parse(matches).map(Self::InitNetwork); let init_genesis = SubCmd::parse(matches).map(Self::InitGenesisValidator); - join_network.or(init_network).or(init_genesis) + join_network + .or(fetch_wasms) + .or(init_network) + .or(init_genesis) }) } @@ -1285,6 +1290,7 @@ pub mod cmds { App::new(Self::CMD) .about("Utilities.") .subcommand(JoinNetwork::def()) + .subcommand(FetchWasms::def()) .subcommand(InitNetwork::def()) .subcommand(InitGenesisValidator::def()) .setting(AppSettings::SubcommandRequiredElseHelp) @@ -1310,6 +1316,25 @@ pub mod cmds { } } + #[derive(Clone, Debug)] + pub struct FetchWasms(pub args::FetchWasms); + + impl SubCmd for FetchWasms { + const CMD: &'static str = "fetch-wasms"; + + fn parse(matches: &ArgMatches) -> Option { + matches + .subcommand_matches(Self::CMD) + .map(|matches| Self(args::FetchWasms::parse(matches))) + } + + fn def() -> App { + App::new(Self::CMD) + .about("Ensure pre-built wasms are present") + .add_args::() + } + } + #[derive(Clone, Debug)] pub struct InitNetwork(pub args::InitNetwork); @@ -2923,6 +2948,22 @@ pub mod args { } } + #[derive(Clone, Debug)] + pub struct FetchWasms { + pub chain_id: ChainId, + } + + impl Args for FetchWasms { + fn parse(matches: &ArgMatches) -> Self { + let chain_id = CHAIN_ID.parse(matches); + Self { chain_id } + } + + fn def(app: App) -> App { + app.arg(CHAIN_ID.def().about("The chain ID. The chain must be known in the https://github.com/heliaxdev/anoma-network-config repository, in which case it should have pre-built wasms available for download.")) + } + } + #[derive(Clone, Debug)] pub struct InitNetwork { pub genesis_path: PathBuf, diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index c377648b65..322976c728 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -347,6 +347,23 @@ pub async fn join_network( println!("Successfully configured for chain ID {}", chain_id); } +pub async fn fetch_wasms( + global_args: args::Global, + args::FetchWasms { chain_id }: args::FetchWasms, +) { + fetch_wasms_aux(&global_args.base_dir, &chain_id).await; +} + +pub async fn fetch_wasms_aux(base_dir: &Path, chain_id: &ChainId) { + let wasm_dir = { + let mut path = base_dir.to_owned(); + path.push(chain_id.as_str()); + path.push("wasm"); + path + }; + wasm_loader::pre_fetch_wasm(&wasm_dir).await; +} + /// Length of a Tendermint Node ID in bytes const TENDERMINT_NODE_ID_LENGTH: usize = 20; diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index b6cb424457..cc95499bc6 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -100,9 +100,8 @@ impl Checksums { } } -/// Download all the pre-build WASMs, or if they're already downloaded, verify -/// their checksums. Download all the pre-build WASMs, or if they're already -/// downloaded, verify their checksums. +/// Download all the pre-built wasms, or if they're already downloaded, verify +/// their checksums. pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { #[cfg(feature = "dev")] { From 80032fdd28bc5c5edb778f3fb5f9d7ad4d04fdac Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 11:12:17 +0100 Subject: [PATCH 148/373] fetch-wasms: print to stdout when fetching --- apps/src/lib/client/utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 322976c728..ff9f9a526a 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -355,6 +355,7 @@ pub async fn fetch_wasms( } pub async fn fetch_wasms_aux(base_dir: &Path, chain_id: &ChainId) { + println!("Fetching wasms for chain ID {}...", chain_id); let wasm_dir = { let mut path = base_dir.to_owned(); path.push(chain_id.as_str()); From 59d1bf4a28e6a95cd663e382d41018fce5b0bcb5 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 11:15:02 +0100 Subject: [PATCH 149/373] `anomac utils join-network` should call fetch-wasms --- apps/src/lib/client/utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index ff9f9a526a..697387fd15 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -343,6 +343,7 @@ pub async fn join_network( .await .unwrap(); } + fetch_wasms_aux(&base_dir, &chain_id).await; println!("Successfully configured for chain ID {}", chain_id); } From 9475edc1d8ac71c692be9678ce75a75131585f2c Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 11:19:40 +0100 Subject: [PATCH 150/373] Add changelog --- .../unreleased/improvements/1159-anomac-download-wasms.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1159-anomac-download-wasms.md diff --git a/.changelog/unreleased/improvements/1159-anomac-download-wasms.md b/.changelog/unreleased/improvements/1159-anomac-download-wasms.md new file mode 100644 index 0000000000..20ae41a073 --- /dev/null +++ b/.changelog/unreleased/improvements/1159-anomac-download-wasms.md @@ -0,0 +1,2 @@ +- Add functionality to anomac to download wasms for a given chain + ([#1159](https://github.com/anoma/anoma/pull/1159)) \ No newline at end of file From 587e0376546af5102ad54468fd1c4ecfff5d11a0 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Sat, 25 Jun 2022 15:27:40 +0100 Subject: [PATCH 151/373] open_genesis_config should return a human-friendly error --- apps/src/bin/anoma-client/cli.rs | 2 +- apps/src/bin/anoma-node/cli.rs | 2 +- apps/src/bin/anoma-wallet/cli.rs | 2 +- apps/src/lib/cli.rs | 13 +++++++------ apps/src/lib/cli/context.rs | 14 ++++++++------ apps/src/lib/cli/utils.rs | 7 ++++--- apps/src/lib/client/utils.rs | 11 ++++++----- apps/src/lib/config/genesis.rs | 22 ++++++++++++++++++---- apps/src/lib/node/ledger/shell/mod.rs | 17 ++++++++--------- apps/src/lib/wallet/mod.rs | 4 ++-- apps/src/lib/wallet/store.rs | 6 +++--- tests/src/e2e/ledger_tests.rs | 2 +- tests/src/e2e/setup.rs | 2 +- 13 files changed, 61 insertions(+), 43 deletions(-) diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index d04eeff9ab..bca54f2c6e 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -6,7 +6,7 @@ use namada_apps::cli::cmds::*; use namada_apps::client::{gossip, rpc, tx, utils}; pub async fn main() -> Result<()> { - match cli::anoma_client_cli() { + match cli::anoma_client_cli()? { cli::AnomaClient::WithContext(cmd_box) => { let (cmd, ctx) = *cmd_box; use AnomaClientWithContext as Sub; diff --git a/apps/src/bin/anoma-node/cli.rs b/apps/src/bin/anoma-node/cli.rs index 407a6b7378..1597b5fbc1 100644 --- a/apps/src/bin/anoma-node/cli.rs +++ b/apps/src/bin/anoma-node/cli.rs @@ -5,7 +5,7 @@ use namada_apps::cli::{self, args, cmds}; use namada_apps::node::{gossip, ledger, matchmaker}; pub fn main() -> Result<()> { - let (cmd, mut ctx) = cli::anoma_node_cli(); + let (cmd, mut ctx) = cli::anoma_node_cli()?; if let Some(mode) = ctx.global_args.mode.clone() { ctx.config.ledger.tendermint.tendermint_mode = mode; } diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index 9807516a93..7eb879f96f 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -12,7 +12,7 @@ use namada_apps::cli::{args, cmds, Context}; use namada_apps::wallet::DecryptionError; pub fn main() -> Result<()> { - let (cmd, ctx) = cli::anoma_wallet_cli(); + let (cmd, ctx) = cli::anoma_wallet_cli()?; match cmd { cmds::AnomaWallet::Key(sub) => match sub { cmds::WalletKey::Gen(cmds::KeyGen(args)) => { diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index 5eef95b34d..45e4b95b33 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -10,6 +10,7 @@ pub mod context; mod utils; use clap::{crate_authors, AppSettings, ArgMatches}; +use color_eyre::eyre::Result; pub use utils::safe_exit; use utils::*; @@ -3053,7 +3054,7 @@ pub fn anoma_cli() -> (cmds::Anoma, String) { safe_exit(2); } -pub fn anoma_node_cli() -> (cmds::AnomaNode, Context) { +pub fn anoma_node_cli() -> Result<(cmds::AnomaNode, Context)> { let app = anoma_node_app(); cmds::AnomaNode::parse_or_print_help(app) } @@ -3063,7 +3064,7 @@ pub enum AnomaClient { WithContext(Box<(cmds::AnomaClientWithContext, Context)>), } -pub fn anoma_client_cli() -> AnomaClient { +pub fn anoma_client_cli() -> Result { let app = anoma_client_app(); let mut app = cmds::AnomaClient::add_sub(app); let matches = app.clone().get_matches(); @@ -3072,11 +3073,11 @@ pub fn anoma_client_cli() -> AnomaClient { let global_args = args::Global::parse(&matches); match cmd { cmds::AnomaClient::WithContext(sub_cmd) => { - let context = Context::new(global_args); - AnomaClient::WithContext(Box::new((sub_cmd, context))) + let context = Context::new(global_args)?; + Ok(AnomaClient::WithContext(Box::new((sub_cmd, context)))) } cmds::AnomaClient::WithoutContext(sub_cmd) => { - AnomaClient::WithoutContext(sub_cmd, global_args) + Ok(AnomaClient::WithoutContext(sub_cmd, global_args)) } } } @@ -3087,7 +3088,7 @@ pub fn anoma_client_cli() -> AnomaClient { } } -pub fn anoma_wallet_cli() -> (cmds::AnomaWallet, Context) { +pub fn anoma_wallet_cli() -> Result<(cmds::AnomaWallet, Context)> { let app = anoma_wallet_app(); cmds::AnomaWallet::parse_or_print_help(app) } diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 8189b633bf..dd911cc8d3 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf}; use std::rc::Rc; use std::str::FromStr; +use color_eyre::eyre::Result; use namada::types::address::Address; use namada::types::chain::ChainId; use namada::types::key::*; @@ -49,7 +50,7 @@ pub struct Context { } impl Context { - pub fn new(global_args: args::Global) -> Self { + pub fn new(global_args: args::Global) -> Result { let global_config = read_or_try_new_global_config(&global_args); tracing::info!("Chain ID: {}", global_config.default_chain_id); @@ -65,9 +66,10 @@ impl Context { let genesis_file_path = global_args .base_dir .join(format!("{}.toml", global_config.default_chain_id.as_str())); - let wallet = Wallet::load_or_new_from_genesis(&chain_dir, move || { - genesis_config::open_genesis_config(genesis_file_path) - }); + let wallet = Wallet::load_or_new_from_genesis( + &chain_dir, + genesis_config::open_genesis_config(&genesis_file_path)?, + ); // If the WASM dir specified, put it in the config match global_args.wasm_dir.as_ref() { @@ -96,12 +98,12 @@ impl Context { } } } - Self { + Ok(Self { global_args, wallet, global_config, config, - } + }) } /// Parse and/or look-up the value from the context. diff --git a/apps/src/lib/cli/utils.rs b/apps/src/lib/cli/utils.rs index f47ec42696..56965d72ef 100644 --- a/apps/src/lib/cli/utils.rs +++ b/apps/src/lib/cli/utils.rs @@ -4,6 +4,7 @@ use std::marker::PhantomData; use std::str::FromStr; use clap::ArgMatches; +use color_eyre::eyre::Result; use super::args; use super::context::{Context, FromContext}; @@ -16,14 +17,14 @@ pub trait Cmd: Sized { fn add_sub(app: App) -> App; fn parse(matches: &ArgMatches) -> Option; - fn parse_or_print_help(app: App) -> (Self, Context) { + fn parse_or_print_help(app: App) -> Result<(Self, Context)> { let mut app = Self::add_sub(app); let matches = app.clone().get_matches(); match Self::parse(&matches) { Some(cmd) => { let global_args = args::Global::parse(&matches); - let context = Context::new(global_args); - (cmd, context) + let context = Context::new(global_args)?; + Ok((cmd, context)) } None => { app.print_help().unwrap(); diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index c377648b65..a72cb4b20e 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -267,10 +267,10 @@ pub async fn join_network( let genesis_file_path = base_dir.join(format!("{}.toml", chain_id.as_str())); - let mut wallet = - Wallet::load_or_new_from_genesis(&chain_dir, move || { - genesis_config::open_genesis_config(genesis_file_path) - }); + let mut wallet = Wallet::load_or_new_from_genesis( + &chain_dir, + genesis_config::open_genesis_config(genesis_file_path).unwrap(), + ); let address = wallet .find_address(&validator_alias) @@ -378,7 +378,8 @@ pub fn init_network( archive_dir, }: args::InitNetwork, ) { - let mut config = genesis_config::open_genesis_config(&genesis_path); + let mut config = + genesis_config::open_genesis_config(&genesis_path).unwrap(); // Update the WASM checksums let checksums = diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..067669aec8 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -26,6 +26,7 @@ pub mod genesis_config { use std::path::Path; use std::str::FromStr; + use eyre::Context; use hex; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::{EpochDuration, Parameters}; @@ -594,9 +595,22 @@ pub mod genesis_config { genesis } - pub fn open_genesis_config(path: impl AsRef) -> GenesisConfig { - let config_file = std::fs::read_to_string(path).unwrap(); - toml::from_str(&config_file).unwrap() + pub fn open_genesis_config( + path: impl AsRef, + ) -> color_eyre::eyre::Result { + let config_file = + std::fs::read_to_string(&path).wrap_err_with(|| { + format!( + "couldn't read genesis config file from {}", + path.as_ref().to_string_lossy() + ) + })?; + toml::from_str(&config_file).wrap_err_with(|| { + format!( + "couldn't parse TOML from {}", + path.as_ref().to_string_lossy() + ) + }) } pub fn write_genesis_config( @@ -608,7 +622,7 @@ pub mod genesis_config { } pub fn read_genesis_config(path: impl AsRef) -> Genesis { - load_genesis_config(open_genesis_config(path)) + load_genesis_config(open_genesis_config(path).unwrap()) } } diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d93ec23c56..58e87f5aea 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -259,11 +259,10 @@ where ); let wallet = wallet::Wallet::load_or_new_from_genesis( wallet_path, - move || { - genesis::genesis_config::open_genesis_config( - genesis_path, - ) - }, + genesis::genesis_config::open_genesis_config( + genesis_path, + ) + .unwrap(), ); wallet .take_validator_data() @@ -599,10 +598,10 @@ where let genesis_path = &self .base_dir .join(format!("{}.toml", self.chain_id.as_str())); - let mut wallet = - wallet::Wallet::load_or_new_from_genesis(wallet_path, move || { - genesis::genesis_config::open_genesis_config(genesis_path) - }); + let mut wallet = wallet::Wallet::load_or_new_from_genesis( + wallet_path, + genesis::genesis_config::open_genesis_config(genesis_path).unwrap(), + ); self.mode.get_validator_address().map(|addr| { let pk_bytes = self .storage diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index 5fec7dca98..df707dfb60 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -68,9 +68,9 @@ impl Wallet { /// addresses loaded from the genesis file, if not found. pub fn load_or_new_from_genesis( store_dir: &Path, - load_genesis: impl FnOnce() -> GenesisConfig, + genesis_cfg: GenesisConfig, ) -> Self { - let store = Store::load_or_new_from_genesis(store_dir, load_genesis) + let store = Store::load_or_new_from_genesis(store_dir, genesis_cfg) .unwrap_or_else(|err| { eprintln!("Unable to load the wallet: {}", err); cli::safe_exit(1) diff --git a/apps/src/lib/wallet/store.rs b/apps/src/lib/wallet/store.rs index 95454d3d2a..5d16552c0f 100644 --- a/apps/src/lib/wallet/store.rs +++ b/apps/src/lib/wallet/store.rs @@ -135,15 +135,15 @@ impl Store { /// the genesis file, if not found. pub fn load_or_new_from_genesis( store_dir: &Path, - load_genesis: impl FnOnce() -> GenesisConfig, + genesis_cfg: GenesisConfig, ) -> Result { Self::load(store_dir).or_else(|_| { #[cfg(not(feature = "dev"))] - let store = Self::new(load_genesis()); + let store = Self::new(genesis_cfg); #[cfg(feature = "dev")] let store = { // The function is unused in dev - let _ = load_genesis; + let _ = genesis_cfg; Self::new() }; store.save(store_dir).map_err(|err| { diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 552e675e67..a39a06344d 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1619,7 +1619,7 @@ fn test_genesis_validators() -> Result<()> { // 2. Initialize a new network with the 2 validators let mut genesis = genesis_config::open_genesis_config( working_dir.join(setup::SINGLE_NODE_NET_GENESIS), - ); + )?; let update_validator_config = |ix: u8, mut config: genesis_config::ValidatorConfig| { // Setup tokens balances and validity predicates diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 5c0a52c30b..c53726d5e7 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -112,7 +112,7 @@ pub fn network( // Open the source genesis file let genesis = genesis_config::open_genesis_config( working_dir.join(SINGLE_NODE_NET_GENESIS), - ); + )?; // Run the provided function on it let genesis = update_genesis(genesis); From fac06df0c9028f2ef6cbaa6c1982ad16f79e1cbe Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 11:27:01 +0100 Subject: [PATCH 152/373] Add changelog --- .changelog/unreleased/improvements/1176-genesis-config-error.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1176-genesis-config-error.md diff --git a/.changelog/unreleased/improvements/1176-genesis-config-error.md b/.changelog/unreleased/improvements/1176-genesis-config-error.md new file mode 100644 index 0000000000..3e7f9eb996 --- /dev/null +++ b/.changelog/unreleased/improvements/1176-genesis-config-error.md @@ -0,0 +1,2 @@ +- Improve the error message that is displayed when anoma binaries are run without + having joined a chain ([#1176](https://github.com/anoma/anoma/pull/1176)) \ No newline at end of file From be44806a81d75a9972ee9e2a04a76c672c45aad5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 9 Aug 2022 17:34:25 -0400 Subject: [PATCH 153/373] fmt and clippy --- apps/src/bin/anoma-wallet/cli.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/src/bin/anoma-wallet/cli.rs b/apps/src/bin/anoma-wallet/cli.rs index e32564db7d..15d0ef2d03 100644 --- a/apps/src/bin/anoma-wallet/cli.rs +++ b/apps/src/bin/anoma-wallet/cli.rs @@ -194,9 +194,8 @@ fn address_list(ctx: Context) { fn address_or_alias_find(ctx: Context, args: args::AddressOrAliasFind) { let wallet = ctx.wallet; if args.address.is_some() && args.alias.is_some() { - assert!( - false, - "This should not be happening, as clap should emit its own error \ + panic!( + "This should not be happening: clap should emit its own error \ message." ); } else if args.alias.is_some() { From fbf9930cd206c9843643bbd41aa3bbadaedaf792 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 10 Aug 2022 11:16:29 -0400 Subject: [PATCH 154/373] wrap libsecp256k1::SecretKey in a Box within SecretKey struct --- shared/src/types/key/secp256k1.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 677fc3c4b3..18d74dd5e8 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -136,7 +136,7 @@ impl From for PublicKey { /// Secp256k1 secret key #[derive(Debug, Clone)] -pub struct SecretKey(pub libsecp256k1::SecretKey); +pub struct SecretKey(pub Box); impl super::SecretKey for SecretKey { type PublicKey = PublicKey; @@ -179,14 +179,14 @@ impl<'de> Deserialize<'de> for SecretKey { serde::Deserialize::deserialize(deserializer)?; let key = libsecp256k1::SecretKey::parse_slice(&arr_res) .map_err(D::Error::custom); - Ok(SecretKey(key.unwrap())) + Ok(SecretKey(Box::new(key.unwrap()))) } } impl BorshDeserialize for SecretKey { fn deserialize(buf: &mut &[u8]) -> std::io::Result { // deserialize the bytes first - Ok(SecretKey( + Ok(SecretKey(Box::new( libsecp256k1::SecretKey::parse( &(BorshDeserialize::deserialize(buf)?), ) @@ -196,7 +196,7 @@ impl BorshDeserialize for SecretKey { format!("Error decoding secp256k1 secret key: {}", e), ) })?, - )) + ))) } } @@ -417,7 +417,7 @@ impl super::SigScheme for SigScheme { where R: CryptoRng + RngCore, { - SecretKey(libsecp256k1::SecretKey::random(csprng)) + SecretKey(Box::new(libsecp256k1::SecretKey::random(csprng))) } /// Sign the data with a key From 96793181b3ef04b0535c4df0ec76903b7afe0d8f Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 10 Aug 2022 11:17:12 -0400 Subject: [PATCH 155/373] new test for zeroizing secp256k1 keys --- shared/src/types/key/mod.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 1e12667346..a287e2f8e3 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -478,18 +478,21 @@ mod more_tests { } #[test] - fn zeroize_keypair_seck256k1() { + fn zeroize_keypair_secp256k1() { use rand::thread_rng; - let sk = secp256k1::SigScheme::generate(&mut thread_rng()); - let sk_bytes = sk.0.serialize(); - let len = sk_bytes.len(); - let ptr = sk_bytes.as_ptr(); + let mut sk = secp256k1::SigScheme::generate(&mut thread_rng()); + let sk_scalar = sk.0.to_scalar_ref(); + let len = sk_scalar.0.len(); + let ptr = sk_scalar.0.as_ref().as_ptr(); + + let original_data = sk_scalar.0.clone(); drop(sk); - assert_eq!(&[0u8; 32], unsafe { + assert_ne!(&original_data, unsafe { core::slice::from_raw_parts(ptr, len) }); + } } From 41fa6f5c20376b1c2ba38cacb1fa3c35a7a938e9 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 10 Aug 2022 11:25:58 -0400 Subject: [PATCH 156/373] use brentstone/libsecp256k1 crate fork as dependency for now --- Cargo.lock | 17 ++++++----------- shared/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e43983c3df..577fd171e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3431,15 +3431,13 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +version = "0.7.0" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64 0.13.0", "digest 0.9.0", "hmac-drbg 0.3.0", - "lazy_static 1.4.0", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -3452,8 +3450,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -3463,8 +3460,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -3472,8 +3468,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -3932,7 +3927,7 @@ dependencies = [ "ibc-proto", "ics23", "itertools 0.10.3", - "libsecp256k1 0.7.1", + "libsecp256k1 0.7.0", "loupe", "namada_proof_of_stake", "parity-wasm", diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 3b9153095c..8db53e8891 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -64,7 +64,7 @@ ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c3 ics23 = "0.6.7" itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} -libsecp256k1 = {version = "0.7.0", default-features = false, features = ["std", "hmac", "lazy-static-context"]} +libsecp256k1 = {git = "https://github.com/brentstone/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} parity-wasm = {version = "0.42.2", optional = true} # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} From 905c0ac49f8b16838ac78d6ca68022f87c7d8078 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 10 Aug 2022 11:30:24 -0400 Subject: [PATCH 157/373] fmt && clippy --- shared/src/types/key/mod.rs | 3 +-- wasm/tx_template/Cargo.lock | 15 +++++---------- wasm/vp_template/Cargo.lock | 15 +++++---------- wasm/wasm_source/Cargo.lock | 15 +++++---------- wasm_for_tests/wasm_source/Cargo.lock | 15 +++++---------- 5 files changed, 21 insertions(+), 42 deletions(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a287e2f8e3..d8ccbb5a64 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -486,13 +486,12 @@ mod more_tests { let len = sk_scalar.0.len(); let ptr = sk_scalar.0.as_ref().as_ptr(); - let original_data = sk_scalar.0.clone(); + let original_data = sk_scalar.0; drop(sk); assert_ne!(&original_data, unsafe { core::slice::from_raw_parts(ptr, len) }); - } } diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index c7734c3587..69f39c3149 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1260,15 +1260,13 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +version = "0.7.0" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", "digest 0.9.0", "hmac-drbg", - "lazy_static", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -1281,8 +1279,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1292,8 +1289,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1301,8 +1297,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index cc9146fd62..2a11bdbe7e 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1260,15 +1260,13 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +version = "0.7.0" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", "digest 0.9.0", "hmac-drbg", - "lazy_static", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -1281,8 +1279,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1292,8 +1289,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1301,8 +1297,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 8f2ed44ca5..bf93c417bc 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1260,15 +1260,13 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +version = "0.7.0" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", "digest 0.9.0", "hmac-drbg", - "lazy_static", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -1281,8 +1279,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1292,8 +1289,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1301,8 +1297,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 96927ae10c..1184027373 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1270,15 +1270,13 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +version = "0.7.0" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", "digest 0.9.0", "hmac-drbg", - "lazy_static", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -1291,8 +1289,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1302,8 +1299,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1311,8 +1307,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] From 390d0c175cbc500f8bd29a5a775718d285be62bf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 10 Aug 2022 17:51:52 +0000 Subject: [PATCH 158/373] [ci skip] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 91c2317ccf..51e1ae8ed6 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.5aa466cd8ecbe9c5f9b777956052f4e0164f099c30260475f0e9cd71bbd99e0d.wasm", - "tx_from_intent.wasm": "tx_from_intent.95f421a3caa886186655c92aee1545e95d893ad6ce003e5317dc16ca5ef2076b.wasm", - "tx_ibc.wasm": "tx_ibc.c3c5dafe1a1740a848394c3264e8802184181658c12356f99ce95388922220e7.wasm", - "tx_init_account.wasm": "tx_init_account.9e70d6ca9ee4c0b9ca62a58b95f52321df145f5c84fff44f5a88bba0771a1a17.wasm", - "tx_init_nft.wasm": "tx_init_nft.7d57769d2da3d1dba1775763d6d33305335c8232220a459c847e512ef7ef1165.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.20a4bb9daa9499b39b564124a4cc07421b24ba1c36f8c8f48fda6772b295f841.wasm", - "tx_init_validator.wasm": "tx_init_validator.a7cf7bbb695a3a8c618a8811a4a7c061231b0272b06234fdcfb5264e9938895d.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.24272eface53a9a4c4d927ae33c6e139c56779ecae4854095153a0300da59cbc.wasm", - "tx_transfer.wasm": "tx_transfer.9f13d688b915a150dcfd2472c5ba698fddfc4a3a080e81f06b3a061af72508d9.wasm", - "tx_unbond.wasm": "tx_unbond.e9c6c5f9fd6987afd3213d533735d2bb6349a9a002f0f6d1b8fb1b6ea1581cfd.wasm", - "tx_update_vp.wasm": "tx_update_vp.d47bfe6ef55a86bce36093feeff74fe09ec7cffebf9696dd37658756a4eb793d.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.e1c327bbe49d7b6b5445ad641d9f154813ae73c98ba8df7b85c5a06dc4ede41e.wasm", - "tx_withdraw.wasm": "tx_withdraw.7f83fe1f8bb0fa1864c64d329cec54c317524715644907066ab322f5b2be0056.wasm", - "vp_nft.wasm": "vp_nft.955cbe702d2925a209529cc5d7b810768fe3e6c597907a941417bc50ca31e42e.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.35c7df353e7d4ffd986f92a81a2b49bd85f4b0265d85be9c38fe88c389d61ce3.wasm", - "vp_token.wasm": "vp_token.abbca3decf5ca2fb46b7de0589b52355c413c3281f6c8aa13868008747b41484.wasm", - "vp_user.wasm": "vp_user.e4a2076ebd3d458a2d57768007105a0e3baed597456e401b3a2787d4314e511f.wasm" + "tx_bond.wasm": "tx_bond.a953ccd5f3e5a70f660f06e2429ab6790bd5c86436ab533e2d91c1a57bf28e0f.wasm", + "tx_from_intent.wasm": "tx_from_intent.20d8d6e20e7214b6f1483679bc989703af9114951c0962b633dc3f515f87f0d4.wasm", + "tx_ibc.wasm": "tx_ibc.26926af0f7880206ec051556eefe108a857bea52707c76d2412d92fed1e49ce9.wasm", + "tx_init_account.wasm": "tx_init_account.18139aeefa5b7a37817e23f732154e280aaca2c95dd34030c0da91f96cefc23a.wasm", + "tx_init_nft.wasm": "tx_init_nft.b29a8df5144db4efb161e599aad13562dd380794cc61d1706739517a010dff14.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.7a009b485f06496325b5a9c8bdbdd84d397023f6e3fc4ea6308febb7f8257d12.wasm", + "tx_init_validator.wasm": "tx_init_validator.3a6ee75303f852906d3abc1d1867b11af120b19b83398e49ec4f0ba0860265d5.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.16261316acc03c1f9c5febf071615ec9fcab42b44e273195d5208c653e963b29.wasm", + "tx_transfer.wasm": "tx_transfer.ed8e56c3ae22aca30d8f4dfbf10be01a95ffade7ecc41075881379d2d5d8de80.wasm", + "tx_unbond.wasm": "tx_unbond.65df72ef27a727e3a47155b0ae2e17e9b0e8ef05fe7e8abf17a94b56646c7820.wasm", + "tx_update_vp.wasm": "tx_update_vp.a21170806108d0a43cb398b47976bab413c72e18380ceafc6f1e7f802c081f6e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.aab2edb75cec87287f9341cdf02c51fe82afcc4440e6edb681ca86d3ca70e05b.wasm", + "tx_withdraw.wasm": "tx_withdraw.96507a8a02fe6173683e64464daa1a75b0350146f3beb03524b290b62978f4fd.wasm", + "vp_nft.wasm": "vp_nft.077e5c6af4475408957dc268d564fd32639937f035908106e1fabea630025c07.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.c0dfac5cda28884122149855cb7856aa93d7ca941bcd597b3a5847915e979158.wasm", + "vp_token.wasm": "vp_token.029bbe6ab79e69b9e5dc16ef8cd634f774b94528dfda4ff7ba2a281d8c558a17.wasm", + "vp_user.wasm": "vp_user.07a825bc15e57b205092a74a1f426b5a78b98a644381a027ce411dc615a7a456.wasm" } \ No newline at end of file From 1f00013d07bacd2637b0f6ade1bcc4372a5a4b63 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 11 Aug 2022 00:08:52 -0400 Subject: [PATCH 159/373] update wasm checksums --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 51e1ae8ed6..e1b0bf27cc 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.a953ccd5f3e5a70f660f06e2429ab6790bd5c86436ab533e2d91c1a57bf28e0f.wasm", - "tx_from_intent.wasm": "tx_from_intent.20d8d6e20e7214b6f1483679bc989703af9114951c0962b633dc3f515f87f0d4.wasm", - "tx_ibc.wasm": "tx_ibc.26926af0f7880206ec051556eefe108a857bea52707c76d2412d92fed1e49ce9.wasm", - "tx_init_account.wasm": "tx_init_account.18139aeefa5b7a37817e23f732154e280aaca2c95dd34030c0da91f96cefc23a.wasm", - "tx_init_nft.wasm": "tx_init_nft.b29a8df5144db4efb161e599aad13562dd380794cc61d1706739517a010dff14.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.7a009b485f06496325b5a9c8bdbdd84d397023f6e3fc4ea6308febb7f8257d12.wasm", - "tx_init_validator.wasm": "tx_init_validator.3a6ee75303f852906d3abc1d1867b11af120b19b83398e49ec4f0ba0860265d5.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.16261316acc03c1f9c5febf071615ec9fcab42b44e273195d5208c653e963b29.wasm", - "tx_transfer.wasm": "tx_transfer.ed8e56c3ae22aca30d8f4dfbf10be01a95ffade7ecc41075881379d2d5d8de80.wasm", - "tx_unbond.wasm": "tx_unbond.65df72ef27a727e3a47155b0ae2e17e9b0e8ef05fe7e8abf17a94b56646c7820.wasm", - "tx_update_vp.wasm": "tx_update_vp.a21170806108d0a43cb398b47976bab413c72e18380ceafc6f1e7f802c081f6e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.aab2edb75cec87287f9341cdf02c51fe82afcc4440e6edb681ca86d3ca70e05b.wasm", - "tx_withdraw.wasm": "tx_withdraw.96507a8a02fe6173683e64464daa1a75b0350146f3beb03524b290b62978f4fd.wasm", - "vp_nft.wasm": "vp_nft.077e5c6af4475408957dc268d564fd32639937f035908106e1fabea630025c07.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.c0dfac5cda28884122149855cb7856aa93d7ca941bcd597b3a5847915e979158.wasm", - "vp_token.wasm": "vp_token.029bbe6ab79e69b9e5dc16ef8cd634f774b94528dfda4ff7ba2a281d8c558a17.wasm", - "vp_user.wasm": "vp_user.07a825bc15e57b205092a74a1f426b5a78b98a644381a027ce411dc615a7a456.wasm" + "tx_bond.wasm": "tx_bond.dff97b2ae7129c92a25a4716e050a7a9ac2c98fb21dc8e8e6915ea58faa2b2a5.wasm", + "tx_from_intent.wasm": "tx_from_intent.e95e3959831c0ae989bac970335d0f819f0a0f8e0e383b02cb92d66c156875fc.wasm", + "tx_ibc.wasm": "tx_ibc.d43c21f95b75fa5dfc8b9fc2fe367ddfc85307ba0cd08b56150fabda4b5fc12a.wasm", + "tx_init_account.wasm": "tx_init_account.7215124f55ba573d9b3927dec63a4510d2d12fd142859e9f0aa8eb51b20362a2.wasm", + "tx_init_nft.wasm": "tx_init_nft.56fd7317cebdfcc0d7ccb9f3282747cf4b702fede3d981ac76ea258578847b2c.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.4f94f17d4e4c96420b256cfd248774dbcda8a83722e5b455bfb61e49d4ebd83a.wasm", + "tx_init_validator.wasm": "tx_init_validator.4afb48e53136e6c583951d1b429175939c0ab6a2dc217e56484a816d6d41bcb1.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.5274f30ea997dae4dd22404ae5ca3984ff875ba937dfb8fc78327d15d79fd463.wasm", + "tx_transfer.wasm": "tx_transfer.ce559325d9bfa23265a90fa13f289b252c479c40acf44e461e50f58cf796fa43.wasm", + "tx_unbond.wasm": "tx_unbond.ba16c538e0f7730e5ee16f82ad9cda2b13fd4a5595dea706f563352025526f83.wasm", + "tx_update_vp.wasm": "tx_update_vp.d7334588988b6cd467417f5d0a9274dcc8e7c7c21937c1d68bd1c23bcfeff9f2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.e3069273031df484d9d8956ea1a0796defed1828bf55d4c95d952e8929450245.wasm", + "tx_withdraw.wasm": "tx_withdraw.de9ccde4442ba2c7fb14fdc3a9592ae87d76e3895a323a44d61d781265c66c6d.wasm", + "vp_nft.wasm": "vp_nft.bf6b3169beeab0dbffd26815a828f17736c669da7d978d8203ac1075ef480cd8.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.dfbe1df808212b8d1c5ae425153f05cb87c156de17dfd4a9fc108b8f1cbdbf6b.wasm", + "vp_token.wasm": "vp_token.9eb6e754753d15fc32cc19fb392c33c6db36e111bbb0daa2cb02b05b335a3ffd.wasm", + "vp_user.wasm": "vp_user.8f5429ee42d39818ac4a6a8e7ac135435d8946f3a81f741a72c805e79c74ea3f.wasm" } \ No newline at end of file From 563cf23084bdd5dfd3a25816996b6071b15ece93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 7 Jul 2022 11:38:59 +0200 Subject: [PATCH 160/373] changelog: add #1221 --- .changelog/unreleased/testing/1221-e2e-keep-temp-fix.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/testing/1221-e2e-keep-temp-fix.md diff --git a/.changelog/unreleased/testing/1221-e2e-keep-temp-fix.md b/.changelog/unreleased/testing/1221-e2e-keep-temp-fix.md new file mode 100644 index 0000000000..3c61ceb518 --- /dev/null +++ b/.changelog/unreleased/testing/1221-e2e-keep-temp-fix.md @@ -0,0 +1,2 @@ +- Fixed ANOMA_E2E_KEEP_TEMP=true to work in e2e::setup::network + ([#1221](https://github.com/anoma/anoma/issues/1221)) \ No newline at end of file From 602ff81ed783df62878a25778d4d112effa52bc9 Mon Sep 17 00:00:00 2001 From: leontiad Date: Mon, 27 Jun 2022 10:00:14 +0200 Subject: [PATCH 161/373] wallet: increase keys encryption password iterations --- apps/src/lib/wallet/keys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/wallet/keys.rs b/apps/src/lib/wallet/keys.rs index 1c521e7515..0f1f43189e 100644 --- a/apps/src/lib/wallet/keys.rs +++ b/apps/src/lib/wallet/keys.rs @@ -235,6 +235,6 @@ fn encryption_salt() -> kdf::Salt { /// Make encryption secret key from a password. fn encryption_key(salt: &kdf::Salt, password: String) -> kdf::SecretKey { kdf::Password::from_slice(password.as_bytes()) - .and_then(|password| kdf::derive_key(&password, salt, 3, 1 << 16, 32)) + .and_then(|password| kdf::derive_key(&password, salt, 3, 1 << 17, 32)) .expect("Generation of encryption secret key shouldn't fail") } From 87415fcab8ae6b218575a286c11ce7def142cb43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 30 Jun 2022 12:11:26 +0200 Subject: [PATCH 162/373] changelog: add #1225 --- .changelog/unreleased/improvements/1168-pbkdf-iterations.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/1168-pbkdf-iterations.md diff --git a/.changelog/unreleased/improvements/1168-pbkdf-iterations.md b/.changelog/unreleased/improvements/1168-pbkdf-iterations.md new file mode 100644 index 0000000000..417e0f8af8 --- /dev/null +++ b/.changelog/unreleased/improvements/1168-pbkdf-iterations.md @@ -0,0 +1,2 @@ +- Wallet: Increase the number of iterations used for keys encryption to the + recommended value. ([#1168](https://github.com/anoma/anoma/issues/1168)) \ No newline at end of file From 80fa394fa711dc4d51376edbd466ca7f0ac3af26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 25 Jul 2022 12:49:18 +0200 Subject: [PATCH 163/373] shell: remove TM consensus evidence parameters --- .../lib/node/ledger/shell/finalize_block.rs | 12 --------- apps/src/lib/node/ledger/shell/init_chain.rs | 9 ------- apps/src/lib/node/ledger/shell/mod.rs | 3 +-- apps/src/lib/node/ledger/shell/queries.rs | 27 ------------------- 4 files changed, 1 insertion(+), 50 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1f758a2f2e..592b5d6cf8 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -488,18 +488,6 @@ where let update = ValidatorUpdate { pub_key, power }; response.validator_updates.push(update); }); - - // Update evidence parameters - let (epoch_duration, _gas) = - parameters::read_epoch_parameter(&self.storage) - .expect("Couldn't read epoch duration parameters"); - let pos_params = self.storage.read_pos_params(); - let evidence_params = - self.get_evidence_params(&epoch_duration, &pos_params); - response.consensus_param_updates = Some(ConsensusParams { - evidence: Some(evidence_params), - ..response.consensus_param_updates.take().unwrap_or_default() - }); } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index a989338751..a86681fbb5 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -257,15 +257,6 @@ where ); ibc::init_genesis_storage(&mut self.storage); - let evidence_params = self.get_evidence_params( - &genesis.parameters.epoch_duration, - &genesis.pos_params, - ); - response.consensus_params = Some(ConsensusParams { - evidence: Some(evidence_params), - ..response.consensus_params.unwrap_or_default() - }); - // Set the initial validator set for validator in genesis.validators { let mut abci_validator = abci::ValidatorUpdate::default(); diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d93ec23c56..16690c9e17 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -29,7 +29,7 @@ use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{ DBIter, Sha256Hasher, Storage, StorageHasher, DB, }; -use namada::ledger::{ibc, parameters, pos}; +use namada::ledger::{ibc, pos}; use namada::proto::{self, Tx}; use namada::types::chain::ChainId; use namada::types::key::*; @@ -50,7 +50,6 @@ use tendermint_proto::abci::{ RequestPrepareProposal, ValidatorUpdate, }; use tendermint_proto::crypto::public_key; -use tendermint_proto::types::ConsensusParams; use thiserror::Error; use tokio::sync::mpsc::UnboundedSender; use tower_abci::{request, response}; diff --git a/apps/src/lib/node/ledger/shell/queries.rs b/apps/src/lib/node/ledger/shell/queries.rs index a643501d9e..d50dd2405e 100644 --- a/apps/src/lib/node/ledger/shell/queries.rs +++ b/apps/src/lib/node/ledger/shell/queries.rs @@ -1,18 +1,13 @@ //! Shell methods for querying state -use std::cmp::max; use borsh::{BorshDeserialize, BorshSerialize}; use ferveo_common::TendermintValidator; -use namada::ledger::parameters::EpochDuration; -use namada::ledger::pos::PosParams; use namada::types::address::Address; use namada::types::key; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::storage::{Key, PrefixValue}; use namada::types::token::{self, Amount}; use tendermint_proto::crypto::{ProofOp, ProofOps}; -use tendermint_proto::google::protobuf; -use tendermint_proto::types::EvidenceParams; use super::*; use crate::node::ledger::response; @@ -263,28 +258,6 @@ where } } - pub fn get_evidence_params( - &self, - epoch_duration: &EpochDuration, - pos_params: &PosParams, - ) -> EvidenceParams { - // Minimum number of epochs before tokens are unbonded and can be - // withdrawn - let len_before_unbonded = max(pos_params.unbonding_len as i64 - 1, 0); - let max_age_num_blocks: i64 = - epoch_duration.min_num_of_blocks as i64 * len_before_unbonded; - let min_duration_secs = epoch_duration.min_duration.0 as i64; - let max_age_duration = Some(protobuf::Duration { - seconds: min_duration_secs * len_before_unbonded, - nanos: 0, - }); - EvidenceParams { - max_age_num_blocks, - max_age_duration, - ..EvidenceParams::default() - } - } - /// Lookup data about a validator from their protocol signing key #[allow(dead_code)] pub fn get_validator_from_protocol_pk( From cbcda142cbc9b5567423e16f0248f68fe93b5c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 25 Jul 2022 12:50:14 +0200 Subject: [PATCH 164/373] shell: skip and log outdated evidence in the shell --- apps/src/lib/node/ledger/shell/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 16690c9e17..b4143cb970 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -410,6 +410,13 @@ where continue; } }; + if evidence_epoch + pos_params.unbonding_len <= current_epoch { + tracing::info!( + "Skipping outdated evidence from epoch \ + {evidence_epoch}" + ); + continue; + } let slash_type = match EvidenceType::from_i32(evidence.r#type) { Some(r#type) => match r#type { EvidenceType::DuplicateVote => { From 80e9916d12562f70d92575a73a916e348d4b9d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 25 Jul 2022 12:54:05 +0200 Subject: [PATCH 165/373] changelog: add #1248 --- .../unreleased/improvements/1248-remove-evidence-params.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/1248-remove-evidence-params.md diff --git a/.changelog/unreleased/improvements/1248-remove-evidence-params.md b/.changelog/unreleased/improvements/1248-remove-evidence-params.md new file mode 100644 index 0000000000..97297a93e1 --- /dev/null +++ b/.changelog/unreleased/improvements/1248-remove-evidence-params.md @@ -0,0 +1,3 @@ +- Replace Tendermint consensus evidence parameters with + application level evidence filter for outdated evidence. + ([#1248](https://github.com/anoma/anoma/pull/1248)) \ No newline at end of file From 32e78ce27f14573c0d7e50b9fca3efc09305313d Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 11 Aug 2022 17:10:09 -0400 Subject: [PATCH 166/373] handle secp256k1 in key_to_tendermint() --- apps/src/lib/node/ledger/shell/mod.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index d93ec23c56..3d64a8cb58 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -65,11 +65,22 @@ use crate::node::ledger::{protocol, storage, tendermint_node}; use crate::wallet::ValidatorData; use crate::{config, wallet}; -fn key_to_tendermint( - pk: &PK, +fn key_to_tendermint ( + pk: &common::PublicKey, ) -> std::result::Result { - ed25519::PublicKey::try_from_pk(pk) - .map(|pk| public_key::Sum::Ed25519(pk.try_to_vec().unwrap())) + println!("\nKEY TO TENDERMINT\n"); + match pk { + common::PublicKey::Ed25519(_) => { + println!("\nEd25519\n"); + ed25519::PublicKey::try_from_pk(pk) + .map(|pk| public_key::Sum::Ed25519(pk.try_to_vec().unwrap())) + }, + common::PublicKey::Secp256k1(_) => { + println!("\nSecp256k1\n"); + secp256k1::PublicKey::try_from_pk(pk) + .map(|pk| public_key::Sum::Secp256k1(pk.try_to_vec().unwrap())) + }, + } } #[derive(Error, Debug)] From 77f2ead7faf4736f152a8d1497de399fc554984f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 17:50:33 +0200 Subject: [PATCH 167/373] tendermint: fix address written to TM to correspond to consensus key --- apps/src/lib/client/tx.rs | 8 ++---- apps/src/lib/client/utils.rs | 7 +---- apps/src/lib/node/ledger/tendermint_node.rs | 29 +++++---------------- shared/src/types/key/mod.rs | 11 ++++++++ 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6f1fd96707..52df27ce31 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -331,15 +331,11 @@ pub async fn submit_init_validator( }; // add validator address and keys to the wallet ctx.wallet - .add_validator_data(validator_address.clone(), validator_keys); + .add_validator_data(validator_address, validator_keys); ctx.wallet.save().unwrap_or_else(|err| eprintln!("{}", err)); let tendermint_home = ctx.config.ledger.tendermint_dir(); - tendermint_node::write_validator_key( - &tendermint_home, - &validator_address, - &consensus_key, - ); + tendermint_node::write_validator_key(&tendermint_home, &consensus_key); tendermint_node::write_validator_state(tendermint_home); println!(); diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index c377648b65..89119ead79 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -288,7 +288,6 @@ pub async fn join_network( // Write consensus key to tendermint home tendermint_node::write_validator_key( &tm_home_dir, - &address, &*pre_genesis_wallet.consensus_key, ); @@ -516,11 +515,7 @@ pub fn init_network( wallet.gen_key(Some(alias), unsafe_dont_encrypt); // Write consensus key for Tendermint - tendermint_node::write_validator_key( - &tm_home_dir, - &address, - &keypair, - ); + tendermint_node::write_validator_key(&tm_home_dir, &keypair); keypair.ref_to() }); diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index 136f925df4..ffaca7dd37 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -4,7 +4,6 @@ use std::process::Stdio; use std::str::FromStr; use borsh::BorshSerialize; -use namada::types::address::Address; use namada::types::chain::ChainId; use namada::types::key::*; use namada::types::time::DateTimeUtc; @@ -95,23 +94,10 @@ pub async fn run( #[cfg(feature = "dev")] { - let genesis = &crate::config::genesis::genesis(); let consensus_key = crate::wallet::defaults::validator_keypair(); // write the validator key file if it didn't already exist if !has_validator_key { - write_validator_key_async( - &home_dir, - &genesis - .validators - .first() - .expect( - "There should be one genesis validator in \"dev\" mode", - ) - .pos_data - .address, - &consensus_key, - ) - .await; + write_validator_key_async(&home_dir, &consensus_key).await; } } write_tm_genesis(&home_dir, chain_id, genesis_time, &config).await; @@ -199,16 +185,17 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { /// Convert a common signing scheme validator key into JSON for /// Tendermint fn validator_key_to_json( - address: &Address, sk: &SK, ) -> std::result::Result { - let address = address.raw_hash().unwrap(); ed25519::SecretKey::try_from_sk(sk).map(|sk| { let pk: ed25519::PublicKey = sk.ref_to(); + let pk_common = common::PublicKey::try_from_pk(&pk) + .expect("must be able to convert ed25519 to common"); + let raw_hash = tm_consensus_key_raw_hash(&pk_common); let ck_arr = [sk.try_to_vec().unwrap(), pk.try_to_vec().unwrap()].concat(); json!({ - "address": address, + "address": raw_hash, "pub_key": { "type": "tendermint/PubKeyEd25519", "value": base64::encode(pk.try_to_vec().unwrap()), @@ -224,7 +211,6 @@ fn validator_key_to_json( /// Initialize validator private key for Tendermint pub async fn write_validator_key_async( home_dir: impl AsRef, - address: &Address, consensus_key: &common::SecretKey, ) { let home_dir = home_dir.as_ref(); @@ -241,7 +227,7 @@ pub async fn write_validator_key_async( .open(&path) .await .expect("Couldn't create private validator key file"); - let key = validator_key_to_json(address, consensus_key).unwrap(); + let key = validator_key_to_json(consensus_key).unwrap(); let data = serde_json::to_vec_pretty(&key) .expect("Couldn't encode private validator key file"); file.write_all(&data[..]) @@ -252,7 +238,6 @@ pub async fn write_validator_key_async( /// Initialize validator private key for Tendermint pub fn write_validator_key( home_dir: impl AsRef, - address: &Address, consensus_key: &common::SecretKey, ) { let home_dir = home_dir.as_ref(); @@ -267,7 +252,7 @@ pub fn write_validator_key( .truncate(true) .open(&path) .expect("Couldn't create private validator key file"); - let key = validator_key_to_json(address, consensus_key).unwrap(); + let key = validator_key_to_json(consensus_key).unwrap(); serde_json::to_writer_pretty(file, &key) .expect("Couldn't write private validator key file"); } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a1343cd7e5..d8e781e0b2 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -332,6 +332,17 @@ impl From<&PK> for PublicKeyHash { } } +/// Convert validator's consensus key into address raw hash that is compatible +/// with Tendermint +pub fn tm_consensus_key_raw_hash(pk: &common::PublicKey) -> String { + match pk { + common::PublicKey::Ed25519(pk) => { + let pkh = PublicKeyHash::from(pk); + pkh.0 + } + } +} + /// Helpers for testing with keys. #[cfg(any(test, feature = "testing"))] pub mod testing { From 79f2f833912cb7889fdf6a5c3be764b982fbbf46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 18:27:16 +0200 Subject: [PATCH 168/373] PoS: fix the validator's raw hash to correspond to consensus key --- proof_of_stake/src/lib.rs | 28 +++++++++++++++++++++------- shared/src/ledger/pos/storage.rs | 8 ++++++-- vm_env/src/proof_of_stake.rs | 8 ++++++-- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index a137eb8a91..7fadf6bff1 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -153,8 +153,12 @@ pub trait PosReadOnly { pub trait PosActions: PosReadOnly { /// Write PoS parameters. fn write_pos_params(&mut self, params: &PosParams); - /// Write PoS validator's raw hash its address. - fn write_validator_address_raw_hash(&mut self, address: &Self::Address); + /// Write PoS validator's raw hash of its consensus key. + fn write_validator_address_raw_hash( + &mut self, + address: &Self::Address, + consensus_key: &Self::PublicKey, + ); /// Write PoS validator's staking reward address, into which staking rewards /// will be credited. fn write_validator_staking_reward_address( @@ -243,6 +247,7 @@ pub trait PosActions: PosReadOnly { ), ); } + let consensus_key_clone = consensus_key.clone(); let BecomeValidatorData { consensus_key, state, @@ -262,7 +267,7 @@ pub trait PosActions: PosReadOnly { self.write_validator_consensus_key(address, consensus_key); self.write_validator_state(address, state); self.write_validator_set(validator_set); - self.write_validator_address_raw_hash(address); + self.write_validator_address_raw_hash(address, &consensus_key_clone); self.write_validator_total_deltas(address, total_deltas); self.write_validator_voting_power(address, voting_power); Ok(()) @@ -539,7 +544,7 @@ pub trait PosBase { /// Read PoS parameters. fn read_pos_params(&self) -> PosParams; - /// Read PoS raw hash of validator's address. + /// Read PoS raw hash of validator's consensus key. fn read_validator_address_raw_hash( &self, raw_hash: impl AsRef, @@ -574,8 +579,12 @@ pub trait PosBase { /// Write PoS parameters. fn write_pos_params(&mut self, params: &PosParams); - /// Write PoS validator's raw hash its address. - fn write_validator_address_raw_hash(&mut self, address: &Self::Address); + /// Write PoS validator's raw hash of its consensus key. + fn write_validator_address_raw_hash( + &mut self, + address: &Self::Address, + consensus_key: &Self::PublicKey, + ); /// Write PoS validator's staking reward address, into which staking rewards /// will be credited. fn write_validator_staking_reward_address( @@ -685,7 +694,12 @@ pub trait PosBase { voting_power, bond: (bond_id, bond), } = res?; - self.write_validator_address_raw_hash(address); + self.write_validator_address_raw_hash( + address, + consensus_key + .get(current_epoch) + .expect("Consensus key must be set"), + ); self.write_validator_staking_reward_address( address, &staking_reward_address, diff --git a/shared/src/ledger/pos/storage.rs b/shared/src/ledger/pos/storage.rs index cfe1126b88..366ce489b5 100644 --- a/shared/src/ledger/pos/storage.rs +++ b/shared/src/ledger/pos/storage.rs @@ -454,8 +454,12 @@ where self.write(¶ms_key(), encode(params)).unwrap(); } - fn write_validator_address_raw_hash(&mut self, address: &Self::Address) { - let raw_hash = address.raw_hash().unwrap(); + fn write_validator_address_raw_hash( + &mut self, + address: &Self::Address, + consensus_key: &Self::PublicKey, + ) { + let raw_hash = key::tm_consensus_key_raw_hash(consensus_key); self.write(&validator_address_raw_hash_key(raw_hash), encode(address)) .unwrap(); } diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index 8e4bba4223..4ec53dafa4 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -174,8 +174,12 @@ impl namada_proof_of_stake::PosActions for PoS { tx::write(params_key().to_string(), params) } - fn write_validator_address_raw_hash(&mut self, address: &Self::Address) { - let raw_hash = address.raw_hash().unwrap().to_owned(); + fn write_validator_address_raw_hash( + &mut self, + address: &Self::Address, + consensus_key: &Self::PublicKey, + ) { + let raw_hash = key::tm_consensus_key_raw_hash(consensus_key); tx::write( validator_address_raw_hash_key(raw_hash).to_string(), address, From 3d7ab76c08f9f69f9e5337d4a541fb90fdc7c367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 18:27:43 +0200 Subject: [PATCH 169/373] tests/PoS: fix the validator's raw hash to correspond to consensus key --- tests/src/native_vp/pos.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index be1844c6cd..83878f6965 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -655,6 +655,8 @@ pub mod testing { }, ValidatorAddressRawHash { address: Address, + #[derivative(Debug = "ignore")] + consensus_key: PublicKey, }, } @@ -816,12 +818,14 @@ pub mod testing { match self { ValidPosAction::InitValidator(addr) => { let offset = DynEpochOffset::PipelineLen; + let consensus_key = key::testing::keypair_1().ref_to(); vec![ PosStorageChange::SpawnAccount { address: addr.clone(), }, PosStorageChange::ValidatorAddressRawHash { address: addr.clone(), + consensus_key: consensus_key.clone(), }, PosStorageChange::ValidatorSet { validator: addr.clone(), @@ -830,7 +834,7 @@ pub mod testing { }, PosStorageChange::ValidatorConsensusKey { validator: addr.clone(), - pk: key::testing::keypair_1().ref_to(), + pk: consensus_key, }, PosStorageChange::ValidatorStakingRewardsAddress { validator: addr.clone(), @@ -1285,8 +1289,11 @@ pub mod testing { } PoS.write_total_voting_power(total_voting_powers) } - PosStorageChange::ValidatorAddressRawHash { address } => { - PoS.write_validator_address_raw_hash(&address); + PosStorageChange::ValidatorAddressRawHash { + address, + consensus_key, + } => { + PoS.write_validator_address_raw_hash(&address, &consensus_key); } PosStorageChange::ValidatorSet { validator, From 3a5fae7024c2b354194797fe7bb9d849bd4ddb00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 18:52:47 +0200 Subject: [PATCH 170/373] ledger/shell: fix validator look-up from tm raw hash --- apps/src/lib/node/ledger/shell/mod.rs | 14 +------------- shared/src/types/key/mod.rs | 5 +++++ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index b4143cb970..8ada136300 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -442,19 +442,7 @@ where } }; let validator_raw_hash = match evidence.validator { - Some(validator) => { - match String::from_utf8(validator.address) { - Ok(raw_hash) => raw_hash, - Err(err) => { - tracing::error!( - "Evidence failed to decode validator \ - address from utf-8 with {}", - err - ); - continue; - } - } - } + Some(validator) => tm_raw_hash_to_string(validator.address), None => { tracing::error!( "Evidence without a validator {:#?}", diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index d8e781e0b2..59dd6b2cd5 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -343,6 +343,11 @@ pub fn tm_consensus_key_raw_hash(pk: &common::PublicKey) -> String { } } +/// Convert Tendermint validator's raw hash bytes to Anoma raw hash string +pub fn tm_raw_hash_to_string(raw_hash: impl AsRef<[u8]>) -> String { + hex::encode_upper(raw_hash) +} + /// Helpers for testing with keys. #[cfg(any(test, feature = "testing"))] pub mod testing { From e4022b817de577e0d940ea2afd0b58c87207bebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 22 Jul 2022 09:13:58 +0200 Subject: [PATCH 171/373] shell: fix slashing log msg --- apps/src/lib/node/ledger/shell/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 8ada136300..be3ac0ba59 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -385,6 +385,7 @@ where let pos_params = self.storage.read_pos_params(); let current_epoch = self.storage.block.epoch; for evidence in byzantine_validators { + tracing::info!("Processing evidence {evidence:?}."); let evidence_height = match u64::try_from(evidence.height) { Ok(height) => height, Err(err) => { @@ -466,9 +467,9 @@ where }; tracing::info!( "Slashing {} for {} in epoch {}, block height {}", - evidence_epoch, - slash_type, validator, + slash_type, + evidence_epoch, evidence_height ); if let Err(err) = self.storage.slash( From 243c92ea49b1e66530791d72153894c7ff81a9f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 10:27:29 +0200 Subject: [PATCH 172/373] deps: enable secp256k1 in tendermint-rs note that to make cargo deps, which complained about: ``` error: failed to select a version for `signature` ``` it was needed to run `cargo update -p signature` to: ``` Updating signature v1.5.0 -> v1.4.0 ``` --- Cargo.lock | 150 ++++++++++++++++++++++++- apps/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- wasm/tx_template/Cargo.lock | 154 +++++++++++++++++++++++++- wasm/vp_template/Cargo.lock | 154 +++++++++++++++++++++++++- wasm/wasm_source/Cargo.lock | 154 +++++++++++++++++++++++++- wasm_for_tests/wasm_source/Cargo.lock | 154 +++++++++++++++++++++++++- 7 files changed, 750 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 577fd171e2..ef6dac4c3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -519,6 +519,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.9.3" @@ -1101,6 +1107,12 @@ dependencies = [ "windows", ] +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -1272,6 +1284,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.5", + "rand_core 0.6.3", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -1302,6 +1326,16 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.5", + "subtle 2.4.1", +] + [[package]] name = "ct-codecs" version = "1.1.1" @@ -1484,6 +1518,15 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1646,6 +1689,18 @@ dependencies = [ "memmap2", ] +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "1.5.2" @@ -1693,6 +1748,24 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array 0.14.5", + "group", + "rand_core 0.6.3", + "sec1", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "embed-resource" version = "1.7.2" @@ -1888,6 +1961,16 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.3", + "subtle 2.4.1", +] + [[package]] name = "file-lock" version = "2.1.4" @@ -2276,6 +2359,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle 2.4.1", +] + [[package]] name = "group-threshold-cryptography" version = "0.1.0" @@ -2452,6 +2546,16 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + [[package]] name = "hmac-drbg" version = "0.2.0" @@ -2889,6 +2993,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "sec1", + "sha2 0.9.9", +] + [[package]] name = "keccak" version = "0.1.2" @@ -5450,6 +5567,17 @@ dependencies = [ "quick-error 1.2.3", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ring" version = "0.16.20" @@ -5749,6 +5877,18 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array 0.14.5", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "security-framework" version = "2.3.1" @@ -6050,9 +6190,13 @@ dependencies = [ [[package]] name = "signature" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] [[package]] name = "simple-error" @@ -6338,10 +6482,12 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures 0.3.21", + "k256", "num-traits 0.2.15", "once_cell", "prost 0.9.0", "prost-types 0.9.0", + "ripemd160", "serde 1.0.137", "serde_bytes", "serde_json", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 7a4dffac76..a07918be13 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -105,7 +105,7 @@ sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", b sysinfo = {version = "=0.21.1", default-features = false} tar = "0.4.37" # temporarily using fork work-around -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["secp256k1"]} tendermint-config = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} tendermint-rpc = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["http-client", "websocket-client"]} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 8db53e8891..9ea1eed52f 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -82,7 +82,7 @@ sha2 = "0.9.3" sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/prost-0.9", default-features = false, features = ["std", "borsh"]} tempfile = {version = "3.2.0", optional = true} # temporarily using fork work-around for https://github.com/informalsystems/tendermint-rs/issues/971 -tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["secp256k1"]} tendermint-proto = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} thiserror = "1.0.30" tracing = "0.1.30" diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 69f39c3149..b49df82fa9 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -205,6 +205,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.13.0" @@ -403,6 +409,12 @@ dependencies = [ "syn", ] +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -537,6 +549,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -557,6 +581,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -618,6 +652,15 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + [[package]] name = "derivative" version = "2.2.0" @@ -686,6 +729,18 @@ dependencies = [ "memmap2", ] +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "1.4.0" @@ -728,6 +783,24 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array", + "group", + "rand_core 0.6.3", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -807,6 +880,16 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -938,6 +1021,17 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "gumdrop" version = "0.8.0" @@ -1016,7 +1110,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac", + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", "digest 0.9.0", ] @@ -1028,7 +1132,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.8.1", ] [[package]] @@ -1224,6 +1328,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "sec1", + "sha2 0.9.9", +] + [[package]] name = "keccak" version = "0.1.0" @@ -2048,6 +2165,17 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -2208,6 +2336,18 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "0.11.0" @@ -2324,9 +2464,13 @@ dependencies = [ [[package]] name = "signature" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] [[package]] name = "simple-error" @@ -2460,10 +2604,12 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", + "k256", "num-traits", "once_cell", "prost", "prost-types", + "ripemd160", "serde", "serde_bytes", "serde_json", diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 2a11bdbe7e..b1e4fbc04a 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -205,6 +205,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.13.0" @@ -403,6 +409,12 @@ dependencies = [ "syn", ] +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -537,6 +549,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -557,6 +581,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -618,6 +652,15 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + [[package]] name = "derivative" version = "2.2.0" @@ -686,6 +729,18 @@ dependencies = [ "memmap2", ] +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "1.4.0" @@ -728,6 +783,24 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array", + "group", + "rand_core 0.6.3", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -807,6 +880,16 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -938,6 +1021,17 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "gumdrop" version = "0.8.0" @@ -1016,7 +1110,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac", + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", "digest 0.9.0", ] @@ -1028,7 +1132,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.8.1", ] [[package]] @@ -1224,6 +1328,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "sec1", + "sha2 0.9.9", +] + [[package]] name = "keccak" version = "0.1.0" @@ -2048,6 +2165,17 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -2208,6 +2336,18 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "0.11.0" @@ -2324,9 +2464,13 @@ dependencies = [ [[package]] name = "signature" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] [[package]] name = "simple-error" @@ -2460,10 +2604,12 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", + "k256", "num-traits", "once_cell", "prost", "prost-types", + "ripemd160", "serde", "serde_bytes", "serde_json", diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index bf93c417bc..52324468f1 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -205,6 +205,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.13.0" @@ -403,6 +409,12 @@ dependencies = [ "syn", ] +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -537,6 +549,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -557,6 +581,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -618,6 +652,15 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + [[package]] name = "derivative" version = "2.2.0" @@ -686,6 +729,18 @@ dependencies = [ "memmap2", ] +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "1.4.0" @@ -728,6 +783,24 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array", + "group", + "rand_core 0.6.3", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -807,6 +880,16 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -938,6 +1021,17 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "gumdrop" version = "0.8.0" @@ -1016,7 +1110,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac", + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", "digest 0.9.0", ] @@ -1028,7 +1132,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.8.1", ] [[package]] @@ -1224,6 +1328,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "sec1", + "sha2 0.9.9", +] + [[package]] name = "keccak" version = "0.1.0" @@ -2074,6 +2191,17 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -2234,6 +2362,18 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "0.11.0" @@ -2350,9 +2490,13 @@ dependencies = [ [[package]] name = "signature" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] [[package]] name = "simple-error" @@ -2486,10 +2630,12 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", + "k256", "num-traits", "once_cell", "prost", "prost-types", + "ripemd160", "serde", "serde_bytes", "serde_json", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 1184027373..c2017046d1 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -205,6 +205,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.13.0" @@ -403,6 +409,12 @@ dependencies = [ "syn", ] +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -538,6 +550,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -558,6 +582,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -619,6 +653,15 @@ dependencies = [ "syn", ] +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + [[package]] name = "derivative" version = "2.2.0" @@ -687,6 +730,18 @@ dependencies = [ "memmap2", ] +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "1.4.1" @@ -729,6 +784,24 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array", + "group", + "rand_core 0.6.3", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "enum-iterator" version = "0.7.0" @@ -808,6 +881,16 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "fixedbitset" version = "0.4.1" @@ -939,6 +1022,17 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.3", + "subtle", +] + [[package]] name = "gumdrop" version = "0.8.1" @@ -1026,7 +1120,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac", + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", "digest 0.9.0", ] @@ -1038,7 +1142,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.8.1", ] [[package]] @@ -1234,6 +1338,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "sec1", + "sha2 0.9.9", +] + [[package]] name = "keccak" version = "0.1.0" @@ -2080,6 +2197,17 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -2240,6 +2368,18 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "semver" version = "0.11.0" @@ -2356,9 +2496,13 @@ dependencies = [ [[package]] name = "signature" -version = "1.5.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.3", +] [[package]] name = "simple-error" @@ -2492,10 +2636,12 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", + "k256", "num-traits", "once_cell", "prost", "prost-types", + "ripemd160", "serde", "serde_bytes", "serde_json", From 2821c76ed1852039cbc4f49cc2bf2fefd562837b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 12:27:39 +0200 Subject: [PATCH 173/373] make fmt --- apps/src/lib/node/ledger/shell/mod.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 3d64a8cb58..eb47160f9c 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -65,7 +65,7 @@ use crate::node::ledger::{protocol, storage, tendermint_node}; use crate::wallet::ValidatorData; use crate::{config, wallet}; -fn key_to_tendermint ( +fn key_to_tendermint( pk: &common::PublicKey, ) -> std::result::Result { println!("\nKEY TO TENDERMINT\n"); @@ -73,13 +73,13 @@ fn key_to_tendermint ( common::PublicKey::Ed25519(_) => { println!("\nEd25519\n"); ed25519::PublicKey::try_from_pk(pk) - .map(|pk| public_key::Sum::Ed25519(pk.try_to_vec().unwrap())) - }, + .map(|pk| public_key::Sum::Ed25519(pk.try_to_vec().unwrap())) + } common::PublicKey::Secp256k1(_) => { println!("\nSecp256k1\n"); secp256k1::PublicKey::try_from_pk(pk) - .map(|pk| public_key::Sum::Secp256k1(pk.try_to_vec().unwrap())) - }, + .map(|pk| public_key::Sum::Secp256k1(pk.try_to_vec().unwrap())) + } } } From 85825772df3b006f72eb035415f863155d547cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 12:27:47 +0200 Subject: [PATCH 174/373] update rustdoc on PKH --- shared/src/types/key/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index d8ccbb5a64..666cc3fb5e 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -285,7 +285,8 @@ pub trait SigScheme: Eq + Ord + Debug + Serialize + Default { ) -> Result<(), VerifySigError>; } -/// Ed25519 public key hash +/// Public key hash derived from `common::Key` borsh encoded bytes (hex string +/// of the first 40 chars of sha256 hash) #[derive( Debug, Clone, From 1fad6c111e88291c8b9fcb43c64c82df322e887f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 13:13:53 +0200 Subject: [PATCH 175/373] must use ed25519 for validator consensus key and node ID --- apps/src/lib/client/tx.rs | 3 ++- apps/src/lib/wallet/pre_genesis.rs | 13 ++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6404e09555..cbb4c5b2a7 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -192,7 +192,8 @@ pub async fn submit_init_validator( println!("Generating consensus key..."); ctx.wallet .gen_key( - scheme, + // Note that TM only allows ed25519 for consensus key + SchemeType::Ed25519, Some(consensus_key_alias.clone()), unsafe_dont_encrypt, ) diff --git a/apps/src/lib/wallet/pre_genesis.rs b/apps/src/lib/wallet/pre_genesis.rs index 4a6b4a4679..72f719d1e4 100644 --- a/apps/src/lib/wallet/pre_genesis.rs +++ b/apps/src/lib/wallet/pre_genesis.rs @@ -144,10 +144,17 @@ impl ValidatorWallet { fn gen(scheme: SchemeType, unsafe_dont_encrypt: bool) -> Self { let password = wallet::read_and_confirm_pwd(unsafe_dont_encrypt); let (account_key, account_sk) = gen_key_to_store(scheme, &password); - let (consensus_key, consensus_sk) = gen_key_to_store(scheme, &password); + let (consensus_key, consensus_sk) = gen_key_to_store( + // Note that TM only allows ed25519 for consensus key + SchemeType::Ed25519, + &password, + ); let (rewards_key, rewards_sk) = gen_key_to_store(scheme, &password); - let (tendermint_node_key, tendermint_node_sk) = - gen_key_to_store(scheme, &password); + let (tendermint_node_key, tendermint_node_sk) = gen_key_to_store( + // Note that TM only allows ed25519 for node IDs + SchemeType::Ed25519, + &password, + ); let validator_keys = store::Store::gen_validator_keys(None, scheme); let store = ValidatorStore { account_key, From 271b0f2a5dac9914c4b1a214e0cc7acf3c8c9659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 14:04:43 +0200 Subject: [PATCH 176/373] pick scheme for generating validator keys --- apps/src/lib/client/tx.rs | 3 ++- apps/src/lib/client/utils.rs | 5 ++++- apps/src/lib/wallet/mod.rs | 5 +---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index cbb4c5b2a7..69c8c967bf 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -218,7 +218,8 @@ pub async fn submit_init_validator( println!("Generating protocol signing key..."); } // Generate the validator keys - let validator_keys = ctx.wallet.gen_validator_keys(protocol_key).unwrap(); + let validator_keys = + ctx.wallet.gen_validator_keys(protocol_key, scheme).unwrap(); let protocol_key = validator_keys.get_protocol_keypair().ref_to(); let dkg_key = validator_keys .dkg_keypair diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 4b8f5d6f07..9043a14db7 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -607,7 +607,10 @@ pub fn init_network( ); let validator_keys = wallet - .gen_validator_keys(Some(protocol_pk.clone())) + .gen_validator_keys( + Some(protocol_pk.clone()), + SchemeType::Ed25519, + ) .expect("Generating new validator keys should not fail"); let pk = validator_keys.dkg_keypair.as_ref().unwrap().public(); wallet.add_validator_data(address.clone(), validator_keys); diff --git a/apps/src/lib/wallet/mod.rs b/apps/src/lib/wallet/mod.rs index b5c68dd2c1..b3048ef97d 100644 --- a/apps/src/lib/wallet/mod.rs +++ b/apps/src/lib/wallet/mod.rs @@ -119,11 +119,8 @@ impl Wallet { pub fn gen_validator_keys( &mut self, protocol_pk: Option, + scheme: SchemeType, ) -> Result { - let scheme = match protocol_pk.as_ref().unwrap() { - common::PublicKey::Ed25519(_) => SchemeType::Ed25519, - common::PublicKey::Secp256k1(_) => SchemeType::Secp256k1, - }; let protocol_keypair = protocol_pk.map(|pk| { self.find_key_by_pkh(&PublicKeyHash::from(&pk)) .ok() From 5b04291677876bd4739f3a54be0bc1096020bd53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 15:25:17 +0200 Subject: [PATCH 177/373] shared: optional secp256k1 signing and verification to avoid wasm bloat --- apps/Cargo.toml | 2 +- shared/Cargo.toml | 7 ++- shared/src/types/key/secp256k1.rs | 90 ++++++++++++++++++--------- wasm/tx_template/Cargo.lock | 37 +---------- wasm/vp_template/Cargo.lock | 37 +---------- wasm/wasm_source/Cargo.lock | 37 +---------- wasm_for_tests/wasm_source/Cargo.lock | 37 +---------- 7 files changed, 77 insertions(+), 170 deletions(-) diff --git a/apps/Cargo.toml b/apps/Cargo.toml index a07918be13..9f9b65ffd5 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -46,7 +46,7 @@ std = ["ed25519-consensus/std", "rand/std", "rand_core/std"] testing = ["dev"] [dependencies] -namada = {path = "../shared", features = ["wasm-runtime", "ferveo-tpke", "rand"]} +namada = {path = "../shared", features = ["wasm-runtime", "ferveo-tpke", "rand", "secp256k1-sign-verify"]} ark-serialize = "0.3.0" ark-std = "0.3.0" async-std = {version = "1.9.0", features = ["unstable"]} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 9ea1eed52f..a4736558a2 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -41,6 +41,11 @@ wasm-runtime = [ "wasmer-vm", "wasmer", ] +# secp256k1 key signing and verification, disabled in WASM build by default as +# it bloats the build a lot +secp256k1-sign-verify = [ + "libsecp256k1/hmac", +] [dependencies] namada_proof_of_stake = {path = "../proof_of_stake"} @@ -64,7 +69,7 @@ ibc-proto = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c3 ics23 = "0.6.7" itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} -libsecp256k1 = {git = "https://github.com/brentstone/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} +libsecp256k1 = {git = "https://github.com/brentstone/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} parity-wasm = {version = "0.42.2", optional = true} # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 18d74dd5e8..e6607eda84 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -12,7 +12,6 @@ use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; use serde::ser::SerializeTuple; use serde::{Deserialize, Serialize, Serializer}; -use sha2::{Digest, Sha256}; use super::{ ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, RefTo, @@ -422,10 +421,21 @@ impl super::SigScheme for SigScheme { /// Sign the data with a key fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { - let hash = Sha256::digest(data.as_ref()); - let message = libsecp256k1::Message::parse_slice(hash.as_ref()) - .expect("Message encoding should not fail"); - Signature(libsecp256k1::sign(&message, &keypair.0).0) + #[cfg(not(features = "secp256k1-sign-verify"))] + { + // to avoid `unused-variables` warn + let _ = (keypair, data); + panic!("\"secp256k1-sign-verify\" feature must be enabled"); + } + + #[cfg(features = "secp256k1-sign-verify")] + { + use sha2::{Digest, Sha256}; + let hash = Sha256::digest(data.as_ref()); + let message = libsecp256k1::Message::parse_slice(hash.as_ref()) + .expect("Message encoding should not fail"); + Signature(libsecp256k1::sign(&message, &keypair.0).0) + } } fn verify_signature( @@ -433,19 +443,31 @@ impl super::SigScheme for SigScheme { data: &T, sig: &Self::Signature, ) -> Result<(), VerifySigError> { - let bytes = &data - .try_to_vec() - .map_err(VerifySigError::DataEncodingError)?[..]; - let hash = Sha256::digest(bytes); - let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) - .expect("Error parsing given data"); - let check = libsecp256k1::verify(message, &sig.0, &pk.0); - match check { - true => Ok(()), - false => Err(VerifySigError::SigVerifyError(format!( - "Error verifying secp256k1 signature: {}", - libsecp256k1::Error::InvalidSignature - ))), + #[cfg(not(features = "secp256k1-sign-verify"))] + { + // to avoid `unused-variables` warn + let _ = (pk, data, sig); + panic!("\"secp256k1-sign-verify\" feature must be enabled"); + } + + #[cfg(features = "secp256k1-sign-verify")] + { + use sha2::{Digest, Sha256}; + let bytes = &data + .try_to_vec() + .map_err(VerifySigError::DataEncodingError)?[..]; + let hash = Sha256::digest(bytes); + let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) + .expect("Error parsing given data"); + let is_valid = libsecp256k1::verify(message, &sig.0, &pk.0); + if is_valid { + Ok(()) + } else { + Err(VerifySigError::SigVerifyError(format!( + "Error verifying secp256k1 signature: {}", + libsecp256k1::Error::InvalidSignature + ))) + } } } @@ -454,16 +476,28 @@ impl super::SigScheme for SigScheme { data: &[u8], sig: &Self::Signature, ) -> Result<(), VerifySigError> { - let hash = Sha256::digest(data); - let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) - .expect("Error parsing raw data"); - let check = libsecp256k1::verify(message, &sig.0, &pk.0); - match check { - true => Ok(()), - false => Err(VerifySigError::SigVerifyError(format!( - "Error verifying secp256k1 signature: {}", - libsecp256k1::Error::InvalidSignature - ))), + #[cfg(not(features = "secp256k1-sign-verify"))] + { + // to avoid `unused-variables` warn + let _ = (pk, data, sig); + panic!("\"secp256k1-sign-verify\" feature must be enabled"); + } + + #[cfg(features = "secp256k1-sign-verify")] + { + use sha2::{Digest, Sha256}; + let hash = Sha256::digest(data); + let message = &libsecp256k1::Message::parse_slice(hash.as_ref()) + .expect("Error parsing raw data"); + let is_valid = libsecp256k1::verify(message, &sig.0, &pk.0); + if is_valid { + Ok(()) + } else { + Err(VerifySigError::SigVerifyError(format!( + "Error verifying secp256k1 signature: {}", + libsecp256k1::Error::InvalidSignature + ))) + } } } } diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index b49df82fa9..d41c7c2f4f 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -571,16 +571,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto-mac" version = "0.11.1" @@ -1104,37 +1094,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac", "digest 0.9.0", ] -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array", - "hmac 0.8.1", -] - [[package]] name = "http" version = "0.2.6" @@ -1383,14 +1352,12 @@ dependencies = [ "arrayref", "base64", "digest 0.9.0", - "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand", "serde", "sha2 0.9.9", - "typenum", ] [[package]] @@ -2172,7 +2139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac", "zeroize", ] diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index b1e4fbc04a..7fafa8a32c 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -571,16 +571,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto-mac" version = "0.11.1" @@ -1104,37 +1094,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac", "digest 0.9.0", ] -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array", - "hmac 0.8.1", -] - [[package]] name = "http" version = "0.2.6" @@ -1383,14 +1352,12 @@ dependencies = [ "arrayref", "base64", "digest 0.9.0", - "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand", "serde", "sha2 0.9.9", - "typenum", ] [[package]] @@ -2172,7 +2139,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac", "zeroize", ] diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 52324468f1..65deccc0dd 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -571,16 +571,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto-mac" version = "0.11.1" @@ -1104,37 +1094,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", + "crypto-mac", "digest 0.9.0", ] -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array", - "hmac 0.8.1", -] - [[package]] name = "http" version = "0.2.6" @@ -1383,14 +1352,12 @@ dependencies = [ "arrayref", "base64", "digest 0.9.0", - "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand", "serde", "sha2 0.9.9", - "typenum", ] [[package]] @@ -2198,7 +2165,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac", "zeroize", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index c2017046d1..9df5195b2f 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -572,16 +572,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "crypto-mac" version = "0.11.1" @@ -1114,35 +1104,14 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ + "crypto-mac", "digest 0.9.0", - "generic-array", - "hmac 0.8.1", ] [[package]] @@ -1393,14 +1362,12 @@ dependencies = [ "arrayref", "base64", "digest 0.9.0", - "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand", "serde", "sha2 0.9.9", - "typenum", ] [[package]] @@ -2204,7 +2171,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac", "zeroize", ] From a3821106162f6f5cc31c581f361aa7817ec89622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 15:39:04 +0200 Subject: [PATCH 178/373] client: add check on validator consensus key --- apps/src/lib/client/tx.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 69c8c967bf..1d41ebbc77 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -187,8 +187,16 @@ pub async fn submit_init_validator( .ref_to() }); - let consensus_key = - ctx.get_opt_cached(&consensus_key).unwrap_or_else(|| { + let consensus_key = ctx + .get_opt_cached(&consensus_key) + .map(|key| match *key { + common::SecretKey::Ed25519(_) => key, + common::SecretKey::Secp256k1(_) => { + eprintln!("Consensus key can only be ed25519"); + safe_exit(1) + } + }) + .unwrap_or_else(|| { println!("Generating consensus key..."); ctx.wallet .gen_key( From 2ae644807eb402fa270c44ecbef573695a093d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 16:04:24 +0200 Subject: [PATCH 179/373] update wasm checksums --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index e1b0bf27cc..7255ff1b23 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.dff97b2ae7129c92a25a4716e050a7a9ac2c98fb21dc8e8e6915ea58faa2b2a5.wasm", - "tx_from_intent.wasm": "tx_from_intent.e95e3959831c0ae989bac970335d0f819f0a0f8e0e383b02cb92d66c156875fc.wasm", - "tx_ibc.wasm": "tx_ibc.d43c21f95b75fa5dfc8b9fc2fe367ddfc85307ba0cd08b56150fabda4b5fc12a.wasm", - "tx_init_account.wasm": "tx_init_account.7215124f55ba573d9b3927dec63a4510d2d12fd142859e9f0aa8eb51b20362a2.wasm", - "tx_init_nft.wasm": "tx_init_nft.56fd7317cebdfcc0d7ccb9f3282747cf4b702fede3d981ac76ea258578847b2c.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.4f94f17d4e4c96420b256cfd248774dbcda8a83722e5b455bfb61e49d4ebd83a.wasm", - "tx_init_validator.wasm": "tx_init_validator.4afb48e53136e6c583951d1b429175939c0ab6a2dc217e56484a816d6d41bcb1.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.5274f30ea997dae4dd22404ae5ca3984ff875ba937dfb8fc78327d15d79fd463.wasm", - "tx_transfer.wasm": "tx_transfer.ce559325d9bfa23265a90fa13f289b252c479c40acf44e461e50f58cf796fa43.wasm", - "tx_unbond.wasm": "tx_unbond.ba16c538e0f7730e5ee16f82ad9cda2b13fd4a5595dea706f563352025526f83.wasm", - "tx_update_vp.wasm": "tx_update_vp.d7334588988b6cd467417f5d0a9274dcc8e7c7c21937c1d68bd1c23bcfeff9f2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.e3069273031df484d9d8956ea1a0796defed1828bf55d4c95d952e8929450245.wasm", - "tx_withdraw.wasm": "tx_withdraw.de9ccde4442ba2c7fb14fdc3a9592ae87d76e3895a323a44d61d781265c66c6d.wasm", - "vp_nft.wasm": "vp_nft.bf6b3169beeab0dbffd26815a828f17736c669da7d978d8203ac1075ef480cd8.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.dfbe1df808212b8d1c5ae425153f05cb87c156de17dfd4a9fc108b8f1cbdbf6b.wasm", - "vp_token.wasm": "vp_token.9eb6e754753d15fc32cc19fb392c33c6db36e111bbb0daa2cb02b05b335a3ffd.wasm", - "vp_user.wasm": "vp_user.8f5429ee42d39818ac4a6a8e7ac135435d8946f3a81f741a72c805e79c74ea3f.wasm" + "tx_bond.wasm": "tx_bond.e84410a18ebc648dc5ad31253cde0c5eda6fdb60ecb52270e77bfa647ca73dfb.wasm", + "tx_from_intent.wasm": "tx_from_intent.f64c701353f68b6bc2ce1f820cbc2d1ee59d844bbcc0c9d5132e50d1113884e1.wasm", + "tx_ibc.wasm": "tx_ibc.0ea35cf8d9caa7469be0ec7336caa7dc7d59304da1841e7cba1f1de16659e17e.wasm", + "tx_init_account.wasm": "tx_init_account.0605cd847b77ea1df9457b20e2f8f38cca6766843e32f1143069c3e8e1f27d32.wasm", + "tx_init_nft.wasm": "tx_init_nft.0b7e0f2d18806ef6699908627b18f5aa80ad5f996799f6e8ff8fc3493b88f5d1.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.2b53b0766349f870b1752d03200db53d444ed00bf25aed3c0185353b7f812f7c.wasm", + "tx_init_validator.wasm": "tx_init_validator.e440f76c73e7373a45ac7e8d71a9d7f48e347ca2bf331187a3e25c513b4e68a8.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.427533a7269bb9b583733edd5ce40bf42d7d82dea7530d6636ed66bd543f1dd4.wasm", + "tx_transfer.wasm": "tx_transfer.416f9fcc6007e806c6553d89dd1a920702c7f67fd17636390856aab23ea5b288.wasm", + "tx_unbond.wasm": "tx_unbond.01de9ad17a65eb42a6ccc5f6e161dc04d26105b8f2addbef7e65c81a910fa09f.wasm", + "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.cc4fb7fce012585ababa8d9c69ed80700daf1d220677120c0d204d271f9b14ef.wasm", + "tx_withdraw.wasm": "tx_withdraw.d1b794a17a3ebec5b69019b8ac8827d3c8b7b0653d6df6c896f4408c813b7d2e.wasm", + "vp_nft.wasm": "vp_nft.3f313ff59b46dd7a84f669f0c29ef0ba39c5659e34ea67c5012f7d4d72dfbc3b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.be0ce9dec2f3f0ce6124914cb43b646007f3674a1dc3929091a3403f2d1a62d0.wasm", + "vp_token.wasm": "vp_token.226b2678018a4aab7e3e7e4953544d4c482c7d6c39f5bb6515a95e08c30fb521.wasm", + "vp_user.wasm": "vp_user.1e4712ef3522415c323ebb3085dbb2c0ffa10997db493fdc925c61f7c7c5e536.wasm" } \ No newline at end of file From ea0017619fb6268ec10a468bfa5e9c7ba4b55c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 16:16:08 +0200 Subject: [PATCH 180/373] test: allow to sign and verify secp256k1 --- shared/Cargo.toml | 1 + shared/src/types/key/secp256k1.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index a4736558a2..b7ba23d542 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -103,6 +103,7 @@ zeroize = "1.5.5" [dev-dependencies] assert_matches = "1.5.0" byte-unit = "4.0.13" +libsecp256k1 = {git = "https://github.com/brentstone/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} pretty_assertions = "0.7.2" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index e6607eda84..99bcbb3f67 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -421,14 +421,14 @@ impl super::SigScheme for SigScheme { /// Sign the data with a key fn sign(keypair: &SecretKey, data: impl AsRef<[u8]>) -> Self::Signature { - #[cfg(not(features = "secp256k1-sign-verify"))] + #[cfg(not(any(test, features = "secp256k1-sign-verify")))] { // to avoid `unused-variables` warn let _ = (keypair, data); panic!("\"secp256k1-sign-verify\" feature must be enabled"); } - #[cfg(features = "secp256k1-sign-verify")] + #[cfg(any(test, features = "secp256k1-sign-verify"))] { use sha2::{Digest, Sha256}; let hash = Sha256::digest(data.as_ref()); @@ -443,14 +443,14 @@ impl super::SigScheme for SigScheme { data: &T, sig: &Self::Signature, ) -> Result<(), VerifySigError> { - #[cfg(not(features = "secp256k1-sign-verify"))] + #[cfg(not(any(test, features = "secp256k1-sign-verify")))] { // to avoid `unused-variables` warn let _ = (pk, data, sig); panic!("\"secp256k1-sign-verify\" feature must be enabled"); } - #[cfg(features = "secp256k1-sign-verify")] + #[cfg(any(test, features = "secp256k1-sign-verify"))] { use sha2::{Digest, Sha256}; let bytes = &data @@ -476,14 +476,14 @@ impl super::SigScheme for SigScheme { data: &[u8], sig: &Self::Signature, ) -> Result<(), VerifySigError> { - #[cfg(not(features = "secp256k1-sign-verify"))] + #[cfg(not(any(test, features = "secp256k1-sign-verify")))] { // to avoid `unused-variables` warn let _ = (pk, data, sig); panic!("\"secp256k1-sign-verify\" feature must be enabled"); } - #[cfg(features = "secp256k1-sign-verify")] + #[cfg(any(test, features = "secp256k1-sign-verify"))] { use sha2::{Digest, Sha256}; let hash = Sha256::digest(data); From 95f8d3910cc8257c324b172af2ce119957b033f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 22 Jul 2022 09:14:16 +0200 Subject: [PATCH 181/373] test/e2e: add test for double signing slashing --- apps/src/lib/client/utils.rs | 3 +- tests/src/e2e/ledger_tests.rs | 137 ++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 89119ead79..e22d716a6b 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1129,7 +1129,8 @@ fn network_configs_url_prefix(chain_id: &ChainId) -> String { }) } -fn write_tendermint_node_key( +/// Write the node key into tendermint config dir. +pub fn write_tendermint_node_key( tm_home_dir: &Path, node_sk: ed25519::SecretKey, ) -> ed25519::PublicKey { diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 552e675e67..efb763eff8 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1845,3 +1845,140 @@ fn test_genesis_validators() -> Result<()> { Ok(()) } + +/// In this test we intentionally make a validator node double sign blocks +/// to test that slashing evidence is received and processed by the ledger +/// correctly: +/// 1. Run 2 genesis validator ledger nodes +/// 2. Copy the first genesis validator base-dir +/// 3. Increment its ports and generate new node ID to avoid conflict +/// 4. Run it to get it to double vote and sign blocks +/// 5. Submit a valid token transfer tx to validator 0 +/// 6. Wait for double signing evidence +#[test] +fn double_signing_gets_slashed() -> Result<()> { + use std::net::SocketAddr; + use std::str::FromStr; + + use namada::types::key::{ed25519, SigScheme}; + use namada_apps::client; + use namada_apps::config::Config; + + // Setup 2 genesis validator nodes + let test = + setup::network(|genesis| setup::add_validators(1, genesis), None)?; + + // 1. Run 2 genesis validator ledger nodes + let args = ["ledger"]; + let mut validator_0 = + run_as!(test, Who::Validator(0), Bin::Node, args, Some(40))?; + validator_0.exp_string("Anoma ledger node started")?; + validator_0.exp_string("This node is a validator")?; + let _bg_validator_0 = validator_0.background(); + let mut validator_1 = + run_as!(test, Who::Validator(1), Bin::Node, args, Some(40))?; + validator_1.exp_string("Anoma ledger node started")?; + validator_1.exp_string("This node is a validator")?; + let bg_validator_1 = validator_1.background(); + + // 2. Copy the first genesis validator base-dir + let validator_0_base_dir = test.get_base_dir(&Who::Validator(0)); + let validator_0_base_dir_copy = + test.test_dir.path().join("validator-0-copy"); + fs_extra::dir::copy( + &validator_0_base_dir, + &validator_0_base_dir_copy, + &fs_extra::dir::CopyOptions { + copy_inside: true, + ..Default::default() + }, + ) + .unwrap(); + + // 3. Increment its ports and generate new node ID to avoid conflict + + // Same as in `genesis/e2e-tests-single-node.toml` for `validator-0` + let net_address_0 = SocketAddr::from_str("127.0.0.1:27656").unwrap(); + let net_address_port_0 = net_address_0.port(); + + let update_config = |ix: u8, mut config: Config| { + let first_port = net_address_port_0 + 6 * (ix as u16 + 1); + config.ledger.tendermint.p2p_address.set_port(first_port); + config + .ledger + .tendermint + .rpc_address + .set_port(first_port + 1); + config.ledger.shell.ledger_address.set_port(first_port + 2); + config + }; + + let validator_0_copy_config = update_config( + 2, + Config::load(&validator_0_base_dir_copy, &test.net.chain_id, None), + ); + validator_0_copy_config + .write(&validator_0_base_dir_copy, &test.net.chain_id, true) + .unwrap(); + + // Generate a new node key + use rand::prelude::ThreadRng; + use rand::thread_rng; + + let mut rng: ThreadRng = thread_rng(); + let node_sk = ed25519::SigScheme::generate(&mut rng); + let tm_home_dir = validator_0_base_dir_copy + .join(test.net.chain_id.as_str()) + .join("tendermint"); + let _node_pk = + client::utils::write_tendermint_node_key(&tm_home_dir, node_sk); + + // 4. Run it to get it to double vote and sign block + let loc = format!("{}:{}", std::file!(), std::line!()); + // This node will only connect to `validator_1`, so that nodes + // `validator_0` and `validator_0_copy` should start double signing + let mut validator_0_copy = setup::run_cmd( + Bin::Node, + args, + Some(40), + &test.working_dir, + validator_0_base_dir_copy, + "validator", + loc, + )?; + validator_0_copy.exp_string("Anoma ledger node started")?; + validator_0_copy.exp_string("This node is a validator")?; + let _bg_validator_0_copy = validator_0_copy.background(); + + // 5. Submit a valid token transfer tx to validator 0 + let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); + let tx_args = [ + "transfer", + "--source", + BERTHA, + "--target", + ALBERT, + "--token", + XAN, + "--amount", + "10.1", + "--fee-amount", + "0", + "--gas-limit", + "0", + "--fee-token", + XAN, + "--ledger-address", + &validator_one_rpc, + ]; + let mut client = run!(test, Bin::Client, tx_args, Some(40))?; + client.exp_string("Transaction is valid.")?; + client.assert_success(); + + // 6. Wait for double signing evidence + let mut validator_1 = bg_validator_1.foreground(); + validator_1.exp_string("Processing evidence")?; + validator_1.exp_string("Slashing")?; + + Ok(()) +} From e3a92d1229d9771077e09656741082dabb10e859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 22 Jul 2022 11:04:42 +0200 Subject: [PATCH 182/373] client/utils: switch off validator's p2p addr strict mode in localhost --- apps/src/lib/client/utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index e22d716a6b..f6cdaaf9c2 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -856,6 +856,7 @@ pub fn init_network( consensus_timeout_commit; config.ledger.tendermint.p2p_allow_duplicate_ip = allow_duplicate_ip; + config.ledger.tendermint.p2p_addr_book_strict = !localhost; // Clear the net address from the config and use it to set ports let net_address = validator_config.net_address.take().unwrap(); let first_port = SocketAddr::from_str(&net_address).unwrap().port(); From e67b5421ac1a0953fb9d71f90cd580bfdc0a9933 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 29 Jul 2022 18:51:35 +0200 Subject: [PATCH 183/373] vm/host_env: disallow raw byte updates of validity predicates --- shared/src/vm/host_env.rs | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index ac9f89c31e..ee9cd54704 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -40,15 +40,10 @@ pub enum TxRuntimeError { OutOfGas(gas::Error), #[error("Trying to modify storage for an address that doesn't exit {0}")] UnknownAddressStorageModification(Address), - #[error("Trying to update a validity predicate with an invalid WASM {0}")] - UpdateVpInvalid(WasmValidationError), + #[error("Trying to use a validity predicate with an invalid WASM {0}")] + InvalidVpCode(WasmValidationError), #[error("A validity predicate of an account cannot be deleted")] CannotDeleteVp, - #[error( - "Trying to initialize an account with an invalid validity predicate \ - WASM {0}" - )] - InitAccountInvalidVpWasm(WasmValidationError), #[error("Storage modification error: {0}")] StorageModificationError(write_log::Error), #[error("Storage error: {0}")] @@ -807,6 +802,9 @@ where tracing::debug!("tx_update {}, {:?}", key, value); let key = Key::parse(key).map_err(TxRuntimeError::StorageDataError)?; + if key.is_validity_predicate().is_some() { + tx_validate_vp_code(env, &value)?; + } check_address_existence(env, &key)?; @@ -1363,8 +1361,7 @@ where .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; tx_add_gas(env, gas)?; - tx_add_gas(env, code.len() as u64 * WASM_VALIDATION_GAS_PER_BYTE)?; - validate_untrusted_wasm(&code).map_err(TxRuntimeError::UpdateVpInvalid)?; + tx_validate_vp_code(env, &code)?; let write_log = unsafe { env.ctx.write_log.get() }; let (gas, _size_diff) = write_log @@ -1393,9 +1390,7 @@ where .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; tx_add_gas(env, gas)?; - tx_add_gas(env, code.len() as u64 * WASM_VALIDATION_GAS_PER_BYTE)?; - validate_untrusted_wasm(&code) - .map_err(TxRuntimeError::InitAccountInvalidVpWasm)?; + tx_validate_vp_code(env, &code)?; #[cfg(feature = "wasm-runtime")] { let vp_wasm_cache = unsafe { env.ctx.vp_wasm_cache.get() }; @@ -1696,6 +1691,21 @@ where Ok(()) } +/// Validate a VP WASM code in a tx environment. +fn tx_validate_vp_code( + env: &TxEnv, + code: &[u8], +) -> TxResult<()> +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + tx_add_gas(env, code.len() as u64 * WASM_VALIDATION_GAS_PER_BYTE)?; + validate_untrusted_wasm(code).map_err(TxRuntimeError::InvalidVpCode) +} + /// Evaluate a validity predicate with the given input data. pub fn vp_eval( env: &VpEnv<'static, MEM, DB, H, EVAL, CA>, From 4f8c283c17443548829cf85e250d48281bc72507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 29 Jul 2022 19:01:16 +0200 Subject: [PATCH 184/373] changelog: add #240 --- .../unreleased/improvements/240-host-env-vp-write-check.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/240-host-env-vp-write-check.md diff --git a/.changelog/unreleased/improvements/240-host-env-vp-write-check.md b/.changelog/unreleased/improvements/240-host-env-vp-write-check.md new file mode 100644 index 0000000000..ca42bc57ef --- /dev/null +++ b/.changelog/unreleased/improvements/240-host-env-vp-write-check.md @@ -0,0 +1,2 @@ +- Validate WASM code of validity predicates written by transactions. + ([#240](https://github.com/anoma/anoma/pull/240)) From cdfb974c53eabfb1db70fc7f7c42b5b15451b2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 09:56:33 +0200 Subject: [PATCH 185/373] make: add recipes to build wasm in debug --- Makefile | 11 ++++++++++- wasm/wasm_source/Makefile | 13 +++++++++++-- wasm/wasm_source/README.md | 3 ++- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 3474f6c8e6..be833edfb9 100644 --- a/Makefile +++ b/Makefile @@ -142,12 +142,21 @@ build-wasm-image-docker: build-wasm-scripts-docker: build-wasm-image-docker docker run --rm -v ${PWD}:/__w/namada/namada namada-wasm make build-wasm-scripts +debug-wasm-scripts-docker: build-wasm-image-docker + docker run --rm -v ${PWD}:/usr/local/rust/wasm anoma-wasm make debug-wasm-scripts + # Build the validity predicate, transactions, matchmaker and matchmaker filter wasm build-wasm-scripts: make -C $(wasms) make opt-wasm make checksum-wasm +# Debug build the validity predicate, transactions, matchmaker and matchmaker filter wasm +debug-wasm-scripts: + make -C $(wasms) debug + make opt-wasm + make checksum-wasm + # need python checksum-wasm: python3 wasm/checksums.py @@ -171,4 +180,4 @@ test-miri: MIRIFLAGS="-Zmiri-disable-isolation" $(cargo) +$(nightly) miri test -.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker build-wasm-scripts clean-wasm-scripts dev-deps test-miri +.PHONY : build check build-release clippy install run-ledger run-gossip reset-ledger test test-debug fmt watch clean build-doc doc build-wasm-scripts-docker debug-wasm-scripts-docker build-wasm-scripts debug-wasm-scripts clean-wasm-scripts dev-deps test-miri diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 39562a4a1e..a643f3c0e4 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -23,9 +23,13 @@ wasms += vp_testnet_faucet wasms += vp_token wasms += vp_user -# Build all wasms +# Build all wasms in release mode all: $(wasms) +# Build all wasms in debug mode +debug: + $(foreach wasm,$(wasms),make debug_$(wasm) && ) true + # `cargo check` all wasms check: $(foreach wasm,$(wasms),make check_$(wasm) && ) true @@ -53,6 +57,11 @@ $(wasms): %: RUSTFLAGS='-C link-arg=-s' $(cargo) build --release --target wasm32-unknown-unknown --features $@ && \ cp "./target/wasm32-unknown-unknown/release/namada_wasm.wasm" ../$@.wasm +# Build a selected wasm in debug mode +$(patsubst %,debug_%,$(wasms)): debug_%: + RUSTFLAGS='-C link-arg=-s' $(cargo) build --target wasm32-unknown-unknown --features $* && \ + cp "./target/wasm32-unknown-unknown/debug/anoma_wasm.wasm" ../$*.wasm + # `cargo check` one of the wasms, e.g. `make check_tx_transfer` $(patsubst %,check_%,$(wasms)): check_%: $(cargo) check --target wasm32-unknown-unknown --features $* @@ -78,4 +87,4 @@ clean: deps: $(rustup) target add wasm32-unknown-unknown -.PHONY : all check test clippy fmt fmt-check clean deps +.PHONY : all debug check test clippy fmt fmt-check clean deps diff --git a/wasm/wasm_source/README.md b/wasm/wasm_source/README.md index b3142156a4..423e29d034 100644 --- a/wasm/wasm_source/README.md +++ b/wasm/wasm_source/README.md @@ -13,7 +13,8 @@ make all # Each source that is included here can also be build and checked individually, e.g. for "tx_transfer" source: -make tx_transfer # build +make tx_transfer # optimized build (strips `debug_log!` statements) +make debug_tx_transfer # debug build make check_tx_transfer # cargo check make test_tx_transfer # cargo test make watch_tx_transfer # cargo watch From 907ac27d1a4d0bebf9edbef48f020d3ae7fc09b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 11:24:41 +0200 Subject: [PATCH 186/373] tx/vp_prelude: improve debug_log! macro --- tx_prelude/src/lib.rs | 26 +++++++++++++++++++------- vp_prelude/src/lib.rs | 26 +++++++++++++++++++------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 315c68384e..00b459106b 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -8,14 +8,26 @@ pub use namada_vm_env::tx_prelude::*; -/// Log a string in a debug build. The message will be printed at the -/// `tracing::Level::Info`. Any `debug_log!` statements are only enabled in -/// non optimized builds by default. An optimized build will not execute -/// `debug_log!` statements unless `-C debug-assertions` is passed to the -/// compiler. +/// Format and log a string in a debug build. +/// +/// In WASM target debug build, the message will be printed at the +/// `tracing::Level::Info` when executed in the VM. An optimized build will +/// omit any `debug_log!` statements unless `-C debug-assertions` is passed to +/// the compiler. +/// +/// In non-WASM target, the message is simply printed out to stdout. #[macro_export] macro_rules! debug_log { ($($arg:tt)*) => {{ - (if cfg!(debug_assertions) { log_string(format!($($arg)*)) }) - }} + ( + if cfg!(target_arch = "wasm32") { + if cfg!(debug_assertions) + { + log_string(format!($($arg)*)); + } + } else { + println!($($arg)*); + } + ) + }}; } diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 957354d848..6aea71c8b6 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -33,14 +33,26 @@ pub fn is_vp_whitelisted(vp_bytes: &[u8]) -> bool { whitelist.is_empty() || whitelist.contains(&vp_hash.to_string()) } -/// Log a string in a debug build. The message will be printed at the -/// `tracing::Level::Info`. Any `debug_log!` statements are only enabled in -/// non optimized builds by default. An optimized build will not execute -/// `debug_log!` statements unless `-C debug-assertions` is passed to the -/// compiler. +/// Format and log a string in a debug build. +/// +/// In WASM target debug build, the message will be printed at the +/// `tracing::Level::Info` when executed in the VM. An optimized build will +/// omit any `debug_log!` statements unless `-C debug-assertions` is passed to +/// the compiler. +/// +/// In non-WASM target, the message is simply printed out to stdout. #[macro_export] macro_rules! debug_log { ($($arg:tt)*) => {{ - (if cfg!(debug_assertions) { log_string(format!($($arg)*)) }) - }} + ( + if cfg!(target_arch = "wasm32") { + if cfg!(debug_assertions) + { + log_string(format!($($arg)*)); + } + } else { + println!($($arg)*); + } + ) + }}; } From e910b21bbb76a1c862fa723178fbdf4ef571e280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 10:00:55 +0200 Subject: [PATCH 187/373] changelog: add #1243 --- .changelog/unreleased/miscellaneous/1243-debug-wasm-build.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/1243-debug-wasm-build.md diff --git a/.changelog/unreleased/miscellaneous/1243-debug-wasm-build.md b/.changelog/unreleased/miscellaneous/1243-debug-wasm-build.md new file mode 100644 index 0000000000..2acf6479aa --- /dev/null +++ b/.changelog/unreleased/miscellaneous/1243-debug-wasm-build.md @@ -0,0 +1,2 @@ +- Added a make recipe to build WASM in debug mode with `make debug-wasm-scripts` + ([#1243](https://github.com/anoma/anoma/pull/1243)) \ No newline at end of file From 5763877e626732f371cb9153ec1653aa751b5bd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 6 May 2022 16:32:44 +0200 Subject: [PATCH 188/373] shared/key: add arb_common_keypair testing strategy --- shared/src/types/key/mod.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a1343cd7e5..cd06092ec9 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -379,6 +379,12 @@ pub mod testing { }) } + /// Generate an arbitrary [`common::SecretKey`]. + pub fn arb_common_keypair() -> impl Strategy { + arb_keypair::() + .prop_map(|keypair| keypair.try_to_sk().unwrap()) + } + /// Generate a new random [`super::SecretKey`]. pub fn gen_keypair() -> S::SecretKey { let mut rng: ThreadRng = thread_rng(); From 275a636af011ecfae4906eee157996b8d73141ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 6 May 2022 16:33:15 +0200 Subject: [PATCH 189/373] shared/token: add arb_amount testing strategy --- shared/src/types/token.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index f3e4cd4ed2..5e4d0c579c 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -393,3 +393,15 @@ mod tests { assert_eq!("0", zero.to_string()); } } + +/// Helpers for testing with addresses. +#[cfg(any(test, feature = "testing"))] +pub mod testing { + use proptest::prelude::*; + use super::*; + + /// Generate an arbitrary token amount + pub fn arb_amount() -> impl Strategy { + any::().prop_map(Amount::from) + } +} \ No newline at end of file From 07a68212e5bd174da26b973fd4bc32cf7261b387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 6 May 2022 16:33:27 +0200 Subject: [PATCH 190/373] wasm: add tx_bond tests --- wasm/wasm_source/src/tx_bond.rs | 344 ++++++++++++++++++++++++++++++++ 1 file changed, 344 insertions(+) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 9a5309f927..2394265acc 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -17,3 +17,347 @@ fn apply_tx(tx_data: Vec) { panic!() } } + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use namada::ledger::pos::PosParams; + use namada::proto::Tx; + use namada_tests::log::test; + use namada_tests::native_vp::pos::init_pos; + use namada_tests::native_vp::TestNativeVpEnv; + use namada_tests::tx::*; + use namada_tx_prelude::address::testing::{ + arb_established_address, arb_non_internal_address, + }; + use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::key::testing::arb_common_keypair; + use namada_tx_prelude::key::RefTo; + use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; + use namada_tx_prelude::token; + use namada_vp_prelude::proof_of_stake::types::{ + Bond, VotingPower, VotingPowerDelta, + }; + use namada_vp_prelude::proof_of_stake::{ + staking_token_address, BondId, GenesisValidator, PosVP, + }; + use proptest::prelude::*; + + use super::*; + + proptest! { + /// In this test we setup the ledger and PoS system with an arbitrary + /// initial state with 1 genesis validator, arbitrary PoS parameters and + /// a we generate an arbitrary bond that we'd like to apply. + /// + /// After we apply the bond, we're checking that all the storage values + /// in PoS system have been updated as expected and then we also check + /// that this transaction is accepted by the PoS validity predicate. + #[test] + fn test_tx_bond( + (initial_stake, bond) in arb_initial_stake_and_bond(), + // A key to sign the transaction + key in arb_common_keypair(), + pos_params in arb_pos_params()) { + test_tx_bond_aux(initial_stake, bond, key, pos_params) + } + } + + fn test_tx_bond_aux( + initial_stake: token::Amount, + bond: transaction::pos::Bond, + key: key::common::SecretKey, + pos_params: PosParams, + ) { + let staking_reward_address = address::testing::established_address_1(); + let consensus_key = key::testing::keypair_1().ref_to(); + let staking_reward_key = key::testing::keypair_2().ref_to(); + + let genesis_validators = [GenesisValidator { + address: bond.validator.clone(), + staking_reward_address, + tokens: initial_stake, + consensus_key, + staking_reward_key, + }]; + + init_pos(&genesis_validators[..], &pos_params); + tx_host_env::with(|tx_env| { + if let Some(source) = &bond.source { + tx_env.spawn_accounts([source]); + } + + // Ensure that the bond's source has enough tokens for the bond + let target = bond.source.as_ref().unwrap_or(&bond.validator); + tx_env.credit_tokens(target, &staking_token_address(), bond.amount); + }); + + let tx_code = vec![]; + let tx_data = bond.try_to_vec().unwrap(); + let tx = Tx::new(tx_code, Some(tx_data)); + let signed_tx = tx.sign(&key); + let tx_data = signed_tx.data.unwrap(); + + // Read the data before the tx is executed + let pos_balance_key = token::balance_key( + &staking_token_address(), + &Address::Internal(InternalAddress::PoS), + ); + let pos_balance_pre: token::Amount = + read(&pos_balance_key.to_string()).expect("PoS must have balance"); + assert_eq!(pos_balance_pre, initial_stake); + let total_voting_powers_pre = PoS.read_total_voting_power(); + let validator_sets_pre = PoS.read_validator_set(); + let validator_voting_powers_pre = + PoS.read_validator_voting_power(&bond.validator).unwrap(); + + apply_tx(tx_data); + + // Read the data after the tx is executed + + // The following storage keys should be updated: + + // - `#{PoS}/validator/#{validator}/total_deltas` + let total_delta_post = PoS.read_validator_total_deltas(&bond.validator); + for epoch in 0..pos_params.pipeline_len { + assert_eq!( + total_delta_post.as_ref().unwrap().get(epoch), + Some(initial_stake.into()), + "The total deltas before the pipeline offset must not change \ + - checking in epoch: {epoch}" + ); + } + for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { + let expected_stake = + i128::from(initial_stake) + i128::from(bond.amount); + assert_eq!( + total_delta_post.as_ref().unwrap().get(epoch), + Some(expected_stake), + "The total deltas at and after the pipeline offset epoch must \ + be incremented by the bonded amount - checking in epoch: \ + {epoch}" + ); + } + + // - `#{staking_token}/balance/#{PoS}` + let pos_balance_post: token::Amount = + read(&pos_balance_key.to_string()).unwrap(); + assert_eq!(pos_balance_pre + bond.amount, pos_balance_post); + + // - `#{PoS}/bond/#{owner}/#{validator}` + let bond_src = bond + .source + .clone() + .unwrap_or_else(|| bond.validator.clone()); + let bond_id = BondId { + validator: bond.validator.clone(), + source: bond_src, + }; + let bonds_post = PoS.read_bond(&bond_id).unwrap(); + match &bond.source { + Some(_) => { + // This bond was a delegation + for epoch in 0..pos_params.pipeline_len { + let bond: Option> = + bonds_post.get(epoch); + assert!( + bond.is_none(), + "Delegation before pipeline offset should be empty - \ + checking epoch {epoch}" + ); + } + for epoch in pos_params.pipeline_len..=pos_params.unbonding_len + { + let start_epoch = + namada_tx_prelude::proof_of_stake::types::Epoch::from( + pos_params.pipeline_len, + ); + let expected_bond = + HashMap::from_iter([(start_epoch, bond.amount)]); + let bond: Bond = + bonds_post.get(epoch).unwrap(); + assert_eq!( + bond.deltas, expected_bond, + "Delegation at and after pipeline offset should be \ + equal to the bonded amount - checking epoch {epoch}" + ); + } + } + None => { + let genesis_epoch = + namada_tx_prelude::proof_of_stake::types::Epoch::from(0); + // It was a self-bond + for epoch in 0..pos_params.pipeline_len { + let expected_bond = + HashMap::from_iter([(genesis_epoch, initial_stake)]); + let bond: Bond = + bonds_post.get(epoch).expect( + "Genesis validator should already have self-bond", + ); + assert_eq!( + bond.deltas, expected_bond, + "Delegation before pipeline offset should be equal to \ + the genesis initial stake - checking epoch {epoch}" + ); + } + for epoch in pos_params.pipeline_len..=pos_params.unbonding_len + { + let start_epoch = + namada_tx_prelude::proof_of_stake::types::Epoch::from( + pos_params.pipeline_len, + ); + let expected_bond = HashMap::from_iter([ + (genesis_epoch, initial_stake), + (start_epoch, bond.amount), + ]); + let bond: Bond = + bonds_post.get(epoch).unwrap(); + assert_eq!( + bond.deltas, expected_bond, + "Delegation at and after pipeline offset should \ + contain genesis stake and the bonded amount - \ + checking epoch {epoch}" + ); + } + } + } + + // If the voting power from validator's initial stake is different + // from the voting power after the bond is applied, we expect the + // following 3 fields to be updated: + // - `#{PoS}/total_voting_power` (optional) + // - `#{PoS}/validator_set` (optional) + // - `#{PoS}/validator/#{validator}/voting_power` (optional) + let total_voting_powers_post = PoS.read_total_voting_power(); + let validator_sets_post = PoS.read_validator_set(); + let validator_voting_powers_post = + PoS.read_validator_voting_power(&bond.validator).unwrap(); + + let voting_power_pre = + VotingPower::from_tokens(initial_stake, &pos_params); + let voting_power_post = + VotingPower::from_tokens(initial_stake + bond.amount, &pos_params); + if voting_power_pre == voting_power_post { + // None of the optional storage fields should have been updated + assert_eq!(total_voting_powers_pre, total_voting_powers_post); + assert_eq!(validator_sets_pre, validator_sets_post); + assert_eq!( + validator_voting_powers_pre, + validator_voting_powers_post + ); + } else { + for epoch in 0..pos_params.pipeline_len { + let total_voting_power_pre = total_voting_powers_pre.get(epoch); + let total_voting_power_post = + total_voting_powers_post.get(epoch); + assert_eq!( + total_voting_power_pre, total_voting_power_post, + "Total voting power before pipeline offset must not \ + change - checking epoch {epoch}" + ); + + let validator_set_pre = validator_sets_pre.get(epoch); + let validator_set_post = validator_sets_post.get(epoch); + assert_eq!( + validator_set_pre, validator_set_post, + "Validator set before pipeline offset must not change - \ + checking epoch {epoch}" + ); + + let validator_voting_power_pre = + validator_voting_powers_pre.get(epoch); + let validator_voting_power_post = + validator_voting_powers_post.get(epoch); + assert_eq!( + validator_voting_power_pre, validator_voting_power_post, + "Validator's voting power before pipeline offset must not \ + change - checking epoch {epoch}" + ); + } + for epoch in pos_params.pipeline_len..=pos_params.unbonding_len { + let total_voting_power_pre = + total_voting_powers_pre.get(epoch).unwrap(); + let total_voting_power_post = + total_voting_powers_post.get(epoch).unwrap(); + assert_ne!( + total_voting_power_pre, total_voting_power_post, + "Total voting power at and after pipeline offset must \ + have changed - checking epoch {epoch}" + ); + + let validator_set_pre = validator_sets_pre.get(epoch).unwrap(); + let validator_set_post = + validator_sets_post.get(epoch).unwrap(); + assert_ne!( + validator_set_pre, validator_set_post, + "Validator set at and after pipeline offset must have \ + changed - checking epoch {epoch}" + ); + + let validator_voting_power_pre = + validator_voting_powers_pre.get(epoch).unwrap(); + let validator_voting_power_post = + validator_voting_powers_post.get(epoch).unwrap(); + assert_ne!( + validator_voting_power_pre, validator_voting_power_post, + "Validator's voting power at and after pipeline offset \ + must have changed - checking epoch {epoch}" + ); + + // Expected voting power from the model ... + let expected_validator_voting_power: VotingPowerDelta = + voting_power_post.try_into().unwrap(); + // ... must match the voting power read from storage + assert_eq!( + validator_voting_power_post, + expected_validator_voting_power + ); + } + } + + // Use the tx_env to run PoS VP + let tx_env = tx_host_env::take(); + let vp_env = TestNativeVpEnv::new(tx_env); + let result = vp_env.validate_tx(PosVP::new, |_tx_data| {}); + let result = + result.expect("Validation of valid changes must not fail!"); + assert!( + result, + "PoS Validity predicate must accept this transaction" + ); + } + + prop_compose! { + /// Generates an initial validator stake and a bond, while making sure + /// that the `initial_stake + bond.amount <= u64::MAX` to avoid + /// overflow. + fn arb_initial_stake_and_bond() + // Generate initial stake + (initial_stake in token::testing::arb_amount()) + // Use the initial stake to limit the bond amount + (bond in arb_bond(u64::MAX - u64::from(initial_stake)), + // Use the generated initial stake too too + initial_stake in Just(initial_stake), + ) -> (token::Amount, transaction::pos::Bond) { + (initial_stake, bond) + } + } + + fn arb_bond( + max_amount: u64, + ) -> impl Strategy { + ( + arb_established_address(), + prop::option::of(arb_non_internal_address()), + token::testing::arb_amount_ceiled(max_amount), + ) + .prop_map(|(validator, source, amount)| { + transaction::pos::Bond { + validator: Address::Established(validator), + amount, + source, + } + }) + } +} From 9ccbece8d0e03398609b3fcad56c540b3ddf37a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 6 May 2022 17:17:22 +0200 Subject: [PATCH 191/373] shared/token: add arb_amount_ceiled testing strategy --- shared/src/types/token.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 5e4d0c579c..f3011514a1 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -398,10 +398,16 @@ mod tests { #[cfg(any(test, feature = "testing"))] pub mod testing { use proptest::prelude::*; + use super::*; /// Generate an arbitrary token amount pub fn arb_amount() -> impl Strategy { any::().prop_map(Amount::from) } -} \ No newline at end of file + + /// Generate an arbitrary token amount up to and including given `max` value + pub fn arb_amount_ceiled(max: u64) -> impl Strategy { + (0..=max).prop_map(Amount::from) + } +} From d1887fdf98bbe72d08a6bb13705a7026a60ee8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 11 May 2022 13:01:21 +0200 Subject: [PATCH 192/373] tests: expose native_vp test helpers --- tests/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 1b75f83bdc..f1d2b812bc 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -9,8 +9,7 @@ mod vm_host_env; pub use vm_host_env::{ibc, tx, vp}; #[cfg(test)] mod e2e; -#[cfg(test)] -mod native_vp; +pub mod native_vp; pub mod storage; /// Using this import requires `tracing` and `tracing-subscriber` dependencies. From 0b278a67c9a5de17c1909e28ee1cd1bf345c9f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 11 May 2022 13:01:49 +0200 Subject: [PATCH 193/373] PoS: add PartialOrd, Ord, PartialEq and Eq for Epoched an EpochedDelta --- proof_of_stake/src/epoched.rs | 50 +++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index 29137b49bd..bb0f2eeafa 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -11,7 +11,17 @@ use crate::PosParams; /// Data that may have values set for future epochs, up to an epoch at offset as /// set via the `Offset` type parameter. -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, +)] pub struct Epoched where Data: Clone + BorshDeserialize + BorshSerialize + BorshSchema, @@ -27,7 +37,17 @@ where /// Data that may have delta values (a difference from the predecessor epoch) /// set for future epochs, up to an epoch at offset as set via the `Offset` type /// parameter. -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, +)] pub struct EpochedDelta where Data: Clone @@ -56,7 +76,17 @@ pub trait EpochOffset: } /// Offset at pipeline length. -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, +)] pub struct OffsetPipelineLen; impl EpochOffset for OffsetPipelineLen { fn value(params: &PosParams) -> u64 { @@ -69,7 +99,17 @@ impl EpochOffset for OffsetPipelineLen { } /// Offset at unbonding length. -#[derive(Debug, Clone, BorshDeserialize, BorshSerialize, BorshSchema)] +#[derive( + Debug, + Clone, + BorshDeserialize, + BorshSerialize, + BorshSchema, + PartialEq, + Eq, + PartialOrd, + Ord, +)] pub struct OffsetUnboundingLen; impl EpochOffset for OffsetUnboundingLen { fn value(params: &PosParams) -> u64 { @@ -82,7 +122,7 @@ impl EpochOffset for OffsetUnboundingLen { } /// Offset length dynamic choice. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum DynEpochOffset { /// Offset at pipeline length. PipelineLen, From ba9983e08b47a36a1373841bf90cde7058a001e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 11 May 2022 15:00:53 +0200 Subject: [PATCH 194/373] tests: add re-usable PoS initialization helper --- tests/src/native_vp/pos.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index be1844c6cd..cf7114d5ed 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -99,6 +99,37 @@ //! - add arb invalid storage changes //! - add slashes +use anoma::ledger::pos::anoma_proof_of_stake::PosBase; +use anoma_vm_env::proof_of_stake::{ + staking_token_address, GenesisValidator, PosParams, +}; + +use crate::tx::tx_host_env; + +/// initialize proof-of-stake genesis with the given list of validators and +/// parameters. +pub fn init_pos(genesis_validators: &[GenesisValidator], params: &PosParams) { + tx_host_env::init(); + + tx_host_env::with(|tx_env| { + // Ensure that all the used + // addresses exist + tx_env.spawn_accounts([&staking_token_address()]); + for validator in genesis_validators { + tx_env.spawn_accounts([ + &validator.address, + &validator.staking_reward_address, + ]); + } + // Initialize PoS storage + let start_epoch = 0; + tx_env + .storage + .init_genesis(params, genesis_validators.iter(), start_epoch) + .unwrap(); + }); +} + #[cfg(test)] mod tests { From cfe2971b751b6fb2acd36ba89d25fecf24a8171d Mon Sep 17 00:00:00 2001 From: ajinkya Date: Fri, 13 May 2022 11:08:29 +0200 Subject: [PATCH 195/373] tests: make native pos vp module public --- tests/src/native_vp/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index be450a7086..3fcce71f43 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -1,4 +1,4 @@ -mod pos; +pub mod pos; use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::storage::mockdb::MockDB; From 72aaf3ac9eda9eb8a60b2837d4ac16c303ac9353 Mon Sep 17 00:00:00 2001 From: ajinkya Date: Fri, 13 May 2022 12:03:42 +0200 Subject: [PATCH 196/373] tests: reuse init_pos for native pos vp test --- tests/src/native_vp/pos.rs | 47 +++++++++++++-------------------- wasm/wasm_source/src/tx_bond.rs | 4 ++- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index cf7114d5ed..23b7c569e9 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -99,8 +99,9 @@ //! - add arb invalid storage changes //! - add slashes -use anoma::ledger::pos::anoma_proof_of_stake::PosBase; -use anoma_vm_env::proof_of_stake::{ +use namada::ledger::pos::namada_proof_of_stake::PosBase; +use namada::types::storage::Epoch; +use namada_vm_env::proof_of_stake::{ staking_token_address, GenesisValidator, PosParams, }; @@ -108,7 +109,11 @@ use crate::tx::tx_host_env; /// initialize proof-of-stake genesis with the given list of validators and /// parameters. -pub fn init_pos(genesis_validators: &[GenesisValidator], params: &PosParams) { +pub fn init_pos( + genesis_validators: &[GenesisValidator], + params: &PosParams, + start_epoch: Epoch, +) { tx_host_env::init(); tx_host_env::with(|tx_env| { @@ -121,11 +126,15 @@ pub fn init_pos(genesis_validators: &[GenesisValidator], params: &PosParams) { &validator.staking_reward_address, ]); } + tx_env.storage.block.epoch = start_epoch; // Initialize PoS storage - let start_epoch = 0; tx_env .storage - .init_genesis(params, genesis_validators.iter(), start_epoch) + .init_genesis( + params, + genesis_validators.iter(), + u64::from(start_epoch), + ) .unwrap(); }); } @@ -133,12 +142,11 @@ pub fn init_pos(genesis_validators: &[GenesisValidator], params: &PosParams) { #[cfg(test)] mod tests { - use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::pos::PosParams; use namada::types::storage::Epoch; use namada::types::token; use namada_vm_env::proof_of_stake::parameters::testing::arb_pos_params; - use namada_vm_env::proof_of_stake::{staking_token_address, PosVP}; + use namada_vm_env::proof_of_stake::PosVP; use namada_vm_env::tx_prelude::Address; use proptest::prelude::*; use proptest::prop_state_machine; @@ -150,8 +158,9 @@ mod tests { arb_invalid_pos_action, arb_valid_pos_action, InvalidPosAction, ValidPosAction, }; + use super::*; use crate::native_vp::TestNativeVpEnv; - use crate::tx::{tx_host_env, TestTxEnv}; + use crate::tx::tx_host_env; prop_state_machine! { #![proptest_config(Config { @@ -220,28 +229,8 @@ mod tests { ) -> Self::ConcreteState { println!(); println!("New test case"); - // Initialize the transaction env - let mut tx_env = TestTxEnv::default(); - - // Set the epoch - let storage = &mut tx_env.storage; - storage.block.epoch = initial_state.epoch; - - // Initialize PoS storage - storage - .init_genesis( - &initial_state.params, - [].into_iter(), - initial_state.epoch, - ) - .unwrap(); - - // Make sure that the staking token account exist - tx_env.spawn_accounts([staking_token_address()]); - - // Use the `tx_env` for host env calls - tx_host_env::set(tx_env); + init_pos(&[], &initial_state.params, initial_state.epoch); // The "genesis" block state for change in initial_state.committed_valid_actions { diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 2394265acc..c07f3878db 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -24,6 +24,7 @@ mod tests { use namada::ledger::pos::PosParams; use namada::proto::Tx; + use namada::types::storage::Epoch; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -82,7 +83,8 @@ mod tests { staking_reward_key, }]; - init_pos(&genesis_validators[..], &pos_params); + init_pos(&genesis_validators[..], &pos_params, Epoch(0)); + tx_host_env::with(|tx_env| { if let Some(source) = &bond.source { tx_env.spawn_accounts([source]); From 5baeda5aed396fad198a291b7603e3d0a5bcc6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 2 Jun 2022 15:30:06 +0200 Subject: [PATCH 197/373] PoS: fix type s/OffsetUnboundingLen/OffsetUnbondingLen --- proof_of_stake/src/epoched.rs | 8 ++++---- proof_of_stake/src/types.rs | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index bb0f2eeafa..8524346b8c 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -110,8 +110,8 @@ impl EpochOffset for OffsetPipelineLen { PartialOrd, Ord, )] -pub struct OffsetUnboundingLen; -impl EpochOffset for OffsetUnboundingLen { +pub struct OffsetUnbondingLen; +impl EpochOffset for OffsetUnbondingLen { fn value(params: &PosParams) -> u64 { params.unbonding_len } @@ -610,7 +610,7 @@ mod tests { #[test] fn epoched_state_machine_with_unbounding_offset( - sequential 1..20 => EpochedAbstractStateMachine); + sequential 1..20 => EpochedAbstractStateMachine); #[test] fn epoched_delta_state_machine_with_pipeline_offset( @@ -618,7 +618,7 @@ mod tests { #[test] fn epoched_delta_state_machine_with_unbounding_offset( - sequential 1..20 => EpochedDeltaAbstractStateMachine); + sequential 1..20 => EpochedDeltaAbstractStateMachine); } /// Abstract representation of [`Epoched`]. diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 45342ef277..ce6b0b225a 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -11,7 +11,7 @@ use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use crate::epoched::{ - Epoched, EpochedDelta, OffsetPipelineLen, OffsetUnboundingLen, + Epoched, EpochedDelta, OffsetPipelineLen, OffsetUnbondingLen, }; use crate::parameters::PosParams; @@ -22,22 +22,21 @@ pub type ValidatorConsensusKeys = pub type ValidatorStates = Epoched; /// Epoched validator's total deltas. pub type ValidatorTotalDeltas = - EpochedDelta; + EpochedDelta; /// Epoched validator's voting power. pub type ValidatorVotingPowers = - EpochedDelta; + EpochedDelta; /// Epoched bond. pub type Bonds = EpochedDelta, OffsetPipelineLen>; /// Epoched unbond. pub type Unbonds = - EpochedDelta, OffsetUnboundingLen>; + EpochedDelta, OffsetUnbondingLen>; /// Epoched validator set. pub type ValidatorSets
= - Epoched, OffsetUnboundingLen>; + Epoched, OffsetUnbondingLen>; /// Epoched total voting power. -pub type TotalVotingPowers = - EpochedDelta; +pub type TotalVotingPowers = EpochedDelta; /// Epoch identifier. Epochs are identified by consecutive natural numbers. /// From f9fd2d03ac8102cd96deba29137296c849a3568f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 2 Jun 2022 15:32:05 +0200 Subject: [PATCH 198/373] PoS: fix Bonds data type and application of unbonding on it --- apps/src/lib/client/rpc.rs | 75 ++++++++++++++++----- apps/src/lib/client/tx.rs | 2 +- proof_of_stake/src/epoched.rs | 21 ++++++ proof_of_stake/src/lib.rs | 64 +++++++++++------- proof_of_stake/src/types.rs | 36 ++++++---- proof_of_stake/src/validation.rs | 94 ++++++++++++++++++++++++--- shared/src/ledger/governance/utils.rs | 14 +++- tests/src/native_vp/pos.rs | 44 ++++++------- wasm/wasm_source/src/tx_bond.rs | 6 +- 9 files changed, 264 insertions(+), 92 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..465c1b4c0f 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1194,7 +1194,7 @@ fn process_bonds_query( let mut total_active = total_active.unwrap_or_else(|| 0.into()); let mut current_total: token::Amount = 0.into(); for bond in bonds.iter() { - for (epoch_start, &(mut delta)) in bond.deltas.iter().sorted() { + for (epoch_start, &(mut delta)) in bond.pos_deltas.iter().sorted() { writeln!(w, " Active from epoch {}: Δ {}", epoch_start, delta) .unwrap(); delta = apply_slashes(slashes, delta, *epoch_start, None, Some(w)); @@ -1653,25 +1653,56 @@ pub async fn get_proposal_offline_votes( let bonds_iter = query_storage_prefix::(client.clone(), key).await; if let Some(bonds) = bonds_iter { - for (key, epoched_amount) in bonds { - let bond = epoched_amount - .get(proposal.tally_epoch) - .expect("Delegation bond should be definied."); + for (key, epoched_bonds) in bonds { + // Look-up slashes for the validator in this key and + // apply them if any + let validator = pos::get_validator_address_from_bond(&key) + .expect( + "Delegation key should contain validator address.", + ); + let slashes_key = pos::validator_slashes_key(&validator); + let slashes = query_storage_value::( + client, + &slashes_key, + ) + .await + .unwrap_or_default(); + let mut delegated_amount: token::Amount = 0.into(); let epoch = namada::ledger::pos::types::Epoch::from( proposal.tally_epoch.0, ); - let amount = *bond - .deltas - .get(&epoch) - .expect("Delegation amount should be definied."); - let validator_address = - pos::get_validator_address_from_bond(&key).expect( - "Delegation key should contain validator address.", + let bond = epoched_bonds + .get(epoch) + .expect("Delegation bond should be definied."); + let mut to_deduct = bond.neg_deltas; + for (start_epoch, &(mut delta)) in + bond.pos_deltas.iter().sorted() + { + // deduct bond's neg_deltas + if to_deduct > delta { + to_deduct -= delta; + // If the whole bond was deducted, continue to + // the next one + continue; + } else { + delta -= to_deduct; + to_deduct = token::Amount::default(); + } + + delta = apply_slashes( + &slashes, + delta, + *start_epoch, + None, + None, ); + delegated_amount += delta; + } + if proposal_vote.vote.is_yay() { - yay_delegators.insert(validator_address, amount); + yay_delegators.insert(validator, delegated_amount); } else { - nay_delegators.insert(validator_address, amount); + nay_delegators.insert(validator, delegated_amount); } } } @@ -1746,7 +1777,21 @@ pub async fn get_bond_amount_at( Some(epoched_bonds) => { let mut delegated_amount: token::Amount = 0.into(); for bond in epoched_bonds.iter() { - for (epoch_start, &(mut delta)) in bond.deltas.iter().sorted() { + let mut to_deduct = bond.neg_deltas; + for (epoch_start, &(mut delta)) in + bond.pos_deltas.iter().sorted() + { + // deduct bond's neg_deltas + if to_deduct > delta { + to_deduct -= delta; + // If the whole bond was deducted, continue to + // the next one + continue; + } else { + delta -= to_deduct; + to_deduct = token::Amount::default(); + } + delta = apply_slashes( &slashes, delta, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 6f1fd96707..e1b96665ff 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -867,7 +867,7 @@ pub async fn submit_unbond(ctx: Context, args: args::Unbond) { Some(bonds) => { let mut bond_amount: token::Amount = 0.into(); for bond in bonds.iter() { - for delta in bond.deltas.values() { + for delta in bond.pos_deltas.values() { bond_amount += *delta; } } diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index 8524346b8c..5191345df3 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -584,6 +584,27 @@ where } } + /// Apply the given `f` function on each delta value in reverse order + /// (starting from the future-most epoch) while the given function returns + /// `true`. + pub fn rev_while( + &self, + mut f: impl FnMut(&Data, Epoch) -> bool, + current_epoch: impl Into, + params: &PosParams, + ) { + let epoch = current_epoch.into(); + let offset = Offset::value(params) as usize; + for ix in (0..offset + 1).rev() { + if let Some(Some(current)) = self.data.get(ix) { + let keep_going = f(current, epoch + ix); + if !keep_going { + break; + } + } + } + } + /// Get the epoch of the last update pub fn last_update(&self) -> Epoch { self.last_update diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index a137eb8a91..0c7a50c31d 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -64,7 +64,7 @@ pub trait PosReadOnly { + Copy + Add + AddAssign - + Sub + + Sub + PartialOrd + Into + From @@ -1174,10 +1174,15 @@ where source: address.clone(), validator: address.clone(), }; - let mut deltas = HashMap::default(); - deltas.insert(current_epoch, *tokens); - let bond = - EpochedDelta::init_at_genesis(Bond { deltas }, current_epoch); + let mut pos_deltas = HashMap::default(); + pos_deltas.insert(current_epoch, *tokens); + let bond = EpochedDelta::init_at_genesis( + Bond { + pos_deltas, + neg_deltas: Default::default(), + }, + current_epoch, + ); Ok(GenesisValidatorData { address: address.clone(), staking_reward_address: staking_reward_address.clone(), @@ -1479,15 +1484,21 @@ where // Update or create the bond let mut value = Bond { - deltas: HashMap::default(), + pos_deltas: HashMap::default(), + neg_deltas: TokenAmount::default(), }; value - .deltas + .pos_deltas .insert(current_epoch + update_offset.value(params), amount); let bond = match current_bond { - None => EpochedDelta::init(value, current_epoch, params), + None => EpochedDelta::init_at_offset( + value, + current_epoch, + update_offset, + params, + ), Some(mut bond) => { - bond.add(value, current_epoch, params); + bond.add_at_offset(value, current_epoch, update_offset, params); bond } }; @@ -1605,6 +1616,7 @@ where + AddAssign + Into + From + + Sub + SubAssign + BorshDeserialize + BorshSerialize @@ -1615,7 +1627,7 @@ where + Clone + Copy + Add - + Sub + + Sub + From + Neg + Into @@ -1650,27 +1662,25 @@ where let mut slashed_amount = TokenAmount::default(); // Decrement the bond deltas starting from the rightmost value (a bond in a // future-most epoch) until whole amount is decremented - bond.rev_update_while( + bond.rev_while( |bonds, _epoch| { - bonds.deltas.retain(|epoch_start, bond_delta| { + for (epoch_start, bond_delta) in bonds.pos_deltas.iter() { if *to_unbond == 0.into() { return true; } let mut unbonded = HashMap::default(); let unbond_end = current_epoch + update_offset.value(params) - 1; - // We need to accumulate the slashed delta for multiple slashes - // applicable to a bond, where each slash should be - // calculated from the delta reduced by the previous slash. - let applied_delta = if to_unbond > bond_delta { + // We need to accumulate the slashed delta for multiple + // slashes applicable to a bond, where + // each slash should be calculated from + // the delta reduced by the previous slash. + let applied_delta = if *to_unbond > *bond_delta { unbonded.insert((*epoch_start, unbond_end), *bond_delta); *to_unbond -= *bond_delta; - let applied_delta = *bond_delta; - *bond_delta = 0.into(); - applied_delta + *bond_delta } else { unbonded.insert((*epoch_start, unbond_end), *to_unbond); - *bond_delta -= *to_unbond; let applied_delta = *to_unbond; *to_unbond = 0.into(); applied_delta @@ -1690,9 +1700,7 @@ where // For each decremented bond value write a new unbond unbond.add(Unbond { deltas: unbonded }, current_epoch, params); - // Remove bonds with no tokens left - *bond_delta != 0.into() - }); + } // Stop the update once all the tokens are unbonded *to_unbond != 0.into() }, @@ -1700,6 +1708,16 @@ where params, ); + bond.add_at_offset( + Bond { + pos_deltas: Default::default(), + neg_deltas: amount, + }, + current_epoch, + update_offset, + params, + ); + // Update validator set. This has to be done before we update the // `validator_total_deltas`, because we need to look-up the validator with // its voting power before the change. diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index ce6b0b225a..4835f05a96 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -28,7 +28,7 @@ pub type ValidatorVotingPowers = EpochedDelta; /// Epoched bond. pub type Bonds = - EpochedDelta, OffsetPipelineLen>; + EpochedDelta, OffsetUnbondingLen>; /// Epoched unbond. pub type Unbonds = EpochedDelta, OffsetUnbondingLen>; @@ -287,14 +287,18 @@ pub enum ValidatorState { Debug, Clone, Default, BorshDeserialize, BorshSerialize, BorshSchema, )] pub struct Bond { - /// A key is a the epoch set for the bond. This is used in unbonding, where - /// it's needed for slash epoch range check. + /// Bonded positive deltas. A key is a the epoch set for the bond. This is + /// used in unbonding, where it's needed for slash epoch range check. /// /// TODO: For Bonds, there's unnecessary redundancy with this hash map. /// We only need to keep the start `Epoch` for the Epoched head element /// (i.e. the current epoch data), the rest of the array can be calculated /// from the offset from the head - pub deltas: HashMap, + pub pos_deltas: HashMap, + /// Unbonded negative deltas. The values are recorded as positive, but + /// should be subtracted when we're finding the total for some given + /// epoch. + pub neg_deltas: Token, } /// An unbond contains unbonded tokens from a validator's self-bond or a @@ -545,13 +549,15 @@ where impl Bond where - Token: Clone + Copy + Add + Default, + Token: Clone + Copy + Add + Sub + Default, { /// Find the sum of all the bonds amounts. pub fn sum(&self) -> Token { - self.deltas + let pos_deltas_sum: Token = self + .pos_deltas .iter() - .fold(Default::default(), |acc, (_epoch, amount)| acc + *amount) + .fold(Default::default(), |acc, (_epoch, amount)| acc + *amount); + pos_deltas_sum - self.neg_deltas } } @@ -562,24 +568,26 @@ where type Output = Self; fn add(mut self, rhs: Self) -> Self::Output { - // This is almost the same as `self.delta.extend(rhs.delta);`, except - // that we add values where a key is present on both sides. - let iter = rhs.deltas.into_iter(); - let reserve = if self.deltas.is_empty() { + // This is almost the same as `self.pos_deltas.extend(rhs.pos_deltas);`, + // except that we add values where a key is present on both + // sides. + let iter = rhs.pos_deltas.into_iter(); + let reserve = if self.pos_deltas.is_empty() { iter.size_hint().0 } else { (iter.size_hint().0 + 1) / 2 }; - self.deltas.reserve(reserve); + self.pos_deltas.reserve(reserve); iter.for_each(|(k, v)| { // Add or insert - match self.deltas.get_mut(&k) { + match self.pos_deltas.get_mut(&k) { Some(value) => *value += v, None => { - self.deltas.insert(k, v); + self.pos_deltas.insert(k, v); } } }); + self.neg_deltas += rhs.neg_deltas; self } } diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 41485bcbb0..58c6b0c26c 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -96,6 +96,17 @@ where got: u64, expected: u64, }, + + #[error( + "Bond ID {id} must be subtracted at the correct epoch. Got epoch \ + {got}, expected {expected}" + )] + InvalidNegDeltaEpoch { + id: BondId
, + got: u64, + expected: u64, + }, + #[error( "Invalid validator {address} sum of total deltas. Total Δ \ {total_delta}, bonds Δ {bond_delta}" @@ -840,22 +851,31 @@ where // pre, not both pre and post to avoid rounding errors let mut slashed_deltas: HashMap = HashMap::default(); - + let mut neg_deltas: HashMap = + Default::default(); // Iter from the first epoch of `pre` to the last epoch of // `post` for epoch in Epoch::iter_range( pre.last_update(), - pre_offset + pipeline_offset + 1, + pre_offset + unbonding_offset + 1, ) { if let Some(bond) = pre.get_delta_at_epoch(epoch) { - for (start_epoch, delta) in bond.deltas.iter() { + for (start_epoch, delta) in bond.pos_deltas.iter() { let delta = TokenChange::from(*delta); slashed_deltas.insert(*start_epoch, -delta); pre_bonds.insert(*start_epoch, delta); } + let ins_epoch = if epoch <= current_epoch { + current_epoch + } else { + epoch + }; + let entry = + neg_deltas.entry(ins_epoch).or_default(); + *entry -= TokenChange::from(bond.neg_deltas); } if let Some(bond) = post.get_delta_at_epoch(epoch) { - for (start_epoch, delta) in bond.deltas.iter() { + for (start_epoch, delta) in bond.pos_deltas.iter() { // An empty bond must be deleted if *delta == TokenAmount::default() { errors.push(Error::EmptyBond(id.clone())) @@ -901,7 +921,7 @@ where if epoch != pipeline_epoch { match pre_bonds.get(start_epoch) { Some(pre_delta) => { - if &delta > pre_delta { + if &delta != pre_delta { errors.push( Error::InvalidNewBondEpoch { id: id.clone(), @@ -925,6 +945,40 @@ where } } } + if epoch != unbonding_epoch { + match neg_deltas.get(&epoch) { + Some(deltas) => { + if -*deltas + != TokenChange::from( + bond.neg_deltas, + ) + { + errors.push( + Error::InvalidNegDeltaEpoch { + id: id.clone(), + got: epoch.into(), + expected: unbonding_epoch + .into(), + }, + ) + } + } + None => { + if bond.neg_deltas != 0.into() { + errors.push( + Error::InvalidNegDeltaEpoch { + id: id.clone(), + got: epoch.into(), + expected: unbonding_epoch + .into(), + }, + ) + } + } + } + } + let entry = neg_deltas.entry(epoch).or_default(); + *entry += TokenChange::from(bond.neg_deltas); } } // Check slashes @@ -942,7 +996,13 @@ where .values() .fold(TokenChange::default(), |acc, delta| { acc + *delta - }); + }) + - neg_deltas + .values() + .fold(TokenChange::default(), |acc, delta| { + acc + *delta + }); + if total != TokenChange::default() { let bond_entry = bond_delta.entry(id.validator).or_default(); @@ -956,18 +1016,30 @@ where } let mut total_delta = TokenChange::default(); for epoch in - Epoch::iter_range(current_epoch, pipeline_offset + 1) + Epoch::iter_range(current_epoch, unbonding_offset + 1) { if let Some(bond) = post.get_delta_at_epoch(epoch) { // A new bond must be initialized at // `pipeline_offset` - if epoch != pipeline_epoch { + if epoch != pipeline_epoch + && !bond.pos_deltas.is_empty() + { + dbg!(&bond.pos_deltas); errors.push(Error::EpochedDataWrongEpoch { got: epoch.into(), expected: vec![pipeline_epoch.into()], }) } - for (start_epoch, delta) in bond.deltas.iter() { + if epoch != unbonding_epoch + && bond.neg_deltas != 0.into() + { + errors.push(Error::InvalidNegDeltaEpoch { + id: id.clone(), + got: epoch.into(), + expected: unbonding_epoch.into(), + }) + } + for (start_epoch, delta) in bond.pos_deltas.iter() { if *start_epoch != epoch { errors.push(Error::InvalidBondStartEpoch { id: id.clone(), @@ -989,6 +1061,7 @@ where let delta = TokenChange::from(delta); total_delta += delta } + total_delta -= TokenChange::from(bond.neg_deltas) } } // An empty bond must be deleted @@ -1006,7 +1079,7 @@ where let index = index as usize; let epoch = pre.last_update() + index; if let Some(bond) = pre.get_delta_at_epoch(epoch) { - for (start_epoch, delta) in &bond.deltas { + for (start_epoch, delta) in &bond.pos_deltas { let mut delta = *delta; // Check slashes for slash in &slashes { @@ -1021,6 +1094,7 @@ where let delta = TokenChange::from(delta); total_delta -= delta } + total_delta += TokenChange::from(bond.neg_deltas) } } let bond_entry = diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index aaca277f91..a4282a5c73 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -155,9 +155,21 @@ where (Some(epoched_bonds), Some(slashes)) => { let mut delegated_amount: token::Amount = 0.into(); for bond in epoched_bonds.iter() { + let mut to_deduct = bond.neg_deltas; for (start_epoch, &(mut delta)) in - bond.deltas.iter().sorted() + bond.pos_deltas.iter().sorted() { + // deduct bond's neg_deltas + if to_deduct > delta { + to_deduct -= delta; + // If the whole bond was deducted, continue to + // the next one + continue; + } else { + delta -= to_deduct; + to_deduct = token::Amount::default(); + } + let start_epoch = Epoch::from(*start_epoch); delta = apply_slashes(&slashes, delta, start_epoch); if epoch >= start_epoch { diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 23b7c569e9..abff6e8c2b 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -34,7 +34,7 @@ //! the modifications of its predecessor transition). //! //! The PoS storage modifications are modelled using -//! [`testing::PosStorageChange`]. +//! `testing::PosStorageChange`. //! //! - Bond: Requires a validator account in the state (the `#{validator}` //! segments in the keys below). Some of the storage change are optional, @@ -1151,9 +1151,10 @@ pub mod testing { let amount: u64 = delta.try_into().unwrap(); let amount: token::Amount = amount.into(); let mut value = Bond { - deltas: HashMap::default(), + pos_deltas: HashMap::default(), + neg_deltas: Default::default(), }; - value.deltas.insert( + value.pos_deltas.insert( (current_epoch + offset.value(params)).into(), amount, ); @@ -1178,34 +1179,27 @@ pub mod testing { ); bonds } - None => Bonds::init(value, current_epoch, params), + None => Bonds::init_at_offset( + value, + current_epoch, + offset, + params, + ), } } else { let mut bonds = bonds.unwrap_or_else(|| { Bonds::init(Default::default(), current_epoch, params) }); let to_unbond: u64 = (-delta).try_into().unwrap(); - let mut to_unbond: token::Amount = to_unbond.into(); - let to_unbond = &mut to_unbond; - bonds.rev_update_while( - |bonds, _epoch| { - bonds.deltas.retain(|_epoch_start, bond_delta| { - if *to_unbond == 0.into() { - return true; - } - if to_unbond > bond_delta { - *to_unbond -= *bond_delta; - *bond_delta = 0.into(); - } else { - *bond_delta -= *to_unbond; - *to_unbond = 0.into(); - } - // Remove bonds with no tokens left - *bond_delta != 0.into() - }); - *to_unbond != 0.into() + let to_unbond: token::Amount = to_unbond.into(); + + bonds.add_at_offset( + Bond { + pos_deltas: Default::default(), + neg_deltas: to_unbond, }, current_epoch, + offset, params, ); bonds @@ -1237,7 +1231,7 @@ pub mod testing { && bond_epoch >= bonds.last_update().into() { if let Some(bond) = bonds.get_delta_at_epoch(bond_epoch) { - for (start_epoch, delta) in &bond.deltas { + for (start_epoch, delta) in &bond.pos_deltas { if delta >= &to_unbond { value.deltas.insert( ( @@ -1612,7 +1606,7 @@ pub mod testing { // any u64 but `0` let arb_delta = - prop_oneof![(-(u64::MAX as i128)..0), (1..=u64::MAX as i128),]; + prop_oneof![(-(u32::MAX as i128)..0), (1..=u32::MAX as i128),]; prop_oneof![ ( diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index c07f3878db..5d0c79b9b9 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -180,7 +180,7 @@ mod tests { let bond: Bond = bonds_post.get(epoch).unwrap(); assert_eq!( - bond.deltas, expected_bond, + bond.pos_deltas, expected_bond, "Delegation at and after pipeline offset should be \ equal to the bonded amount - checking epoch {epoch}" ); @@ -198,7 +198,7 @@ mod tests { "Genesis validator should already have self-bond", ); assert_eq!( - bond.deltas, expected_bond, + bond.pos_deltas, expected_bond, "Delegation before pipeline offset should be equal to \ the genesis initial stake - checking epoch {epoch}" ); @@ -216,7 +216,7 @@ mod tests { let bond: Bond = bonds_post.get(epoch).unwrap(); assert_eq!( - bond.deltas, expected_bond, + bond.pos_deltas, expected_bond, "Delegation at and after pipeline offset should \ contain genesis stake and the bonded amount - \ checking epoch {epoch}" From 80f9191d611955c2e05cf41ffa2e2c14834f5a42 Mon Sep 17 00:00:00 2001 From: ajinkya Date: Mon, 30 May 2022 12:16:34 +0200 Subject: [PATCH 199/373] wasm: tx_unbond tests --- wasm/wasm_source/src/tx_unbond.rs | 397 ++++++++++++++++++++++++++++++ 1 file changed, 397 insertions(+) diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 5d2662ed5c..d73c3d7ec9 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -18,3 +18,400 @@ fn apply_tx(tx_data: Vec) { panic!() } } + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use namada::ledger::pos::PosParams; + use namada::proto::Tx; + use namada::types::storage::Epoch; + use namada_tests::log::test; + use namada_tests::native_vp::pos::init_pos; + use namada_tests::native_vp::TestNativeVpEnv; + use namada_tests::tx::*; + use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::key::testing::arb_common_keypair; + use namada_tx_prelude::key::RefTo; + use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; + use namada_tx_prelude::token; + use namada_vp_prelude::proof_of_stake::types::{ + Bond, Unbond, VotingPower, VotingPowerDelta, + }; + use namada_vp_prelude::proof_of_stake::{ + staking_token_address, BondId, GenesisValidator, PosVP, + }; + use proptest::prelude::*; + + use super::*; + + proptest! { + /// In this test we setup the ledger and PoS system with an arbitrary + /// initial state with 1 genesis validator, a delegation bond if the + /// unbond is for a delegation, arbitrary PoS parameters and + /// a we generate an arbitrary unbond that we'd like to apply. + /// + /// After we apply the unbond, we're checking that all the storage values + /// in PoS system have been updated as expected and then we also check + /// that this transaction is accepted by the PoS validity predicate. + #[test] + fn test_tx_unbond( + (initial_stake, unbond) in arb_initial_stake_and_unbond(), + // A key to sign the transaction + key in arb_common_keypair(), + pos_params in arb_pos_params()) { + test_tx_unbond_aux(initial_stake, unbond, key, pos_params) + } + } + + fn test_tx_unbond_aux( + initial_stake: token::Amount, + unbond: transaction::pos::Unbond, + key: key::common::SecretKey, + pos_params: PosParams, + ) { + let is_delegation = matches!( + &unbond.source, Some(source) if *source != unbond.validator); + let staking_reward_address = address::testing::established_address_1(); + let consensus_key = key::testing::keypair_1().ref_to(); + let staking_reward_key = key::testing::keypair_2().ref_to(); + + let genesis_validators = [GenesisValidator { + address: unbond.validator.clone(), + staking_reward_address, + tokens: if is_delegation { + // If we're unbonding a delegation, we'll give the initial stake + // to the delegation instead of the validator + token::Amount::default() + } else { + initial_stake + }, + consensus_key, + staking_reward_key, + }]; + + init_pos(&genesis_validators[..], &pos_params, Epoch(0)); + + tx_host_env::with(|tx_env| { + if is_delegation { + let source = unbond.source.as_ref().unwrap(); + tx_env.spawn_accounts([source]); + + // To allow to unbond delegation, there must be a delegation + // bond first. + // First, credit the bond's source with the initial stake, + // before we initialize the bond below + tx_env.credit_tokens( + source, + &staking_token_address(), + initial_stake, + ); + } + }); + + if is_delegation { + // Initialize the delegation - unlike genesis validator's self-bond, + // this happens at pipeline offset + namada_tx_prelude::proof_of_stake::bond_tokens( + unbond.source.as_ref(), + &unbond.validator, + initial_stake, + ) + .unwrap(); + } + tx_host_env::commit_tx_and_block(); + + let tx_code = vec![]; + let tx_data = unbond.try_to_vec().unwrap(); + let tx = Tx::new(tx_code, Some(tx_data)); + let signed_tx = tx.sign(&key); + let tx_data = signed_tx.data.unwrap(); + + let unbond_src = unbond + .source + .clone() + .unwrap_or_else(|| unbond.validator.clone()); + let unbond_id = BondId { + validator: unbond.validator.clone(), + source: unbond_src, + }; + + let pos_balance_key = token::balance_key( + &staking_token_address(), + &Address::Internal(InternalAddress::PoS), + ); + let pos_balance_pre: token::Amount = + read(&pos_balance_key.to_string()).expect("PoS must have balance"); + assert_eq!(pos_balance_pre, initial_stake); + let total_voting_powers_pre = PoS.read_total_voting_power(); + let validator_sets_pre = PoS.read_validator_set(); + let validator_voting_powers_pre = + PoS.read_validator_voting_power(&unbond.validator).unwrap(); + let bonds_pre = PoS.read_bond(&unbond_id).unwrap(); + dbg!(&bonds_pre); + + apply_tx(tx_data); + + // Read the data after the tx is executed + + // The following storage keys should be updated: + + // - `#{PoS}/validator/#{validator}/total_deltas` + let total_delta_post = + PoS.read_validator_total_deltas(&unbond.validator); + + let expected_deltas_at_pipeline = if is_delegation { + // When this is a delegation, there will be no bond until pipeline + 0.into() + } else { + // Before pipeline offset, there can only be self-bond + initial_stake + }; + + // Before pipeline offset, there can only be self-bond for genesis + // validator. In case of a delegation the state is setup so that there + // is no bond until pipeline offset. + for epoch in 0..pos_params.pipeline_len { + assert_eq!( + total_delta_post.as_ref().unwrap().get(epoch), + Some(expected_deltas_at_pipeline.into()), + "The total deltas before the pipeline offset must not change \ + - checking in epoch: {epoch}" + ); + } + + // At and after pipeline offset, there can be either delegation or + // self-bond, both of which are initialized to the same `initial_stake` + for epoch in pos_params.pipeline_len..pos_params.unbonding_len { + assert_eq!( + total_delta_post.as_ref().unwrap().get(epoch), + Some(initial_stake.into()), + "The total deltas before the unbonding offset must not change \ + - checking in epoch: {epoch}" + ); + } + + { + let epoch = pos_params.unbonding_len + 1; + let expected_stake = + i128::from(initial_stake) - i128::from(unbond.amount); + assert_eq!( + total_delta_post.as_ref().unwrap().get(epoch), + Some(expected_stake), + "The total deltas after the unbonding offset epoch must be \ + decremented by the unbonded amount - checking in epoch: \ + {epoch}" + ); + } + + // - `#{staking_token}/balance/#{PoS}` + let pos_balance_post: token::Amount = + read(&pos_balance_key.to_string()).unwrap(); + assert_eq!( + pos_balance_pre, pos_balance_post, + "Unbonding doesn't affect PoS system balance" + ); + + // - `#{PoS}/unbond/#{owner}/#{validator}` + let unbonds_post = PoS.read_unbond(&unbond_id).unwrap(); + let bonds_post = PoS.read_bond(&unbond_id).unwrap(); + for epoch in 0..pos_params.unbonding_len { + let unbond: Option> = unbonds_post.get(epoch); + + assert!( + unbond.is_none(), + "There should be no unbond until unbonding offset - checking \ + epoch {epoch}" + ); + } + let start_epoch = match &unbond.source { + Some(_) => { + // This bond was a delegation + namada_tx_prelude::proof_of_stake::types::Epoch::from( + pos_params.pipeline_len, + ) + } + None => { + // This bond was a genesis validator self-bond + namada_tx_prelude::proof_of_stake::types::Epoch::default() + } + }; + let end_epoch = namada_tx_prelude::proof_of_stake::types::Epoch::from( + pos_params.unbonding_len - 1, + ); + + let expected_unbond = + HashMap::from_iter([((start_epoch, end_epoch), unbond.amount)]); + let actual_unbond: Unbond = + unbonds_post.get(pos_params.unbonding_len).unwrap(); + assert_eq!( + actual_unbond.deltas, expected_unbond, + "Delegation at unbonding offset should be equal to the unbonded \ + amount" + ); + + for epoch in pos_params.pipeline_len..pos_params.unbonding_len { + let bond: Bond = bonds_post.get(epoch).unwrap(); + let expected_bond = + HashMap::from_iter([(start_epoch, initial_stake)]); + assert_eq!( + bond.pos_deltas, expected_bond, + "Before unbonding offset, the bond should be untouched, \ + checking epoch {epoch}" + ); + } + { + let epoch = pos_params.unbonding_len + 1; + let bond: Bond = bonds_post.get(epoch).unwrap(); + let expected_bond = + HashMap::from_iter([(start_epoch, initial_stake)]); + assert_eq!( + bond.pos_deltas, expected_bond, + "At unbonding offset, the pos deltas should not change, \ + checking epoch {epoch}" + ); + assert_eq!( + bond.neg_deltas, unbond.amount, + "At unbonding offset, the unbonded amount should have been \ + deducted, checking epoch {epoch}" + ) + } + // If the voting power from validator's initial stake is different + // from the voting power after the bond is applied, we expect the + // following 3 fields to be updated: + // - `#{PoS}/total_voting_power` (optional) + // - `#{PoS}/validator_set` (optional) + // - `#{PoS}/validator/#{validator}/voting_power` (optional) + let total_voting_powers_post = PoS.read_total_voting_power(); + let validator_sets_post = PoS.read_validator_set(); + let validator_voting_powers_post = + PoS.read_validator_voting_power(&unbond.validator).unwrap(); + + let voting_power_pre = + VotingPower::from_tokens(initial_stake, &pos_params); + let voting_power_post = VotingPower::from_tokens( + initial_stake - unbond.amount, + &pos_params, + ); + if voting_power_pre == voting_power_post { + // None of the optional storage fields should have been updated + assert_eq!(total_voting_powers_pre, total_voting_powers_post); + assert_eq!(validator_sets_pre, validator_sets_post); + assert_eq!( + validator_voting_powers_pre, + validator_voting_powers_post + ); + } else { + for epoch in 0..pos_params.unbonding_len { + let total_voting_power_pre = total_voting_powers_pre.get(epoch); + let total_voting_power_post = + total_voting_powers_post.get(epoch); + assert_eq!( + total_voting_power_pre, total_voting_power_post, + "Total voting power before pipeline offset must not \ + change - checking epoch {epoch}" + ); + + let validator_set_pre = validator_sets_pre.get(epoch); + let validator_set_post = validator_sets_post.get(epoch); + assert_eq!( + validator_set_pre, validator_set_post, + "Validator set before pipeline offset must not change - \ + checking epoch {epoch}" + ); + + let validator_voting_power_pre = + validator_voting_powers_pre.get(epoch); + let validator_voting_power_post = + validator_voting_powers_post.get(epoch); + assert_eq!( + validator_voting_power_pre, validator_voting_power_post, + "Validator's voting power before pipeline offset must not \ + change - checking epoch {epoch}" + ); + } + { + let epoch = pos_params.unbonding_len; + let total_voting_power_pre = + total_voting_powers_pre.get(epoch).unwrap(); + let total_voting_power_post = + total_voting_powers_post.get(epoch).unwrap(); + assert_ne!( + total_voting_power_pre, total_voting_power_post, + "Total voting power at and after pipeline offset must \ + have changed - checking epoch {epoch}" + ); + + let validator_set_pre = validator_sets_pre.get(epoch).unwrap(); + let validator_set_post = + validator_sets_post.get(epoch).unwrap(); + assert_ne!( + validator_set_pre, validator_set_post, + "Validator set at and after pipeline offset must have \ + changed - checking epoch {epoch}" + ); + + let validator_voting_power_pre = + validator_voting_powers_pre.get(epoch).unwrap(); + let validator_voting_power_post = + validator_voting_powers_post.get(epoch).unwrap(); + assert_ne!( + validator_voting_power_pre, validator_voting_power_post, + "Validator's voting power at and after pipeline offset \ + must have changed - checking epoch {epoch}" + ); + + // Expected voting power from the model ... + let expected_validator_voting_power: VotingPowerDelta = + voting_power_post.try_into().unwrap(); + // ... must match the voting power read from storage + assert_eq!( + validator_voting_power_post, + expected_validator_voting_power + ); + } + } + + // Use the tx_env to run PoS VP + let tx_env = tx_host_env::take(); + let vp_env = TestNativeVpEnv::new(tx_env); + let result = vp_env.validate_tx(PosVP::new, |_tx_data| {}); + let result = + result.expect("Validation of valid changes must not fail!"); + assert!( + result, + "PoS Validity predicate must accept this transaction" + ); + } + + fn arb_initial_stake_and_unbond() + -> impl Strategy { + // Generate initial stake + token::testing::arb_amount().prop_flat_map(|initial_stake| { + // Use the initial stake to limit the bond amount + let unbond = arb_unbond(u64::from(initial_stake)); + // Use the generated initial stake too too + (Just(initial_stake), unbond) + }) + } + + /// Generates an initial validator stake and a unbond, while making sure + /// that the `initial_stake >= unbond.amount`. + fn arb_unbond( + max_amount: u64, + ) -> impl Strategy { + ( + address::testing::arb_established_address(), + prop::option::of(address::testing::arb_non_internal_address()), + token::testing::arb_amount_ceiled(max_amount), + ) + .prop_map(|(validator, source, amount)| { + let validator = Address::Established(validator); + transaction::pos::Unbond { + validator, + amount, + source, + } + }) + } +} From ef97a463e36a2f309e4e1b230c17ac04f7b8fa32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Jun 2022 11:47:42 +0200 Subject: [PATCH 200/373] wasm: test tx_withdraw --- wasm/wasm_source/src/tx_withdraw.rs | 204 ++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 27bd984a66..243619cf15 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -21,3 +21,207 @@ fn apply_tx(tx_data: Vec) { } } } + +#[cfg(test)] +mod tests { + use namada::ledger::pos::PosParams; + use namada::proto::Tx; + use namada::types::storage::Epoch; + use namada_tests::log::test; + use namada_tests::native_vp::pos::init_pos; + use namada_tests::native_vp::TestNativeVpEnv; + use namada_tests::tx::*; + use namada_tx_prelude::address::testing::{ + arb_established_address, arb_non_internal_address, + }; + use namada_tx_prelude::address::InternalAddress; + use namada_tx_prelude::key::testing::arb_common_keypair; + use namada_tx_prelude::key::RefTo; + use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; + use namada_vp_prelude::proof_of_stake::{ + staking_token_address, BondId, GenesisValidator, PosVP, + }; + use proptest::prelude::*; + + use super::*; + + proptest! { + /// In this test we setup the ledger and PoS system with an arbitrary + /// initial state with 1 genesis validator, a delegation bond if the + /// withdrawal is for a delegation, arbitrary PoS parameters and + /// a we generate an arbitrary withdrawal that we'd like to apply. + /// + /// After we apply the withdrawal, we're checking that all the storage + /// values in PoS system have been updated as expected and then we also + /// check that this transaction is accepted by the PoS validity + /// predicate. + #[test] + fn test_tx_withdraw( + (initial_stake, unbonded_amount) in arb_initial_stake_and_unbonded_amount(), + withdraw in arb_withdraw(), + // A key to sign the transaction + key in arb_common_keypair(), + pos_params in arb_pos_params()) { + test_tx_withdraw_aux(initial_stake, unbonded_amount, withdraw, key, + pos_params) + } + } + + fn test_tx_withdraw_aux( + initial_stake: token::Amount, + unbonded_amount: token::Amount, + withdraw: transaction::pos::Withdraw, + key: key::common::SecretKey, + pos_params: PosParams, + ) { + let is_delegation = matches!( + &withdraw.source, Some(source) if *source != withdraw.validator); + let staking_reward_address = address::testing::established_address_1(); + let consensus_key = key::testing::keypair_1().ref_to(); + let staking_reward_key = key::testing::keypair_2().ref_to(); + + let genesis_validators = [GenesisValidator { + address: withdraw.validator.clone(), + staking_reward_address, + tokens: if is_delegation { + // If we're withdrawing a delegation, we'll give the initial + // stake to the delegation instead of the + // validator + token::Amount::default() + } else { + initial_stake + }, + consensus_key, + staking_reward_key, + }]; + + init_pos(&genesis_validators[..], &pos_params, Epoch(0)); + + tx_host_env::with(|tx_env| { + if is_delegation { + let source = withdraw.source.as_ref().unwrap(); + tx_env.spawn_accounts([source]); + + // To allow to unbond delegation, there must be a delegation + // bond first. + // First, credit the bond's source with the initial stake, + // before we initialize the bond below + tx_env.credit_tokens( + source, + &staking_token_address(), + initial_stake, + ); + } + }); + + if is_delegation { + // Initialize the delegation - unlike genesis validator's self-bond, + // this happens at pipeline offset + namada_tx_prelude::proof_of_stake::bond_tokens( + withdraw.source.as_ref(), + &withdraw.validator, + initial_stake, + ) + .unwrap(); + } + + // Unbond the `unbonded_amount` at the starting epoch 0 + namada_tx_prelude::proof_of_stake::unbond_tokens( + withdraw.source.as_ref(), + &withdraw.validator, + unbonded_amount, + ) + .unwrap(); + + tx_host_env::commit_tx_and_block(); + + // Fast forward to unbonding offset epoch so that it's possible to + // withdraw the unbonded tokens + tx_host_env::with(|env| { + for _ in 0..pos_params.unbonding_len { + env.storage.block.epoch = env.storage.block.epoch.next(); + } + }); + assert_eq!( + tx_host_env::with(|env| env.storage.block.epoch), + Epoch(pos_params.unbonding_len) + ); + + let tx_code = vec![]; + let tx_data = withdraw.try_to_vec().unwrap(); + let tx = Tx::new(tx_code, Some(tx_data)); + let signed_tx = tx.sign(&key); + let tx_data = signed_tx.data.unwrap(); + + // Read data before we apply tx: + let pos_balance_key = token::balance_key( + &staking_token_address(), + &Address::Internal(InternalAddress::PoS), + ); + let pos_balance_pre: token::Amount = + read(&pos_balance_key.to_string()).expect("PoS must have balance"); + assert_eq!(pos_balance_pre, initial_stake); + let unbond_src = withdraw + .source + .clone() + .unwrap_or_else(|| withdraw.validator.clone()); + let unbond_id = BondId { + validator: withdraw.validator, + source: unbond_src, + }; + let unbonds_pre = PoS.read_unbond(&unbond_id).unwrap(); + assert_eq!( + unbonds_pre.get(pos_params.unbonding_len).unwrap().sum(), + unbonded_amount + ); + + apply_tx(tx_data); + + // Read the data after the tx is executed + let unbonds_post = PoS.read_unbond(&unbond_id); + assert!( + unbonds_post.is_none(), + "Because we're withdraw the full unbonded amount, there should be \ + no unbonds left" + ); + let pos_balance_post: token::Amount = + read(&pos_balance_key.to_string()).expect("PoS must have balance"); + assert_eq!(pos_balance_pre - pos_balance_post, unbonded_amount); + + // Use the tx_env to run PoS VP + let tx_env = tx_host_env::take(); + let vp_env = TestNativeVpEnv::new(tx_env); + let result = vp_env.validate_tx(PosVP::new, |_tx_data| {}); + let result = + result.expect("Validation of valid changes must not fail!"); + assert!( + result, + "PoS Validity predicate must accept this transaction" + ); + } + + fn arb_initial_stake_and_unbonded_amount() + -> impl Strategy { + // Generate initial stake + token::testing::arb_amount().prop_flat_map(|initial_stake| { + // Use the initial stake to limit the unbonded amount from the stake + let unbonded_amount = + token::testing::arb_amount_ceiled(initial_stake.into()); + // Use the generated initial stake too too + (Just(initial_stake), unbonded_amount) + }) + } + + fn arb_withdraw() -> impl Strategy { + ( + arb_established_address(), + prop::option::of(arb_non_internal_address()), + ) + .prop_map(|(validator, source)| { + transaction::pos::Withdraw { + validator: Address::Established(validator), + source, + } + }) + } +} From 553fc54750bc6b2ac82dcd9a6faa04d894a8d40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 8 Jun 2022 12:55:56 +0200 Subject: [PATCH 201/373] Changelog: add #462 --- .changelog/unreleased/testing/462-pos-tx-tests.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/testing/462-pos-tx-tests.md diff --git a/.changelog/unreleased/testing/462-pos-tx-tests.md b/.changelog/unreleased/testing/462-pos-tx-tests.md new file mode 100644 index 0000000000..09bacbc5f0 --- /dev/null +++ b/.changelog/unreleased/testing/462-pos-tx-tests.md @@ -0,0 +1,2 @@ +- Test PoS transaction for bonding, unbonding and withdrawal. Fixed an issue + found on unbonding. ([#462](https://github.com/anoma/anoma/issues/462)) \ No newline at end of file From 65bdbfdb53449c4005487123890eee3b0841a435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 15 Jul 2022 16:17:45 +0200 Subject: [PATCH 202/373] tests/pos: add proptest-regressions file --- tests/proptest-regressions/native_vp/pos.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/proptest-regressions/native_vp/pos.txt diff --git a/tests/proptest-regressions/native_vp/pos.txt b/tests/proptest-regressions/native_vp/pos.txt new file mode 100644 index 0000000000..bb8cc1b222 --- /dev/null +++ b/tests/proptest-regressions/native_vp/pos.txt @@ -0,0 +1 @@ +cc 65720acc67508ccd2fefc1ca42477075ae53a7d1e3c8f31324cfb8f06587457e \ No newline at end of file From 36e9c58230dfc7ed8fa143400edcbfd4980f2c90 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 15 Jul 2022 01:15:24 +0200 Subject: [PATCH 203/373] clean up documentation --- apps/src/lib/client/rpc.rs | 2 +- proof_of_stake/src/types.rs | 4 ++-- shared/src/proto/types.rs | 2 +- shared/src/types/storage.rs | 2 +- tests/src/vm_host_env/tx.rs | 2 +- wasm/wasm_source/src/tx_bond.rs | 8 ++++---- wasm/wasm_source/src/tx_unbond.rs | 6 +++--- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 465c1b4c0f..665a14fe2c 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1673,7 +1673,7 @@ pub async fn get_proposal_offline_votes( ); let bond = epoched_bonds .get(epoch) - .expect("Delegation bond should be definied."); + .expect("Delegation bond should be defined."); let mut to_deduct = bond.neg_deltas; for (start_epoch, &(mut delta)) in bond.pos_deltas.iter().sorted() diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 4835f05a96..2577b4efb1 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -281,13 +281,13 @@ pub enum ValidatorState { // TODO consider adding `Jailed` } -/// A bond is validator's self-bond or a delegation from a regular account to a +/// A bond is either a validator's self-bond or a delegation from a regular account to a /// validator. #[derive( Debug, Clone, Default, BorshDeserialize, BorshSerialize, BorshSchema, )] pub struct Bond { - /// Bonded positive deltas. A key is a the epoch set for the bond. This is + /// Bonded positive deltas. A key is the epoch set for the bond. This is /// used in unbonding, where it's needed for slash epoch range check. /// /// TODO: For Bonds, there's unnecessary redundancy with this hash map. diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 7a936dd58f..bc1f072e45 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -39,7 +39,7 @@ pub type Result = std::result::Result; /// /// Because the signature is not checked by the ledger, we don't inline it into /// the `Tx` type directly. Instead, the signature is attached to the `tx.data`, -/// which is can then be checked by a validity predicate wasm. +/// which can then be checked by a validity predicate wasm. #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] pub struct SignedTxData { /// The original tx data bytes, if any diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index fc87bc8d51..32f9afeb20 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -274,7 +274,7 @@ impl Key { self.len() == 0 } - /// Returns a key of the validity predicate of the given address + /// Returns a key of the validity predicate of the given address. /// Only this function can push "?" segment for validity predicate pub fn validity_predicate(addr: &Address) -> Self { let mut segments = Self::from(addr.to_db_key()).segments; diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 3a684e8382..382e1edc5d 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -101,7 +101,7 @@ impl TestTxEnv { } /// Fake accounts existence by initializating their VP storage. - /// This is needed for accounts that are being modified by a tx test to be + /// This is needed for accounts that are being modified by a tx test to /// pass account existence check in `tx_write` function. pub fn spawn_accounts( &mut self, diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 5d0c79b9b9..49803b65b5 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -49,10 +49,10 @@ mod tests { proptest! { /// In this test we setup the ledger and PoS system with an arbitrary - /// initial state with 1 genesis validator, arbitrary PoS parameters and - /// a we generate an arbitrary bond that we'd like to apply. + /// initial state with 1 genesis validator and arbitrary PoS parameters. We then + /// generate an arbitrary bond that we'd like to apply. /// - /// After we apply the bond, we're checking that all the storage values + /// After we apply the bond, we check that all the storage values /// in PoS system have been updated as expected and then we also check /// that this transaction is accepted by the PoS validity predicate. #[test] @@ -339,7 +339,7 @@ mod tests { (initial_stake in token::testing::arb_amount()) // Use the initial stake to limit the bond amount (bond in arb_bond(u64::MAX - u64::from(initial_stake)), - // Use the generated initial stake too too + // Use the generated initial stake too initial_stake in Just(initial_stake), ) -> (token::Amount, transaction::pos::Bond) { (initial_stake, bond) diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index d73c3d7ec9..99097c0559 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -48,10 +48,10 @@ mod tests { proptest! { /// In this test we setup the ledger and PoS system with an arbitrary /// initial state with 1 genesis validator, a delegation bond if the - /// unbond is for a delegation, arbitrary PoS parameters and - /// a we generate an arbitrary unbond that we'd like to apply. + /// unbond is for a delegation, arbitrary PoS parameters, and + /// we generate an arbitrary unbond that we'd like to apply. /// - /// After we apply the unbond, we're checking that all the storage values + /// After we apply the unbond, we check that all the storage values /// in PoS system have been updated as expected and then we also check /// that this transaction is accepted by the PoS validity predicate. #[test] From 4bc4b590f0a15f7efe16e87936133e2504d13c17 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 15 Jul 2022 11:32:53 +0200 Subject: [PATCH 204/373] removed a deprecated function, spelling fixes --- proof_of_stake/src/epoched.rs | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/proof_of_stake/src/epoched.rs b/proof_of_stake/src/epoched.rs index 5191345df3..f13bec3ee0 100644 --- a/proof_of_stake/src/epoched.rs +++ b/proof_of_stake/src/epoched.rs @@ -562,28 +562,6 @@ where ); } - /// Update the delta values in reverse order (starting from the future-most - /// epoch) while the update function returns `true`. - pub fn rev_update_while( - &mut self, - mut update_value: impl FnMut(&mut Data, Epoch) -> bool, - current_epoch: impl Into, - params: &PosParams, - ) { - let epoch = current_epoch.into(); - self.update_data(epoch, params); - - let offset = Offset::value(params) as usize; - for ix in (0..offset + 1).rev() { - if let Some(Some(current)) = self.data.get_mut(ix) { - let keep_going = update_value(current, epoch + ix); - if !keep_going { - break; - } - } - } - } - /// Apply the given `f` function on each delta value in reverse order /// (starting from the future-most epoch) while the given function returns /// `true`. @@ -630,7 +608,7 @@ mod tests { sequential 1..20 => EpochedAbstractStateMachine); #[test] - fn epoched_state_machine_with_unbounding_offset( + fn epoched_state_machine_with_unbonding_offset( sequential 1..20 => EpochedAbstractStateMachine); #[test] @@ -638,7 +616,7 @@ mod tests { sequential 1..20 => EpochedDeltaAbstractStateMachine); #[test] - fn epoched_delta_state_machine_with_unbounding_offset( + fn epoched_delta_state_machine_with_unbonding_offset( sequential 1..20 => EpochedDeltaAbstractStateMachine); } From 4fa9eff165689e9de480b706883f23112755e1fd Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 21 Jul 2022 11:42:42 +0200 Subject: [PATCH 205/373] quick doc fix --- shared/src/types/storage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 32f9afeb20..f60bb66ccf 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -400,7 +400,7 @@ pub enum DbKeySeg { impl KeySeg for DbKeySeg { fn parse(mut string: String) -> Result { - // a separator should not included + // a separator should not be included if string.contains(KEY_SEGMENT_SEPARATOR) { return Err(Error::InvalidKeySeg(string)); } From 117959ded122d7f4d90de2bfe270cfe75bc1126b Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 21 Jul 2022 11:48:05 +0200 Subject: [PATCH 206/373] Update comments --- proof_of_stake/src/types.rs | 4 ++-- tests/src/native_vp/pos.rs | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 2577b4efb1..03c472a436 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -281,8 +281,8 @@ pub enum ValidatorState { // TODO consider adding `Jailed` } -/// A bond is either a validator's self-bond or a delegation from a regular account to a -/// validator. +/// A bond is either a validator's self-bond or a delegation from a regular +/// account to a validator. #[derive( Debug, Clone, Default, BorshDeserialize, BorshSerialize, BorshSchema, )] diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index abff6e8c2b..1fe56710c6 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -50,7 +50,7 @@ //! - Unbond: Requires a bond in the state (the `#{owner}` and `#{validator}` //! segments in the keys below must be the owner and a validator of an //! existing bond). The bond's total amount must be greater or equal to the -//! amount that' being unbonded. Some of the storage changes are optional, +//! amount that is being unbonded. Some of the storage changes are optional, //! which depends on whether the unbonding decreases voting power of the //! validator. //! - `#{PoS}/bond/#{owner}/#{validator}` @@ -616,11 +616,14 @@ pub mod testing { #[derivative(Debug)] pub enum PosStorageChange { - /// Ensure that the account exists when initialing a valid new + /// Ensure that the account exists when initializing a valid new /// validator or delegation from a new owner SpawnAccount { address: Address, }, + /// Add tokens included in a new bond at given offset. Bonded tokens + /// are added at pipeline offset and unbonded tokens are added as + /// negative values at unbonding offset. Bond { owner: Address, validator: Address, From 4b43766ec21f2cb06578bbbeea7d09cd00972e76 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 21 Jul 2022 12:06:31 +0200 Subject: [PATCH 207/373] doc fixed in proof_of_stake/ --- proof_of_stake/src/lib.rs | 12 ++++++------ proof_of_stake/src/parameters.rs | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 0c7a50c31d..c00ec478ef 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -733,12 +733,12 @@ pub trait PosBase { let prev_validators = previous_epoch.and_then(|epoch| validators.get(epoch)); - // If the validator never been active before and it doesn't have more - // than 0 voting power, we should not tell Tendermint to update it until - // it does. Tendermint uses 0 voting power as a way to signal - // that a validator has been removed from the validator set, but - // fails if we attempt to give it a new validator with 0 voting - // power. + // If the validator has never been active before and it doesn't have + // more than 0 voting power, we should not tell Tendermint to + // update it until it does. Tendermint uses 0 voting power as a + // way to signal that a validator has been removed from the + // validator set, but fails if we attempt to give it a new + // validator with 0 voting power. // For active validators, this would only ever happen until all the // validator slots are filled with non-0 voting power validators, but we // still need to guard against it. diff --git a/proof_of_stake/src/parameters.rs b/proof_of_stake/src/parameters.rs index 84bd59d4a5..7ee0abdf98 100644 --- a/proof_of_stake/src/parameters.rs +++ b/proof_of_stake/src/parameters.rs @@ -78,7 +78,8 @@ const MAX_TOTAL_VOTING_POWER: i64 = i64::MAX / 8; const TOKEN_MAX_AMOUNT: u64 = u64::MAX / 1_000_000; impl PosParams { - /// Validate PoS parameters values. Returns empty list the values are valid. + /// Validate PoS parameters values. Returns an empty list if the values are + /// valid. #[must_use] pub fn validate(&self) -> Vec { let mut errors = vec![]; From c4a61c40c8a62e5dbcb43add05a02d8366ff1340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 29 Jul 2022 18:47:26 +0200 Subject: [PATCH 208/373] pos/vp: remove redundant validity predicate storage key check --- shared/src/ledger/pos/vp.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 26be440536..106d27c00d 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -110,7 +110,7 @@ where &self, tx_data: &[u8], keys_changed: &BTreeSet, - verifiers: &BTreeSet
, + _verifiers: &BTreeSet
, ) -> Result { use validation::Data; use validation::DataUpdate::{self, *}; @@ -126,16 +126,6 @@ where Some(id) => return Ok(is_proposal_accepted(&self.ctx, id)), _ => return Ok(false), } - } else if let Some(owner) = key.is_validity_predicate() { - let has_pre = self.ctx.has_key_pre(key)?; - let has_post = self.ctx.has_key_post(key)?; - if has_pre && has_post { - // VP updates must be verified by the owner - return Ok(!verifiers.contains(owner)); - } else if has_pre || !has_post { - // VP cannot be deleted - return Ok(false); - } } else if is_validator_set_key(key) { let pre = self.ctx.read_pre(key)?.and_then(|bytes| { ValidatorSets::try_from_slice(&bytes[..]).ok() From 74cce908d988725e4d7684b1610879c71b9b25e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 2 Aug 2022 11:53:59 +0000 Subject: [PATCH 209/373] [ci skip] wasm checksums update --- wasm/checksums.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5d670022b7..7ffb8914bd 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.71acaf1ff3a4ac1ce6550c4ce594cf70d684213769f45641be5cdb4b7321184e.wasm", - "tx_from_intent.wasm": "tx_from_intent.e18907b1fb0e1c4e4946228c0ec57838a783a5d88c6b3129d44935386693cee8.wasm", + "tx_bond.wasm": "tx_bond.f3929836717f36bee3a1cc81903c76ecd972a17c3a798fe2f65f68c270b57069.wasm", + "tx_from_intent.wasm": "tx_from_intent.9dfb9eaaf04fdc8f1827d613350c0e5c5132ba49a7a46d1df01997111467c884.wasm", "tx_ibc.wasm": "tx_ibc.38ee97db63b765f1639ee24dd3e7f264139f146a56f7c06edafde63eaecedade.wasm", "tx_init_account.wasm": "tx_init_account.6be05f8ca240d9e978dfe6a1566b3860e07fa3aee334003fc5d85a5a1ae99cf7.wasm", "tx_init_nft.wasm": "tx_init_nft.5f710b641c38cbe7822c2359bf7a764e864e952f4660ccff9b58b97fbaa4b3a2.wasm", "tx_init_proposal.wasm": "tx_init_proposal.25666fc8fbf251bd4ad478cb968f1d50a8100a0a7aae0e3ee5e50e28304e8e3e.wasm", - "tx_init_validator.wasm": "tx_init_validator.2b1a4c947242ddddf94a491b8c571185f649913606bfa7d37f668ddc86fe1049.wasm", + "tx_init_validator.wasm": "tx_init_validator.a74bf786fad66314bde052ba472e5c139683bee14747fdf4e7cbd9cac2825080.wasm", "tx_mint_nft.wasm": "tx_mint_nft.9af8eea9219db09161b98405c18ea3c82db03b982f69a66ce1d117e64a715c3c.wasm", "tx_transfer.wasm": "tx_transfer.87e4fba9d2476388e0f2b1d4bc00c396ac7b8b7b38d5079735fe53bc5984b583.wasm", - "tx_unbond.wasm": "tx_unbond.61f2ac46e7680ec35aba367ec908ac63aa43d6765cd07da932aa9ea45d77be46.wasm", + "tx_unbond.wasm": "tx_unbond.d621f53b6f06e79e15b150a238568198f106114eed3657f742a06efd495c1bab.wasm", "tx_update_vp.wasm": "tx_update_vp.00d828bdf510d92d971ca59015945c8c00f285a802a655451cc5ee87c5ee99bc.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.92ed628aca7856e2b0fe461c0a0a65c6f7c1f585ac542c10feceef0e4b9ce611.wasm", - "tx_withdraw.wasm": "tx_withdraw.fb54d108a6fe7c6db16e573110c4a9f52158ec993e04e5a06e1425b8dd676c54.wasm", + "tx_withdraw.wasm": "tx_withdraw.1de970ab016aaeb18b202eb8bf72ac03a95bd31015e6045c9d2d53363208da67.wasm", "vp_nft.wasm": "vp_nft.3421840146f8d2e6c3ce3a0d94bbb0dad6ef296c50e9d00744179d290f32745b.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.1b37ab6939280fbb54512918053362fae34e3db74f029ef31c9f08e767539afb.wasm", "vp_token.wasm": "vp_token.08dcec2d3ecb0942d0a244bbd176f9a42424ec09544120db1b30c0d97ed7dbb1.wasm", - "vp_user.wasm": "vp_user.404f089f0c73fcf906f7d6059d74c6bab3e2e3d3a108394503bbc22588ff95b9.wasm" + "vp_user.wasm": "vp_user.e80b07692ae5642ec32de1d5128aa4c1b7081c15117fb670aa8fa123c18eaa1c.wasm" } \ No newline at end of file From d5ccb629438e79aec4713751fcf24e4597aef867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 17:04:05 +0200 Subject: [PATCH 210/373] test: add seed for failed PoS VP test this is from https://github.com/anoma/namada/runs/7808308405 --- tests/proptest-regressions/native_vp/pos.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/proptest-regressions/native_vp/pos.txt b/tests/proptest-regressions/native_vp/pos.txt index bb8cc1b222..ad157e817b 100644 --- a/tests/proptest-regressions/native_vp/pos.txt +++ b/tests/proptest-regressions/native_vp/pos.txt @@ -1 +1,2 @@ -cc 65720acc67508ccd2fefc1ca42477075ae53a7d1e3c8f31324cfb8f06587457e \ No newline at end of file +cc 65720acc67508ccd2fefc1ca42477075ae53a7d1e3c8f31324cfb8f06587457e +cc 45b2dd2ed9619ceef6135ee6ca34406621c8a6429ffa153bbda3ce79dd4e006c \ No newline at end of file From 1a57908bfb608145daef30c6110ea67713cfc0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 20 Jul 2022 20:54:52 +0200 Subject: [PATCH 211/373] ledger: add tx and VP traits with host env functions --- shared/src/ledger/mod.rs | 1 + shared/src/ledger/tx_env.rs | 129 +++++++++++++++++++++++++++++ shared/src/ledger/vp_env.rs | 160 ++++++++++++++++++++++++++++++++---- 3 files changed, 275 insertions(+), 15 deletions(-) create mode 100644 shared/src/ledger/tx_env.rs diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 2d545f96f2..7dd7bc2d46 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -9,4 +9,5 @@ pub mod parameters; pub mod pos; pub mod storage; pub mod treasury; +pub mod tx_env; pub mod vp_env; diff --git a/shared/src/ledger/tx_env.rs b/shared/src/ledger/tx_env.rs new file mode 100644 index 0000000000..578357b733 --- /dev/null +++ b/shared/src/ledger/tx_env.rs @@ -0,0 +1,129 @@ +//! Transaction environment contains functions that can be called from +//! inside a tx. + +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::types::address::Address; +use crate::types::ibc::IbcEvent; +use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; +use crate::types::time::Rfc3339String; + +/// Transaction host functions +pub trait TxEnv { + /// Storage read prefix iterator + type PrefixIter; + + /// Host functions possible errors, extensible with custom user errors. + type Error; + + /// Storage read Borsh encoded value. It will try to read from the write log + /// first and if no entry found then from the storage and then decode it if + /// found. + fn read( + &self, + key: &storage::Key, + ) -> Result, Self::Error>; + + /// Storage read raw bytes. It will try to read from the write log first and + /// if no entry found then from the storage. + fn read_bytes( + &self, + key: &storage::Key, + ) -> Result>, Self::Error>; + + /// Check if the storage contains the given key. It will try + /// to check the write log first and if no entry found then the storage. + fn has_key(&self, key: &storage::Key) -> Result; + + /// Getting the chain ID. + fn get_chain_id(&self) -> Result; + + /// Getting the block height. The height is that of the block to which the + /// current transaction is being applied. + fn get_block_height(&self) -> Result; + + /// Getting the block hash. The height is that of the block to which the + /// current transaction is being applied. + fn get_block_hash(&self) -> Result; + + /// Getting the block epoch. The epoch is that of the block to which the + /// current transaction is being applied. + fn get_block_epoch(&self) -> Result; + + /// Get time of the current block header as rfc 3339 string + fn get_block_time(&self) -> Result; + + /// Storage prefix iterator. It will try to get an iterator from the + /// storage. + fn iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result; + + /// Storage prefix iterator next. It will try to read from the write log + /// first and if no entry found then from the storage. + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error>; + + // --- MUTABLE ---- + + /// Write a value to be encoded with Borsh at the given key to storage. + fn write( + &mut self, + key: &storage::Key, + val: T, + ) -> Result<(), Self::Error>; + + /// Write a value as bytes at the given key to storage. + fn write_bytes( + &mut self, + key: &storage::Key, + val: impl AsRef<[u8]>, + ) -> Result<(), Self::Error>; + + /// Write a temporary value to be encoded with Borsh at the given key to + /// storage. + fn write_temp( + &mut self, + key: &storage::Key, + val: T, + ) -> Result<(), Self::Error>; + + /// Write a temporary value as bytes at the given key to storage. + fn write_bytes_temp( + &mut self, + key: &storage::Key, + val: impl AsRef<[u8]>, + ) -> Result<(), Self::Error>; + + /// Delete a value at the given key from storage. + fn delete(&mut self, key: &storage::Key) -> Result<(), Self::Error>; + + /// Insert a verifier address. This address must exist on chain, otherwise + /// the transaction will be rejected. + /// + /// Validity predicates of each verifier addresses inserted in the + /// transaction will validate the transaction and will receive all the + /// changed storage keys and initialized accounts in their inputs. + fn insert_verifier(&mut self, addr: &Address) -> Result<(), Self::Error>; + + /// Initialize a new account generates a new established address and + /// writes the given code as its validity predicate into the storage. + fn init_account( + &mut self, + code: impl AsRef<[u8]>, + ) -> Result; + + /// Update a validity predicate + fn update_validity_predicate( + &mut self, + addr: &Address, + code: impl AsRef<[u8]>, + ) -> Result<(), Self::Error>; + + /// Emit an IBC event. There can be only one event per transaction. On + /// multiple calls, only the last emitted event will be used. + fn emit_ibc_event(&mut self, event: &IbcEvent) -> Result<(), Self::Error>; +} diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 1f59613e54..1a77c38312 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -3,6 +3,7 @@ use std::num::TryFromIntError; +use borsh::BorshDeserialize; use thiserror::Error; use super::gas::MIN_STORAGE_GAS; @@ -12,8 +13,134 @@ use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{self, write_log, Storage, StorageHasher}; use crate::proto::Tx; use crate::types::hash::Hash; +use crate::types::key::common; use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key}; +/// Validity predicate's environment is available for native VPs and WASM VPs +pub trait VpEnv { + /// Storage read prefix iterator + type PrefixIter; + + /// Host functions possible error. + /// + /// In a native VP this may be out-of-gas error, however, because WASM VP is + /// sandboxed and gas accounting is injected by and handled in the host, + /// in WASM VP this error is [`std::convert::Infallible`]. + type Error; + + /// Storage read prior state Borsh encoded value (before tx execution). It + /// will try to read from the storage and decode it if found. + fn read_pre( + &self, + key: &Key, + ) -> Result, Self::Error>; + + /// Storage read prior state raw bytes (before tx execution). It + /// will try to read from the storage. + fn read_bytes_pre(&self, key: &Key) + -> Result>, Self::Error>; + + /// Storage read posterior state Borsh encoded value (after tx execution). + /// It will try to read from the write log first and if no entry found + /// then from the storage and then decode it if found. + fn read_post( + &self, + key: &Key, + ) -> Result, Self::Error>; + + /// Storage read posterior state raw bytes (after tx execution). It will try + /// to read from the write log first and if no entry found then from the + /// storage. + fn read_bytes_post( + &self, + key: &Key, + ) -> Result>, Self::Error>; + + /// Storage read temporary state Borsh encoded value (after tx execution). + /// It will try to read from only the write log and then decode it if + /// found. + fn read_temp( + &self, + key: &Key, + ) -> Result, Self::Error>; + + /// Storage read temporary state raw bytes (after tx execution). It will try + /// to read from only the write log. + fn read_bytes_temp( + &self, + key: &Key, + ) -> Result>, Self::Error>; + + /// Storage `has_key` in prior state (before tx execution). It will try to + /// read from the storage. + fn has_key_pre(&self, key: &Key) -> Result; + + /// Storage `has_key` in posterior state (after tx execution). It will try + /// to check the write log first and if no entry found then the storage. + fn has_key_post(&self, key: &Key) -> Result; + + /// Getting the chain ID. + fn get_chain_id(&self) -> Result; + + /// Getting the block height. The height is that of the block to which the + /// current transaction is being applied. + fn get_block_height(&self) -> Result; + + /// Getting the block hash. The height is that of the block to which the + /// current transaction is being applied. + fn get_block_hash(&self) -> Result; + + /// Getting the block epoch. The epoch is that of the block to which the + /// current transaction is being applied. + fn get_block_epoch(&self) -> Result; + + /// Storage prefix iterator. It will try to get an iterator from the + /// storage. + fn iter_prefix( + &self, + prefix: &Key, + ) -> Result; + + /// Storage prefix iterator for prior state (before tx execution). It will + /// try to read from the storage. + fn iter_pre_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error>; + + /// Storage prefix iterator next for posterior state (after tx execution). + /// It will try to read from the write log first and if no entry found + /// then from the storage. + fn iter_post_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error>; + + /// Evaluate a validity predicate with given data. The address, changed + /// storage keys and verifiers will have the same values as the input to + /// caller's validity predicate. + /// + /// If the execution fails for whatever reason, this will return `false`. + /// Otherwise returns the result of evaluation. + fn eval( + &self, + vp_code: Vec, + input_data: Vec, + ) -> Result; + + /// Verify a transaction signature. The signature is expected to have been + /// produced on the encoded transaction [`crate::proto::Tx`] + /// using [`crate::proto::Tx::sign`]. + fn verify_tx_signature( + &self, + pk: &common::PublicKey, + sig: &common::Signature, + ) -> Result; + + /// Get a tx hash + fn get_tx_code_hash(&self) -> Result; +} + /// These runtime errors will abort VP execution immediately #[allow(missing_docs)] #[derive(Error, Debug)] @@ -37,10 +164,10 @@ pub enum RuntimeError { } /// VP environment function result -pub type Result = std::result::Result; +pub type EnvResult = std::result::Result; /// Add a gas cost incured in a validity predicate -pub fn add_gas(gas_meter: &mut VpGasMeter, used_gas: u64) -> Result<()> { +pub fn add_gas(gas_meter: &mut VpGasMeter, used_gas: u64) -> EnvResult<()> { let result = gas_meter.add(used_gas).map_err(RuntimeError::OutOfGas); if let Err(err) = &result { tracing::info!("Stopping VP execution because of gas error: {}", err); @@ -55,7 +182,7 @@ pub fn read_pre( storage: &Storage, write_log: &WriteLog, key: &Key, -) -> Result>> +) -> EnvResult>> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -96,7 +223,7 @@ pub fn read_post( storage: &Storage, write_log: &WriteLog, key: &Key, -) -> Result>> +) -> EnvResult>> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -137,7 +264,7 @@ pub fn read_temp( gas_meter: &mut VpGasMeter, write_log: &WriteLog, key: &Key, -) -> Result>> { +) -> EnvResult>> { // Try to read from the write log first let (log_val, gas) = write_log.read(key); add_gas(gas_meter, gas)?; @@ -156,7 +283,7 @@ pub fn has_key_pre( gas_meter: &mut VpGasMeter, storage: &Storage, key: &Key, -) -> Result +) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -174,7 +301,7 @@ pub fn has_key_post( storage: &Storage, write_log: &WriteLog, key: &Key, -) -> Result +) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -204,7 +331,7 @@ where pub fn get_chain_id( gas_meter: &mut VpGasMeter, storage: &Storage, -) -> Result +) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -219,7 +346,7 @@ where pub fn get_block_height( gas_meter: &mut VpGasMeter, storage: &Storage, -) -> Result +) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -234,7 +361,7 @@ where pub fn get_block_hash( gas_meter: &mut VpGasMeter, storage: &Storage, -) -> Result +) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -246,7 +373,10 @@ where /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. -pub fn get_tx_code_hash(gas_meter: &mut VpGasMeter, tx: &Tx) -> Result { +pub fn get_tx_code_hash( + gas_meter: &mut VpGasMeter, + tx: &Tx, +) -> EnvResult { let hash = Hash(tx.code_hash()); add_gas(gas_meter, MIN_STORAGE_GAS)?; Ok(hash) @@ -257,7 +387,7 @@ pub fn get_tx_code_hash(gas_meter: &mut VpGasMeter, tx: &Tx) -> Result { pub fn get_block_epoch( gas_meter: &mut VpGasMeter, storage: &Storage, -) -> Result +) -> EnvResult where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -272,7 +402,7 @@ pub fn iter_prefix<'a, DB, H>( gas_meter: &mut VpGasMeter, storage: &'a Storage, prefix: &Key, -) -> Result<>::PrefixIter> +) -> EnvResult<>::PrefixIter> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -287,7 +417,7 @@ where pub fn iter_pre_next( gas_meter: &mut VpGasMeter, iter: &mut >::PrefixIter, -) -> Result)>> +) -> EnvResult)>> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, { @@ -305,7 +435,7 @@ pub fn iter_post_next( gas_meter: &mut VpGasMeter, write_log: &WriteLog, iter: &mut >::PrefixIter, -) -> Result)>> +) -> EnvResult)>> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, { From 5d8c61a63479672cf7ac0d9583c0aa5c9ba5e31e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 20 Jul 2022 20:56:08 +0200 Subject: [PATCH 212/373] shared/vm: rename host_env TxEnv/VpEnv to TxVmEnv/VpVmEnv --- shared/src/vm/host_env.rs | 134 ++++++++++++++++----------------- shared/src/vm/wasm/host_env.rs | 10 +-- shared/src/vm/wasm/run.rs | 8 +- 3 files changed, 76 insertions(+), 76 deletions(-) diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index ac9f89c31e..fc7fd33e0c 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -68,7 +68,7 @@ pub enum TxRuntimeError { type TxResult = std::result::Result; /// A transaction's host environment -pub struct TxEnv<'a, MEM, DB, H, CA> +pub struct TxVmEnv<'a, MEM, DB, H, CA> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -112,7 +112,7 @@ where pub cache_access: std::marker::PhantomData, } -impl<'a, MEM, DB, H, CA> TxEnv<'a, MEM, DB, H, CA> +impl<'a, MEM, DB, H, CA> TxVmEnv<'a, MEM, DB, H, CA> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -167,7 +167,7 @@ where } } -impl Clone for TxEnv<'_, MEM, DB, H, CA> +impl Clone for TxVmEnv<'_, MEM, DB, H, CA> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -207,7 +207,7 @@ where } /// A validity predicate's host environment -pub struct VpEnv<'a, MEM, DB, H, EVAL, CA> +pub struct VpVmEnv<'a, MEM, DB, H, EVAL, CA> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -282,7 +282,7 @@ pub trait VpEvaluator { ) -> HostEnvResult; } -impl<'a, MEM, DB, H, EVAL, CA> VpEnv<'a, MEM, DB, H, EVAL, CA> +impl<'a, MEM, DB, H, EVAL, CA> VpVmEnv<'a, MEM, DB, H, EVAL, CA> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -331,7 +331,7 @@ where } } -impl Clone for VpEnv<'_, MEM, DB, H, EVAL, CA> +impl Clone for VpVmEnv<'_, MEM, DB, H, EVAL, CA> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -435,7 +435,7 @@ where /// Called from tx wasm to request to use the given gas amount pub fn tx_charge_gas( - env: &TxEnv, + env: &TxVmEnv, used_gas: i32, ) -> TxResult<()> where @@ -454,7 +454,7 @@ where /// Add a gas cost incured in a transaction pub fn tx_add_gas( - env: &TxEnv, + env: &TxVmEnv, used_gas: u64, ) -> TxResult<()> where @@ -477,9 +477,9 @@ where /// Called from VP wasm to request to use the given gas amount pub fn vp_charge_gas( - env: &VpEnv, + env: &VpVmEnv, used_gas: i32, -) -> vp_env::Result<()> +) -> vp_env::EnvResult<()> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -499,7 +499,7 @@ where /// Storage `has_key` function exposed to the wasm VM Tx environment. It will /// try to check the write log first and if no entry found then the storage. pub fn tx_has_key( - env: &TxEnv, + env: &TxVmEnv, key_ptr: u64, key_len: u64, ) -> TxResult @@ -555,7 +555,7 @@ where /// Returns `-1` when the key is not present, or the length of the data when /// the key is present (the length may be `0`). pub fn tx_read( - env: &TxEnv, + env: &TxVmEnv, key_ptr: u64, key_len: u64, ) -> TxResult @@ -645,7 +645,7 @@ where /// any) back to the guest, the second step reads the value from cache into a /// pre-allocated buffer with the obtained size. pub fn tx_result_buffer( - env: &TxEnv, + env: &TxVmEnv, result_ptr: u64, ) -> TxResult<()> where @@ -667,7 +667,7 @@ where /// It will try to get an iterator from the storage and return the corresponding /// ID of the iterator. pub fn tx_iter_prefix( - env: &TxEnv, + env: &TxVmEnv, prefix_ptr: u64, prefix_len: u64, ) -> TxResult @@ -702,7 +702,7 @@ where /// Returns `-1` when the key is not present, or the length of the data when /// the key is present (the length may be `0`). pub fn tx_iter_next( - env: &TxEnv, + env: &TxVmEnv, iter_id: u64, ) -> TxResult where @@ -781,7 +781,7 @@ where /// Storage write function exposed to the wasm VM Tx environment. The given /// key/value will be written to the write log. pub fn tx_write( - env: &TxEnv, + env: &TxVmEnv, key_ptr: u64, key_len: u64, val_ptr: u64, @@ -822,7 +822,7 @@ where /// given key/value will be written only to the write log. It will be never /// written to the storage. pub fn tx_write_temp( - env: &TxEnv, + env: &TxVmEnv, key_ptr: u64, key_len: u64, val_ptr: u64, @@ -860,7 +860,7 @@ where } fn check_address_existence( - env: &TxEnv, + env: &TxVmEnv, key: &Key, ) -> TxResult<()> where @@ -904,7 +904,7 @@ where /// Storage delete function exposed to the wasm VM Tx environment. The given /// key/value will be written as deleted to the write log. pub fn tx_delete( - env: &TxEnv, + env: &TxVmEnv, key_ptr: u64, key_len: u64, ) -> TxResult<()> @@ -938,7 +938,7 @@ where /// Emitting an IBC event function exposed to the wasm VM Tx environment. /// The given IBC event will be set to the write log. pub fn tx_emit_ibc_event( - env: &TxEnv, + env: &TxVmEnv, event_ptr: u64, event_len: u64, ) -> TxResult<()> @@ -966,10 +966,10 @@ where /// Returns `-1` when the key is not present, or the length of the data when /// the key is present (the length may be `0`). pub fn vp_read_pre( - env: &VpEnv, + env: &VpVmEnv, key_ptr: u64, key_len: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1017,10 +1017,10 @@ where /// Returns `-1` when the key is not present, or the length of the data when /// the key is present (the length may be `0`). pub fn vp_read_post( - env: &VpEnv, + env: &VpVmEnv, key_ptr: u64, key_len: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1063,10 +1063,10 @@ where /// Returns `-1` when the key is not present, or the length of the data when /// the key is present (the length may be `0`). pub fn vp_read_temp( - env: &VpEnv, + env: &VpVmEnv, key_ptr: u64, key_len: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1111,9 +1111,9 @@ where /// any) back to the guest, the second step reads the value from cache into a /// pre-allocated buffer with the obtained size. pub fn vp_result_buffer( - env: &VpEnv, + env: &VpVmEnv, result_ptr: u64, -) -> vp_env::Result<()> +) -> vp_env::EnvResult<()> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1134,10 +1134,10 @@ where /// Storage `has_key` in prior state (before tx execution) function exposed to /// the wasm VM VP environment. It will try to read from the storage. pub fn vp_has_key_pre( - env: &VpEnv, + env: &VpVmEnv, key_ptr: u64, key_len: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1165,10 +1165,10 @@ where /// to the wasm VM VP environment. It will try to check the write log first and /// if no entry found then the storage. pub fn vp_has_key_post( - env: &VpEnv, + env: &VpVmEnv, key_ptr: u64, key_len: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1197,10 +1197,10 @@ where /// It will try to get an iterator from the storage and return the corresponding /// ID of the iterator. pub fn vp_iter_prefix( - env: &VpEnv, + env: &VpVmEnv, prefix_ptr: u64, prefix_len: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1231,9 +1231,9 @@ where /// Returns `-1` when the key is not present, or the length of the data when /// the key is present (the length may be `0`). pub fn vp_iter_pre_next( - env: &VpEnv, + env: &VpVmEnv, iter_id: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1271,9 +1271,9 @@ where /// Returns `-1` when the key is not present, or the length of the data when /// the key is present (the length may be `0`). pub fn vp_iter_post_next( - env: &VpEnv, + env: &VpVmEnv, iter_id: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1308,7 +1308,7 @@ where /// Verifier insertion function exposed to the wasm VM Tx environment. pub fn tx_insert_verifier( - env: &TxEnv, + env: &TxVmEnv, addr_ptr: u64, addr_len: u64, ) -> TxResult<()> @@ -1335,7 +1335,7 @@ where /// Update a validity predicate function exposed to the wasm VM Tx environment pub fn tx_update_validity_predicate( - env: &TxEnv, + env: &TxVmEnv, addr_ptr: u64, addr_len: u64, code_ptr: u64, @@ -1376,7 +1376,7 @@ where /// Initialize a new account established address. pub fn tx_init_account( - env: &TxEnv, + env: &TxVmEnv, code_ptr: u64, code_len: u64, result_ptr: u64, @@ -1419,7 +1419,7 @@ where /// Getting the chain ID function exposed to the wasm VM Tx environment. pub fn tx_get_chain_id( - env: &TxEnv, + env: &TxVmEnv, result_ptr: u64, ) -> TxResult<()> where @@ -1442,7 +1442,7 @@ where /// environment. The height is that of the block to which the current /// transaction is being applied. pub fn tx_get_block_height( - env: &TxEnv, + env: &TxVmEnv, ) -> TxResult where MEM: VmMemory, @@ -1459,7 +1459,7 @@ where /// Getting the block hash function exposed to the wasm VM Tx environment. The /// hash is that of the block to which the current transaction is being applied. pub fn tx_get_block_hash( - env: &TxEnv, + env: &TxVmEnv, result_ptr: u64, ) -> TxResult<()> where @@ -1482,7 +1482,7 @@ where /// environment. The epoch is that of the block to which the current /// transaction is being applied. pub fn tx_get_block_epoch( - env: &TxEnv, + env: &TxVmEnv, ) -> TxResult where MEM: VmMemory, @@ -1498,9 +1498,9 @@ where /// Getting the chain ID function exposed to the wasm VM VP environment. pub fn vp_get_chain_id( - env: &VpEnv, + env: &VpVmEnv, result_ptr: u64, -) -> vp_env::Result<()> +) -> vp_env::EnvResult<()> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1522,8 +1522,8 @@ where /// environment. The height is that of the block to which the current /// transaction is being applied. pub fn vp_get_block_height( - env: &VpEnv, -) -> vp_env::Result + env: &VpVmEnv, +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1541,7 +1541,7 @@ where /// environment. The time is that of the block header to which the current /// transaction is being applied. pub fn tx_get_block_time( - env: &TxEnv, + env: &TxVmEnv, ) -> TxResult where MEM: VmMemory, @@ -1576,9 +1576,9 @@ where /// Getting the block hash function exposed to the wasm VM VP environment. The /// hash is that of the block to which the current transaction is being applied. pub fn vp_get_block_hash( - env: &VpEnv, + env: &VpVmEnv, result_ptr: u64, -) -> vp_env::Result<()> +) -> vp_env::EnvResult<()> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1598,9 +1598,9 @@ where /// Getting the transaction hash function exposed to the wasm VM VP environment. pub fn vp_get_tx_code_hash( - env: &VpEnv, + env: &VpVmEnv, result_ptr: u64, -) -> vp_env::Result<()> +) -> vp_env::EnvResult<()> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1622,8 +1622,8 @@ where /// environment. The epoch is that of the block to which the current /// transaction is being applied. pub fn vp_get_block_epoch( - env: &VpEnv, -) -> vp_env::Result + env: &VpVmEnv, +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1639,12 +1639,12 @@ where /// Verify a transaction signature. pub fn vp_verify_tx_signature( - env: &VpEnv, + env: &VpVmEnv, pk_ptr: u64, pk_len: u64, sig_ptr: u64, sig_len: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1678,7 +1678,7 @@ where /// printed at the [`tracing::Level::INFO`]. This function is for development /// only. pub fn tx_log_string( - env: &TxEnv, + env: &TxVmEnv, str_ptr: u64, str_len: u64, ) -> TxResult<()> @@ -1698,12 +1698,12 @@ where /// Evaluate a validity predicate with the given input data. pub fn vp_eval( - env: &VpEnv<'static, MEM, DB, H, EVAL, CA>, + env: &VpVmEnv<'static, MEM, DB, H, EVAL, CA>, vp_code_ptr: u64, vp_code_len: u64, input_data_ptr: u64, input_data_len: u64, -) -> vp_env::Result +) -> vp_env::EnvResult where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1734,10 +1734,10 @@ where /// printed at the [`tracing::Level::INFO`]. This function is for development /// only. pub fn vp_log_string( - env: &VpEnv, + env: &VpVmEnv, str_ptr: u64, str_len: u64, -) -> vp_env::Result<()> +) -> vp_env::EnvResult<()> where MEM: VmMemory, DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -1773,13 +1773,13 @@ pub mod testing { result_buffer: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, #[cfg(feature = "wasm-runtime")] tx_wasm_cache: &mut TxCache, - ) -> TxEnv<'static, NativeMemory, DB, H, CA> + ) -> TxVmEnv<'static, NativeMemory, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, CA: WasmCacheAccess, { - TxEnv::new( + TxVmEnv::new( NativeMemory::default(), storage, write_log, @@ -1808,14 +1808,14 @@ pub mod testing { keys_changed: &BTreeSet, eval_runner: &EVAL, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, - ) -> VpEnv<'static, NativeMemory, DB, H, EVAL, CA> + ) -> VpVmEnv<'static, NativeMemory, DB, H, EVAL, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, EVAL: VpEvaluator, CA: WasmCacheAccess, { - VpEnv::new( + VpVmEnv::new( NativeMemory::default(), address, storage, diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 3b6715f383..1a50c5533d 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -9,11 +9,11 @@ use wasmer::{ }; use crate::ledger::storage::{self, StorageHasher}; -use crate::vm::host_env::{TxEnv, VpEnv, VpEvaluator}; +use crate::vm::host_env::{TxVmEnv, VpEvaluator, VpVmEnv}; use crate::vm::wasm::memory::WasmMemory; use crate::vm::{host_env, WasmCacheAccess}; -impl WasmerEnv for TxEnv<'_, WasmMemory, DB, H, CA> +impl WasmerEnv for TxVmEnv<'_, WasmMemory, DB, H, CA> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -27,7 +27,7 @@ where } } -impl WasmerEnv for VpEnv<'_, WasmMemory, DB, H, EVAL, CA> +impl WasmerEnv for VpVmEnv<'_, WasmMemory, DB, H, EVAL, CA> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -48,7 +48,7 @@ where pub fn tx_imports( wasm_store: &Store, initial_memory: Memory, - env: TxEnv<'static, WasmMemory, DB, H, CA>, + env: TxVmEnv<'static, WasmMemory, DB, H, CA>, ) -> ImportObject where DB: storage::DB + for<'iter> storage::DBIter<'iter>, @@ -87,7 +87,7 @@ where pub fn vp_imports( wasm_store: &Store, initial_memory: Memory, - env: VpEnv<'static, WasmMemory, DB, H, EVAL, CA>, + env: VpVmEnv<'static, WasmMemory, DB, H, EVAL, CA>, ) -> ImportObject where DB: storage::DB + for<'iter> storage::DBIter<'iter>, diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 75fbfb4add..d9977393d8 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -17,7 +17,7 @@ use crate::proto::Tx; use crate::types::address::Address; use crate::types::internal::HostEnvResult; use crate::types::storage::Key; -use crate::vm::host_env::{TxEnv, VpCtx, VpEnv, VpEvaluator}; +use crate::vm::host_env::{TxVmEnv, VpCtx, VpEvaluator, VpVmEnv}; use crate::vm::prefix_iter::PrefixIterators; use crate::vm::types::VpInput; use crate::vm::wasm::host_env::{tx_imports, vp_imports}; @@ -94,7 +94,7 @@ where let mut verifiers = BTreeSet::new(); let mut result_buffer: Option> = None; - let env = TxEnv::new( + let env = TxVmEnv::new( WasmMemory::default(), storage, write_log, @@ -189,7 +189,7 @@ where cache_access: PhantomData, }; - let env = VpEnv::new( + let env = VpVmEnv::new( WasmMemory::default(), address, storage, @@ -344,7 +344,7 @@ where let keys_changed = unsafe { ctx.keys_changed.get() }; let verifiers = unsafe { ctx.verifiers.get() }; let vp_wasm_cache = unsafe { ctx.vp_wasm_cache.get() }; - let env = VpEnv { + let env = VpVmEnv { memory: WasmMemory::default(), ctx, }; From fe365d9d19f5d1f69eac3456924fb31d21881d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 20 Jul 2022 20:57:12 +0200 Subject: [PATCH 213/373] shared/ledger/native_vp: implement VpEnv --- apps/src/lib/node/ledger/protocol/mod.rs | 3 + shared/src/ledger/native_vp.rs | 203 +++++++++++++---------- 2 files changed, 120 insertions(+), 86 deletions(-) diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index e5f191d6e7..cac7cacb42 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -249,10 +249,13 @@ where } Address::Internal(internal_addr) => { let ctx = native_vp::Ctx::new( + addr, storage, write_log, tx, gas_meter, + &keys_changed, + &verifiers, vp_wasm_cache.clone(), ); let tx_data = match tx.data.as_ref() { diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index faad84a0f4..3893e847ed 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -3,27 +3,20 @@ use std::cell::RefCell; use std::collections::BTreeSet; -use thiserror::Error; - +pub use super::vp_env::VpEnv; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{Storage, StorageHasher}; use crate::ledger::{storage, vp_env}; use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; +use crate::types::hash::Hash; use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key}; use crate::vm::prefix_iter::PrefixIterators; use crate::vm::WasmCacheAccess; -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Host context error: {0}")] - ContextError(vp_env::RuntimeError), -} - -/// Native VP function result -pub type Result = std::result::Result; +/// Possible error in a native VP host function call +pub type Error = vp_env::RuntimeError; /// A native VP module should implement its validation logic using this trait. pub trait NativeVp { @@ -54,6 +47,8 @@ where H: StorageHasher, CA: WasmCacheAccess, { + /// The address of the account that owns the VP + pub address: &'a Address, /// Storage prefix iterators. pub iterators: RefCell>, /// VP gas meter. @@ -64,6 +59,11 @@ where pub write_log: &'a WriteLog, /// The transaction code is used for signature verification pub tx: &'a Tx, + /// The storage keys that have been changed. Used for calls to `eval`. + pub keys_changed: &'a BTreeSet, + /// The verifiers whose validity predicates should be triggered. Used for + /// calls to `eval`. + pub verifiers: &'a BTreeSet
, /// VP WASM compilation cache #[cfg(feature = "wasm-runtime")] pub vp_wasm_cache: crate::vm::wasm::VpCache, @@ -79,20 +79,27 @@ where CA: 'static + WasmCacheAccess, { /// Initialize a new context for native VP call + #[allow(clippy::too_many_arguments)] pub fn new( + address: &'a Address, storage: &'a Storage, write_log: &'a WriteLog, tx: &'a Tx, gas_meter: VpGasMeter, + keys_changed: &'a BTreeSet, + verifiers: &'a BTreeSet
, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: crate::vm::wasm::VpCache, ) -> Self { Self { + address, iterators: RefCell::new(PrefixIterators::default()), gas_meter: RefCell::new(gas_meter), storage, write_log, tx, + keys_changed, + verifiers, #[cfg(feature = "wasm-runtime")] vp_wasm_cache, #[cfg(not(feature = "wasm-runtime"))] @@ -101,153 +108,163 @@ where } /// Add a gas cost incured in a validity predicate - pub fn add_gas(&self, used_gas: u64) -> Result<()> { + pub fn add_gas(&self, used_gas: u64) -> Result<(), vp_env::RuntimeError> { vp_env::add_gas(&mut *self.gas_meter.borrow_mut(), used_gas) - .map_err(Error::ContextError) + } +} + +impl<'a, DB, H, CA> VpEnv for Ctx<'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type Error = Error; + type PrefixIter = >::PrefixIter; + + fn read_pre( + &self, + key: &Key, + ) -> Result, Self::Error> { + vp_env::read_pre( + &mut *self.gas_meter.borrow_mut(), + self.storage, + self.write_log, + key, + ) + .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) } - /// Storage read prior state (before tx execution). It will try to read from - /// the storage. - pub fn read_pre(&self, key: &Key) -> Result>> { + fn read_bytes_pre( + &self, + key: &Key, + ) -> Result>, Self::Error> { vp_env::read_pre( &mut *self.gas_meter.borrow_mut(), self.storage, self.write_log, key, ) - .map_err(Error::ContextError) } - /// Storage read posterior state (after tx execution). It will try to read - /// from the write log first and if no entry found then from the - /// storage. - pub fn read_post(&self, key: &Key) -> Result>> { + fn read_post( + &self, + key: &Key, + ) -> Result, Self::Error> { vp_env::read_post( &mut *self.gas_meter.borrow_mut(), self.storage, self.write_log, key, ) - .map_err(Error::ContextError) + .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) } - /// Storage read temporary state (after tx execution). It will try to read - /// from only the write log. - pub fn read_temp(&self, key: &Key) -> Result>> { + fn read_bytes_post( + &self, + key: &Key, + ) -> Result>, Self::Error> { + vp_env::read_post( + &mut *self.gas_meter.borrow_mut(), + self.storage, + self.write_log, + key, + ) + } + + fn read_temp( + &self, + key: &Key, + ) -> Result, Self::Error> { vp_env::read_temp( &mut *self.gas_meter.borrow_mut(), self.write_log, key, ) - .map_err(Error::ContextError) + .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) } - /// Storage `has_key` in prior state (before tx execution). It will try to - /// read from the storage. - pub fn has_key_pre(&self, key: &Key) -> Result { + fn read_bytes_temp( + &self, + key: &Key, + ) -> Result>, Self::Error> { + vp_env::read_temp( + &mut *self.gas_meter.borrow_mut(), + self.write_log, + key, + ) + } + + fn has_key_pre(&self, key: &Key) -> Result { vp_env::has_key_pre( &mut *self.gas_meter.borrow_mut(), self.storage, key, ) - .map_err(Error::ContextError) } - /// Storage `has_key` in posterior state (after tx execution). It will try - /// to check the write log first and if no entry found then the storage. - pub fn has_key_post(&self, key: &Key) -> Result { + fn has_key_post(&self, key: &Key) -> Result { vp_env::has_key_post( &mut *self.gas_meter.borrow_mut(), self.storage, self.write_log, key, ) - .map_err(Error::ContextError) } - /// Getting the chain ID. - pub fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { vp_env::get_chain_id(&mut *self.gas_meter.borrow_mut(), self.storage) - .map_err(Error::ContextError) } - /// Getting the block height. The height is that of the block to which the - /// current transaction is being applied. - pub fn get_block_height(&self) -> Result { + fn get_block_height(&self) -> Result { vp_env::get_block_height( &mut *self.gas_meter.borrow_mut(), self.storage, ) - .map_err(Error::ContextError) } - /// Getting the block hash. The height is that of the block to which the - /// current transaction is being applied. - pub fn get_block_hash(&self) -> Result { + fn get_block_hash(&self) -> Result { vp_env::get_block_hash(&mut *self.gas_meter.borrow_mut(), self.storage) - .map_err(Error::ContextError) } - /// Getting the block epoch. The epoch is that of the block to which the - /// current transaction is being applied. - pub fn get_block_epoch(&self) -> Result { + fn get_block_epoch(&self) -> Result { vp_env::get_block_epoch(&mut *self.gas_meter.borrow_mut(), self.storage) - .map_err(Error::ContextError) } - /// Storage prefix iterator. It will try to get an iterator from the - /// storage. - pub fn iter_prefix( + fn iter_prefix( &self, prefix: &Key, - ) -> Result<>::PrefixIter> { + ) -> Result { vp_env::iter_prefix( &mut *self.gas_meter.borrow_mut(), self.storage, prefix, ) - .map_err(Error::ContextError) } - /// Storage prefix iterator for prior state (before tx execution). It will - /// try to read from the storage. - pub fn iter_pre_next( + fn iter_pre_next( &self, - iter: &mut >::PrefixIter, - ) -> Result)>> { + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error> { vp_env::iter_pre_next::(&mut *self.gas_meter.borrow_mut(), iter) - .map_err(Error::ContextError) } - /// Storage prefix iterator next for posterior state (after tx execution). - /// It will try to read from the write log first and if no entry found - /// then from the storage. - pub fn iter_post_next( + fn iter_post_next( &self, - iter: &mut >::PrefixIter, - ) -> Result)>> { + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error> { vp_env::iter_post_next::( &mut *self.gas_meter.borrow_mut(), self.write_log, iter, ) - .map_err(Error::ContextError) } - /// Evaluate a validity predicate with given data. The address, changed - /// storage keys and verifiers will have the same values as the input to - /// caller's validity predicate. - /// - /// If the execution fails for whatever reason, this will return `false`. - /// Otherwise returns the result of evaluation. - pub fn eval( - &mut self, - address: &Address, - keys_changed: &BTreeSet, - verifiers: &BTreeSet
, + fn eval( + &self, vp_code: Vec, input_data: Vec, - ) -> bool { + ) -> Result { #[cfg(feature = "wasm-runtime")] { use std::marker::PhantomData; @@ -263,39 +280,53 @@ where let mut iterators: PrefixIterators<'_, DB> = PrefixIterators::default(); let mut result_buffer: Option> = None; + let mut vp_wasm_cache = self.vp_wasm_cache.clone(); let ctx = VpCtx::new( - address, + self.address, self.storage, self.write_log, &mut *self.gas_meter.borrow_mut(), self.tx, &mut iterators, - verifiers, + self.verifiers, &mut result_buffer, - keys_changed, + self.keys_changed, &eval_runner, - &mut self.vp_wasm_cache, + &mut vp_wasm_cache, ); match eval_runner.eval_native_result(ctx, vp_code, input_data) { - Ok(result) => result, + Ok(result) => Ok(result), Err(err) => { tracing::warn!( "VP eval from a native VP failed with: {}", err ); - false + Ok(false) } } } #[cfg(not(feature = "wasm-runtime"))] { - let _ = (address, keys_changed, verifiers, vp_code, input_data); + // This line is here to prevent unused var clippy warning + let _ = (vp_code, input_data); unimplemented!( "The \"wasm-runtime\" feature must be enabled to use the \ `eval` function." ) } } + + fn verify_tx_signature( + &self, + pk: &crate::types::key::common::PublicKey, + sig: &crate::types::key::common::Signature, + ) -> Result { + Ok(self.tx.verify_sig(pk, sig).is_ok()) + } + + fn get_tx_code_hash(&self) -> Result { + vp_env::get_tx_code_hash(&mut *self.gas_meter.borrow_mut(), self.tx) + } } From 4e46acaa3f0ac7879b135779cb82af204cbe4a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 20 Jul 2022 20:58:28 +0200 Subject: [PATCH 214/373] shared: update native_vp implementations for VpEnv methods --- proof_of_stake/src/lib.rs | 222 +++++++++++------- shared/src/ledger/governance/vp.rs | 9 +- shared/src/ledger/ibc/handler.rs | 309 ++++++++++++++++--------- shared/src/ledger/ibc/vp/channel.rs | 26 +-- shared/src/ledger/ibc/vp/client.rs | 11 +- shared/src/ledger/ibc/vp/connection.rs | 5 +- shared/src/ledger/ibc/vp/mod.rs | 246 ++++++++++++++++---- shared/src/ledger/ibc/vp/port.rs | 5 +- shared/src/ledger/ibc/vp/token.rs | 53 +++-- shared/src/ledger/pos/vp.rs | 146 ++++++------ shared/src/ledger/treasury/mod.rs | 71 ++---- 11 files changed, 693 insertions(+), 410 deletions(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index a137eb8a91..144c88c596 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -94,122 +94,164 @@ pub trait PosReadOnly { + BorshSerialize + BorshSchema; + /// Underlying read (and write in [`PosActions`]) interface errors + type Error; + /// Address of the PoS account const POS_ADDRESS: Self::Address; + /// Address of the staking token /// TODO: this should be `const`, but in the ledger `address::xan` is not a /// `const fn` fn staking_token_address() -> Self::Address; /// Read PoS parameters. - fn read_pos_params(&self) -> PosParams; + fn read_pos_params(&self) -> Result; /// Read PoS validator's staking reward address. fn read_validator_staking_reward_address( &self, key: &Self::Address, - ) -> Option; + ) -> Result, Self::Error>; /// Read PoS validator's consensus key (used for signing block votes). fn read_validator_consensus_key( &self, key: &Self::Address, - ) -> Option>; + ) -> Result>, Self::Error>; /// Read PoS validator's state. fn read_validator_state( &self, key: &Self::Address, - ) -> Option; + ) -> Result, Self::Error>; /// Read PoS validator's total deltas of their bonds (validator self-bonds /// and delegations). fn read_validator_total_deltas( &self, key: &Self::Address, - ) -> Option>; + ) -> Result>, Self::Error>; /// Read PoS validator's voting power. fn read_validator_voting_power( &self, key: &Self::Address, - ) -> Option; + ) -> Result, Self::Error>; /// Read PoS slashes applied to a validator. - fn read_validator_slashes(&self, key: &Self::Address) -> Vec; + fn read_validator_slashes( + &self, + key: &Self::Address, + ) -> Result, Self::Error>; /// Read PoS bond (validator self-bond or a delegation). fn read_bond( &self, key: &BondId, - ) -> Option>; + ) -> Result>, Self::Error>; /// Read PoS unbond (unbonded tokens from validator self-bond or a /// delegation). fn read_unbond( &self, key: &BondId, - ) -> Option>; + ) -> Result>, Self::Error>; /// Read PoS validator set (active and inactive). - fn read_validator_set(&self) -> ValidatorSets; + fn read_validator_set( + &self, + ) -> Result, Self::Error>; /// Read PoS total voting power of all validators (active and inactive). - fn read_total_voting_power(&self) -> TotalVotingPowers; + fn read_total_voting_power(&self) + -> Result; } /// PoS system trait to be implemented in integration that can read and write /// PoS data. pub trait PosActions: PosReadOnly { + /// Error in `PosActions::become_validator` + type BecomeValidatorError: From + + From>; + + /// Error in `PosActions::bond_tokens` + type BondError: From + From>; + + /// Error in `PosActions::unbond_tokens` + type UnbondError: From + + From>; + + /// Error in `PosActions::withdraw_tokens` + type WithdrawError: From + From>; + /// Write PoS parameters. - fn write_pos_params(&mut self, params: &PosParams); + fn write_pos_params( + &mut self, + params: &PosParams, + ) -> Result<(), Self::Error>; /// Write PoS validator's raw hash its address. - fn write_validator_address_raw_hash(&mut self, address: &Self::Address); + fn write_validator_address_raw_hash( + &mut self, + address: &Self::Address, + ) -> Result<(), Self::Error>; /// Write PoS validator's staking reward address, into which staking rewards /// will be credited. fn write_validator_staking_reward_address( &mut self, key: &Self::Address, value: Self::Address, - ); + ) -> Result<(), Self::Error>; /// Write PoS validator's consensus key (used for signing block votes). fn write_validator_consensus_key( &mut self, key: &Self::Address, value: ValidatorConsensusKeys, - ); + ) -> Result<(), Self::Error>; /// Write PoS validator's state. fn write_validator_state( &mut self, key: &Self::Address, value: ValidatorStates, - ); + ) -> Result<(), Self::Error>; /// Write PoS validator's total deltas of their bonds (validator self-bonds /// and delegations). fn write_validator_total_deltas( &mut self, key: &Self::Address, value: ValidatorTotalDeltas, - ); + ) -> Result<(), Self::Error>; /// Write PoS validator's voting power. fn write_validator_voting_power( &mut self, key: &Self::Address, value: ValidatorVotingPowers, - ); + ) -> Result<(), Self::Error>; /// Write PoS bond (validator self-bond or a delegation). fn write_bond( &mut self, key: &BondId, value: Bonds, - ); + ) -> Result<(), Self::Error>; /// Write PoS unbond (unbonded tokens from validator self-bond or a /// delegation). fn write_unbond( &mut self, key: &BondId, value: Unbonds, - ); + ) -> Result<(), Self::Error>; /// Write PoS validator set (active and inactive). - fn write_validator_set(&mut self, value: ValidatorSets); + fn write_validator_set( + &mut self, + value: ValidatorSets, + ) -> Result<(), Self::Error>; /// Write PoS total voting power of all validators (active and inactive). - fn write_total_voting_power(&mut self, value: TotalVotingPowers); + fn write_total_voting_power( + &mut self, + value: TotalVotingPowers, + ) -> Result<(), Self::Error>; /// Delete an emptied PoS bond (validator self-bond or a delegation). - fn delete_bond(&mut self, key: &BondId); + fn delete_bond( + &mut self, + key: &BondId, + ) -> Result<(), Self::Error>; /// Delete an emptied PoS unbond (unbonded tokens from validator self-bond /// or a delegation). - fn delete_unbond(&mut self, key: &BondId); + fn delete_unbond( + &mut self, + key: &BondId, + ) -> Result<(), Self::Error>; /// Transfer tokens from the `src` to the `dest`. fn transfer( @@ -218,7 +260,7 @@ pub trait PosActions: PosReadOnly { amount: Self::TokenAmount, src: &Self::Address, dest: &Self::Address, - ); + ) -> Result<(), Self::Error>; /// Attempt to update the given account to become a validator. fn become_validator( @@ -227,21 +269,19 @@ pub trait PosActions: PosReadOnly { staking_reward_address: &Self::Address, consensus_key: &Self::PublicKey, current_epoch: impl Into, - ) -> Result<(), BecomeValidatorError> { + ) -> Result<(), Self::BecomeValidatorError> { let current_epoch = current_epoch.into(); - let params = self.read_pos_params(); - let mut validator_set = self.read_validator_set(); - if self.is_validator(address) { - return Err(BecomeValidatorError::AlreadyValidator( - address.clone(), - )); + let params = self.read_pos_params()?; + let mut validator_set = self.read_validator_set()?; + if self.is_validator(address)? { + Err(BecomeValidatorError::AlreadyValidator(address.clone()))?; } if address == staking_reward_address { - return Err( + Err( BecomeValidatorError::StakingRewardAddressEqValidatorAddress( address.clone(), ), - ); + )?; } let BecomeValidatorData { consensus_key, @@ -258,20 +298,24 @@ pub trait PosActions: PosReadOnly { self.write_validator_staking_reward_address( address, staking_reward_address.clone(), - ); - self.write_validator_consensus_key(address, consensus_key); - self.write_validator_state(address, state); - self.write_validator_set(validator_set); - self.write_validator_address_raw_hash(address); - self.write_validator_total_deltas(address, total_deltas); - self.write_validator_voting_power(address, voting_power); + )?; + self.write_validator_consensus_key(address, consensus_key)?; + self.write_validator_state(address, state)?; + self.write_validator_set(validator_set)?; + self.write_validator_address_raw_hash(address)?; + self.write_validator_total_deltas(address, total_deltas)?; + self.write_validator_voting_power(address, voting_power)?; Ok(()) } /// Check if the given address is a validator by checking that it has some /// state. - fn is_validator(&self, address: &Self::Address) -> bool { - self.read_validator_state(address).is_some() + fn is_validator( + &self, + address: &Self::Address, + ) -> Result { + let state = self.read_validator_state(address)?; + Ok(state.is_some()) } /// Self-bond tokens to a validator when `source` is `None` or equal to @@ -283,29 +327,27 @@ pub trait PosActions: PosReadOnly { validator: &Self::Address, amount: Self::TokenAmount, current_epoch: impl Into, - ) -> Result<(), BondError> { + ) -> Result<(), Self::BondError> { let current_epoch = current_epoch.into(); if let Some(source) = source { - if source != validator && self.is_validator(source) { - return Err(BondError::SourceMustNotBeAValidator( - source.clone(), - )); + if source != validator && self.is_validator(source)? { + Err(BondError::SourceMustNotBeAValidator(source.clone()))?; } } - let params = self.read_pos_params(); - let validator_state = self.read_validator_state(validator); + let params = self.read_pos_params()?; + let validator_state = self.read_validator_state(validator)?; let source = source.unwrap_or(validator); let bond_id = BondId { source: source.clone(), validator: validator.clone(), }; - let bond = self.read_bond(&bond_id); + let bond = self.read_bond(&bond_id)?; let validator_total_deltas = - self.read_validator_total_deltas(validator); + self.read_validator_total_deltas(validator)?; let validator_voting_power = - self.read_validator_voting_power(validator); - let mut total_voting_power = self.read_total_voting_power(); - let mut validator_set = self.read_validator_set(); + self.read_validator_voting_power(validator)?; + let mut total_voting_power = self.read_total_voting_power()?; + let mut validator_set = self.read_validator_set()?; let BondData { bond, @@ -323,12 +365,11 @@ pub trait PosActions: PosReadOnly { &mut validator_set, current_epoch, )?; - - self.write_bond(&bond_id, bond); - self.write_validator_total_deltas(validator, validator_total_deltas); - self.write_validator_voting_power(validator, validator_voting_power); - self.write_total_voting_power(total_voting_power); - self.write_validator_set(validator_set); + self.write_bond(&bond_id, bond)?; + self.write_validator_total_deltas(validator, validator_total_deltas)?; + self.write_validator_voting_power(validator, validator_voting_power)?; + self.write_total_voting_power(total_voting_power)?; + self.write_validator_set(validator_set)?; // Transfer the bonded tokens from the source to PoS self.transfer( @@ -336,8 +377,7 @@ pub trait PosActions: PosReadOnly { amount, source, &Self::POS_ADDRESS, - ); - + )?; Ok(()) } @@ -350,28 +390,32 @@ pub trait PosActions: PosReadOnly { validator: &Self::Address, amount: Self::TokenAmount, current_epoch: impl Into, - ) -> Result<(), UnbondError> { + ) -> Result<(), Self::UnbondError> { let current_epoch = current_epoch.into(); - let params = self.read_pos_params(); + let params = self.read_pos_params()?; let source = source.unwrap_or(validator); let bond_id = BondId { source: source.clone(), validator: validator.clone(), }; - let mut bond = - self.read_bond(&bond_id).ok_or(UnbondError::NoBondFound)?; - let unbond = self.read_unbond(&bond_id); - let mut validator_total_deltas = - self.read_validator_total_deltas(validator).ok_or_else(|| { + let mut bond = match self.read_bond(&bond_id)? { + Some(val) => val, + None => Err(UnbondError::NoBondFound)?, + }; + let unbond = self.read_unbond(&bond_id)?; + let mut validator_total_deltas = self + .read_validator_total_deltas(validator)? + .ok_or_else(|| { UnbondError::ValidatorHasNoBonds(validator.clone()) })?; - let mut validator_voting_power = - self.read_validator_voting_power(validator).ok_or_else(|| { + let mut validator_voting_power = self + .read_validator_voting_power(validator)? + .ok_or_else(|| { UnbondError::ValidatorHasNoVotingPower(validator.clone()) })?; - let slashes = self.read_validator_slashes(validator); - let mut total_voting_power = self.read_total_voting_power(); - let mut validator_set = self.read_validator_set(); + let slashes = self.read_validator_slashes(validator)?; + let mut total_voting_power = self.read_total_voting_power()?; + let mut validator_set = self.read_validator_set()?; let UnbondData { unbond } = unbond_tokens( ¶ms, @@ -394,18 +438,18 @@ pub trait PosActions: PosReadOnly { ); match total_bonds { Some(total_bonds) if total_bonds.sum() != 0.into() => { - self.write_bond(&bond_id, bond); + self.write_bond(&bond_id, bond)?; } _ => { // If the bond is left empty, delete it - self.delete_bond(&bond_id) + self.delete_bond(&bond_id)? } } - self.write_unbond(&bond_id, unbond); - self.write_validator_total_deltas(validator, validator_total_deltas); - self.write_validator_voting_power(validator, validator_voting_power); - self.write_total_voting_power(total_voting_power); - self.write_validator_set(validator_set); + self.write_unbond(&bond_id, unbond)?; + self.write_validator_total_deltas(validator, validator_total_deltas)?; + self.write_validator_voting_power(validator, validator_voting_power)?; + self.write_total_voting_power(total_voting_power)?; + self.write_validator_set(validator_set)?; Ok(()) } @@ -418,17 +462,17 @@ pub trait PosActions: PosReadOnly { source: Option<&Self::Address>, validator: &Self::Address, current_epoch: impl Into, - ) -> Result> { + ) -> Result { let current_epoch = current_epoch.into(); - let params = self.read_pos_params(); + let params = self.read_pos_params()?; let source = source.unwrap_or(validator); let bond_id = BondId { source: source.clone(), validator: validator.clone(), }; - let unbond = self.read_unbond(&bond_id); - let slashes = self.read_validator_slashes(&bond_id.validator); + let unbond = self.read_unbond(&bond_id)?; + let slashes = self.read_validator_slashes(&bond_id.validator)?; let WithdrawData { unbond, @@ -449,11 +493,11 @@ pub trait PosActions: PosReadOnly { ); match total_unbonds { Some(total_unbonds) if total_unbonds.sum() != 0.into() => { - self.write_unbond(&bond_id, unbond); + self.write_unbond(&bond_id, unbond)?; } _ => { // If the unbond is left empty, delete it - self.delete_unbond(&bond_id) + self.delete_unbond(&bond_id)? } } @@ -463,7 +507,7 @@ pub trait PosActions: PosReadOnly { withdrawn, &Self::POS_ADDRESS, source, - ); + )?; Ok(slashed) } diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 6ccfc7a33e..ad941806c4 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -7,6 +7,7 @@ use super::storage as gov_storage; use crate::ledger::native_vp::{self, Ctx}; use crate::ledger::pos::{self as pos_storage, BondId, Bonds}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::vp_env::VpEnv; use crate::types::address::{xan as m1t, Address, InternalAddress}; use crate::types::storage::{Epoch, Key}; use crate::types::token; @@ -350,7 +351,7 @@ where let max_content_length = read(ctx, &max_content_length_parameter_key, ReadType::PRE).ok(); let has_pre_content = ctx.has_key_pre(&content_key).ok(); - let post_content = ctx.read_post(&content_key).unwrap(); + let post_content = ctx.read_bytes_post(&content_key).unwrap(); match (has_pre_content, post_content, max_content_length) { ( Some(has_pre_content), @@ -377,7 +378,7 @@ where let max_content_length = read(ctx, &max_content_length_parameter_key, ReadType::PRE).ok(); let has_pre_content = ctx.has_key_pre(&content_key).ok(); - let post_content = ctx.read_post(&content_key).unwrap(); + let post_content = ctx.read_bytes_post(&content_key).unwrap(); match (has_pre_content, post_content, max_content_length) { ( Some(has_pre_content), @@ -504,8 +505,8 @@ where T: Clone + BorshDeserialize, { let storage_result = match read_type { - ReadType::PRE => context.read_pre(key), - ReadType::POST => context.read_post(key), + ReadType::PRE => context.read_bytes_pre(key), + ReadType::POST => context.read_bytes_post(key), }; match storage_result { diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index bf45759535..5cbee20756 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -122,35 +122,56 @@ pub type Result = std::result::Result; /// IBC trait to be implemented in integration that can read and write pub trait IbcActions { + /// IBC action error + type Error: From; + /// Read IBC-related data - fn read_ibc_data(&self, key: &Key) -> Option>; + fn read_ibc_data( + &self, + key: &Key, + ) -> std::result::Result>, Self::Error>; /// Write IBC-related data - fn write_ibc_data(&self, key: &Key, data: impl AsRef<[u8]>); + fn write_ibc_data( + &mut self, + key: &Key, + data: impl AsRef<[u8]>, + ) -> std::result::Result<(), Self::Error>; /// Delete IBC-related data - fn delete_ibc_data(&self, key: &Key); + fn delete_ibc_data( + &mut self, + key: &Key, + ) -> std::result::Result<(), Self::Error>; /// Emit an IBC event - fn emit_ibc_event(&self, event: AnomaIbcEvent); + fn emit_ibc_event( + &mut self, + event: AnomaIbcEvent, + ) -> std::result::Result<(), Self::Error>; /// Transfer token fn transfer_token( - &self, + &mut self, src: &Address, dest: &Address, token: &Address, amount: Amount, - ); + ) -> std::result::Result<(), Self::Error>; /// Get the current height of this chain - fn get_height(&self) -> BlockHeight; + fn get_height(&self) -> std::result::Result; /// Get the current time of the tendermint header of this chain - fn get_header_time(&self) -> Rfc3339String; + fn get_header_time( + &self, + ) -> std::result::Result; /// dispatch according to ICS26 routing - fn dispatch(&self, tx_data: &[u8]) -> Result<()> { + fn dispatch_ibc_action( + &mut self, + tx_data: &[u8], + ) -> std::result::Result<(), Self::Error> { let ibc_msg = IbcMessage::decode(tx_data).map_err(Error::IbcData)?; match &ibc_msg.0 { Ics26Envelope::Ics2Msg(ics02_msg) => match ics02_msg { @@ -200,14 +221,17 @@ pub trait IbcActions { } /// Create a new client - fn create_client(&self, msg: &MsgCreateAnyClient) -> Result<()> { + fn create_client( + &mut self, + msg: &MsgCreateAnyClient, + ) -> std::result::Result<(), Self::Error> { let counter_key = storage::client_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; let client_type = msg.client_state.client_type(); let client_id = client_id(client_type, counter)?; // client type let client_type_key = storage::client_type_key(&client_id); - self.write_ibc_data(&client_type_key, client_type.as_str().as_bytes()); + self.write_ibc_data(&client_type_key, client_type.as_str().as_bytes())?; // client state let client_state_key = storage::client_state_key(&client_id); self.write_ibc_data( @@ -215,7 +239,7 @@ pub trait IbcActions { msg.client_state .encode_vec() .expect("encoding shouldn't fail"), - ); + )?; // consensus state let height = msg.client_state.latest_height(); let consensus_state_key = @@ -225,29 +249,33 @@ pub trait IbcActions { msg.consensus_state .encode_vec() .expect("encoding shouldn't fail"), - ); + )?; self.set_client_update_time(&client_id)?; let event = make_create_client_event(&client_id, msg) .try_into() .unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Update a client - fn update_client(&self, msg: &MsgUpdateAnyClient) -> Result<()> { + fn update_client( + &mut self, + msg: &MsgUpdateAnyClient, + ) -> std::result::Result<(), Self::Error> { // get and update the client let client_id = msg.client_id.clone(); let client_state_key = storage::client_state_key(&client_id); - let value = self.read_ibc_data(&client_state_key).ok_or_else(|| { - Error::Client(format!( - "The client to be updated doesn't exist: ID {}", - client_id - )) - })?; + let value = + self.read_ibc_data(&client_state_key)?.ok_or_else(|| { + Error::Client(format!( + "The client to be updated doesn't exist: ID {}", + client_id + )) + })?; let client_state = AnyClientState::decode_vec(&value).map_err(Error::Decoding)?; let (new_client_state, new_consensus_state) = @@ -259,7 +287,7 @@ pub trait IbcActions { new_client_state .encode_vec() .expect("encoding shouldn't fail"), - ); + )?; let consensus_state_key = storage::consensus_state_key(&client_id, height); self.write_ibc_data( @@ -267,20 +295,23 @@ pub trait IbcActions { new_consensus_state .encode_vec() .expect("encoding shouldn't fail"), - ); + )?; self.set_client_update_time(&client_id)?; let event = make_update_client_event(&client_id, msg) .try_into() .unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Upgrade a client - fn upgrade_client(&self, msg: &MsgUpgradeAnyClient) -> Result<()> { + fn upgrade_client( + &mut self, + msg: &MsgUpgradeAnyClient, + ) -> std::result::Result<(), Self::Error> { let client_state_key = storage::client_state_key(&msg.client_id); let height = msg.client_state.latest_height(); let consensus_state_key = @@ -290,26 +321,29 @@ pub trait IbcActions { msg.client_state .encode_vec() .expect("encoding shouldn't fail"), - ); + )?; self.write_ibc_data( &consensus_state_key, msg.consensus_state .encode_vec() .expect("encoding shouldn't fail"), - ); + )?; self.set_client_update_time(&msg.client_id)?; let event = make_upgrade_client_event(&msg.client_id, msg) .try_into() .unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Initialize a connection for ConnectionOpenInit - fn init_connection(&self, msg: &MsgConnectionOpenInit) -> Result<()> { + fn init_connection( + &mut self, + msg: &MsgConnectionOpenInit, + ) -> std::result::Result<(), Self::Error> { let counter_key = storage::connection_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; // new connection @@ -319,18 +353,21 @@ pub trait IbcActions { self.write_ibc_data( &conn_key, connection.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_open_init_connection_event(&conn_id, msg) .try_into() .unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Initialize a connection for ConnectionOpenTry - fn try_connection(&self, msg: &MsgConnectionOpenTry) -> Result<()> { + fn try_connection( + &mut self, + msg: &MsgConnectionOpenTry, + ) -> std::result::Result<(), Self::Error> { let counter_key = storage::connection_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; // new connection @@ -340,20 +377,23 @@ pub trait IbcActions { self.write_ibc_data( &conn_key, connection.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_open_try_connection_event(&conn_id, msg) .try_into() .unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Open the connection for ConnectionOpenAck - fn ack_connection(&self, msg: &MsgConnectionOpenAck) -> Result<()> { + fn ack_connection( + &mut self, + msg: &MsgConnectionOpenAck, + ) -> std::result::Result<(), Self::Error> { let conn_key = storage::connection_key(&msg.connection_id); - let value = self.read_ibc_data(&conn_key).ok_or_else(|| { + let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { Error::Connection(format!( "The connection to be opened doesn't exist: ID {}", msg.connection_id @@ -369,18 +409,21 @@ pub trait IbcActions { self.write_ibc_data( &conn_key, connection.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_open_ack_connection_event(msg).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Open the connection for ConnectionOpenConfirm - fn confirm_connection(&self, msg: &MsgConnectionOpenConfirm) -> Result<()> { + fn confirm_connection( + &mut self, + msg: &MsgConnectionOpenConfirm, + ) -> std::result::Result<(), Self::Error> { let conn_key = storage::connection_key(&msg.connection_id); - let value = self.read_ibc_data(&conn_key).ok_or_else(|| { + let value = self.read_ibc_data(&conn_key)?.ok_or_else(|| { Error::Connection(format!( "The connection to be opend doesn't exist: ID {}", msg.connection_id @@ -392,16 +435,19 @@ pub trait IbcActions { self.write_ibc_data( &conn_key, connection.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_open_confirm_connection_event(msg).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Initialize a channel for ChannelOpenInit - fn init_channel(&self, msg: &MsgChannelOpenInit) -> Result<()> { + fn init_channel( + &mut self, + msg: &MsgChannelOpenInit, + ) -> std::result::Result<(), Self::Error> { self.bind_port(&msg.port_id)?; let counter_key = storage::channel_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; @@ -412,18 +458,21 @@ pub trait IbcActions { self.write_ibc_data( &channel_key, msg.channel.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_open_init_channel_event(&channel_id, msg) .try_into() .unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Initialize a channel for ChannelOpenTry - fn try_channel(&self, msg: &MsgChannelOpenTry) -> Result<()> { + fn try_channel( + &mut self, + msg: &MsgChannelOpenTry, + ) -> std::result::Result<(), Self::Error> { self.bind_port(&msg.port_id)?; let counter_key = storage::channel_counter_key(); let counter = self.get_and_inc_counter(&counter_key)?; @@ -434,22 +483,25 @@ pub trait IbcActions { self.write_ibc_data( &channel_key, msg.channel.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_open_try_channel_event(&channel_id, msg) .try_into() .unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Open the channel for ChannelOpenAck - fn ack_channel(&self, msg: &MsgChannelOpenAck) -> Result<()> { + fn ack_channel( + &mut self, + msg: &MsgChannelOpenAck, + ) -> std::result::Result<(), Self::Error> { let port_channel_id = port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key).ok_or_else(|| { + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { Error::Channel(format!( "The channel to be opened doesn't exist: Port/Channel {}", port_channel_id @@ -463,20 +515,23 @@ pub trait IbcActions { self.write_ibc_data( &channel_key, channel.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_open_ack_channel_event(msg).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Open the channel for ChannelOpenConfirm - fn confirm_channel(&self, msg: &MsgChannelOpenConfirm) -> Result<()> { + fn confirm_channel( + &mut self, + msg: &MsgChannelOpenConfirm, + ) -> std::result::Result<(), Self::Error> { let port_channel_id = port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key).ok_or_else(|| { + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { Error::Channel(format!( "The channel to be opened doesn't exist: Port/Channel {}", port_channel_id @@ -488,20 +543,23 @@ pub trait IbcActions { self.write_ibc_data( &channel_key, channel.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_open_confirm_channel_event(msg).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Close the channel for ChannelCloseInit - fn close_init_channel(&self, msg: &MsgChannelCloseInit) -> Result<()> { + fn close_init_channel( + &mut self, + msg: &MsgChannelCloseInit, + ) -> std::result::Result<(), Self::Error> { let port_channel_id = port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key).ok_or_else(|| { + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { Error::Channel(format!( "The channel to be closed doesn't exist: Port/Channel {}", port_channel_id @@ -513,23 +571,23 @@ pub trait IbcActions { self.write_ibc_data( &channel_key, channel.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_close_init_channel_event(msg).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Close the channel for ChannelCloseConfirm fn close_confirm_channel( - &self, + &mut self, msg: &MsgChannelCloseConfirm, - ) -> Result<()> { + ) -> std::result::Result<(), Self::Error> { let port_channel_id = port_channel_id(msg.port_id.clone(), msg.channel_id.clone()); let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key).ok_or_else(|| { + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { Error::Channel(format!( "The channel to be closed doesn't exist: Port/Channel {}", port_channel_id @@ -541,22 +599,22 @@ pub trait IbcActions { self.write_ibc_data( &channel_key, channel.encode_vec().expect("encoding shouldn't fail"), - ); + )?; let event = make_close_confirm_channel_event(msg).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Send a packet fn send_packet( - &self, + &mut self, port_channel_id: PortChannelId, data: Vec, timeout_height: Height, timeout_timestamp: Timestamp, - ) -> Result<()> { + ) -> std::result::Result<(), Self::Error> { // get and increment the next sequence send let seq_key = storage::next_sequence_send_key(&port_channel_id); let sequence = self.get_and_inc_sequence(&seq_key)?; @@ -564,7 +622,7 @@ pub trait IbcActions { // get the channel for the destination info. let channel_key = storage::channel_key(&port_channel_id); let channel = self - .read_ibc_data(&channel_key) + .read_ibc_data(&channel_key)? .expect("cannot get the channel to be closed"); let channel = ChannelEnd::decode_vec(&channel).expect("cannot get the channel"); @@ -595,16 +653,19 @@ pub trait IbcActions { commitment .encode(&mut commitment_bytes) .expect("encoding shouldn't fail"); - self.write_ibc_data(&commitment_key, commitment_bytes); + self.write_ibc_data(&commitment_key, commitment_bytes)?; let event = make_send_packet_event(packet).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Receive a packet - fn receive_packet(&self, msg: &MsgRecvPacket) -> Result<()> { + fn receive_packet( + &mut self, + msg: &MsgRecvPacket, + ) -> std::result::Result<(), Self::Error> { // check the packet data if let Ok(data) = serde_json::from_slice(&msg.packet.data) { self.receive_token(&msg.packet, &data)?; @@ -616,7 +677,7 @@ pub trait IbcActions { &msg.packet.destination_channel, msg.packet.sequence, ); - self.write_ibc_data(&receipt_key, PacketReceipt::default().as_bytes()); + self.write_ibc_data(&receipt_key, PacketReceipt::default().as_bytes())?; // store the ack let ack_key = storage::ack_key( @@ -625,7 +686,7 @@ pub trait IbcActions { msg.packet.sequence, ); let ack = PacketAck::default().encode_to_vec(); - self.write_ibc_data(&ack_key, ack.clone()); + self.write_ibc_data(&ack_key, ack.clone())?; // increment the next sequence receive let port_channel_id = port_channel_id( @@ -638,28 +699,34 @@ pub trait IbcActions { let event = make_write_ack_event(msg.packet.clone(), ack) .try_into() .unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Receive a acknowledgement - fn acknowledge_packet(&self, msg: &MsgAcknowledgement) -> Result<()> { + fn acknowledge_packet( + &mut self, + msg: &MsgAcknowledgement, + ) -> std::result::Result<(), Self::Error> { let commitment_key = storage::commitment_key( &msg.packet.source_port, &msg.packet.source_channel, msg.packet.sequence, ); - self.delete_ibc_data(&commitment_key); + self.delete_ibc_data(&commitment_key)?; let event = make_ack_event(msg.packet.clone()).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Receive a timeout - fn timeout_packet(&self, msg: &MsgTimeout) -> Result<()> { + fn timeout_packet( + &mut self, + msg: &MsgTimeout, + ) -> std::result::Result<(), Self::Error> { // check the packet data if let Ok(data) = serde_json::from_slice(&msg.packet.data) { self.refund_token(&msg.packet, &data)?; @@ -671,7 +738,7 @@ pub trait IbcActions { &msg.packet.source_channel, msg.packet.sequence, ); - self.delete_ibc_data(&commitment_key); + self.delete_ibc_data(&commitment_key)?; // close the channel let port_channel_id = port_channel_id( @@ -679,7 +746,7 @@ pub trait IbcActions { msg.packet.source_channel.clone(), ); let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key).ok_or_else(|| { + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { Error::Channel(format!( "The channel to be closed doesn't exist: Port/Channel {}", port_channel_id @@ -692,17 +759,20 @@ pub trait IbcActions { self.write_ibc_data( &channel_key, channel.encode_vec().expect("encoding shouldn't fail"), - ); + )?; } let event = make_timeout_event(msg.packet.clone()).try_into().unwrap(); - self.emit_ibc_event(event); + self.emit_ibc_event(event)?; Ok(()) } /// Receive a timeout for TimeoutOnClose - fn timeout_on_close_packet(&self, msg: &MsgTimeoutOnClose) -> Result<()> { + fn timeout_on_close_packet( + &mut self, + msg: &MsgTimeoutOnClose, + ) -> std::result::Result<(), Self::Error> { // check the packet data if let Ok(data) = serde_json::from_slice(&msg.packet.data) { self.refund_token(&msg.packet, &data)?; @@ -714,7 +784,7 @@ pub trait IbcActions { &msg.packet.source_channel, msg.packet.sequence, ); - self.delete_ibc_data(&commitment_key); + self.delete_ibc_data(&commitment_key)?; // close the channel let port_channel_id = port_channel_id( @@ -722,7 +792,7 @@ pub trait IbcActions { msg.packet.source_channel.clone(), ); let channel_key = storage::channel_key(&port_channel_id); - let value = self.read_ibc_data(&channel_key).ok_or_else(|| { + let value = self.read_ibc_data(&channel_key)?.ok_or_else(|| { Error::Channel(format!( "The channel to be closed doesn't exist: Port/Channel {}", port_channel_id @@ -735,15 +805,18 @@ pub trait IbcActions { self.write_ibc_data( &channel_key, channel.encode_vec().expect("encoding shouldn't fail"), - ); + )?; } Ok(()) } /// Set the timestamp and the height for the client update - fn set_client_update_time(&self, client_id: &ClientId) -> Result<()> { - let time = Time::parse_from_rfc3339(&self.get_header_time().0) + fn set_client_update_time( + &mut self, + client_id: &ClientId, + ) -> std::result::Result<(), Self::Error> { + let time = Time::parse_from_rfc3339(&self.get_header_time()?.0) .map_err(|e| { Error::Time(format!("The time of the header is invalid: {}", e)) })?; @@ -751,36 +824,42 @@ pub trait IbcActions { self.write_ibc_data( &key, time.encode_vec().expect("encoding shouldn't fail"), - ); + )?; // the revision number is always 0 - let height = Height::new(0, self.get_height().0); + let height = Height::new(0, self.get_height()?.0); let height_key = storage::client_update_height_key(client_id); // write the current height as u64 self.write_ibc_data( &height_key, height.encode_vec().expect("Encoding shouldn't fail"), - ); + )?; Ok(()) } /// Get and increment the counter - fn get_and_inc_counter(&self, key: &Key) -> Result { - let value = self.read_ibc_data(key).ok_or_else(|| { + fn get_and_inc_counter( + &mut self, + key: &Key, + ) -> std::result::Result { + let value = self.read_ibc_data(key)?.ok_or_else(|| { Error::Counter(format!("The counter doesn't exist: {}", key)) })?; let value: [u8; 8] = value.try_into().map_err(|_| { Error::Counter(format!("The counter value wasn't u64: Key {}", key)) })?; let counter = u64::from_be_bytes(value); - self.write_ibc_data(key, (counter + 1).to_be_bytes()); + self.write_ibc_data(key, (counter + 1).to_be_bytes())?; Ok(counter) } /// Get and increment the sequence - fn get_and_inc_sequence(&self, key: &Key) -> Result { - let index = match self.read_ibc_data(key) { + fn get_and_inc_sequence( + &mut self, + key: &Key, + ) -> std::result::Result { + let index = match self.read_ibc_data(key)? { Some(v) => { let index: [u8; 8] = v.try_into().map_err(|_| { Error::Sequence(format!( @@ -793,29 +872,35 @@ pub trait IbcActions { // when the sequence has never been used, returns the initial value None => 1, }; - self.write_ibc_data(key, (index + 1).to_be_bytes()); + self.write_ibc_data(key, (index + 1).to_be_bytes())?; Ok(index.into()) } /// Bind a new port - fn bind_port(&self, port_id: &PortId) -> Result<()> { + fn bind_port( + &mut self, + port_id: &PortId, + ) -> std::result::Result<(), Self::Error> { let port_key = storage::port_key(port_id); - match self.read_ibc_data(&port_key) { + match self.read_ibc_data(&port_key)? { Some(_) => {} None => { // create a new capability and claim it let index_key = storage::capability_index_key(); let cap_index = self.get_and_inc_counter(&index_key)?; - self.write_ibc_data(&port_key, cap_index.to_be_bytes()); + self.write_ibc_data(&port_key, cap_index.to_be_bytes())?; let cap_key = storage::capability_key(cap_index); - self.write_ibc_data(&cap_key, port_id.as_bytes()); + self.write_ibc_data(&cap_key, port_id.as_bytes())?; } } Ok(()) } /// Send the specified token by escrowing or burning - fn send_token(&self, msg: &MsgTransfer) -> Result<()> { + fn send_token( + &mut self, + msg: &MsgTransfer, + ) -> std::result::Result<(), Self::Error> { let data = FungibleTokenPacketData::from(msg.clone()); let source = Address::decode(data.sender.clone()).map_err(|e| { Error::SendingToken(format!( @@ -852,7 +937,7 @@ pub trait IbcActions { if data.denomination.starts_with(&prefix) { // sink zone let burn = Address::Internal(InternalAddress::IbcBurn); - self.transfer_token(&source, &burn, &token, amount); + self.transfer_token(&source, &burn, &token, amount)?; } else { // source zone let escrow = @@ -860,7 +945,7 @@ pub trait IbcActions { msg.source_port.to_string(), msg.source_channel.to_string(), )); - self.transfer_token(&source, &escrow, &token, amount); + self.transfer_token(&source, &escrow, &token, amount)?; } // send a packet @@ -880,10 +965,10 @@ pub trait IbcActions { /// Receive the specified token by unescrowing or minting fn receive_token( - &self, + &mut self, packet: &Packet, data: &FungibleTokenPacketData, - ) -> Result<()> { + ) -> std::result::Result<(), Self::Error> { let dest = Address::decode(data.receiver.clone()).map_err(|e| { Error::ReceivingToken(format!( "Invalid receiver address: receiver {}, error {}", @@ -922,21 +1007,21 @@ pub trait IbcActions { packet.destination_port.to_string(), packet.destination_channel.to_string(), )); - self.transfer_token(&escrow, &dest, &token, amount); + self.transfer_token(&escrow, &dest, &token, amount)?; } else { // mint the token because the sender chain is the source let mint = Address::Internal(InternalAddress::IbcMint); - self.transfer_token(&mint, &dest, &token, amount); + self.transfer_token(&mint, &dest, &token, amount)?; } Ok(()) } /// Refund the specified token by unescrowing or minting fn refund_token( - &self, + &mut self, packet: &Packet, data: &FungibleTokenPacketData, - ) -> Result<()> { + ) -> std::result::Result<(), Self::Error> { let dest = Address::decode(data.sender.clone()).map_err(|e| { Error::ReceivingToken(format!( "Invalid sender address: sender {}, error {}", @@ -971,7 +1056,7 @@ pub trait IbcActions { if data.denomination.starts_with(&prefix) { // mint the token because the sender chain is the sink zone let mint = Address::Internal(InternalAddress::IbcMint); - self.transfer_token(&mint, &dest, &token, amount); + self.transfer_token(&mint, &dest, &token, amount)?; } else { // unescrow the token because the sender chain is the source zone let escrow = @@ -979,7 +1064,7 @@ pub trait IbcActions { packet.source_port.to_string(), packet.source_channel.to_string(), )); - self.transfer_token(&escrow, &dest, &token, amount); + self.transfer_token(&escrow, &dest, &token, amount)?; } Ok(()) } diff --git a/shared/src/ledger/ibc/vp/channel.rs b/shared/src/ledger/ibc/vp/channel.rs index 354899f31d..08ed322452 100644 --- a/shared/src/ledger/ibc/vp/channel.rs +++ b/shared/src/ledger/ibc/vp/channel.rs @@ -45,7 +45,7 @@ use crate::ibc::core::ics24_host::identifier::{ use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::proofs::Proofs; use crate::ibc::timestamp::Timestamp; -use crate::ledger::native_vp::Error as NativeVpError; +use crate::ledger::native_vp::{Error as NativeVpError, VpEnv}; use crate::ledger::parameters; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::tendermint::Time; @@ -490,7 +490,7 @@ where } fn get_sequence_pre(&self, key: &Key) -> Result { - match self.ctx.read_pre(key)? { + match self.ctx.read_bytes_pre(key)? { Some(value) => { // As ibc-go, u64 like a counter is encoded with big-endian let index: [u8; 8] = value.try_into().map_err(|_| { @@ -508,7 +508,7 @@ where } fn get_sequence(&self, key: &Key) -> Result { - match self.ctx.read_post(key)? { + match self.ctx.read_bytes_post(key)? { Some(value) => { // As ibc-go, u64 like a counter is encoded with big-endian let index: [u8; 8] = value.try_into().map_err(|_| { @@ -547,7 +547,7 @@ where port_channel_id: &PortChannelId, ) -> Result { let key = channel_key(port_channel_id); - match self.ctx.read_pre(&key) { + match self.ctx.read_bytes_pre(&key) { Ok(Some(value)) => ChannelEnd::decode_vec(&value).map_err(|e| { Error::InvalidChannel(format!( "Decoding the channel failed: Port/Channel {}, {}", @@ -594,7 +594,7 @@ where key: &(PortId, ChannelId, Sequence), ) -> Result { let key = commitment_key(&key.0, &key.1, key.2); - match self.ctx.read_pre(&key)? { + match self.ctx.read_bytes_pre(&key)? { Some(value) => String::decode(&value[..]).map_err(|e| { Error::InvalidPacketInfo(format!( "Decoding the prior commitment failed: {}", @@ -613,7 +613,7 @@ where client_id: &ClientId, ) -> Result { let key = client_update_timestamp_key(client_id); - match self.ctx.read_pre(&key)? { + match self.ctx.read_bytes_pre(&key)? { Some(value) => { let time = Time::decode_vec(&value).map_err(|_| { Error::InvalidTimestamp(format!( @@ -635,7 +635,7 @@ where client_id: &ClientId, ) -> Result { let key = client_update_height_key(client_id); - match self.ctx.read_pre(&key)? { + match self.ctx.read_bytes_pre(&key)? { Some(value) => Height::decode_vec(&value).map_err(|_| { Error::InvalidHeight(format!( "Height conversion failed: ID {}", @@ -671,7 +671,7 @@ where channel_id: port_channel_id.1.clone(), }; let key = channel_key(&port_channel_id); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => ChannelEnd::decode_vec(&value) .map_err(|_| Ics04Error::implementation_specific()), Ok(None) => Err(Ics04Error::channel_not_found( @@ -818,7 +818,7 @@ where key: &(PortId, ChannelId, Sequence), ) -> Ics04Result { let commitment_key = commitment_key(&key.0, &key.1, key.2); - match self.ctx.read_post(&commitment_key) { + match self.ctx.read_bytes_post(&commitment_key) { Ok(Some(value)) => String::decode(&value[..]) .map_err(|_| Ics04Error::implementation_specific()), Ok(None) => Err(Ics04Error::packet_commitment_not_found(key.2)), @@ -832,7 +832,7 @@ where ) -> Ics04Result { let receipt_key = receipt_key(&key.0, &key.1, key.2); let expect = PacketReceipt::default().as_bytes().to_vec(); - match self.ctx.read_post(&receipt_key) { + match self.ctx.read_bytes_post(&receipt_key) { Ok(Some(v)) if v == expect => Ok(Receipt::Ok), _ => Err(Ics04Error::packet_receipt_not_found(key.2)), } @@ -844,7 +844,7 @@ where key: &(PortId, ChannelId, Sequence), ) -> Ics04Result { let ack_key = ack_key(&key.0, &key.1, key.2); - match self.ctx.read_post(&ack_key) { + match self.ctx.read_bytes_post(&ack_key) { Ok(Some(_)) => Ok(PacketAck::default().to_string()), Ok(None) => Err(Ics04Error::packet_commitment_not_found(key.2)), Err(_) => Err(Ics04Error::implementation_specific()), @@ -881,7 +881,7 @@ where height: Height, ) -> Ics04Result { let key = client_update_timestamp_key(client_id); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => { let time = Time::decode_vec(&value) .map_err(|_| Ics04Error::implementation_specific())?; @@ -901,7 +901,7 @@ where height: Height, ) -> Ics04Result { let key = client_update_height_key(client_id); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => Height::decode_vec(&value) .map_err(|_| Ics04Error::implementation_specific()), Ok(None) => Err(Ics04Error::processed_height_not_found( diff --git a/shared/src/ledger/ibc/vp/client.rs b/shared/src/ledger/ibc/vp/client.rs index 4b89e1ce30..453006f356 100644 --- a/shared/src/ledger/ibc/vp/client.rs +++ b/shared/src/ledger/ibc/vp/client.rs @@ -31,6 +31,7 @@ use crate::ibc::core::ics04_channel::context::ChannelReader; use crate::ibc::core::ics23_commitment::commitment::CommitmentRoot; use crate::ibc::core::ics24_host::identifier::ClientId; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; +use crate::ledger::native_vp::VpEnv; use crate::ledger::storage::{self, StorageHasher}; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; @@ -378,7 +379,7 @@ where fn client_state_pre(&self, client_id: &ClientId) -> Result { let key = client_state_key(client_id); - match self.ctx.read_pre(&key) { + match self.ctx.read_bytes_pre(&key) { Ok(Some(value)) => { AnyClientState::decode_vec(&value).map_err(|e| { Error::InvalidClient(format!( @@ -410,7 +411,7 @@ where { fn client_type(&self, client_id: &ClientId) -> Ics02Result { let key = client_type_key(client_id); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => { let type_str = std::str::from_utf8(&value) .map_err(|_| Ics02Error::implementation_specific())?; @@ -427,7 +428,7 @@ where client_id: &ClientId, ) -> Ics02Result { let key = client_state_key(client_id); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => AnyClientState::decode_vec(&value) .map_err(|_| Ics02Error::implementation_specific()), Ok(None) => Err(Ics02Error::client_not_found(client_id.clone())), @@ -441,7 +442,7 @@ where height: Height, ) -> Ics02Result { let key = consensus_state_key(client_id, height); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => AnyConsensusState::decode_vec(&value) .map_err(|_| Ics02Error::implementation_specific()), Ok(None) => Err(Ics02Error::consensus_state_not_found( @@ -459,7 +460,7 @@ where height: Height, ) -> Ics02Result> { let key = consensus_state_key(client_id, height); - match self.ctx.read_pre(&key) { + match self.ctx.read_bytes_pre(&key) { Ok(Some(value)) => { let cs = AnyConsensusState::decode_vec(&value) .map_err(|_| Ics02Error::implementation_specific())?; diff --git a/shared/src/ledger/ibc/vp/connection.rs b/shared/src/ledger/ibc/vp/connection.rs index 2e721bed08..0130dd3b84 100644 --- a/shared/src/ledger/ibc/vp/connection.rs +++ b/shared/src/ledger/ibc/vp/connection.rs @@ -27,6 +27,7 @@ use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOp use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; use crate::ibc::core::ics24_host::identifier::{ClientId, ConnectionId}; +use crate::ledger::native_vp::VpEnv; use crate::ledger::storage::{self, StorageHasher}; use crate::tendermint_proto::Protobuf; use crate::types::ibc::data::{Error as IbcDataError, IbcMessage}; @@ -324,7 +325,7 @@ where conn_id: &ConnectionId, ) -> Result { let key = connection_key(conn_id); - match self.ctx.read_pre(&key) { + match self.ctx.read_bytes_pre(&key) { Ok(Some(value)) => ConnectionEnd::decode_vec(&value).map_err(|e| { Error::InvalidConnection(format!( "Decoding the connection failed: {}", @@ -356,7 +357,7 @@ where conn_id: &ConnectionId, ) -> Ics03Result { let key = connection_key(conn_id); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => ConnectionEnd::decode_vec(&value) .map_err(|_| Ics03Error::implementation_specific()), Ok(None) => Err(Ics03Error::connection_not_found(conn_id.clone())), diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index b6bd15e43b..059862144d 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -17,7 +17,7 @@ pub use token::{Error as IbcTokenError, IbcToken}; use super::storage::{client_id, ibc_prefix, is_client_counter_key, IbcPrefix}; use crate::ibc::core::ics02_client::context::ClientReader; use crate::ibc::events::IbcEvent; -use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::proto::SignedTxData; use crate::types::address::{Address, InternalAddress}; @@ -184,7 +184,7 @@ where } fn read_counter_pre(&self, key: &Key) -> Result { - match self.ctx.read_pre(key) { + match self.ctx.read_bytes_pre(key) { Ok(Some(value)) => { // As ibc-go, u64 like a counter is encoded with big-endian let counter: [u8; 8] = value.try_into().map_err(|_| { @@ -205,7 +205,7 @@ where } fn read_counter(&self, key: &Key) -> Result { - match self.ctx.read_post(key) { + match self.ctx.read_bytes_post(key) { Ok(Some(value)) => { // As ibc-go, u64 like a counter is encoded with big-endian let counter: [u8; 8] = value.try_into().map_err(|_| { @@ -375,6 +375,8 @@ mod tests { use crate::vm::wasm; use crate::types::storage::{BlockHash, BlockHeight}; + const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); + fn get_client_id() -> ClientId { ClientId::from_str("test_client").expect("Creating a client ID failed") } @@ -568,13 +570,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); - let mut keys_changed = BTreeSet::new(); let client_state_key = client_state_key(&get_client_id()); keys_changed.insert(client_state_key); let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; // this should return true because state has been stored @@ -598,13 +608,22 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); let client_state_key = client_state_key(&get_client_id()); keys_changed.insert(client_state_key); let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; // this should fail because no state is stored @@ -668,13 +687,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(client_state_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; // this should return true because state has been stored assert!( @@ -717,13 +744,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; // this should return true because state has been stored assert!( @@ -763,13 +798,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; // this should fail because no client exists let result = ibc @@ -835,13 +878,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; // this should return true because state has been stored assert!( @@ -913,13 +964,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -978,13 +1037,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(conn_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1029,13 +1096,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1099,13 +1174,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1177,13 +1260,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1250,13 +1341,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(channel_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1281,13 +1380,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(port_key(&get_port_id())); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1313,13 +1420,22 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); let cap_key = capability_key(index); keys_changed.insert(cap_key); let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( @@ -1390,13 +1506,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(seq_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1469,13 +1593,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(seq_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1553,13 +1685,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(seq_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1633,13 +1773,21 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); let mut keys_changed = BTreeSet::new(); keys_changed.insert(commitment_key); let verifiers = BTreeSet::new(); - + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( ibc.validate_tx( @@ -1717,12 +1865,20 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); - let mut keys_changed = BTreeSet::new(); keys_changed.insert(receipt_key); let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( @@ -1757,12 +1913,20 @@ mod tests { let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let ctx = Ctx::new(&storage, &write_log, &tx, gas_meter, vp_wasm_cache); - let mut keys_changed = BTreeSet::new(); keys_changed.insert(ack_key); let verifiers = BTreeSet::new(); + let ctx = Ctx::new( + &ADDRESS, + &storage, + &write_log, + &tx, + gas_meter, + &keys_changed, + &verifiers, + vp_wasm_cache, + ); let ibc = Ibc { ctx }; assert!( diff --git a/shared/src/ledger/ibc/vp/port.rs b/shared/src/ledger/ibc/vp/port.rs index 2819cdeab5..073f89147b 100644 --- a/shared/src/ledger/ibc/vp/port.rs +++ b/shared/src/ledger/ibc/vp/port.rs @@ -13,6 +13,7 @@ use crate::ibc::core::ics05_port::capabilities::{Capability, CapabilityName}; use crate::ibc::core::ics05_port::context::{CapabilityReader, PortReader}; use crate::ibc::core::ics05_port::error::Error as Ics05Error; use crate::ibc::core::ics24_host::identifier::PortId; +use crate::ledger::native_vp::VpEnv; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; @@ -122,7 +123,7 @@ where fn get_port_by_capability(&self, cap: &Capability) -> Result { let key = capability_key(cap.index()); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => { let id = std::str::from_utf8(&value).map_err(|e| { Error::InvalidPort(format!( @@ -161,7 +162,7 @@ where port_id: &PortId, ) -> Ics05Result<(Self::ModuleId, Capability)> { let key = port_key(port_id); - match self.ctx.read_post(&key) { + match self.ctx.read_bytes_post(&key) { Ok(Some(value)) => { let index: [u8; 8] = value .try_into() diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index f56382b360..06181bdd45 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -10,7 +10,7 @@ use crate::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::Msg use crate::ibc::core::ics04_channel::msgs::PacketMsg; use crate::ibc::core::ics04_channel::packet::Packet; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; -use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::proto::SignedTxData; use crate::types::address::{Address, Error as AddressError, InternalAddress}; @@ -136,9 +136,10 @@ where // sink zone let target = Address::Internal(InternalAddress::IbcBurn); let target_key = token::balance_key(&token, &target); - let post = - try_decode_token_amount(self.ctx.read_temp(&target_key)?)? - .unwrap_or_default(); + let post = try_decode_token_amount( + self.ctx.read_bytes_temp(&target_key)?, + )? + .unwrap_or_default(); // the previous balance of the burn address should be zero post.change() } else { @@ -149,11 +150,13 @@ where msg.source_channel.to_string(), )); let target_key = token::balance_key(&token, &target); - let pre = try_decode_token_amount(self.ctx.read_pre(&target_key)?)? - .unwrap_or_default(); - let post = - try_decode_token_amount(self.ctx.read_post(&target_key)?)? + let pre = + try_decode_token_amount(self.ctx.read_bytes_pre(&target_key)?)? .unwrap_or_default(); + let post = try_decode_token_amount( + self.ctx.read_bytes_post(&target_key)?, + )? + .unwrap_or_default(); post.change() - pre.change() }; @@ -189,19 +192,22 @@ where packet.destination_channel.to_string(), )); let source_key = token::balance_key(&token, &source); - let pre = try_decode_token_amount(self.ctx.read_pre(&source_key)?)? - .unwrap_or_default(); - let post = - try_decode_token_amount(self.ctx.read_post(&source_key)?)? + let pre = + try_decode_token_amount(self.ctx.read_bytes_pre(&source_key)?)? .unwrap_or_default(); + let post = try_decode_token_amount( + self.ctx.read_bytes_post(&source_key)?, + )? + .unwrap_or_default(); pre.change() - post.change() } else { // the sender is the source let source = Address::Internal(InternalAddress::IbcMint); let source_key = token::balance_key(&token, &source); - let post = - try_decode_token_amount(self.ctx.read_temp(&source_key)?)? - .unwrap_or_default(); + let post = try_decode_token_amount( + self.ctx.read_bytes_temp(&source_key)?, + )? + .unwrap_or_default(); // the previous balance of the mint address should be the maximum Amount::max().change() - post.change() }; @@ -235,9 +241,10 @@ where // sink zone: mint the token for the refund let source = Address::Internal(InternalAddress::IbcMint); let source_key = token::balance_key(&token, &source); - let post = - try_decode_token_amount(self.ctx.read_temp(&source_key)?)? - .unwrap_or_default(); + let post = try_decode_token_amount( + self.ctx.read_bytes_temp(&source_key)?, + )? + .unwrap_or_default(); // the previous balance of the mint address should be the maximum Amount::max().change() - post.change() } else { @@ -248,11 +255,13 @@ where packet.source_channel.to_string(), )); let source_key = token::balance_key(&token, &source); - let pre = try_decode_token_amount(self.ctx.read_pre(&source_key)?)? - .unwrap_or_default(); - let post = - try_decode_token_amount(self.ctx.read_post(&source_key)?)? + let pre = + try_decode_token_amount(self.ctx.read_bytes_pre(&source_key)?)? .unwrap_or_default(); + let post = try_decode_token_amount( + self.ctx.read_bytes_post(&source_key)?, + )? + .unwrap_or_default(); pre.change() - post.change() }; diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 26be440536..80572c1f57 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -27,7 +27,7 @@ use super::{ Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, }; use crate::ledger::governance::vp::is_proposal_accepted; -use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::pos::{ is_validator_address_raw_hash_key, is_validator_consensus_key_key, is_validator_state_key, @@ -137,18 +137,18 @@ where return Ok(false); } } else if is_validator_set_key(key) { - let pre = self.ctx.read_pre(key)?.and_then(|bytes| { + let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { ValidatorSets::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_post(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorSets::try_from_slice(&bytes[..]).ok() }); changes.push(ValidatorSet(Data { pre, post })); } else if let Some(validator) = is_validator_state_key(key) { - let pre = self.ctx.read_pre(key)?.and_then(|bytes| { + let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { ValidatorStates::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_post(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorStates::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -160,11 +160,11 @@ where { let pre = self .ctx - .read_pre(key)? + .read_bytes_pre(key)? .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); let post = self .ctx - .read_post(key)? + .read_bytes_post(key)? .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); changes.push(Validator { address: validator.clone(), @@ -172,10 +172,10 @@ where }); } else if let Some(validator) = is_validator_consensus_key_key(key) { - let pre = self.ctx.read_pre(key)?.and_then(|bytes| { + let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { ValidatorConsensusKeys::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_post(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorConsensusKeys::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -183,10 +183,10 @@ where update: ConsensusKey(Data { pre, post }), }); } else if let Some(validator) = is_validator_total_deltas_key(key) { - let pre = self.ctx.read_pre(key)?.and_then(|bytes| { + let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_post(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -194,10 +194,10 @@ where update: TotalDeltas(Data { pre, post }), }); } else if let Some(validator) = is_validator_voting_power_key(key) { - let pre = self.ctx.read_pre(key)?.and_then(|bytes| { + let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_post(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -209,11 +209,11 @@ where { let pre = self .ctx - .read_pre(key)? + .read_bytes_pre(key)? .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); let post = self .ctx - .read_post(key)? + .read_bytes_post(key)? .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); // Find the raw hashes of the addresses let pre = pre.map(|pre| { @@ -236,26 +236,26 @@ where if owner != &addr { continue; } - let pre = self.ctx.read_pre(key)?.and_then(|bytes| { + let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { token::Amount::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_post(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { token::Amount::try_from_slice(&bytes[..]).ok() }); changes.push(Balance(Data { pre, post })); } else if let Some(bond_id) = is_bond_key(key) { let pre = self .ctx - .read_pre(key)? + .read_bytes_pre(key)? .and_then(|bytes| Bonds::try_from_slice(&bytes[..]).ok()); let post = self .ctx - .read_post(key)? + .read_bytes_post(key)? .and_then(|bytes| Bonds::try_from_slice(&bytes[..]).ok()); // For bonds, we need to look-up slashes let slashes = self .ctx - .read_pre(&validator_slashes_key(&bond_id.validator))? + .read_bytes_pre(&validator_slashes_key(&bond_id.validator))? .and_then(|bytes| Slashes::try_from_slice(&bytes[..]).ok()) .unwrap_or_default(); changes.push(Bond { @@ -266,16 +266,18 @@ where } else if let Some(unbond_id) = is_unbond_key(key) { let pre = self .ctx - .read_pre(key)? + .read_bytes_pre(key)? .and_then(|bytes| Unbonds::try_from_slice(&bytes[..]).ok()); let post = self .ctx - .read_post(key)? + .read_bytes_post(key)? .and_then(|bytes| Unbonds::try_from_slice(&bytes[..]).ok()); // For unbonds, we need to look-up slashes let slashes = self .ctx - .read_pre(&validator_slashes_key(&unbond_id.validator))? + .read_bytes_pre(&validator_slashes_key( + &unbond_id.validator, + ))? .and_then(|bytes| Slashes::try_from_slice(&bytes[..]).ok()) .unwrap_or_default(); changes.push(Unbond { @@ -284,10 +286,10 @@ where slashes, }); } else if is_total_voting_power_key(key) { - let pre = self.ctx.read_pre(key)?.and_then(|bytes| { + let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { TotalVotingPowers::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_post(key)?.and_then(|bytes| { + let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { TotalVotingPowers::try_from_slice(&bytes[..]).ok() }); changes.push(TotalVotingPower(Data { pre, post })); @@ -301,7 +303,7 @@ where } } - let params = self.read_pos_params(); + let params = self.read_pos_params()?; let errors = validate(¶ms, changes, current_epoch); Ok(if errors.is_empty() { true @@ -322,6 +324,7 @@ where CA: 'static + WasmCacheAccess, { type Address = Address; + type Error = native_vp::Error; type PublicKey = key::common::PublicKey; type TokenAmount = token::Amount; type TokenChange = token::Change; @@ -332,88 +335,95 @@ where super::staking_token_address() } - fn read_pos_params(&self) -> PosParams { - let value = self.ctx.read_pre(¶ms_key()).unwrap().unwrap(); - decode(value).unwrap() + fn read_pos_params(&self) -> std::result::Result { + let value = self.ctx.read_bytes_pre(¶ms_key())?.unwrap(); + Ok(decode(value).unwrap()) } fn read_validator_staking_reward_address( &self, key: &Self::Address, - ) -> Option { + ) -> std::result::Result, Self::Error> { let value = self .ctx - .read_pre(&validator_staking_reward_address_key(key)) - .unwrap(); - value.map(|value| decode(value).unwrap()) + .read_bytes_pre(&validator_staking_reward_address_key(key))?; + Ok(value.map(|value| decode(value).unwrap())) } fn read_validator_consensus_key( &self, key: &Self::Address, - ) -> Option { - let value = self - .ctx - .read_pre(&validator_consensus_key_key(key)) - .unwrap(); - value.map(|value| decode(value).unwrap()) + ) -> std::result::Result, Self::Error> { + let value = + self.ctx.read_bytes_pre(&validator_consensus_key_key(key))?; + Ok(value.map(|value| decode(value).unwrap())) } fn read_validator_state( &self, key: &Self::Address, - ) -> Option { - let value = self.ctx.read_pre(&validator_state_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()) + ) -> std::result::Result, Self::Error> { + let value = self.ctx.read_bytes_pre(&validator_state_key(key))?; + Ok(value.map(|value| decode(value).unwrap())) } fn read_validator_total_deltas( &self, key: &Self::Address, - ) -> Option { + ) -> std::result::Result, Self::Error> { let value = - self.ctx.read_pre(&validator_total_deltas_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()) + self.ctx.read_bytes_pre(&validator_total_deltas_key(key))?; + Ok(value.map(|value| decode(value).unwrap())) } fn read_validator_voting_power( &self, key: &Self::Address, - ) -> Option { + ) -> std::result::Result, Self::Error> { let value = - self.ctx.read_pre(&validator_voting_power_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()) + self.ctx.read_bytes_pre(&validator_voting_power_key(key))?; + Ok(value.map(|value| decode(value).unwrap())) } - fn read_validator_slashes(&self, key: &Self::Address) -> Vec { - let value = self.ctx.read_pre(&validator_slashes_key(key)).unwrap(); - value + fn read_validator_slashes( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = self.ctx.read_bytes_pre(&validator_slashes_key(key))?; + Ok(value .map(|value| decode(value).unwrap()) - .unwrap_or_default() + .unwrap_or_default()) } - fn read_bond(&self, key: &BondId) -> Option { - let value = self.ctx.read_pre(&bond_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()) + fn read_bond( + &self, + key: &BondId, + ) -> std::result::Result, Self::Error> { + let value = self.ctx.read_bytes_pre(&bond_key(key))?; + Ok(value.map(|value| decode(value).unwrap())) } - fn read_unbond(&self, key: &BondId) -> Option { - let value = self.ctx.read_pre(&unbond_key(key)).unwrap(); - value.map(|value| decode(value).unwrap()) + fn read_unbond( + &self, + key: &BondId, + ) -> std::result::Result, Self::Error> { + let value = self.ctx.read_bytes_pre(&unbond_key(key))?; + Ok(value.map(|value| decode(value).unwrap())) } - fn read_validator_set(&self) -> ValidatorSets { - let value = self.ctx.read_pre(&validator_set_key()).unwrap().unwrap(); - decode(value).unwrap() + fn read_validator_set( + &self, + ) -> std::result::Result { + let value = self.ctx.read_bytes_pre(&validator_set_key())?.unwrap(); + Ok(decode(value).unwrap()) } - fn read_total_voting_power(&self) -> TotalVotingPowers { - let value = self - .ctx - .read_pre(&total_voting_power_key()) - .unwrap() - .unwrap(); - decode(value).unwrap() + fn read_total_voting_power( + &self, + ) -> std::result::Result { + let value = + self.ctx.read_bytes_pre(&total_voting_power_key())?.unwrap(); + Ok(decode(value).unwrap()) } } diff --git a/shared/src/ledger/treasury/mod.rs b/shared/src/ledger/treasury/mod.rs index 071019059b..97965282da 100644 --- a/shared/src/ledger/treasury/mod.rs +++ b/shared/src/ledger/treasury/mod.rs @@ -11,7 +11,7 @@ use thiserror::Error; use self::storage as treasury_storage; use super::governance::vp::is_proposal_accepted; -use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::types::address::{xan as nam, Address, InternalAddress}; use crate::types::storage::Key; @@ -80,63 +80,30 @@ where let is_max_funds_transfer_key = treasury_storage::get_max_transferable_fund_key(); let balance_key = token::balance_key(&nam(), &ADDRESS); - let max_transfer_amount = - self.ctx.read_pre(&is_max_funds_transfer_key); - let pre_balance = self.ctx.read_pre(&balance_key); - let post_balance = self.ctx.read_post(&balance_key); + let max_transfer_amount: std::result::Result< + Option, + _, + > = self.ctx.read_pre(&is_max_funds_transfer_key); + let pre_balance: std::result::Result< + Option, + _, + > = self.ctx.read_pre(&balance_key); + let post_balance: std::result::Result< + Option, + _, + > = self.ctx.read_post(&balance_key); if addr.ne(&ADDRESS) { return true; } match (max_transfer_amount, pre_balance, post_balance) { ( - Ok(max_transfer_amount), - Ok(pre_balance), - Ok(post_balance), + Ok(Some(max_transfer_amount)), + Ok(Some(pre_balance)), + Ok(Some(post_balance)), ) => { - match ( - max_transfer_amount, - pre_balance, - post_balance, - ) { - ( - Some(max_transfer_amount), - Some(pre_balance), - Some(post_balance), - ) => { - let max_transfer_amount = - token::Amount::try_from_slice( - &max_transfer_amount[..], - ) - .ok(); - let pre_balance = - token::Amount::try_from_slice( - &pre_balance[..], - ) - .ok(); - let post_balance = - token::Amount::try_from_slice( - &post_balance[..], - ) - .ok(); - match ( - max_transfer_amount, - pre_balance, - post_balance, - ) { - ( - Some(max_transfer_amount), - Some(pre_balance), - Some(post_balance), - ) => { - post_balance > pre_balance - || (pre_balance - post_balance - <= max_transfer_amount) - } - _ => false, - } - } - _ => false, - } + post_balance > pre_balance + || (pre_balance - post_balance + <= max_transfer_amount) } _ => false, } From e4b84a00452fd3534fcdaa7ac1eb7fcca4c6bab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 20 Jul 2022 21:01:42 +0200 Subject: [PATCH 215/373] VM: move vm_env sub-mod into tx/vp_prelude and add Tx/VpEnv and Ctx --- Cargo.lock | 10 +- tx_prelude/Cargo.toml | 4 + tx_prelude/src/error.rs | 103 +++++ tx_prelude/src/governance.rs | 78 ++++ tx_prelude/src/ibc.rs | 79 ++++ tx_prelude/src/intent.rs | 18 + tx_prelude/src/lib.rs | 293 +++++++++++++- tx_prelude/src/nft.rs | 89 +++++ tx_prelude/src/proof_of_stake.rs | 338 ++++++++++++++++ tx_prelude/src/token.rs | 53 +++ vm_env/Cargo.toml | 2 - vm_env/src/governance.rs | 81 ---- vm_env/src/ibc.rs | 50 --- vm_env/src/imports.rs | 665 ------------------------------- vm_env/src/intent.rs | 39 -- vm_env/src/key/ed25519.rs | 0 vm_env/src/key/mod.rs | 16 - vm_env/src/lib.rs | 262 +++++++++--- vm_env/src/nft.rs | 194 --------- vm_env/src/proof_of_stake.rs | 261 ------------ vm_env/src/token.rs | 113 ------ vp_prelude/Cargo.toml | 4 + vp_prelude/src/error.rs | 103 +++++ vp_prelude/src/intent.rs | 19 + vp_prelude/src/key.rs | 13 + vp_prelude/src/lib.rs | 284 ++++++++++++- vp_prelude/src/nft.rs | 116 ++++++ vp_prelude/src/token.rs | 61 +++ 28 files changed, 1869 insertions(+), 1479 deletions(-) create mode 100644 tx_prelude/src/error.rs create mode 100644 tx_prelude/src/governance.rs create mode 100644 tx_prelude/src/ibc.rs create mode 100644 tx_prelude/src/intent.rs create mode 100644 tx_prelude/src/nft.rs create mode 100644 tx_prelude/src/proof_of_stake.rs create mode 100644 tx_prelude/src/token.rs delete mode 100644 vm_env/src/governance.rs delete mode 100644 vm_env/src/ibc.rs delete mode 100644 vm_env/src/imports.rs delete mode 100644 vm_env/src/intent.rs delete mode 100644 vm_env/src/key/ed25519.rs delete mode 100644 vm_env/src/key/mod.rs delete mode 100644 vm_env/src/nft.rs delete mode 100644 vm_env/src/proof_of_stake.rs delete mode 100644 vm_env/src/token.rs create mode 100644 vp_prelude/src/error.rs create mode 100644 vp_prelude/src/intent.rs create mode 100644 vp_prelude/src/key.rs create mode 100644 vp_prelude/src/nft.rs create mode 100644 vp_prelude/src/token.rs diff --git a/Cargo.lock b/Cargo.lock index f9c1414272..76df02e9b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4036,8 +4036,12 @@ dependencies = [ name = "namada_tx_prelude" version = "0.7.0" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] @@ -4045,17 +4049,19 @@ name = "namada_vm_env" version = "0.7.0" dependencies = [ "borsh", - "hex", "namada", - "namada_macros", ] [[package]] name = "namada_vp_prelude" version = "0.7.0" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index a35324da05..e916a13e01 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -10,5 +10,9 @@ version = "0.7.0" default = [] [dependencies] +namada = {path = "../shared"} namada_vm_env = {path = "../vm_env"} +namada_macros = {path = "../macros"} +borsh = "0.9.0" sha2 = "0.10.1" +thiserror = "1.0.30" diff --git a/tx_prelude/src/error.rs b/tx_prelude/src/error.rs new file mode 100644 index 0000000000..b34d954166 --- /dev/null +++ b/tx_prelude/src/error.rs @@ -0,0 +1,103 @@ +//! Helpers for error handling in WASM +//! +//! This module is currently duplicated in tx_prelude and vp_prelude crates to +//! be able to implement `From` conversion on error types from other crates, +//! avoiding `error[E0117]: only traits defined in the current crate can be +//! implemented for arbitrary types` + +use thiserror::Error; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + SimpleMessage(&'static str), + #[error("{0}")] + Custom(CustomError), + #[error("{0}: {1}")] + CustomWithMessage(&'static str, CustomError), +} + +/// Result of transaction or VP. +pub type EnvResult = Result; + +pub trait ResultExt { + /// Replace a possible error with a static message in [`EnvResult`]. + fn err_msg(self, msg: &'static str) -> EnvResult; +} + +// This is separate from `ResultExt`, because the implementation requires +// different bounds for `T`. +pub trait ResultExt2 { + /// Convert a [`Result`] into [`EnvResult`]. + fn into_env_result(self) -> EnvResult; + + /// Add a static message to a possible error in [`EnvResult`]. + fn wrap_err(self, msg: &'static str) -> EnvResult; +} + +pub trait OptionExt { + /// Transforms the [`Option`] into a [`EnvResult`], mapping + /// [`Some(v)`] to [`Ok(v)`] and [`None`] to the given static error + /// message. + fn ok_or_err_msg(self, msg: &'static str) -> EnvResult; +} + +impl ResultExt for Result { + fn err_msg(self, msg: &'static str) -> EnvResult { + self.map_err(|_err| Error::new_const(msg)) + } +} + +impl ResultExt2 for Result +where + E: std::error::Error + Send + Sync + 'static, +{ + fn into_env_result(self) -> EnvResult { + self.map_err(Error::new) + } + + fn wrap_err(self, msg: &'static str) -> EnvResult { + self.map_err(|err| Error::wrap(msg, err)) + } +} + +impl OptionExt for Option { + fn ok_or_err_msg(self, msg: &'static str) -> EnvResult { + self.ok_or_else(|| Error::new_const(msg)) + } +} + +impl Error { + /// Create an [`enum@Error`] from a static message. + #[inline] + pub const fn new_const(msg: &'static str) -> Self { + Self::SimpleMessage(msg) + } + + /// Create an [`enum@Error`] from another [`std::error::Error`]. + pub fn new(error: E) -> Self + where + E: Into>, + { + Self::Custom(CustomError(error.into())) + } + + /// Wrap another [`std::error::Error`] with a static message. + pub fn wrap(msg: &'static str, error: E) -> Self + where + E: Into>, + { + Self::CustomWithMessage(msg, CustomError(error.into())) + } +} + +/// A custom error +#[derive(Debug)] +pub struct CustomError(Box); + +impl std::fmt::Display for CustomError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} diff --git a/tx_prelude/src/governance.rs b/tx_prelude/src/governance.rs new file mode 100644 index 0000000000..a0dfce2c77 --- /dev/null +++ b/tx_prelude/src/governance.rs @@ -0,0 +1,78 @@ +//! Governance + +use namada::ledger::governance::storage; +use namada::ledger::governance::vp::ADDRESS as governance_address; +use namada::types::address::xan as m1t; +use namada::types::token::Amount; +use namada::types::transaction::governance::{ + InitProposalData, VoteProposalData, +}; + +use super::*; +use crate::token::transfer; + +/// A proposal creation transaction. +pub fn init_proposal(ctx: &mut Ctx, data: InitProposalData) -> TxResult { + let counter_key = storage::get_counter_key(); + let proposal_id = if let Some(id) = data.id { + id + } else { + ctx.read(&counter_key)?.unwrap() + }; + + let content_key = storage::get_content_key(proposal_id); + ctx.write_bytes(&content_key, data.content)?; + + let author_key = storage::get_author_key(proposal_id); + ctx.write(&author_key, data.author.clone())?; + + let voting_start_epoch_key = + storage::get_voting_start_epoch_key(proposal_id); + ctx.write(&voting_start_epoch_key, data.voting_start_epoch)?; + + let voting_end_epoch_key = storage::get_voting_end_epoch_key(proposal_id); + ctx.write(&voting_end_epoch_key, data.voting_end_epoch)?; + + let grace_epoch_key = storage::get_grace_epoch_key(proposal_id); + ctx.write(&grace_epoch_key, data.grace_epoch)?; + + if let Some(proposal_code) = data.proposal_code { + let proposal_code_key = storage::get_proposal_code_key(proposal_id); + ctx.write_bytes(&proposal_code_key, proposal_code)?; + } + + ctx.write(&counter_key, proposal_id + 1)?; + + let min_proposal_funds_key = storage::get_min_proposal_fund_key(); + let min_proposal_funds: Amount = + ctx.read(&min_proposal_funds_key)?.unwrap(); + + let funds_key = storage::get_funds_key(proposal_id); + ctx.write(&funds_key, min_proposal_funds)?; + + // this key must always be written for each proposal + let committing_proposals_key = + storage::get_committing_proposals_key(proposal_id, data.grace_epoch.0); + ctx.write(&committing_proposals_key, ())?; + + transfer( + ctx, + &data.author, + &governance_address, + &m1t(), + min_proposal_funds, + ) +} + +/// A proposal vote transaction. +pub fn vote_proposal(ctx: &mut Ctx, data: VoteProposalData) -> TxResult { + for delegation in data.delegations { + let vote_key = storage::get_vote_proposal_key( + data.id, + data.voter.clone(), + delegation, + ); + ctx.write(&vote_key, data.vote.clone())?; + } + Ok(()) +} diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs new file mode 100644 index 0000000000..21be213ea3 --- /dev/null +++ b/tx_prelude/src/ibc.rs @@ -0,0 +1,79 @@ +//! IBC lower-level functions for transactions. + +pub use namada::ledger::ibc::handler::{Error, IbcActions, Result}; +use namada::ledger::tx_env::TxEnv; +use namada::types::address::Address; +pub use namada::types::ibc::IbcEvent; +use namada::types::storage::{BlockHeight, Key}; +use namada::types::time::Rfc3339String; +use namada::types::token::Amount; + +use crate::token::transfer; +use crate::Ctx; + +// This is needed to use `ibc::Handler::Error` with `IbcActions` below +impl From for crate::Error { + fn from(err: Error) -> Self { + crate::Error::new(err) + } +} + +impl IbcActions for Ctx { + type Error = crate::Error; + + fn read_ibc_data( + &self, + key: &Key, + ) -> std::result::Result>, Self::Error> { + let data = self.read_bytes(key)?; + Ok(data) + } + + fn write_ibc_data( + &mut self, + key: &Key, + data: impl AsRef<[u8]>, + ) -> std::result::Result<(), Self::Error> { + self.write_bytes(key, data)?; + Ok(()) + } + + fn delete_ibc_data( + &mut self, + key: &Key, + ) -> std::result::Result<(), Self::Error> { + self.delete(key)?; + Ok(()) + } + + fn emit_ibc_event( + &mut self, + event: IbcEvent, + ) -> std::result::Result<(), Self::Error> { + ::emit_ibc_event(self, &event)?; + Ok(()) + } + + fn transfer_token( + &mut self, + src: &Address, + dest: &Address, + token: &Address, + amount: Amount, + ) -> std::result::Result<(), Self::Error> { + transfer(self, src, dest, token, amount)?; + Ok(()) + } + + fn get_height(&self) -> std::result::Result { + let val = self.get_block_height()?; + Ok(val) + } + + fn get_header_time( + &self, + ) -> std::result::Result { + let val = self.get_block_time()?; + Ok(val) + } +} diff --git a/tx_prelude/src/intent.rs b/tx_prelude/src/intent.rs new file mode 100644 index 0000000000..7b6826342e --- /dev/null +++ b/tx_prelude/src/intent.rs @@ -0,0 +1,18 @@ +use std::collections::HashSet; + +use namada::proto::Signed; +use namada::types::intent; +pub use namada::types::intent::*; +use namada::types::key::*; + +use super::*; +pub fn invalidate_exchange( + ctx: &mut Ctx, + intent: &Signed, +) -> TxResult { + let key = intent::invalid_intent_key(&intent.data.addr); + let mut invalid_intent: HashSet = + ctx.read(&key)?.unwrap_or_default(); + invalid_intent.insert(intent.sig.clone()); + ctx.write(&key, &invalid_intent) +} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 315c68384e..5d0009b01b 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -6,7 +6,47 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -pub use namada_vm_env::tx_prelude::*; +mod error; +pub mod governance; +pub mod ibc; +pub mod intent; +pub mod nft; +pub mod proof_of_stake; +pub mod token; + +use core::slice; +use std::marker::PhantomData; + +pub use borsh::{BorshDeserialize, BorshSerialize}; +pub use error::*; +pub use namada::ledger::governance::storage as gov_storage; +pub use namada::ledger::parameters::storage as parameters_storage; +pub use namada::ledger::storage::types::encode; +pub use namada::ledger::treasury::storage as treasury_storage; +pub use namada::ledger::tx_env::TxEnv; +pub use namada::proto::{Signed, SignedTxData}; +pub use namada::types::address::Address; +use namada::types::chain::CHAIN_ID_LENGTH; +use namada::types::internal::HostEnvResult; +use namada::types::storage::{ + BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, +}; +use namada::types::time::Rfc3339String; +pub use namada::types::*; +pub use namada_macros::transaction; +use namada_vm_env::tx::*; +use namada_vm_env::{read_from_buffer, read_key_val_bytes_from_buffer}; + +pub use crate::ibc::IbcActions; +pub use crate::proof_of_stake::{PosRead, PosWrite}; + +/// Log a string. The message will be printed at the `tracing::Level::Info`. +pub fn log_string>(msg: T) { + let msg = msg.as_ref(); + unsafe { + anoma_tx_log_string(msg.as_ptr() as _, msg.len() as _); + } +} /// Log a string in a debug build. The message will be printed at the /// `tracing::Level::Info`. Any `debug_log!` statements are only enabled in @@ -19,3 +59,254 @@ macro_rules! debug_log { (if cfg!(debug_assertions) { log_string(format!($($arg)*)) }) }} } + +/// Execution context provides access to the host environment functions +pub struct Ctx(()); + +impl Ctx { + /// Create a host context. The context on WASM side is only provided by + /// the VM once its being executed (in here it's implicit). But + /// because we want to have interface identical with the native + /// VPs, in which the context is explicit, in here we're just + /// using an empty `Ctx` to "fake" it. + /// + /// # Safety + /// + /// When using `#[transaction]` macro from `anoma_macros`, + /// the constructor should not be called from transactions and validity + /// predicates implementation directly - they receive `&Self` as + /// an argument provided by the macro that wrap the low-level WASM + /// interface with Rust native types. + /// + /// Otherwise, this should only be called once to initialize this "fake" + /// context in order to benefit from type-safety of the host environment + /// methods implemented on the context. + #[allow(clippy::new_without_default)] + pub const unsafe fn new() -> Self { + Self(()) + } +} + +/// Transaction result +pub type TxResult = EnvResult<()>; + +#[derive(Debug)] +pub struct KeyValIterator(pub u64, pub PhantomData); + +impl TxEnv for Ctx { + type Error = Error; + type PrefixIter = KeyValIterator<(String, Vec)>; + + fn read( + &self, + key: &namada::types::storage::Key, + ) -> Result, Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_tx_result_buffer) + .and_then(|t| T::try_from_slice(&t[..]).ok())) + } + + fn read_bytes( + &self, + key: &namada::types::storage::Key, + ) -> Result>, Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_tx_result_buffer)) + } + + fn has_key( + &self, + key: &namada::types::storage::Key, + ) -> Result { + let key = key.to_string(); + let found = + unsafe { anoma_tx_has_key(key.as_ptr() as _, key.len() as _) }; + Ok(HostEnvResult::is_success(found)) + } + + fn get_chain_id(&self) -> Result { + let result = Vec::with_capacity(CHAIN_ID_LENGTH); + unsafe { + anoma_tx_get_chain_id(result.as_ptr() as _); + } + let slice = + unsafe { slice::from_raw_parts(result.as_ptr(), CHAIN_ID_LENGTH) }; + Ok(String::from_utf8(slice.to_vec()) + .expect("Cannot convert the ID string")) + } + + fn get_block_height( + &self, + ) -> Result { + Ok(BlockHeight(unsafe { anoma_tx_get_block_height() })) + } + + fn get_block_hash( + &self, + ) -> Result { + let result = Vec::with_capacity(BLOCK_HASH_LENGTH); + unsafe { + anoma_tx_get_block_hash(result.as_ptr() as _); + } + let slice = unsafe { + slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) + }; + Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) + } + + fn get_block_epoch(&self) -> Result { + Ok(Epoch(unsafe { anoma_tx_get_block_epoch() })) + } + + fn get_block_time(&self) -> Result { + let read_result = unsafe { anoma_tx_get_block_time() }; + let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) + .expect("The block time should exist"); + Ok(Rfc3339String( + String::try_from_slice(&time_value[..]) + .expect("The conversion shouldn't fail"), + )) + } + + fn iter_prefix( + &self, + prefix: &namada::types::storage::Key, + ) -> Result { + let prefix = prefix.to_string(); + let iter_id = unsafe { + anoma_tx_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, Error> { + let read_result = unsafe { anoma_tx_iter_next(iter.0) }; + Ok(read_key_val_bytes_from_buffer( + read_result, + anoma_tx_result_buffer, + )) + } + + fn write( + &mut self, + key: &namada::types::storage::Key, + val: T, + ) -> Result<(), Error> { + let buf = val.try_to_vec().unwrap(); + self.write_bytes(key, buf) + } + + fn write_bytes( + &mut self, + key: &namada::types::storage::Key, + val: impl AsRef<[u8]>, + ) -> Result<(), Error> { + let key = key.to_string(); + unsafe { + anoma_tx_write( + key.as_ptr() as _, + key.len() as _, + val.as_ref().as_ptr() as _, + val.as_ref().len() as _, + ) + }; + Ok(()) + } + + fn write_temp( + &mut self, + key: &namada::types::storage::Key, + val: T, + ) -> Result<(), Error> { + let buf = val.try_to_vec().unwrap(); + self.write_bytes_temp(key, buf) + } + + fn write_bytes_temp( + &mut self, + key: &namada::types::storage::Key, + val: impl AsRef<[u8]>, + ) -> Result<(), Error> { + let key = key.to_string(); + unsafe { + anoma_tx_write_temp( + key.as_ptr() as _, + key.len() as _, + val.as_ref().as_ptr() as _, + val.as_ref().len() as _, + ) + }; + Ok(()) + } + + fn delete( + &mut self, + key: &namada::types::storage::Key, + ) -> Result<(), Error> { + let key = key.to_string(); + unsafe { anoma_tx_delete(key.as_ptr() as _, key.len() as _) }; + Ok(()) + } + + fn insert_verifier(&mut self, addr: &Address) -> Result<(), Error> { + let addr = addr.encode(); + unsafe { anoma_tx_insert_verifier(addr.as_ptr() as _, addr.len() as _) } + Ok(()) + } + + fn init_account( + &mut self, + code: impl AsRef<[u8]>, + ) -> Result { + let code = code.as_ref(); + let result = Vec::with_capacity(address::ESTABLISHED_ADDRESS_BYTES_LEN); + unsafe { + anoma_tx_init_account( + code.as_ptr() as _, + code.len() as _, + result.as_ptr() as _, + ) + }; + let slice = unsafe { + slice::from_raw_parts( + result.as_ptr(), + address::ESTABLISHED_ADDRESS_BYTES_LEN, + ) + }; + Ok(Address::try_from_slice(slice) + .expect("Decoding address created by the ledger shouldn't fail")) + } + + fn update_validity_predicate( + &mut self, + addr: &Address, + code: impl AsRef<[u8]>, + ) -> Result<(), Error> { + let addr = addr.encode(); + let code = code.as_ref(); + unsafe { + anoma_tx_update_validity_predicate( + addr.as_ptr() as _, + addr.len() as _, + code.as_ptr() as _, + code.len() as _, + ) + }; + Ok(()) + } + + fn emit_ibc_event(&mut self, event: &ibc::IbcEvent) -> Result<(), Error> { + let event = BorshSerialize::try_to_vec(event).unwrap(); + unsafe { + anoma_tx_emit_ibc_event(event.as_ptr() as _, event.len() as _) + }; + Ok(()) + } +} diff --git a/tx_prelude/src/nft.rs b/tx_prelude/src/nft.rs new file mode 100644 index 0000000000..4ed179fe27 --- /dev/null +++ b/tx_prelude/src/nft.rs @@ -0,0 +1,89 @@ +use namada::types::address::Address; +use namada::types::nft; +use namada::types::nft::NftToken; +use namada::types::transaction::nft::{CreateNft, MintNft}; + +use super::*; + +/// Initialize a new NFT token address. +pub fn init_nft(ctx: &mut Ctx, nft: CreateNft) -> EnvResult
{ + let address = ctx.init_account(&nft.vp_code)?; + + // write tag + let tag_key = nft::get_tag_key(&address); + ctx.write(&tag_key, &nft.tag)?; + + // write creator + let creator_key = nft::get_creator_key(&address); + ctx.write(&creator_key, &nft.creator)?; + + // write keys + let keys_key = nft::get_keys_key(&address); + ctx.write(&keys_key, &nft.keys)?; + + // write optional keys + let optional_keys_key = nft::get_optional_keys_key(&address); + ctx.write(&optional_keys_key, nft.opt_keys)?; + + // mint tokens + aux_mint_token(ctx, &address, &nft.creator, nft.tokens, &nft.creator)?; + + ctx.insert_verifier(&nft.creator)?; + + Ok(address) +} + +pub fn mint_tokens(ctx: &mut Ctx, nft: MintNft) -> TxResult { + aux_mint_token(ctx, &nft.address, &nft.creator, nft.tokens, &nft.creator) +} + +fn aux_mint_token( + ctx: &mut Ctx, + nft_address: &Address, + creator_address: &Address, + tokens: Vec, + verifier: &Address, +) -> TxResult { + for token in tokens { + // write token metadata + let metadata_key = + nft::get_token_metadata_key(nft_address, &token.id.to_string()); + ctx.write(&metadata_key, &token.metadata)?; + + // write current owner token as creator + let current_owner_key = nft::get_token_current_owner_key( + nft_address, + &token.id.to_string(), + ); + ctx.write( + ¤t_owner_key, + &token + .current_owner + .unwrap_or_else(|| creator_address.clone()), + )?; + + // write value key + let value_key = + nft::get_token_value_key(nft_address, &token.id.to_string()); + ctx.write(&value_key, &token.values)?; + + // write optional value keys + let optional_value_key = nft::get_token_optional_value_key( + nft_address, + &token.id.to_string(), + ); + ctx.write(&optional_value_key, &token.opt_values)?; + + // write approval addresses + let approval_key = + nft::get_token_approval_key(nft_address, &token.id.to_string()); + ctx.write(&approval_key, &token.approvals)?; + + // write burnt propriety + let burnt_key = + nft::get_token_burnt_key(nft_address, &token.id.to_string()); + ctx.write(&burnt_key, token.burnt)?; + } + ctx.insert_verifier(verifier)?; + Ok(()) +} diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs new file mode 100644 index 0000000000..ce856cd876 --- /dev/null +++ b/tx_prelude/src/proof_of_stake.rs @@ -0,0 +1,338 @@ +//! Proof of Stake system integration with functions for transactions + +use namada::ledger::pos::types::Slash; +pub use namada::ledger::pos::*; +use namada::ledger::pos::{ + bond_key, namada_proof_of_stake, params_key, total_voting_power_key, + unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, + validator_set_key, validator_slashes_key, + validator_staking_reward_address_key, validator_state_key, + validator_total_deltas_key, validator_voting_power_key, +}; +use namada::types::address::{self, Address, InternalAddress}; +use namada::types::transaction::InitValidator; +use namada::types::{key, token}; +pub use namada_proof_of_stake::{ + epoched, parameters, types, PosActions as PosWrite, PosReadOnly as PosRead, +}; + +use super::*; + +impl Ctx { + /// Self-bond tokens to a validator when `source` is `None` or equal to + /// the `validator` address, or delegate tokens from the `source` to the + /// `validator`. + pub fn bond_tokens( + &mut self, + source: Option<&Address>, + validator: &Address, + amount: token::Amount, + ) -> TxResult { + let current_epoch = self.get_block_epoch()?; + namada_proof_of_stake::PosActions::bond_tokens( + self, + source, + validator, + amount, + current_epoch, + ) + } + + /// Unbond self-bonded tokens from a validator when `source` is `None` or + /// equal to the `validator` address, or unbond delegated tokens from + /// the `source` to the `validator`. + pub fn unbond_tokens( + &mut self, + source: Option<&Address>, + validator: &Address, + amount: token::Amount, + ) -> TxResult { + let current_epoch = self.get_block_epoch()?; + namada_proof_of_stake::PosActions::unbond_tokens( + self, + source, + validator, + amount, + current_epoch, + ) + } + + /// Withdraw unbonded tokens from a self-bond to a validator when `source` + /// is `None` or equal to the `validator` address, or withdraw unbonded + /// tokens delegated to the `validator` to the `source`. + pub fn withdraw_tokens( + &mut self, + source: Option<&Address>, + validator: &Address, + ) -> EnvResult { + let current_epoch = self.get_block_epoch()?; + namada_proof_of_stake::PosActions::withdraw_tokens( + self, + source, + validator, + current_epoch, + ) + } + + /// Attempt to initialize a validator account. On success, returns the + /// initialized validator account's address and its staking reward address. + pub fn init_validator( + &mut self, + InitValidator { + account_key, + consensus_key, + rewards_account_key, + protocol_key, + dkg_key, + validator_vp_code, + rewards_vp_code, + }: InitValidator, + ) -> EnvResult<(Address, Address)> { + let current_epoch = self.get_block_epoch()?; + // Init validator account + let validator_address = self.init_account(&validator_vp_code)?; + let pk_key = key::pk_key(&validator_address); + self.write(&pk_key, &account_key)?; + let protocol_pk_key = key::protocol_pk_key(&validator_address); + self.write(&protocol_pk_key, &protocol_key)?; + let dkg_pk_key = key::dkg_session_keys::dkg_pk_key(&validator_address); + self.write(&dkg_pk_key, &dkg_key)?; + + // Init staking reward account + let rewards_address = self.init_account(&rewards_vp_code)?; + let pk_key = key::pk_key(&rewards_address); + self.write(&pk_key, &rewards_account_key)?; + + self.become_validator( + &validator_address, + &rewards_address, + &consensus_key, + current_epoch, + )?; + + Ok((validator_address, rewards_address)) + } +} + +impl namada_proof_of_stake::PosReadOnly for Ctx { + type Address = Address; + type Error = crate::Error; + type PublicKey = key::common::PublicKey; + type TokenAmount = token::Amount; + type TokenChange = token::Change; + + const POS_ADDRESS: Self::Address = Address::Internal(InternalAddress::PoS); + + fn staking_token_address() -> Self::Address { + address::xan() + } + + fn read_pos_params(&self) -> Result { + let params = self.read(¶ms_key())?; + Ok(params.expect("PoS params should always be set")) + } + + fn read_validator_staking_reward_address( + &self, + key: &Self::Address, + ) -> Result, Self::Error> { + self.read(&validator_staking_reward_address_key(key)) + } + + fn read_validator_consensus_key( + &self, + key: &Self::Address, + ) -> Result, Self::Error> { + self.read(&validator_consensus_key_key(key)) + } + + fn read_validator_state( + &self, + key: &Self::Address, + ) -> Result, Self::Error> { + self.read(&validator_state_key(key)) + } + + fn read_validator_total_deltas( + &self, + key: &Self::Address, + ) -> Result, Self::Error> { + self.read(&validator_total_deltas_key(key)) + } + + fn read_validator_voting_power( + &self, + key: &Self::Address, + ) -> Result, Self::Error> { + self.read(&validator_voting_power_key(key)) + } + + fn read_validator_slashes( + &self, + key: &Self::Address, + ) -> Result, Self::Error> { + let val = self.read(&validator_slashes_key(key))?; + Ok(val.unwrap_or_default()) + } + + fn read_bond(&self, key: &BondId) -> Result, Self::Error> { + self.read(&bond_key(key)) + } + + fn read_unbond( + &self, + key: &BondId, + ) -> Result, Self::Error> { + self.read(&unbond_key(key)) + } + + fn read_validator_set(&self) -> Result { + let val = self.read(&validator_set_key())?; + Ok(val.expect("Validator sets must always have a value")) + } + + fn read_total_voting_power( + &self, + ) -> Result { + let val = self.read(&total_voting_power_key())?; + Ok(val.expect("Total voting power must always have a value")) + } +} + +impl From> for Error { + fn from(err: namada_proof_of_stake::BecomeValidatorError
) -> Self { + Self::new(err) + } +} + +impl From> for Error { + fn from(err: namada_proof_of_stake::BondError
) -> Self { + Self::new(err) + } +} + +impl From> + for Error +{ + fn from( + err: namada_proof_of_stake::UnbondError, + ) -> Self { + Self::new(err) + } +} + +impl From> for Error { + fn from(err: namada_proof_of_stake::WithdrawError
) -> Self { + Self::new(err) + } +} + +impl namada_proof_of_stake::PosActions for Ctx { + type BecomeValidatorError = crate::Error; + type BondError = crate::Error; + type UnbondError = crate::Error; + type WithdrawError = crate::Error; + + fn write_pos_params( + &mut self, + params: &PosParams, + ) -> Result<(), Self::Error> { + self.write(¶ms_key(), params) + } + + fn write_validator_address_raw_hash( + &mut self, + address: &Self::Address, + ) -> Result<(), Self::Error> { + let raw_hash = address.raw_hash().unwrap().to_owned(); + self.write(&validator_address_raw_hash_key(raw_hash), address) + } + + fn write_validator_staking_reward_address( + &mut self, + key: &Self::Address, + value: Self::Address, + ) -> Result<(), Self::Error> { + self.write(&validator_staking_reward_address_key(key), &value) + } + + fn write_validator_consensus_key( + &mut self, + key: &Self::Address, + value: ValidatorConsensusKeys, + ) -> Result<(), Self::Error> { + self.write(&validator_consensus_key_key(key), &value) + } + + fn write_validator_state( + &mut self, + key: &Self::Address, + value: ValidatorStates, + ) -> Result<(), Self::Error> { + self.write(&validator_state_key(key), &value) + } + + fn write_validator_total_deltas( + &mut self, + key: &Self::Address, + value: ValidatorTotalDeltas, + ) -> Result<(), Self::Error> { + self.write(&validator_total_deltas_key(key), &value) + } + + fn write_validator_voting_power( + &mut self, + key: &Self::Address, + value: ValidatorVotingPowers, + ) -> Result<(), Self::Error> { + self.write(&validator_voting_power_key(key), &value) + } + + fn write_bond( + &mut self, + key: &BondId, + value: Bonds, + ) -> Result<(), Self::Error> { + self.write(&bond_key(key), &value) + } + + fn write_unbond( + &mut self, + key: &BondId, + value: Unbonds, + ) -> Result<(), Self::Error> { + self.write(&unbond_key(key), &value) + } + + fn write_validator_set( + &mut self, + value: ValidatorSets, + ) -> Result<(), Self::Error> { + self.write(&validator_set_key(), &value) + } + + fn write_total_voting_power( + &mut self, + value: TotalVotingPowers, + ) -> Result<(), Self::Error> { + self.write(&total_voting_power_key(), &value) + } + + fn delete_bond(&mut self, key: &BondId) -> Result<(), Self::Error> { + self.delete(&bond_key(key)) + } + + fn delete_unbond(&mut self, key: &BondId) -> Result<(), Self::Error> { + self.delete(&unbond_key(key)) + } + + fn transfer( + &mut self, + token: &Self::Address, + amount: Self::TokenAmount, + src: &Self::Address, + dest: &Self::Address, + ) -> Result<(), Self::Error> { + crate::token::transfer(self, src, dest, token, amount) + } +} diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs new file mode 100644 index 0000000000..2fa86efd45 --- /dev/null +++ b/tx_prelude/src/token.rs @@ -0,0 +1,53 @@ +use namada::types::address::{Address, InternalAddress}; +use namada::types::token; +pub use namada::types::token::*; + +use super::*; + +/// A token transfer that can be used in a transaction. +pub fn transfer( + ctx: &mut Ctx, + src: &Address, + dest: &Address, + token: &Address, + amount: Amount, +) -> TxResult { + let src_key = token::balance_key(token, src); + let dest_key = token::balance_key(token, dest); + let src_bal: Option = ctx.read(&src_key)?; + let mut src_bal = src_bal.unwrap_or_else(|| match src { + Address::Internal(InternalAddress::IbcMint) => Amount::max(), + _ => { + log_string(format!("src {} has no balance", src)); + unreachable!() + } + }); + src_bal.spend(&amount); + let mut dest_bal: Amount = ctx.read(&dest_key)?.unwrap_or_default(); + dest_bal.receive(&amount); + match src { + Address::Internal(InternalAddress::IbcMint) => { + ctx.write_temp(&src_key, src_bal)?; + } + Address::Internal(InternalAddress::IbcBurn) => { + log_string("invalid transfer from the burn address"); + unreachable!() + } + _ => { + ctx.write(&src_key, src_bal)?; + } + } + match dest { + Address::Internal(InternalAddress::IbcMint) => { + log_string("invalid transfer to the mint address"); + unreachable!() + } + Address::Internal(InternalAddress::IbcBurn) => { + ctx.write_temp(&dest_key, dest_bal)?; + } + _ => { + ctx.write(&dest_key, dest_bal)?; + } + } + Ok(()) +} diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index f11d57d1b0..06a9e210ca 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -11,6 +11,4 @@ default = [] [dependencies] namada = {path = "../shared"} -namada_macros = {path = "../macros"} borsh = "0.9.0" -hex = "0.4.3" diff --git a/vm_env/src/governance.rs b/vm_env/src/governance.rs deleted file mode 100644 index db4ea7916f..0000000000 --- a/vm_env/src/governance.rs +++ /dev/null @@ -1,81 +0,0 @@ -/// Tx imports and functions. -pub mod tx { - - use namada::ledger::governance::storage; - use namada::ledger::governance::vp::ADDRESS as governance_address; - use namada::types::address::xan as m1t; - use namada::types::token::Amount; - use namada::types::transaction::governance::{ - InitProposalData, VoteProposalData, - }; - - use crate::imports::tx; - use crate::token::tx::transfer; - - /// A proposal creation transaction. - pub fn init_proposal(data: InitProposalData) { - let counter_key = storage::get_counter_key(); - let proposal_id = if let Some(id) = data.id { - id - } else { - tx::read(&counter_key.to_string()).unwrap() - }; - - let content_key = storage::get_content_key(proposal_id); - tx::write_bytes(&content_key.to_string(), data.content); - - let author_key = storage::get_author_key(proposal_id); - tx::write(&author_key.to_string(), data.author.clone()); - - let voting_start_epoch_key = - storage::get_voting_start_epoch_key(proposal_id); - tx::write(&voting_start_epoch_key.to_string(), data.voting_start_epoch); - - let voting_end_epoch_key = - storage::get_voting_end_epoch_key(proposal_id); - tx::write(&voting_end_epoch_key.to_string(), data.voting_end_epoch); - - let grace_epoch_key = storage::get_grace_epoch_key(proposal_id); - tx::write(&grace_epoch_key.to_string(), data.grace_epoch); - - if let Some(proposal_code) = data.proposal_code { - let proposal_code_key = storage::get_proposal_code_key(proposal_id); - tx::write_bytes(&proposal_code_key.to_string(), proposal_code); - } - - tx::write(&counter_key.to_string(), proposal_id + 1); - - let min_proposal_funds_key = storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = - tx::read(&min_proposal_funds_key.to_string()).unwrap(); - - let funds_key = storage::get_funds_key(proposal_id); - tx::write(&funds_key.to_string(), min_proposal_funds); - - // this key must always be written for each proposal - let committing_proposals_key = storage::get_committing_proposals_key( - proposal_id, - data.grace_epoch.0, - ); - tx::write(&committing_proposals_key.to_string(), ()); - - transfer( - &data.author, - &governance_address, - &m1t(), - min_proposal_funds, - ); - } - - /// A proposal vote transaction. - pub fn vote_proposal(data: VoteProposalData) { - for delegation in data.delegations { - let vote_key = storage::get_vote_proposal_key( - data.id, - data.voter.clone(), - delegation, - ); - tx::write(&vote_key.to_string(), data.vote.clone()); - } - } -} diff --git a/vm_env/src/ibc.rs b/vm_env/src/ibc.rs deleted file mode 100644 index febaa78560..0000000000 --- a/vm_env/src/ibc.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! IBC functions for transactions. - -pub use namada::ledger::ibc::handler::IbcActions; -use namada::types::address::Address; -use namada::types::ibc::IbcEvent; -use namada::types::storage::{BlockHeight, Key}; -use namada::types::time::Rfc3339String; -use namada::types::token::Amount; - -use crate::imports::tx; -use crate::token::tx::transfer; - -/// This struct integrates and gives access to lower-level IBC functions. -pub struct Ibc; - -impl IbcActions for Ibc { - fn read_ibc_data(&self, key: &Key) -> Option> { - tx::read_bytes(key.to_string()) - } - - fn write_ibc_data(&self, key: &Key, data: impl AsRef<[u8]>) { - tx::write_bytes(key.to_string(), data) - } - - fn delete_ibc_data(&self, key: &Key) { - tx::delete(key.to_string()) - } - - fn emit_ibc_event(&self, event: IbcEvent) { - tx::emit_ibc_event(&event) - } - - fn transfer_token( - &self, - src: &Address, - dest: &Address, - token: &Address, - amount: Amount, - ) { - transfer(src, dest, token, amount) - } - - fn get_height(&self) -> BlockHeight { - tx::get_block_height() - } - - fn get_header_time(&self) -> Rfc3339String { - tx::get_block_time() - } -} diff --git a/vm_env/src/imports.rs b/vm_env/src/imports.rs deleted file mode 100644 index 2eabe77e54..0000000000 --- a/vm_env/src/imports.rs +++ /dev/null @@ -1,665 +0,0 @@ -use std::mem::ManuallyDrop; - -use borsh::BorshDeserialize; -use namada::types::internal::HostEnvResult; -use namada::vm::types::KeyVal; - -/// This function is a helper to handle the second step of reading var-len -/// values from the host. -/// -/// In cases where we're reading a value from the host in the guest and -/// we don't know the byte size up-front, we have to read it in 2-steps. The -/// first step reads the value into a result buffer and returns the size (if -/// any) back to the guest, the second step reads the value from cache into a -/// pre-allocated buffer with the obtained size. -fn read_from_buffer( - read_result: i64, - result_buffer: unsafe extern "C" fn(u64), -) -> Option> { - if HostEnvResult::is_fail(read_result) { - None - } else { - let result: Vec = Vec::with_capacity(read_result as _); - // The `result` will be dropped from the `target`, which is - // reconstructed from the same memory - let result = ManuallyDrop::new(result); - let offset = result.as_slice().as_ptr() as u64; - unsafe { result_buffer(offset) }; - let target = unsafe { - Vec::from_raw_parts(offset as _, read_result as _, read_result as _) - }; - Some(target) - } -} - -/// This function is a helper to handle the second step of reading var-len -/// values in a key-value pair from the host. -fn read_key_val_from_buffer( - read_result: i64, - result_buffer: unsafe extern "C" fn(u64), -) -> Option<(String, T)> { - let key_val = read_from_buffer(read_result, result_buffer) - .and_then(|t| KeyVal::try_from_slice(&t[..]).ok()); - key_val.and_then(|key_val| { - // decode the value - T::try_from_slice(&key_val.val) - .map(|val| (key_val.key, val)) - .ok() - }) -} - -/// Transaction environment imports -pub mod tx { - use core::slice; - use std::convert::TryFrom; - use std::marker::PhantomData; - - pub use borsh::{BorshDeserialize, BorshSerialize}; - use namada::types::address; - use namada::types::address::Address; - use namada::types::chain::CHAIN_ID_LENGTH; - use namada::types::ibc::IbcEvent; - use namada::types::internal::HostEnvResult; - use namada::types::storage::{ - BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, - }; - use namada::types::time::Rfc3339String; - - #[derive(Debug)] - pub struct KeyValIterator(pub u64, pub PhantomData); - - /// Try to read a Borsh encoded variable-length value at the given key from - /// storage. - pub fn read(key: impl AsRef) -> Option { - let key = key.as_ref(); - let read_result = - unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; - super::read_from_buffer(read_result, anoma_tx_result_buffer) - .and_then(|t| T::try_from_slice(&t[..]).ok()) - } - - /// Try to read a variable-length value as bytes at the given key from - /// storage. - pub fn read_bytes(key: impl AsRef) -> Option> { - let key = key.as_ref(); - let read_result = - unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; - super::read_from_buffer(read_result, anoma_tx_result_buffer) - } - - /// Check if the given key is present in storage. - pub fn has_key(key: impl AsRef) -> bool { - let key = key.as_ref(); - let found = - unsafe { anoma_tx_has_key(key.as_ptr() as _, key.len() as _) }; - HostEnvResult::is_success(found) - } - - /// Write a value to be encoded with Borsh at the given key to storage. - pub fn write(key: impl AsRef, val: T) { - let buf = val.try_to_vec().unwrap(); - write_bytes(key, buf); - } - - /// Write a value as bytes at the given key to storage. - pub fn write_bytes(key: impl AsRef, val: impl AsRef<[u8]>) { - let key = key.as_ref(); - unsafe { - anoma_tx_write( - key.as_ptr() as _, - key.len() as _, - val.as_ref().as_ptr() as _, - val.as_ref().len() as _, - ) - }; - } - - /// Write a temporary value to be encoded with Borsh at the given key to - /// storage. - pub fn write_temp(key: impl AsRef, val: T) { - let buf = val.try_to_vec().unwrap(); - write_bytes_temp(key, buf); - } - - /// Write a temporary value as bytes at the given key to storage. - pub fn write_bytes_temp(key: impl AsRef, val: impl AsRef<[u8]>) { - let key = key.as_ref(); - unsafe { - anoma_tx_write_temp( - key.as_ptr() as _, - key.len() as _, - val.as_ref().as_ptr() as _, - val.as_ref().len() as _, - ) - }; - } - - /// Delete a value at the given key from storage. - pub fn delete(key: impl AsRef) { - let key = key.as_ref(); - unsafe { anoma_tx_delete(key.as_ptr() as _, key.len() as _) }; - } - - /// Get an iterator with the given prefix. - /// - /// Important note: The prefix iterator will ignore keys that are not yet - /// committed to storage from the block in which this transaction is being - /// applied. It will only find keys that are already committed to - /// storage (i.e. from predecessor blocks). However, it will provide the - /// most up-to-date value for such keys. - pub fn iter_prefix( - prefix: impl AsRef, - ) -> KeyValIterator { - let prefix = prefix.as_ref(); - let iter_id = unsafe { - anoma_tx_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) - }; - KeyValIterator(iter_id, PhantomData) - } - - impl Iterator for KeyValIterator { - type Item = (String, T); - - fn next(&mut self) -> Option<(String, T)> { - let read_result = unsafe { anoma_tx_iter_next(self.0) }; - super::read_key_val_from_buffer(read_result, anoma_tx_result_buffer) - } - } - - /// Insert a verifier address. This address must exist on chain, otherwise - /// the transaction will be rejected. - /// - /// Validity predicates of each verifier addresses inserted in the - /// transaction will validate the transaction and will receive all the - /// changed storage keys and initialized accounts in their inputs. - pub fn insert_verifier(addr: &Address) { - let addr = addr.encode(); - unsafe { anoma_tx_insert_verifier(addr.as_ptr() as _, addr.len() as _) } - } - - /// Update a validity predicate - pub fn update_validity_predicate(addr: &Address, code: impl AsRef<[u8]>) { - let addr = addr.encode(); - let code = code.as_ref(); - unsafe { - anoma_tx_update_validity_predicate( - addr.as_ptr() as _, - addr.len() as _, - code.as_ptr() as _, - code.len() as _, - ) - }; - } - - // Initialize a new account - pub fn init_account(code: impl AsRef<[u8]>) -> Address { - let code = code.as_ref(); - let result = Vec::with_capacity(address::ESTABLISHED_ADDRESS_BYTES_LEN); - unsafe { - anoma_tx_init_account( - code.as_ptr() as _, - code.len() as _, - result.as_ptr() as _, - ) - }; - let slice = unsafe { - slice::from_raw_parts( - result.as_ptr(), - address::ESTABLISHED_ADDRESS_BYTES_LEN, - ) - }; - Address::try_from_slice(slice) - .expect("Decoding address created by the ledger shouldn't fail") - } - - /// Emit an IBC event. There can be only one event per transaction. On - /// multiple calls, only the last emitted event will be used. - pub fn emit_ibc_event(event: &IbcEvent) { - let event = BorshSerialize::try_to_vec(event).unwrap(); - unsafe { - anoma_tx_emit_ibc_event(event.as_ptr() as _, event.len() as _) - }; - } - - /// Get the chain ID - pub fn get_chain_id() -> String { - let result = Vec::with_capacity(CHAIN_ID_LENGTH); - unsafe { - anoma_tx_get_chain_id(result.as_ptr() as _); - } - let slice = - unsafe { slice::from_raw_parts(result.as_ptr(), CHAIN_ID_LENGTH) }; - String::from_utf8(slice.to_vec()).expect("Cannot convert the ID string") - } - - /// Get height of the current block - pub fn get_block_height() -> BlockHeight { - BlockHeight(unsafe { anoma_tx_get_block_height() }) - } - - /// Get time of the current block header as rfc 3339 string - pub fn get_block_time() -> Rfc3339String { - let read_result = unsafe { anoma_tx_get_block_time() }; - let time_value = - super::read_from_buffer(read_result, anoma_tx_result_buffer) - .expect("The block time should exist"); - Rfc3339String( - String::try_from_slice(&time_value[..]) - .expect("The conversion shouldn't fail"), - ) - } - - /// Get hash of the current block - pub fn get_block_hash() -> BlockHash { - let result = Vec::with_capacity(BLOCK_HASH_LENGTH); - unsafe { - anoma_tx_get_block_hash(result.as_ptr() as _); - } - let slice = unsafe { - slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) - }; - BlockHash::try_from(slice).expect("Cannot convert the hash") - } - - /// Get epoch of the current block - pub fn get_block_epoch() -> Epoch { - Epoch(unsafe { anoma_tx_get_block_epoch() }) - } - - /// Log a string. The message will be printed at the `tracing::Level::Info`. - pub fn log_string>(msg: T) { - let msg = msg.as_ref(); - unsafe { - anoma_tx_log_string(msg.as_ptr() as _, msg.len() as _); - } - } - - // These host functions are implemented in the Anoma's [`host_env`] - // module. The environment provides calls to them via this C interface. - extern "C" { - // Read variable-length data when we don't know the size up-front, - // returns the size of the value (can be 0), or -1 if the key is - // not present. If a value is found, it will be placed in the read - // cache, because we cannot allocate a buffer for it before we know - // its size. - fn anoma_tx_read(key_ptr: u64, key_len: u64) -> i64; - - // Read a value from result buffer. - fn anoma_tx_result_buffer(result_ptr: u64); - - // Returns 1 if the key is present, -1 otherwise. - fn anoma_tx_has_key(key_ptr: u64, key_len: u64) -> i64; - - // Write key/value - fn anoma_tx_write( - key_ptr: u64, - key_len: u64, - val_ptr: u64, - val_len: u64, - ); - - // Write a temporary key/value - fn anoma_tx_write_temp( - key_ptr: u64, - key_len: u64, - val_ptr: u64, - val_len: u64, - ); - - // Delete the given key and its value - fn anoma_tx_delete(key_ptr: u64, key_len: u64); - - // Get an ID of a data iterator with key prefix - fn anoma_tx_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; - - // Returns the size of the value (can be 0), or -1 if there's no next - // value. If a value is found, it will be placed in the read - // cache, because we cannot allocate a buffer for it before we know - // its size. - fn anoma_tx_iter_next(iter_id: u64) -> i64; - - // Insert a verifier - fn anoma_tx_insert_verifier(addr_ptr: u64, addr_len: u64); - - // Update a validity predicate - fn anoma_tx_update_validity_predicate( - addr_ptr: u64, - addr_len: u64, - code_ptr: u64, - code_len: u64, - ); - - // Initialize a new account - fn anoma_tx_init_account(code_ptr: u64, code_len: u64, result_ptr: u64); - - // Emit an IBC event - fn anoma_tx_emit_ibc_event(event_ptr: u64, event_len: u64); - - // Get the chain ID - fn anoma_tx_get_chain_id(result_ptr: u64); - - // Get the current block height - fn anoma_tx_get_block_height() -> u64; - - // Get the time of the current block header - fn anoma_tx_get_block_time() -> i64; - - // Get the current block hash - fn anoma_tx_get_block_hash(result_ptr: u64); - - // Get the current block epoch - fn anoma_tx_get_block_epoch() -> u64; - - // Requires a node running with "Info" log level - fn anoma_tx_log_string(str_ptr: u64, str_len: u64); - } -} - -/// Validity predicate environment imports -pub mod vp { - use core::slice; - use std::convert::TryFrom; - use std::marker::PhantomData; - - pub use borsh::{BorshDeserialize, BorshSerialize}; - use namada::types::chain::CHAIN_ID_LENGTH; - use namada::types::hash::{Hash, HASH_LENGTH}; - use namada::types::internal::HostEnvResult; - use namada::types::key::*; - use namada::types::storage::{ - BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, - }; - - pub struct PreKeyValIterator(pub u64, pub PhantomData); - - pub struct PostKeyValIterator(pub u64, pub PhantomData); - - /// Try to read a Borsh encoded variable-length value at the given key from - /// storage before transaction execution. - pub fn read_pre(key: impl AsRef) -> Option { - let key = key.as_ref(); - let read_result = - unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; - super::read_from_buffer(read_result, anoma_vp_result_buffer) - .and_then(|t| T::try_from_slice(&t[..]).ok()) - } - - /// Try to read a variable-length value as bytesat the given key from - /// storage before transaction execution. - pub fn read_bytes_pre(key: impl AsRef) -> Option> { - let key = key.as_ref(); - let read_result = - unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; - super::read_from_buffer(read_result, anoma_vp_result_buffer) - } - - /// Try to read a Borsh encoded variable-length value at the given key from - /// storage after transaction execution. - pub fn read_post(key: impl AsRef) -> Option { - let key = key.as_ref(); - let read_result = - unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; - super::read_from_buffer(read_result, anoma_vp_result_buffer) - .and_then(|t| T::try_from_slice(&t[..]).ok()) - } - - /// Try to read a variable-length value as bytes at the given key from - /// storage after transaction execution. - pub fn read_bytes_post(key: impl AsRef) -> Option> { - let key = key.as_ref(); - let read_result = - unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; - super::read_from_buffer(read_result, anoma_vp_result_buffer) - } - - /// Try to read a Borsh encoded variable-length value at the given key from - /// storage before transaction execution. - pub fn read_temp(key: impl AsRef) -> Option { - let key = key.as_ref(); - let read_result = - unsafe { anoma_vp_read_temp(key.as_ptr() as _, key.len() as _) }; - super::read_from_buffer(read_result, anoma_vp_result_buffer) - .and_then(|t| T::try_from_slice(&t[..]).ok()) - } - - /// Try to read a variable-length value as bytes at the given key from - /// storage before transaction execution. - pub fn read_bytes_temp(key: impl AsRef) -> Option> { - let key = key.as_ref(); - let read_result = - unsafe { anoma_vp_read_temp(key.as_ptr() as _, key.len() as _) }; - super::read_from_buffer(read_result, anoma_vp_result_buffer) - } - - /// Check if the given key was present in storage before transaction - /// execution. - pub fn has_key_pre(key: impl AsRef) -> bool { - let key = key.as_ref(); - let found = - unsafe { anoma_vp_has_key_pre(key.as_ptr() as _, key.len() as _) }; - HostEnvResult::is_success(found) - } - - /// Check if the given key is present in storage after transaction - /// execution. - pub fn has_key_post(key: impl AsRef) -> bool { - let key = key.as_ref(); - let found = - unsafe { anoma_vp_has_key_post(key.as_ptr() as _, key.len() as _) }; - HostEnvResult::is_success(found) - } - - /// Get an iterator with the given prefix before transaction execution - pub fn iter_prefix_pre( - prefix: impl AsRef, - ) -> PreKeyValIterator { - let prefix = prefix.as_ref(); - let iter_id = unsafe { - anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) - }; - PreKeyValIterator(iter_id, PhantomData) - } - - impl Iterator for PreKeyValIterator { - type Item = (String, T); - - fn next(&mut self) -> Option<(String, T)> { - let read_result = unsafe { anoma_vp_iter_pre_next(self.0) }; - super::read_key_val_from_buffer(read_result, anoma_vp_result_buffer) - } - } - - /// Get an iterator with the given prefix after transaction execution - pub fn iter_prefix_post( - prefix: impl AsRef, - ) -> PostKeyValIterator { - let prefix = prefix.as_ref(); - let iter_id = unsafe { - anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) - }; - PostKeyValIterator(iter_id, PhantomData) - } - - impl Iterator for PostKeyValIterator { - type Item = (String, T); - - fn next(&mut self) -> Option<(String, T)> { - let read_result = unsafe { anoma_vp_iter_post_next(self.0) }; - super::read_key_val_from_buffer(read_result, anoma_vp_result_buffer) - } - } - - /// Get the chain ID - pub fn get_chain_id() -> String { - let result = Vec::with_capacity(CHAIN_ID_LENGTH); - unsafe { - anoma_vp_get_chain_id(result.as_ptr() as _); - } - let slice = - unsafe { slice::from_raw_parts(result.as_ptr(), CHAIN_ID_LENGTH) }; - String::from_utf8(slice.to_vec()).expect("Cannot convert the ID string") - } - - /// Get height of the current block - pub fn get_block_height() -> BlockHeight { - BlockHeight(unsafe { anoma_vp_get_block_height() }) - } - - /// Get a block hash - pub fn get_block_hash() -> BlockHash { - let result = Vec::with_capacity(BLOCK_HASH_LENGTH); - unsafe { - anoma_vp_get_block_hash(result.as_ptr() as _); - } - let slice = unsafe { - slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) - }; - BlockHash::try_from(slice).expect("Cannot convert the hash") - } - - /// Get a tx hash - pub fn get_tx_code_hash() -> Hash { - let result = Vec::with_capacity(HASH_LENGTH); - unsafe { - anoma_vp_get_tx_code_hash(result.as_ptr() as _); - } - let slice = - unsafe { slice::from_raw_parts(result.as_ptr(), HASH_LENGTH) }; - Hash::try_from(slice).expect("Cannot convert the hash") - } - - /// Get epoch of the current block - pub fn get_block_epoch() -> Epoch { - Epoch(unsafe { anoma_vp_get_block_epoch() }) - } - - /// Verify a transaction signature. The signature is expected to have been - /// produced on the encoded transaction [`namada::proto::Tx`] - /// using [`namada::proto::Tx::sign`]. - pub fn verify_tx_signature( - pk: &common::PublicKey, - sig: &common::Signature, - ) -> bool { - let pk = BorshSerialize::try_to_vec(pk).unwrap(); - let sig = BorshSerialize::try_to_vec(sig).unwrap(); - let valid = unsafe { - anoma_vp_verify_tx_signature( - pk.as_ptr() as _, - pk.len() as _, - sig.as_ptr() as _, - sig.len() as _, - ) - }; - HostEnvResult::is_success(valid) - } - - /// Log a string. The message will be printed at the `tracing::Level::Info`. - pub fn log_string>(msg: T) { - let msg = msg.as_ref(); - unsafe { - anoma_vp_log_string(msg.as_ptr() as _, msg.len() as _); - } - } - - /// Evaluate a validity predicate with given data. The address, changed - /// storage keys and verifiers will have the same values as the input to - /// caller's validity predicate. - /// - /// If the execution fails for whatever reason, this will return `false`. - /// Otherwise returns the result of evaluation. - pub fn eval(vp_code: Vec, input_data: Vec) -> bool { - let result = unsafe { - anoma_vp_eval( - vp_code.as_ptr() as _, - vp_code.len() as _, - input_data.as_ptr() as _, - input_data.len() as _, - ) - }; - HostEnvResult::is_success(result) - } - - // These host functions are implemented in the Anoma's [`host_env`] - // module. The environment provides calls to them via this C interface. - extern "C" { - // Read variable-length prior state when we don't know the size - // up-front, returns the size of the value (can be 0), or -1 if - // the key is not present. If a value is found, it will be placed in the - // result buffer, because we cannot allocate a buffer for it before - // we know its size. - fn anoma_vp_read_pre(key_ptr: u64, key_len: u64) -> i64; - - // Read variable-length posterior state when we don't know the size - // up-front, returns the size of the value (can be 0), or -1 if - // the key is not present. If a value is found, it will be placed in the - // result buffer, because we cannot allocate a buffer for it before - // we know its size. - fn anoma_vp_read_post(key_ptr: u64, key_len: u64) -> i64; - - // Read variable-length temporary state when we don't know the size - // up-front, returns the size of the value (can be 0), or -1 if - // the key is not present. If a value is found, it will be placed in the - // result buffer, because we cannot allocate a buffer for it before - // we know its size. - fn anoma_vp_read_temp(key_ptr: u64, key_len: u64) -> i64; - - // Read a value from result buffer. - fn anoma_vp_result_buffer(result_ptr: u64); - - // Returns 1 if the key is present in prior state, -1 otherwise. - fn anoma_vp_has_key_pre(key_ptr: u64, key_len: u64) -> i64; - - // Returns 1 if the key is present in posterior state, -1 otherwise. - fn anoma_vp_has_key_post(key_ptr: u64, key_len: u64) -> i64; - - // Get an ID of a data iterator with key prefix - fn anoma_vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; - - // Read variable-length prior state when we don't know the size - // up-front, returns the size of the value (can be 0), or -1 if - // the key is not present. If a value is found, it will be placed in the - // result buffer, because we cannot allocate a buffer for it before - // we know its size. - fn anoma_vp_iter_pre_next(iter_id: u64) -> i64; - - // Read variable-length posterior state when we don't know the size - // up-front, returns the size of the value (can be 0), or -1 if the - // key is not present. If a value is found, it will be placed in the - // result buffer, because we cannot allocate a buffer for it before - // we know its size. - fn anoma_vp_iter_post_next(iter_id: u64) -> i64; - - // Get the chain ID - fn anoma_vp_get_chain_id(result_ptr: u64); - - // Get the current block height - fn anoma_vp_get_block_height() -> u64; - - // Get the current block hash - fn anoma_vp_get_block_hash(result_ptr: u64); - - // Get the current tx hash - fn anoma_vp_get_tx_code_hash(result_ptr: u64); - - // Get the current block epoch - fn anoma_vp_get_block_epoch() -> u64; - - // Verify a transaction signature - fn anoma_vp_verify_tx_signature( - pk_ptr: u64, - pk_len: u64, - sig_ptr: u64, - sig_len: u64, - ) -> i64; - - // Requires a node running with "Info" log level - fn anoma_vp_log_string(str_ptr: u64, str_len: u64); - - fn anoma_vp_eval( - vp_code_ptr: u64, - vp_code_len: u64, - input_data_ptr: u64, - input_data_len: u64, - ) -> i64; - } -} diff --git a/vm_env/src/intent.rs b/vm_env/src/intent.rs deleted file mode 100644 index 226cb708db..0000000000 --- a/vm_env/src/intent.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::collections::HashSet; - -use namada::proto::Signed; -use namada::types::intent; -use namada::types::key::*; - -/// Tx imports and functions. -pub mod tx { - pub use namada::types::intent::*; - - use super::*; - pub fn invalidate_exchange(intent: &Signed) { - use crate::imports::tx; - let key = intent::invalid_intent_key(&intent.data.addr); - let mut invalid_intent: HashSet = - tx::read(&key.to_string()).unwrap_or_default(); - invalid_intent.insert(intent.sig.clone()); - tx::write(&key.to_string(), &invalid_intent) - } -} - -/// Vp imports and functions. -pub mod vp { - pub use namada::types::intent::*; - - use super::*; - - pub fn vp_exchange(intent: &Signed) -> bool { - use crate::imports::vp; - let key = intent::invalid_intent_key(&intent.data.addr); - - let invalid_intent_pre: HashSet = - vp::read_pre(&key.to_string()).unwrap_or_default(); - let invalid_intent_post: HashSet = - vp::read_post(&key.to_string()).unwrap_or_default(); - !invalid_intent_pre.contains(&intent.sig) - && invalid_intent_post.contains(&intent.sig) - } -} diff --git a/vm_env/src/key/ed25519.rs b/vm_env/src/key/ed25519.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/vm_env/src/key/mod.rs b/vm_env/src/key/mod.rs deleted file mode 100644 index 30aea96c46..0000000000 --- a/vm_env/src/key/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -use namada::types::address::Address; - -/// Vp imports and functions. -pub mod vp { - pub use namada::types::key::*; - - use super::*; - use crate::imports::vp; - - /// Get the public key associated with the given address. Panics if not - /// found. - pub fn get(owner: &Address) -> Option { - let key = pk_key(owner).to_string(); - vp::read_pre(&key) - } -} diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 578079d695..53c594dbab 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -1,55 +1,225 @@ -//! This crate contains library code for wasm. Some of the code is re-exported -//! from the `shared` crate. +//! This crate contains the WASM VM low-level interface. #![doc(html_favicon_url = "https://dev.anoma.net/master/favicon.png")] #![doc(html_logo_url = "https://dev.anoma.net/master/rustdoc-logo.png")] #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -pub mod governance; -pub mod ibc; -pub mod imports; -pub mod intent; -pub mod key; -pub mod nft; -pub mod proof_of_stake; -pub mod token; - -pub mod tx_prelude { - pub use namada::ledger::governance::storage; - pub use namada::ledger::parameters::storage as parameters_storage; - pub use namada::ledger::storage::types::encode; - pub use namada::ledger::treasury::storage as treasury_storage; - pub use namada::proto::{Signed, SignedTxData}; - pub use namada::types::address::Address; - pub use namada::types::storage::Key; - pub use namada::types::*; - pub use namada_macros::transaction; - - pub use crate::governance::tx as governance; - pub use crate::ibc::{Ibc, IbcActions}; - pub use crate::imports::tx::*; - pub use crate::intent::tx as intent; - pub use crate::nft::tx as nft; - pub use crate::proof_of_stake::{self, PoS, PosRead, PosWrite}; - pub use crate::token::tx as token; +use std::mem::ManuallyDrop; + +use borsh::BorshDeserialize; +use namada::types::internal::HostEnvResult; +use namada::vm::types::KeyVal; + +/// Transaction environment imports +pub mod tx { + // These host functions are implemented in the Anoma's [`host_env`] + // module. The environment provides calls to them via this C interface. + extern "C" { + // Read variable-length data when we don't know the size up-front, + // returns the size of the value (can be 0), or -1 if the key is + // not present. If a value is found, it will be placed in the read + // cache, because we cannot allocate a buffer for it before we know + // its size. + pub fn anoma_tx_read(key_ptr: u64, key_len: u64) -> i64; + + // Read a value from result buffer. + pub fn anoma_tx_result_buffer(result_ptr: u64); + + // Returns 1 if the key is present, -1 otherwise. + pub fn anoma_tx_has_key(key_ptr: u64, key_len: u64) -> i64; + + // Write key/value + pub fn anoma_tx_write( + key_ptr: u64, + key_len: u64, + val_ptr: u64, + val_len: u64, + ); + + // Write a temporary key/value + pub fn anoma_tx_write_temp( + key_ptr: u64, + key_len: u64, + val_ptr: u64, + val_len: u64, + ); + + // Delete the given key and its value + pub fn anoma_tx_delete(key_ptr: u64, key_len: u64); + + // Get an ID of a data iterator with key prefix + pub fn anoma_tx_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; + + // Returns the size of the value (can be 0), or -1 if there's no next + // value. If a value is found, it will be placed in the read + // cache, because we cannot allocate a buffer for it before we know + // its size. + pub fn anoma_tx_iter_next(iter_id: u64) -> i64; + + // Insert a verifier + pub fn anoma_tx_insert_verifier(addr_ptr: u64, addr_len: u64); + + // Update a validity predicate + pub fn anoma_tx_update_validity_predicate( + addr_ptr: u64, + addr_len: u64, + code_ptr: u64, + code_len: u64, + ); + + // Initialize a new account + pub fn anoma_tx_init_account( + code_ptr: u64, + code_len: u64, + result_ptr: u64, + ); + + // Emit an IBC event + pub fn anoma_tx_emit_ibc_event(event_ptr: u64, event_len: u64); + + // Get the chain ID + pub fn anoma_tx_get_chain_id(result_ptr: u64); + + // Get the current block height + pub fn anoma_tx_get_block_height() -> u64; + + // Get the time of the current block header + pub fn anoma_tx_get_block_time() -> i64; + + // Get the current block hash + pub fn anoma_tx_get_block_hash(result_ptr: u64); + + // Get the current block epoch + pub fn anoma_tx_get_block_epoch() -> u64; + + // Requires a node running with "Info" log level + pub fn anoma_tx_log_string(str_ptr: u64, str_len: u64); + } +} + +/// Validity predicate environment imports +pub mod vp { + // These host functions are implemented in the Anoma's [`host_env`] + // module. The environment provides calls to them via this C interface. + extern "C" { + // Read variable-length prior state when we don't know the size + // up-front, returns the size of the value (can be 0), or -1 if + // the key is not present. If a value is found, it will be placed in the + // result buffer, because we cannot allocate a buffer for it before + // we know its size. + pub fn anoma_vp_read_pre(key_ptr: u64, key_len: u64) -> i64; + + // Read variable-length posterior state when we don't know the size + // up-front, returns the size of the value (can be 0), or -1 if + // the key is not present. If a value is found, it will be placed in the + // result buffer, because we cannot allocate a buffer for it before + // we know its size. + pub fn anoma_vp_read_post(key_ptr: u64, key_len: u64) -> i64; + + // Read variable-length temporary state when we don't know the size + // up-front, returns the size of the value (can be 0), or -1 if + // the key is not present. If a value is found, it will be placed in the + // result buffer, because we cannot allocate a buffer for it before + // we know its size. + pub fn anoma_vp_read_temp(key_ptr: u64, key_len: u64) -> i64; + + // Read a value from result buffer. + pub fn anoma_vp_result_buffer(result_ptr: u64); + + // Returns 1 if the key is present in prior state, -1 otherwise. + pub fn anoma_vp_has_key_pre(key_ptr: u64, key_len: u64) -> i64; + + // Returns 1 if the key is present in posterior state, -1 otherwise. + pub fn anoma_vp_has_key_post(key_ptr: u64, key_len: u64) -> i64; + + // Get an ID of a data iterator with key prefix + pub fn anoma_vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; + + // Read variable-length prior state when we don't know the size + // up-front, returns the size of the value (can be 0), or -1 if + // the key is not present. If a value is found, it will be placed in the + // result buffer, because we cannot allocate a buffer for it before + // we know its size. + pub fn anoma_vp_iter_pre_next(iter_id: u64) -> i64; + + // Read variable-length posterior state when we don't know the size + // up-front, returns the size of the value (can be 0), or -1 if the + // key is not present. If a value is found, it will be placed in the + // result buffer, because we cannot allocate a buffer for it before + // we know its size. + pub fn anoma_vp_iter_post_next(iter_id: u64) -> i64; + + // Get the chain ID + pub fn anoma_vp_get_chain_id(result_ptr: u64); + + // Get the current block height + pub fn anoma_vp_get_block_height() -> u64; + + // Get the current block hash + pub fn anoma_vp_get_block_hash(result_ptr: u64); + + // Get the current tx hash + pub fn anoma_vp_get_tx_code_hash(result_ptr: u64); + + // Get the current block epoch + pub fn anoma_vp_get_block_epoch() -> u64; + + // Verify a transaction signature + pub fn anoma_vp_verify_tx_signature( + pk_ptr: u64, + pk_len: u64, + sig_ptr: u64, + sig_len: u64, + ) -> i64; + + // Requires a node running with "Info" log level + pub fn anoma_vp_log_string(str_ptr: u64, str_len: u64); + + pub fn anoma_vp_eval( + vp_code_ptr: u64, + vp_code_len: u64, + input_data_ptr: u64, + input_data_len: u64, + ) -> i64; + } +} + +/// This function is a helper to handle the second step of reading var-len +/// values from the host. +/// +/// In cases where we're reading a value from the host in the guest and +/// we don't know the byte size up-front, we have to read it in 2-steps. The +/// first step reads the value into a result buffer and returns the size (if +/// any) back to the guest, the second step reads the value from cache into a +/// pre-allocated buffer with the obtained size. +pub fn read_from_buffer( + read_result: i64, + result_buffer: unsafe extern "C" fn(u64), +) -> Option> { + if HostEnvResult::is_fail(read_result) { + None + } else { + let result: Vec = Vec::with_capacity(read_result as _); + // The `result` will be dropped from the `target`, which is + // reconstructed from the same memory + let result = ManuallyDrop::new(result); + let offset = result.as_slice().as_ptr() as u64; + unsafe { result_buffer(offset) }; + let target = unsafe { + Vec::from_raw_parts(offset as _, read_result as _, read_result as _) + }; + Some(target) + } } -pub mod vp_prelude { - // used in the VP input - pub use std::collections::{BTreeSet, HashSet}; - - pub use namada::ledger::governance::storage as gov_storage; - pub use namada::ledger::{parameters, pos as proof_of_stake}; - pub use namada::proto::{Signed, SignedTxData}; - pub use namada::types::address::Address; - pub use namada::types::storage::Key; - pub use namada::types::*; - pub use namada_macros::validity_predicate; - - pub use crate::imports::vp::*; - pub use crate::intent::vp as intent; - pub use crate::key::vp as key; - pub use crate::nft::vp as nft; - pub use crate::token::vp as token; +/// This function is a helper to handle the second step of reading var-len +/// values in a key-value pair from the host. +pub fn read_key_val_bytes_from_buffer( + read_result: i64, + result_buffer: unsafe extern "C" fn(u64), +) -> Option<(String, Vec)> { + let key_val = read_from_buffer(read_result, result_buffer) + .and_then(|t| KeyVal::try_from_slice(&t[..]).ok()); + key_val.map(|key_val| (key_val.key, key_val.val)) } diff --git a/vm_env/src/nft.rs b/vm_env/src/nft.rs deleted file mode 100644 index 4a685acd72..0000000000 --- a/vm_env/src/nft.rs +++ /dev/null @@ -1,194 +0,0 @@ -use namada::types::nft; - -/// Tx imports and functions. -pub mod tx { - use namada::types::address::Address; - use namada::types::nft::NftToken; - use namada::types::transaction::nft::{CreateNft, MintNft}; - - use super::*; - use crate::imports::tx; - pub fn init_nft(nft: CreateNft) -> Address { - let address = tx::init_account(&nft.vp_code); - - // write tag - let tag_key = nft::get_tag_key(&address); - tx::write(&tag_key.to_string(), &nft.tag); - - // write creator - let creator_key = nft::get_creator_key(&address); - tx::write(&creator_key.to_string(), &nft.creator); - - // write keys - let keys_key = nft::get_keys_key(&address); - tx::write(&keys_key.to_string(), &nft.keys); - - // write optional keys - let optional_keys_key = nft::get_optional_keys_key(&address); - tx::write(&optional_keys_key.to_string(), nft.opt_keys); - - // mint tokens - aux_mint_token(&address, &nft.creator, nft.tokens, &nft.creator); - - tx::insert_verifier(&nft.creator); - - address - } - - pub fn mint_tokens(nft: MintNft) { - aux_mint_token(&nft.address, &nft.creator, nft.tokens, &nft.creator); - } - - fn aux_mint_token( - nft_address: &Address, - creator_address: &Address, - tokens: Vec, - verifier: &Address, - ) { - for token in tokens { - // write token metadata - let metadata_key = - nft::get_token_metadata_key(nft_address, &token.id.to_string()); - tx::write(&metadata_key.to_string(), &token.metadata); - - // write current owner token as creator - let current_owner_key = nft::get_token_current_owner_key( - nft_address, - &token.id.to_string(), - ); - tx::write( - ¤t_owner_key.to_string(), - &token - .current_owner - .unwrap_or_else(|| creator_address.clone()), - ); - - // write value key - let value_key = - nft::get_token_value_key(nft_address, &token.id.to_string()); - tx::write(&value_key.to_string(), &token.values); - - // write optional value keys - let optional_value_key = nft::get_token_optional_value_key( - nft_address, - &token.id.to_string(), - ); - tx::write(&optional_value_key.to_string(), &token.opt_values); - - // write approval addresses - let approval_key = - nft::get_token_approval_key(nft_address, &token.id.to_string()); - tx::write(&approval_key.to_string(), &token.approvals); - - // write burnt propriety - let burnt_key = - nft::get_token_burnt_key(nft_address, &token.id.to_string()); - tx::write(&burnt_key.to_string(), token.burnt); - } - tx::insert_verifier(verifier); - } -} - -/// A Nft validity predicate -pub mod vp { - use std::collections::BTreeSet; - - use namada::types::address::Address; - pub use namada::types::nft::*; - use namada::types::storage::Key; - - use crate::imports::vp; - - enum KeyType { - Metadata(Address, String), - Approval(Address, String), - CurrentOwner(Address, String), - Creator(Address), - PastOwners(Address, String), - Unknown, - } - - pub fn vp( - _tx_da_ta: Vec, - nft_address: &Address, - keys_changed: &BTreeSet, - verifiers: &BTreeSet
, - ) -> bool { - keys_changed - .iter() - .all(|key| match get_key_type(key, nft_address) { - KeyType::Creator(_creator_addr) => { - vp::log_string("creator cannot be changed."); - false - } - KeyType::Approval(nft_address, token_id) => { - vp::log_string(format!( - "nft vp, checking approvals with token id: {}", - token_id - )); - - is_creator(&nft_address, verifiers) - || is_approved( - &nft_address, - token_id.as_ref(), - verifiers, - ) - } - KeyType::Metadata(nft_address, token_id) => { - vp::log_string(format!( - "nft vp, checking if metadata changed: {}", - token_id - )); - is_creator(&nft_address, verifiers) - } - _ => is_creator(nft_address, verifiers), - }) - } - - fn is_approved( - nft_address: &Address, - nft_token_id: &str, - verifiers: &BTreeSet
, - ) -> bool { - let approvals_key = - get_token_approval_key(nft_address, nft_token_id).to_string(); - let approval_addresses: Vec
= - vp::read_pre(approvals_key).unwrap_or_default(); - return approval_addresses - .iter() - .any(|addr| verifiers.contains(addr)); - } - - fn is_creator( - nft_address: &Address, - verifiers: &BTreeSet
, - ) -> bool { - let creator_key = get_creator_key(nft_address).to_string(); - let creator_address: Address = vp::read_pre(creator_key).unwrap(); - verifiers.contains(&creator_address) - } - - fn get_key_type(key: &Key, nft_address: &Address) -> KeyType { - let is_creator_key = is_nft_creator_key(key, nft_address); - let is_metadata_key = is_nft_metadata_key(key, nft_address); - let is_approval_key = is_nft_approval_key(key, nft_address); - let is_current_owner_key = is_nft_current_owner_key(key, nft_address); - let is_past_owner_key = is_nft_past_owners_key(key, nft_address); - if let Some(nft_address) = is_creator_key { - return KeyType::Creator(nft_address); - } - if let Some((nft_address, token_id)) = is_metadata_key { - return KeyType::Metadata(nft_address, token_id); - } - if let Some((nft_address, token_id)) = is_approval_key { - return KeyType::Approval(nft_address, token_id); - } - if let Some((nft_address, token_id)) = is_current_owner_key { - return KeyType::CurrentOwner(nft_address, token_id); - } - if let Some((nft_address, token_id)) = is_past_owner_key { - return KeyType::PastOwners(nft_address, token_id); - } - KeyType::Unknown - } -} diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs deleted file mode 100644 index 8e4bba4223..0000000000 --- a/vm_env/src/proof_of_stake.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! Proof of Stake system integration with functions for transactions - -use namada::ledger::pos::namada_proof_of_stake::{ - BecomeValidatorError, BondError, UnbondError, WithdrawError, -}; -use namada::ledger::pos::types::Slash; -pub use namada::ledger::pos::*; -use namada::ledger::pos::{ - bond_key, namada_proof_of_stake, params_key, total_voting_power_key, - unbond_key, validator_address_raw_hash_key, validator_consensus_key_key, - validator_set_key, validator_slashes_key, - validator_staking_reward_address_key, validator_state_key, - validator_total_deltas_key, validator_voting_power_key, -}; -use namada::types::address::{self, Address, InternalAddress}; -use namada::types::transaction::InitValidator; -use namada::types::{key, token}; -pub use namada_proof_of_stake::{ - epoched, parameters, types, PosActions as PosWrite, PosReadOnly as PosRead, -}; - -use crate::imports::tx; - -/// Self-bond tokens to a validator when `source` is `None` or equal to -/// the `validator` address, or delegate tokens from the `source` to the -/// `validator`. -pub fn bond_tokens( - source: Option<&Address>, - validator: &Address, - amount: token::Amount, -) -> Result<(), BondError
> { - let current_epoch = tx::get_block_epoch(); - PoS.bond_tokens(source, validator, amount, current_epoch) -} - -/// Unbond self-bonded tokens from a validator when `source` is `None` or -/// equal to the `validator` address, or unbond delegated tokens from -/// the `source` to the `validator`. -pub fn unbond_tokens( - source: Option<&Address>, - validator: &Address, - amount: token::Amount, -) -> Result<(), UnbondError> { - let current_epoch = tx::get_block_epoch(); - PoS.unbond_tokens(source, validator, amount, current_epoch) -} - -/// Withdraw unbonded tokens from a self-bond to a validator when `source` -/// is `None` or equal to the `validator` address, or withdraw unbonded -/// tokens delegated to the `validator` to the `source`. -pub fn withdraw_tokens( - source: Option<&Address>, - validator: &Address, -) -> Result> { - let current_epoch = tx::get_block_epoch(); - PoS.withdraw_tokens(source, validator, current_epoch) -} - -/// Attempt to initialize a validator account. On success, returns the -/// initialized validator account's address and its staking reward address. -pub fn init_validator( - InitValidator { - account_key, - consensus_key, - rewards_account_key, - protocol_key, - dkg_key, - validator_vp_code, - rewards_vp_code, - }: InitValidator, -) -> Result<(Address, Address), BecomeValidatorError
> { - let current_epoch = tx::get_block_epoch(); - // Init validator account - let validator_address = tx::init_account(&validator_vp_code); - let pk_key = key::pk_key(&validator_address); - tx::write(&pk_key.to_string(), &account_key); - let protocol_pk_key = key::protocol_pk_key(&validator_address); - tx::write(&protocol_pk_key.to_string(), &protocol_key); - let dkg_pk_key = key::dkg_session_keys::dkg_pk_key(&validator_address); - tx::write(&dkg_pk_key.to_string(), &dkg_key); - - // Init staking reward account - let rewards_address = tx::init_account(&rewards_vp_code); - let pk_key = key::pk_key(&rewards_address); - tx::write(&pk_key.to_string(), &rewards_account_key); - - PoS.become_validator( - &validator_address, - &rewards_address, - &consensus_key, - current_epoch, - )?; - Ok((validator_address, rewards_address)) -} - -/// Proof of Stake system. This struct integrates and gives access to -/// lower-level PoS functions. -pub struct PoS; - -impl namada_proof_of_stake::PosReadOnly for PoS { - type Address = Address; - type PublicKey = key::common::PublicKey; - type TokenAmount = token::Amount; - type TokenChange = token::Change; - - const POS_ADDRESS: Self::Address = Address::Internal(InternalAddress::PoS); - - fn staking_token_address() -> Self::Address { - address::xan() - } - - fn read_pos_params(&self) -> PosParams { - tx::read(params_key().to_string()).unwrap() - } - - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> Option { - tx::read(validator_staking_reward_address_key(key).to_string()) - } - - fn read_validator_consensus_key( - &self, - key: &Self::Address, - ) -> Option { - tx::read(validator_consensus_key_key(key).to_string()) - } - - fn read_validator_state( - &self, - key: &Self::Address, - ) -> Option { - tx::read(validator_state_key(key).to_string()) - } - - fn read_validator_total_deltas( - &self, - key: &Self::Address, - ) -> Option { - tx::read(validator_total_deltas_key(key).to_string()) - } - - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> Option { - tx::read(validator_voting_power_key(key).to_string()) - } - - fn read_validator_slashes(&self, key: &Self::Address) -> Vec { - tx::read(validator_slashes_key(key).to_string()).unwrap_or_default() - } - - fn read_bond(&self, key: &BondId) -> Option { - tx::read(bond_key(key).to_string()) - } - - fn read_unbond(&self, key: &BondId) -> Option { - tx::read(unbond_key(key).to_string()) - } - - fn read_validator_set(&self) -> ValidatorSets { - tx::read(validator_set_key().to_string()).unwrap() - } - - fn read_total_voting_power(&self) -> TotalVotingPowers { - tx::read(total_voting_power_key().to_string()).unwrap() - } -} - -impl namada_proof_of_stake::PosActions for PoS { - fn write_pos_params(&mut self, params: &PosParams) { - tx::write(params_key().to_string(), params) - } - - fn write_validator_address_raw_hash(&mut self, address: &Self::Address) { - let raw_hash = address.raw_hash().unwrap().to_owned(); - tx::write( - validator_address_raw_hash_key(raw_hash).to_string(), - address, - ) - } - - fn write_validator_staking_reward_address( - &mut self, - key: &Self::Address, - value: Self::Address, - ) { - tx::write( - validator_staking_reward_address_key(key).to_string(), - &value, - ) - } - - fn write_validator_consensus_key( - &mut self, - key: &Self::Address, - value: ValidatorConsensusKeys, - ) { - tx::write(validator_consensus_key_key(key).to_string(), &value) - } - - fn write_validator_state( - &mut self, - key: &Self::Address, - value: ValidatorStates, - ) { - tx::write(validator_state_key(key).to_string(), &value) - } - - fn write_validator_total_deltas( - &mut self, - key: &Self::Address, - value: ValidatorTotalDeltas, - ) { - tx::write(validator_total_deltas_key(key).to_string(), &value) - } - - fn write_validator_voting_power( - &mut self, - key: &Self::Address, - value: ValidatorVotingPowers, - ) { - tx::write(validator_voting_power_key(key).to_string(), &value) - } - - fn write_bond(&mut self, key: &BondId, value: Bonds) { - tx::write(bond_key(key).to_string(), &value) - } - - fn write_unbond(&mut self, key: &BondId, value: Unbonds) { - tx::write(unbond_key(key).to_string(), &value) - } - - fn write_validator_set(&mut self, value: ValidatorSets) { - tx::write(validator_set_key().to_string(), &value) - } - - fn write_total_voting_power(&mut self, value: TotalVotingPowers) { - tx::write(total_voting_power_key().to_string(), &value) - } - - fn delete_bond(&mut self, key: &BondId) { - tx::delete(bond_key(key).to_string()) - } - - fn delete_unbond(&mut self, key: &BondId) { - tx::delete(unbond_key(key).to_string()) - } - - fn transfer( - &mut self, - token: &Self::Address, - amount: Self::TokenAmount, - src: &Self::Address, - dest: &Self::Address, - ) { - crate::token::tx::transfer(src, dest, token, amount) - } -} diff --git a/vm_env/src/token.rs b/vm_env/src/token.rs deleted file mode 100644 index 8a7367afb9..0000000000 --- a/vm_env/src/token.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::collections::BTreeSet; - -use namada::types::address::{Address, InternalAddress}; -use namada::types::storage::Key; -use namada::types::token; - -/// Vp imports and functions. -pub mod vp { - use namada::types::storage::KeySeg; - pub use namada::types::token::*; - - use super::*; - use crate::imports::vp; - - /// A token validity predicate. - pub fn vp( - token: &Address, - keys_changed: &BTreeSet, - verifiers: &BTreeSet
, - ) -> bool { - let mut change: Change = 0; - let all_checked = keys_changed.iter().all(|key| { - match token::is_balance_key(token, key) { - None => { - // Unknown changes to this address space are disallowed, but - // unknown changes anywhere else are permitted - key.segments.get(0) != Some(&token.to_db_key()) - } - Some(owner) => { - // accumulate the change - let key = key.to_string(); - let pre: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - Amount::max() - } - Address::Internal(InternalAddress::IbcBurn) => { - Amount::default() - } - _ => vp::read_pre(&key).unwrap_or_default(), - }; - let post: Amount = match owner { - Address::Internal(InternalAddress::IbcMint) => { - vp::read_temp(&key).unwrap_or_else(Amount::max) - } - Address::Internal(InternalAddress::IbcBurn) => { - vp::read_temp(&key).unwrap_or_default() - } - _ => vp::read_post(&key).unwrap_or_default(), - }; - let this_change = post.change() - pre.change(); - change += this_change; - // make sure that the spender approved the transaction - if this_change < 0 { - return verifiers.contains(owner); - } - true - } - } - }); - all_checked && change == 0 - } -} - -/// Tx imports and functions. -pub mod tx { - pub use namada::types::token::*; - - use super::*; - use crate::imports::tx; - - /// A token transfer that can be used in a transaction. - pub fn transfer( - src: &Address, - dest: &Address, - token: &Address, - amount: Amount, - ) { - let src_key = token::balance_key(token, src); - let dest_key = token::balance_key(token, dest); - let src_bal: Option = tx::read(&src_key.to_string()); - let mut src_bal = src_bal.unwrap_or_else(|| match src { - Address::Internal(InternalAddress::IbcMint) => Amount::max(), - _ => { - tx::log_string(format!("src {} has no balance", src)); - unreachable!() - } - }); - src_bal.spend(&amount); - let mut dest_bal: Amount = - tx::read(&dest_key.to_string()).unwrap_or_default(); - dest_bal.receive(&amount); - match src { - Address::Internal(InternalAddress::IbcMint) => { - tx::write_temp(&src_key.to_string(), src_bal) - } - Address::Internal(InternalAddress::IbcBurn) => { - tx::log_string("invalid transfer from the burn address"); - unreachable!() - } - _ => tx::write(&src_key.to_string(), src_bal), - } - match dest { - Address::Internal(InternalAddress::IbcMint) => { - tx::log_string("invalid transfer to the mint address"); - unreachable!() - } - Address::Internal(InternalAddress::IbcBurn) => { - tx::write_temp(&dest_key.to_string(), dest_bal) - } - _ => tx::write(&dest_key.to_string(), dest_bal), - } - } -} diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index f59c5ed032..a36f998b83 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -10,5 +10,9 @@ version = "0.7.0" default = [] [dependencies] +namada = {path = "../shared"} namada_vm_env = {path = "../vm_env"} +namada_macros = {path = "../macros"} +borsh = "0.9.0" sha2 = "0.10.1" +thiserror = "1.0.30" diff --git a/vp_prelude/src/error.rs b/vp_prelude/src/error.rs new file mode 100644 index 0000000000..b34d954166 --- /dev/null +++ b/vp_prelude/src/error.rs @@ -0,0 +1,103 @@ +//! Helpers for error handling in WASM +//! +//! This module is currently duplicated in tx_prelude and vp_prelude crates to +//! be able to implement `From` conversion on error types from other crates, +//! avoiding `error[E0117]: only traits defined in the current crate can be +//! implemented for arbitrary types` + +use thiserror::Error; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + SimpleMessage(&'static str), + #[error("{0}")] + Custom(CustomError), + #[error("{0}: {1}")] + CustomWithMessage(&'static str, CustomError), +} + +/// Result of transaction or VP. +pub type EnvResult = Result; + +pub trait ResultExt { + /// Replace a possible error with a static message in [`EnvResult`]. + fn err_msg(self, msg: &'static str) -> EnvResult; +} + +// This is separate from `ResultExt`, because the implementation requires +// different bounds for `T`. +pub trait ResultExt2 { + /// Convert a [`Result`] into [`EnvResult`]. + fn into_env_result(self) -> EnvResult; + + /// Add a static message to a possible error in [`EnvResult`]. + fn wrap_err(self, msg: &'static str) -> EnvResult; +} + +pub trait OptionExt { + /// Transforms the [`Option`] into a [`EnvResult`], mapping + /// [`Some(v)`] to [`Ok(v)`] and [`None`] to the given static error + /// message. + fn ok_or_err_msg(self, msg: &'static str) -> EnvResult; +} + +impl ResultExt for Result { + fn err_msg(self, msg: &'static str) -> EnvResult { + self.map_err(|_err| Error::new_const(msg)) + } +} + +impl ResultExt2 for Result +where + E: std::error::Error + Send + Sync + 'static, +{ + fn into_env_result(self) -> EnvResult { + self.map_err(Error::new) + } + + fn wrap_err(self, msg: &'static str) -> EnvResult { + self.map_err(|err| Error::wrap(msg, err)) + } +} + +impl OptionExt for Option { + fn ok_or_err_msg(self, msg: &'static str) -> EnvResult { + self.ok_or_else(|| Error::new_const(msg)) + } +} + +impl Error { + /// Create an [`enum@Error`] from a static message. + #[inline] + pub const fn new_const(msg: &'static str) -> Self { + Self::SimpleMessage(msg) + } + + /// Create an [`enum@Error`] from another [`std::error::Error`]. + pub fn new(error: E) -> Self + where + E: Into>, + { + Self::Custom(CustomError(error.into())) + } + + /// Wrap another [`std::error::Error`] with a static message. + pub fn wrap(msg: &'static str, error: E) -> Self + where + E: Into>, + { + Self::CustomWithMessage(msg, CustomError(error.into())) + } +} + +/// A custom error +#[derive(Debug)] +pub struct CustomError(Box); + +impl std::fmt::Display for CustomError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} diff --git a/vp_prelude/src/intent.rs b/vp_prelude/src/intent.rs new file mode 100644 index 0000000000..93a93e1183 --- /dev/null +++ b/vp_prelude/src/intent.rs @@ -0,0 +1,19 @@ +use std::collections::HashSet; + +use namada::proto::Signed; +use namada::types::intent; +pub use namada::types::intent::*; +use namada::types::key::*; + +use super::*; + +pub fn vp_exchange(ctx: &Ctx, intent: &Signed) -> EnvResult { + let key = intent::invalid_intent_key(&intent.data.addr); + + let invalid_intent_pre: HashSet = + ctx.read_pre(&key)?.unwrap_or_default(); + let invalid_intent_post: HashSet = + ctx.read_post(&key)?.unwrap_or_default(); + Ok(!invalid_intent_pre.contains(&intent.sig) + && invalid_intent_post.contains(&intent.sig)) +} diff --git a/vp_prelude/src/key.rs b/vp_prelude/src/key.rs new file mode 100644 index 0000000000..5ef2a5e28c --- /dev/null +++ b/vp_prelude/src/key.rs @@ -0,0 +1,13 @@ +//! Cryptographic signature keys + +use namada::types::address::Address; +pub use namada::types::key::*; + +use super::*; + +/// Get the public key associated with the given address. Panics if not +/// found. +pub fn get(ctx: &Ctx, owner: &Address) -> EnvResult> { + let key = pk_key(owner); + ctx.read_pre(&key) +} diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 957354d848..c1be4465f1 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -6,10 +6,37 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] +mod error; +pub mod intent; +pub mod key; +pub mod nft; +pub mod token; + +// used in the VP input use core::convert::AsRef; +use core::slice; +pub use std::collections::{BTreeSet, HashSet}; +use std::convert::TryFrom; +use std::marker::PhantomData; -use namada_vm_env::vp_prelude::hash::Hash; -pub use namada_vm_env::vp_prelude::*; +pub use borsh::{BorshDeserialize, BorshSerialize}; +pub use error::*; +pub use namada::ledger::governance::storage as gov_storage; +pub use namada::ledger::vp_env::VpEnv; +pub use namada::ledger::{parameters, pos as proof_of_stake}; +pub use namada::proto::{Signed, SignedTxData}; +pub use namada::types::address::Address; +use namada::types::chain::CHAIN_ID_LENGTH; +use namada::types::hash::{Hash, HASH_LENGTH}; +use namada::types::internal::HostEnvResult; +use namada::types::key::*; +use namada::types::storage::{ + BlockHash, BlockHeight, Epoch, BLOCK_HASH_LENGTH, +}; +pub use namada::types::*; +pub use namada_macros::validity_predicate; +use namada_vm_env::vp::*; +use namada_vm_env::{read_from_buffer, read_key_val_bytes_from_buffer}; pub use sha2::{Digest, Sha256, Sha384, Sha512}; pub fn sha256(bytes: &[u8]) -> Hash { @@ -17,20 +44,28 @@ pub fn sha256(bytes: &[u8]) -> Hash { Hash(*digest.as_ref()) } -pub fn is_tx_whitelisted() -> bool { - let tx_hash = get_tx_code_hash(); +pub fn is_tx_whitelisted(ctx: &Ctx) -> VpResult { + let tx_hash = ctx.get_tx_code_hash()?; let key = parameters::storage::get_tx_whitelist_storage_key(); - let whitelist: Vec = read_pre(&key.to_string()).unwrap_or_default(); + let whitelist: Vec = ctx.read_pre(&key)?.unwrap_or_default(); // if whitelist is empty, allow any transaction - whitelist.is_empty() || whitelist.contains(&tx_hash.to_string()) + Ok(whitelist.is_empty() || whitelist.contains(&tx_hash.to_string())) } -pub fn is_vp_whitelisted(vp_bytes: &[u8]) -> bool { +pub fn is_vp_whitelisted(ctx: &Ctx, vp_bytes: &[u8]) -> VpResult { let vp_hash = sha256(vp_bytes); let key = parameters::storage::get_vp_whitelist_storage_key(); - let whitelist: Vec = read_pre(&key.to_string()).unwrap_or_default(); + let whitelist: Vec = ctx.read_pre(&key)?.unwrap_or_default(); // if whitelist is empty, allow any transaction - whitelist.is_empty() || whitelist.contains(&vp_hash.to_string()) + Ok(whitelist.is_empty() || whitelist.contains(&vp_hash.to_string())) +} + +/// Log a string. The message will be printed at the `tracing::Level::Info`. +pub fn log_string>(msg: T) { + let msg = msg.as_ref(); + unsafe { + anoma_vp_log_string(msg.as_ptr() as _, msg.len() as _); + } } /// Log a string in a debug build. The message will be printed at the @@ -44,3 +79,234 @@ macro_rules! debug_log { (if cfg!(debug_assertions) { log_string(format!($($arg)*)) }) }} } + +pub struct Ctx(()); + +impl Ctx { + /// Create a host context. The context on WASM side is only provided by + /// the VM once its being executed (in here it's implicit). But + /// because we want to have interface identical with the native + /// VPs, in which the context is explicit, in here we're just + /// using an empty `Ctx` to "fake" it. + /// + /// # Safety + /// + /// When using `#[validity_predicate]` macro from `anoma_macros`, + /// the constructor should not be called from transactions and validity + /// predicates implementation directly - they receive `&Self` as + /// an argument provided by the macro that wrap the low-level WASM + /// interface with Rust native types. + /// + /// Otherwise, this should only be called once to initialize this "fake" + /// context in order to benefit from type-safety of the host environment + /// methods implemented on the context. + #[allow(clippy::new_without_default)] + pub const unsafe fn new() -> Self { + Self(()) + } +} + +/// Validity predicate result +pub type VpResult = EnvResult; + +/// Accept a transaction +pub fn accept() -> VpResult { + Ok(true) +} + +/// Reject a transaction +pub fn reject() -> VpResult { + Ok(false) +} + +#[derive(Debug)] +pub struct KeyValIterator(pub u64, pub PhantomData); + +impl VpEnv for Ctx { + type Error = Error; + type PrefixIter = KeyValIterator<(String, Vec)>; + + fn read_pre( + &self, + key: &storage::Key, + ) -> Result, Self::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer) + .and_then(|bytes| T::try_from_slice(&bytes[..]).ok())) + } + + fn read_bytes_pre( + &self, + key: &storage::Key, + ) -> Result>, Self::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + } + + fn read_post( + &self, + key: &storage::Key, + ) -> Result, Self::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer) + .and_then(|bytes| T::try_from_slice(&bytes[..]).ok())) + } + + fn read_bytes_post( + &self, + key: &storage::Key, + ) -> Result>, Self::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + } + + fn read_temp( + &self, + key: &storage::Key, + ) -> Result, Self::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_temp(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer) + .and_then(|t| T::try_from_slice(&t[..]).ok())) + } + + fn read_bytes_temp( + &self, + key: &storage::Key, + ) -> Result>, Self::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_temp(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + } + + fn has_key_pre(&self, key: &storage::Key) -> Result { + let key = key.to_string(); + let found = + unsafe { anoma_vp_has_key_pre(key.as_ptr() as _, key.len() as _) }; + Ok(HostEnvResult::is_success(found)) + } + + fn has_key_post(&self, key: &storage::Key) -> Result { + let key = key.to_string(); + let found = + unsafe { anoma_vp_has_key_post(key.as_ptr() as _, key.len() as _) }; + Ok(HostEnvResult::is_success(found)) + } + + fn get_chain_id(&self) -> Result { + let result = Vec::with_capacity(CHAIN_ID_LENGTH); + unsafe { + anoma_vp_get_chain_id(result.as_ptr() as _); + } + let slice = + unsafe { slice::from_raw_parts(result.as_ptr(), CHAIN_ID_LENGTH) }; + Ok(String::from_utf8(slice.to_vec()) + .expect("Cannot convert the ID string")) + } + + fn get_block_height(&self) -> Result { + Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) + } + + fn get_block_hash(&self) -> Result { + let result = Vec::with_capacity(BLOCK_HASH_LENGTH); + unsafe { + anoma_vp_get_block_hash(result.as_ptr() as _); + } + let slice = unsafe { + slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) + }; + Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) + } + + fn get_block_epoch(&self) -> Result { + Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) + } + + fn iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + let prefix = prefix.to_string(); + let iter_id = unsafe { + anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) + } + + fn iter_pre_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error> { + let read_result = unsafe { anoma_vp_iter_pre_next(iter.0) }; + Ok(read_key_val_bytes_from_buffer( + read_result, + anoma_vp_result_buffer, + )) + } + + fn iter_post_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error> { + let read_result = unsafe { anoma_vp_iter_post_next(iter.0) }; + Ok(read_key_val_bytes_from_buffer( + read_result, + anoma_vp_result_buffer, + )) + } + + fn eval( + &self, + vp_code: Vec, + input_data: Vec, + ) -> Result { + let result = unsafe { + anoma_vp_eval( + vp_code.as_ptr() as _, + vp_code.len() as _, + input_data.as_ptr() as _, + input_data.len() as _, + ) + }; + Ok(HostEnvResult::is_success(result)) + } + + fn verify_tx_signature( + &self, + pk: &common::PublicKey, + sig: &common::Signature, + ) -> Result { + let pk = BorshSerialize::try_to_vec(pk).unwrap(); + let sig = BorshSerialize::try_to_vec(sig).unwrap(); + let valid = unsafe { + anoma_vp_verify_tx_signature( + pk.as_ptr() as _, + pk.len() as _, + sig.as_ptr() as _, + sig.len() as _, + ) + }; + Ok(HostEnvResult::is_success(valid)) + } + + fn get_tx_code_hash(&self) -> Result { + let result = Vec::with_capacity(HASH_LENGTH); + unsafe { + anoma_vp_get_tx_code_hash(result.as_ptr() as _); + } + let slice = + unsafe { slice::from_raw_parts(result.as_ptr(), HASH_LENGTH) }; + Ok(Hash::try_from(slice).expect("Cannot convert the hash")) + } +} diff --git a/vp_prelude/src/nft.rs b/vp_prelude/src/nft.rs new file mode 100644 index 0000000000..1d5d019169 --- /dev/null +++ b/vp_prelude/src/nft.rs @@ -0,0 +1,116 @@ +//! NFT validity predicate + +use std::collections::BTreeSet; + +use namada::ledger::native_vp::VpEnv; +use namada::types::address::Address; +pub use namada::types::nft::*; +use namada::types::storage::Key; + +use super::{accept, reject, Ctx, EnvResult, VpResult}; + +enum KeyType { + Metadata(Address, String), + Approval(Address, String), + CurrentOwner(Address, String), + Creator(Address), + PastOwners(Address, String), + Unknown, +} + +pub fn vp( + ctx: &Ctx, + _tx_da_ta: Vec, + nft_address: &Address, + keys_changed: &BTreeSet, + verifiers: &BTreeSet
, +) -> VpResult { + for key in keys_changed { + match get_key_type(key, nft_address) { + KeyType::Creator(_creator_addr) => { + super::log_string("creator cannot be changed."); + return reject(); + } + KeyType::Approval(nft_address, token_id) => { + super::log_string(format!( + "nft vp, checking approvals with token id: {}", + token_id + )); + + if !(is_creator(ctx, &nft_address, verifiers)? + || is_approved( + ctx, + &nft_address, + token_id.as_ref(), + verifiers, + )?) + { + return reject(); + } + } + KeyType::Metadata(nft_address, token_id) => { + super::log_string(format!( + "nft vp, checking if metadata changed: {}", + token_id + )); + if !is_creator(ctx, &nft_address, verifiers)? { + return reject(); + } + } + _ => { + if !is_creator(ctx, nft_address, verifiers)? { + return reject(); + } + } + } + } + accept() +} + +fn is_approved( + ctx: &Ctx, + nft_address: &Address, + nft_token_id: &str, + verifiers: &BTreeSet
, +) -> EnvResult { + let approvals_key = get_token_approval_key(nft_address, nft_token_id); + let approval_addresses: Vec
= + ctx.read_pre(&approvals_key)?.unwrap_or_default(); + return Ok(approval_addresses + .iter() + .any(|addr| verifiers.contains(addr))); +} + +fn is_creator( + ctx: &Ctx, + nft_address: &Address, + verifiers: &BTreeSet
, +) -> EnvResult { + let creator_key = get_creator_key(nft_address); + let creator_address: Address = ctx.read_pre(&creator_key)?.unwrap(); + Ok(verifiers.contains(&creator_address)) +} + +fn get_key_type(key: &Key, nft_address: &Address) -> KeyType { + let is_creator_key = is_nft_creator_key(key, nft_address); + let is_metadata_key = is_nft_metadata_key(key, nft_address); + let is_approval_key = is_nft_approval_key(key, nft_address); + let is_current_owner_key = is_nft_current_owner_key(key, nft_address); + let is_past_owner_key = is_nft_past_owners_key(key, nft_address); + if let Some(nft_address) = is_creator_key { + return KeyType::Creator(nft_address); + } + if let Some((nft_address, token_id)) = is_metadata_key { + return KeyType::Metadata(nft_address, token_id); + } + if let Some((nft_address, token_id)) = is_approval_key { + return KeyType::Approval(nft_address, token_id); + } + if let Some((nft_address, token_id)) = is_current_owner_key { + return KeyType::CurrentOwner(nft_address, token_id); + } + if let Some((nft_address, token_id)) = is_past_owner_key { + return KeyType::PastOwners(nft_address, token_id); + } + KeyType::Unknown +} diff --git a/vp_prelude/src/token.rs b/vp_prelude/src/token.rs new file mode 100644 index 0000000000..6670386c4a --- /dev/null +++ b/vp_prelude/src/token.rs @@ -0,0 +1,61 @@ +//! A fungible token validity predicate. + +use std::collections::BTreeSet; + +use namada::types::address::{Address, InternalAddress}; +use namada::types::storage::Key; +/// Vp imports and functions. +use namada::types::storage::KeySeg; +use namada::types::token; +pub use namada::types::token::*; + +use super::*; + +/// A token validity predicate. +pub fn vp( + ctx: &Ctx, + token: &Address, + keys_changed: &BTreeSet, + verifiers: &BTreeSet
, +) -> VpResult { + let mut change: Change = 0; + for key in keys_changed.iter() { + match token::is_balance_key(token, key) { + None => { + // Unknown changes to this address space are disallowed, but + // unknown changes anywhere else are permitted + if key.segments.get(0) == Some(&token.to_db_key()) { + return reject(); + } + } + Some(owner) => { + // accumulate the change + let pre: Amount = match owner { + Address::Internal(InternalAddress::IbcMint) => { + Amount::max() + } + Address::Internal(InternalAddress::IbcBurn) => { + Amount::default() + } + _ => ctx.read_pre(key)?.unwrap_or_default(), + }; + let post: Amount = match owner { + Address::Internal(InternalAddress::IbcMint) => { + ctx.read_temp(key)?.unwrap_or_else(Amount::max) + } + Address::Internal(InternalAddress::IbcBurn) => { + ctx.read_temp(key)?.unwrap_or_default() + } + _ => ctx.read_post(key)?.unwrap_or_default(), + }; + let this_change = post.change() - pre.change(); + change += this_change; + // make sure that the spender approved the transaction + if this_change < 0 && !verifiers.contains(owner) { + return reject(); + } + } + } + } + Ok(change == 0) +} From 71820f6acfa6939469177982fba61b15d82bb7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 20 Jul 2022 21:02:42 +0200 Subject: [PATCH 216/373] tests: update for VM API changes --- Cargo.lock | 3 +- tests/Cargo.toml | 3 +- tests/src/native_vp/mod.rs | 56 ++-- tests/src/native_vp/pos.rs | 137 ++++---- tests/src/vm_host_env/ibc.rs | 174 +++++----- tests/src/vm_host_env/mod.rs | 609 +++++++++++++++-------------------- tests/src/vm_host_env/tx.rs | 13 +- tests/src/vm_host_env/vp.rs | 14 +- 8 files changed, 460 insertions(+), 549 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76df02e9b0..b7fce8b64d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4018,7 +4018,8 @@ dependencies = [ "libp2p", "namada", "namada_apps", - "namada_vm_env", + "namada_tx_prelude", + "namada_vp_prelude", "pretty_assertions", "proptest", "prost 0.9.0", diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ed453ad450..cc437b3686 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -13,7 +13,8 @@ wasm-runtime = ["namada/wasm-runtime"] [dependencies] namada = {path = "../shared", features = ["testing", "ibc-mocks"]} -namada_vm_env = {path = "../vm_env"} +namada_vp_prelude = {path = "../vp_prelude"} +namada_tx_prelude = {path = "../tx_prelude"} chrono = "0.4.19" concat-idents = "1.1.2" prost = "0.9.0" diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index be450a7086..808299a86d 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -1,47 +1,38 @@ mod pos; +use std::collections::BTreeSet; + use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::Sha256Hasher; -use namada::vm::wasm::compilation_cache; -use namada::vm::wasm::compilation_cache::common::Cache; -use namada::vm::{wasm, WasmCacheRwAccess}; -use tempfile::TempDir; +use namada::types::address::Address; +use namada::types::storage; +use namada::vm::WasmCacheRwAccess; use crate::tx::TestTxEnv; type NativeVpCtx<'a> = Ctx<'a, MockDB, Sha256Hasher, WasmCacheRwAccess>; -type VpCache = Cache; #[derive(Debug)] pub struct TestNativeVpEnv { - pub vp_cache_dir: TempDir, - pub vp_wasm_cache: VpCache, pub tx_env: TestTxEnv, + pub address: Address, + pub verifiers: BTreeSet
, + pub keys_changed: BTreeSet, } impl TestNativeVpEnv { - pub fn new(tx_env: TestTxEnv) -> Self { - let (vp_wasm_cache, vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); - - Self { - vp_cache_dir, - vp_wasm_cache, - tx_env, - } - } -} + pub fn from_tx_env(tx_env: TestTxEnv, address: Address) -> Self { + // Find the tx verifiers and keys_changes the same way as protocol would + let verifiers = tx_env.get_verifiers(); -impl Default for TestNativeVpEnv { - fn default() -> Self { - let (vp_wasm_cache, vp_cache_dir) = - wasm::compilation_cache::common::testing::cache(); + let keys_changed = tx_env.all_touched_storage_keys(); Self { - vp_cache_dir, - vp_wasm_cache, - tx_env: TestTxEnv::default(), + address, + tx_env, + verifiers, + keys_changed, } } } @@ -51,20 +42,10 @@ impl TestNativeVpEnv { pub fn validate_tx<'a, T>( &'a self, init_native_vp: impl Fn(NativeVpCtx<'a>) -> T, - // The function is applied on the `tx_data` when called - mut apply_tx: impl FnMut(&[u8]), ) -> Result::Error> where T: NativeVp, { - let tx_data = self.tx_env.tx.data.as_ref().cloned().unwrap_or_default(); - apply_tx(&tx_data); - - // Find the tx verifiers and keys_changes the same way as protocol would - let verifiers = self.tx_env.get_verifiers(); - - let keys_changed = self.tx_env.all_touched_storage_keys(); - let ctx = Ctx { iterators: Default::default(), gas_meter: Default::default(), @@ -72,10 +53,13 @@ impl TestNativeVpEnv { write_log: &self.tx_env.write_log, tx: &self.tx_env.tx, vp_wasm_cache: self.tx_env.vp_wasm_cache.clone(), + address: &self.address, + keys_changed: &self.keys_changed, + verifiers: &self.verifiers, }; let tx_data = self.tx_env.tx.data.as_ref().cloned().unwrap_or_default(); let native_vp = init_native_vp(ctx); - native_vp.validate_tx(&tx_data, &keys_changed, &verifiers) + native_vp.validate_tx(&tx_data, &self.keys_changed, &self.verifiers) } } diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index be1844c6cd..1700be2264 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -105,10 +105,10 @@ mod tests { use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::pos::PosParams; use namada::types::storage::Epoch; - use namada::types::token; - use namada_vm_env::proof_of_stake::parameters::testing::arb_pos_params; - use namada_vm_env::proof_of_stake::{staking_token_address, PosVP}; - use namada_vm_env::tx_prelude::Address; + use namada::types::{address, token}; + use namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params; + use namada_tx_prelude::proof_of_stake::{staking_token_address, PosVP}; + use namada_tx_prelude::Address; use proptest::prelude::*; use proptest::prop_state_machine; use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; @@ -410,10 +410,13 @@ mod tests { fn validate_transitions(&self) { // Use the tx_env to run PoS VP let tx_env = tx_host_env::take(); - let vp_env = TestNativeVpEnv::new(tx_env); - let result = vp_env.validate_tx(PosVP::new, |_tx_data| {}); + + let vp_env = TestNativeVpEnv::from_tx_env(tx_env, address::POS); + let result = vp_env.validate_tx(PosVP::new); + // Put the tx_env back before checking the result tx_host_env::set(vp_env.tx_env); + let result = result.expect("Validation of valid changes must not fail!"); @@ -534,24 +537,25 @@ pub mod testing { use derivative::Derivative; use itertools::Either; use namada::ledger::pos::namada_proof_of_stake::btree_set::BTreeSetShims; + use namada::ledger::tx_env::TxEnv; use namada::types::key::common::PublicKey; use namada::types::key::RefTo; use namada::types::storage::Epoch; use namada::types::{address, key, token}; - use namada_vm_env::proof_of_stake::epoched::{ + use namada_tx_prelude::proof_of_stake::epoched::{ DynEpochOffset, Epoched, EpochedDelta, }; - use namada_vm_env::proof_of_stake::types::{ + use namada_tx_prelude::proof_of_stake::types::{ Bond, Unbond, ValidatorState, VotingPower, VotingPowerDelta, WeightedValidator, }; - use namada_vm_env::proof_of_stake::{ + use namada_tx_prelude::proof_of_stake::{ staking_token_address, BondId, Bonds, PosParams, Unbonds, }; - use namada_vm_env::tx_prelude::{Address, PoS}; + use namada_tx_prelude::Address; use proptest::prelude::*; - use crate::tx::tx_host_env; + use crate::tx::{self, tx_host_env}; #[derive(Clone, Debug, Default)] pub struct TestValidator { @@ -783,8 +787,8 @@ pub mod testing { /// the VP. pub fn apply(self, is_current_tx_valid: bool) { // Read the PoS parameters - use namada_vm_env::tx_prelude::PosRead; - let params = PoS.read_pos_params(); + use namada_tx_prelude::PosRead; + let params = tx::ctx().read_pos_params().unwrap(); let current_epoch = tx_host_env::with(|env| { // Reset the gas meter on each change, so that we never run @@ -811,7 +815,7 @@ pub mod testing { params: &PosParams, current_epoch: Epoch, ) -> PosStorageChanges { - use namada_vm_env::tx_prelude::PosRead; + use namada_tx_prelude::PosRead; match self { ValidPosAction::InitValidator(addr) => { @@ -869,8 +873,10 @@ pub mod testing { // Read the validator's current total deltas (this may be // updated by previous transition(s) within the same // transaction via write log) - let validator_total_deltas = - PoS.read_validator_total_deltas(&validator).unwrap(); + let validator_total_deltas = tx::ctx() + .read_validator_total_deltas(&validator) + .unwrap() + .unwrap(); let total_delta = validator_total_deltas .get_at_offset(current_epoch, offset, params) .unwrap_or_default(); @@ -1007,8 +1013,10 @@ pub mod testing { // Read the validator's current total deltas (this may be // updated by previous transition(s) within the same // transaction via write log) - let validator_total_deltas_cur = - PoS.read_validator_total_deltas(&validator).unwrap(); + let validator_total_deltas_cur = tx::ctx() + .read_validator_total_deltas(&validator) + .unwrap() + .unwrap(); let total_delta_cur = validator_total_deltas_cur .get_at_offset(current_epoch, offset, params) .unwrap_or_default(); @@ -1073,10 +1081,12 @@ pub mod testing { changes } ValidPosAction::Withdraw { owner, validator } => { - let unbonds = PoS.read_unbond(&BondId { - source: owner.clone(), - validator: validator.clone(), - }); + let unbonds = tx::ctx() + .read_unbond(&BondId { + source: owner.clone(), + validator: validator.clone(), + }) + .unwrap(); let token_delta: i128 = unbonds .and_then(|unbonds| unbonds.get(current_epoch)) @@ -1108,7 +1118,7 @@ pub mod testing { // invalid changes is_current_tx_valid: bool, ) { - use namada_vm_env::tx_prelude::{PosRead, PosWrite}; + use namada_tx_prelude::{PosRead, PosWrite}; match change { PosStorageChange::SpawnAccount { address } => { @@ -1126,7 +1136,7 @@ pub mod testing { source: owner, validator, }; - let bonds = PoS.read_bond(&bond_id); + let bonds = tx::ctx().read_bond(&bond_id).unwrap(); let bonds = if delta >= 0 { let amount: u64 = delta.try_into().unwrap(); let amount: token::Amount = amount.into(); @@ -1190,7 +1200,7 @@ pub mod testing { ); bonds }; - PoS.write_bond(&bond_id, bonds); + tx::ctx().write_bond(&bond_id, bonds).unwrap(); } PosStorageChange::Unbond { owner, @@ -1202,8 +1212,8 @@ pub mod testing { source: owner, validator, }; - let bonds = PoS.read_bond(&bond_id).unwrap(); - let unbonds = PoS.read_unbond(&bond_id); + let bonds = tx::ctx().read_bond(&bond_id).unwrap().unwrap(); + let unbonds = tx::ctx().read_unbond(&bond_id).unwrap(); let amount: u64 = delta.try_into().unwrap(); let mut to_unbond: token::Amount = amount.into(); let mut value = Unbond { @@ -1260,10 +1270,11 @@ pub mod testing { } None => Unbonds::init(value, current_epoch, params), }; - PoS.write_unbond(&bond_id, unbonds); + tx::ctx().write_unbond(&bond_id, unbonds).unwrap(); } PosStorageChange::TotalVotingPower { vp_delta, offset } => { - let mut total_voting_powers = PoS.read_total_voting_power(); + let mut total_voting_powers = + tx::ctx().read_total_voting_power().unwrap(); let vp_delta: i64 = vp_delta.try_into().unwrap(); match offset { Either::Left(offset) => { @@ -1283,10 +1294,14 @@ pub mod testing { ); } } - PoS.write_total_voting_power(total_voting_powers) + tx::ctx() + .write_total_voting_power(total_voting_powers) + .unwrap() } PosStorageChange::ValidatorAddressRawHash { address } => { - PoS.write_validator_address_raw_hash(&address); + tx::ctx() + .write_validator_address_raw_hash(&address) + .unwrap(); } PosStorageChange::ValidatorSet { validator, @@ -1302,8 +1317,9 @@ pub mod testing { ); } PosStorageChange::ValidatorConsensusKey { validator, pk } => { - let consensus_key = PoS + let consensus_key = tx::ctx() .read_validator_consensus_key(&validator) + .unwrap() .map(|mut consensus_keys| { consensus_keys.set(pk.clone(), current_epoch, params); consensus_keys @@ -1311,21 +1327,26 @@ pub mod testing { .unwrap_or_else(|| { Epoched::init(pk, current_epoch, params) }); - PoS.write_validator_consensus_key(&validator, consensus_key); + tx::ctx() + .write_validator_consensus_key(&validator, consensus_key) + .unwrap(); } PosStorageChange::ValidatorStakingRewardsAddress { validator, address, } => { - PoS.write_validator_staking_reward_address(&validator, address); + tx::ctx() + .write_validator_staking_reward_address(&validator, address) + .unwrap(); } PosStorageChange::ValidatorTotalDeltas { validator, delta, offset, } => { - let total_deltas = PoS + let total_deltas = tx::ctx() .read_validator_total_deltas(&validator) + .unwrap() .map(|mut total_deltas| { total_deltas.add_at_offset( delta, @@ -1343,15 +1364,18 @@ pub mod testing { params, ) }); - PoS.write_validator_total_deltas(&validator, total_deltas); + tx::ctx() + .write_validator_total_deltas(&validator, total_deltas) + .unwrap(); } PosStorageChange::ValidatorVotingPower { validator, vp_delta: delta, offset, } => { - let voting_power = PoS + let voting_power = tx::ctx() .read_validator_voting_power(&validator) + .unwrap() .map(|mut voting_powers| { match offset { Either::Left(offset) => { @@ -1381,11 +1405,14 @@ pub mod testing { params, ) }); - PoS.write_validator_voting_power(&validator, voting_power); + tx::ctx() + .write_validator_voting_power(&validator, voting_power) + .unwrap(); } PosStorageChange::ValidatorState { validator, state } => { - let state = PoS + let state = tx::ctx() .read_validator_state(&validator) + .unwrap() .map(|mut states| { states.set(state, current_epoch, params); states @@ -1393,16 +1420,15 @@ pub mod testing { .unwrap_or_else(|| { Epoched::init_at_genesis(state, current_epoch) }); - PoS.write_validator_state(&validator, state); + tx::ctx().write_validator_state(&validator, state).unwrap(); } PosStorageChange::StakingTokenPosBalance { delta } => { let balance_key = token::balance_key( &staking_token_address(), - &::POS_ADDRESS, - ) - .to_string(); + &::POS_ADDRESS, + ); let mut balance: token::Amount = - tx_host_env::read(&balance_key).unwrap_or_default(); + tx::ctx().read(&balance_key).unwrap().unwrap_or_default(); if delta < 0 { let to_spend: u64 = (-delta).try_into().unwrap(); let to_spend: token::Amount = to_spend.into(); @@ -1412,16 +1438,17 @@ pub mod testing { let to_recv: token::Amount = to_recv.into(); balance.receive(&to_recv); } - tx_host_env::write(&balance_key, balance); + tx::ctx().write(&balance_key, balance).unwrap(); } PosStorageChange::WithdrawUnbond { owner, validator } => { let bond_id = BondId { source: owner, validator, }; - let mut unbonds = PoS.read_unbond(&bond_id).unwrap(); + let mut unbonds = + tx::ctx().read_unbond(&bond_id).unwrap().unwrap(); unbonds.delete_current(current_epoch, params); - PoS.write_unbond(&bond_id, unbonds); + tx::ctx().write_unbond(&bond_id, unbonds).unwrap(); } } } @@ -1433,12 +1460,12 @@ pub mod testing { current_epoch: Epoch, params: &PosParams, ) { - use namada_vm_env::tx_prelude::{PosRead, PosWrite}; + use namada_tx_prelude::{PosRead, PosWrite}; let validator_total_deltas = - PoS.read_validator_total_deltas(&validator); + tx::ctx().read_validator_total_deltas(&validator).unwrap(); // println!("Read validator set"); - let mut validator_set = PoS.read_validator_set(); + let mut validator_set = tx::ctx().read_validator_set().unwrap(); // println!("Read validator set: {:#?}", validator_set); validator_set.update_from_offset( |validator_set, epoch| { @@ -1545,7 +1572,7 @@ pub mod testing { params, ); // println!("Write validator set {:#?}", validator_set); - PoS.write_validator_set(validator_set); + tx::ctx().write_validator_set(validator_set).unwrap(); } pub fn arb_invalid_pos_action( @@ -1625,8 +1652,8 @@ pub mod testing { /// Apply an invalid PoS storage action. pub fn apply(self) { // Read the PoS parameters - use namada_vm_env::tx_prelude::PosRead; - let params = PoS.read_pos_params(); + use namada_tx_prelude::PosRead; + let params = tx::ctx().read_pos_params().unwrap(); for (epoch, changes) in self.changes { for change in changes { @@ -1641,9 +1668,9 @@ pub mod testing { params: &PosParams, current_epoch: Epoch, ) -> bool { - use namada_vm_env::tx_prelude::PosRead; + use namada_tx_prelude::PosRead; - let validator_sets = PoS.read_validator_set(); + let validator_sets = tx::ctx().read_validator_set().unwrap(); let validator_set = validator_sets .get_at_offset(current_epoch, DynEpochOffset::PipelineLen, params) .unwrap(); diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 13e7bd3882..0f94214a35 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -1,5 +1,5 @@ use core::time::Duration; -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use std::str::FromStr; use namada::ibc::applications::ics20_fungible_token_transfer::msgs::transfer::MsgTransfer; @@ -60,24 +60,22 @@ use namada::ledger::ibc::vp::{ use namada::ledger::native_vp::{Ctx, NativeVp}; use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::Sha256Hasher; +use namada::ledger::tx_env::TxEnv; use namada::proto::Tx; use namada::tendermint_proto::Protobuf; use namada::types::address::{self, Address, InternalAddress}; use namada::types::ibc::data::FungibleTokenPacketData; -use namada::types::ibc::IbcEvent; -use namada::types::storage::{BlockHash, BlockHeight, Key}; -use namada::types::time::Rfc3339String; +use namada::types::storage::{self, BlockHash, BlockHeight}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; -use tempfile::TempDir; -use crate::tx::*; +use crate::tx::{self, *}; const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; +const ADDRESS: Address = Address::Internal(InternalAddress::Ibc); pub struct TestIbcVp<'a> { pub ibc: Ibc<'a, MockDB, Sha256Hasher, WasmCacheRwAccess>, - pub keys_changed: BTreeSet, } impl<'a> TestIbcVp<'a> { @@ -85,14 +83,16 @@ impl<'a> TestIbcVp<'a> { &self, tx_data: &[u8], ) -> std::result::Result { - self.ibc - .validate_tx(tx_data, &self.keys_changed, &BTreeSet::new()) + self.ibc.validate_tx( + tx_data, + self.ibc.ctx.keys_changed, + self.ibc.ctx.verifiers, + ) } } pub struct TestIbcTokenVp<'a> { pub token: IbcToken<'a, MockDB, Sha256Hasher, WasmCacheRwAccess>, - pub keys_changed: BTreeSet, } impl<'a> TestIbcTokenVp<'a> { @@ -100,82 +100,19 @@ impl<'a> TestIbcTokenVp<'a> { &self, tx_data: &[u8], ) -> std::result::Result { - self.token - .validate_tx(tx_data, &self.keys_changed, &BTreeSet::new()) + self.token.validate_tx( + tx_data, + self.token.ctx.keys_changed, + self.token.ctx.verifiers, + ) } } -pub struct TestIbcActions; - -impl IbcActions for TestIbcActions { - /// Read IBC-related data - fn read_ibc_data(&self, key: &Key) -> Option> { - tx_host_env::read_bytes(key.to_string()) - } - - /// Write IBC-related data - fn write_ibc_data(&self, key: &Key, data: impl AsRef<[u8]>) { - tx_host_env::write_bytes(key.to_string(), data) - } - - /// Delete IBC-related data - fn delete_ibc_data(&self, key: &Key) { - tx_host_env::delete(key.to_string()) - } - - /// Emit an IBC event - fn emit_ibc_event(&self, event: IbcEvent) { - tx_host_env::emit_ibc_event(&event) - } - - fn transfer_token( - &self, - src: &Address, - dest: &Address, - token: &Address, - amount: Amount, - ) { - let src_key = token::balance_key(token, src); - let dest_key = token::balance_key(token, dest); - let src_bal: Option = tx_host_env::read(&src_key.to_string()); - let mut src_bal = src_bal.unwrap_or_else(|| match src { - Address::Internal(InternalAddress::IbcMint) => Amount::max(), - _ => unreachable!(), - }); - src_bal.spend(&amount); - let mut dest_bal: Amount = - tx_host_env::read(&dest_key.to_string()).unwrap_or_default(); - dest_bal.receive(&amount); - match src { - Address::Internal(InternalAddress::IbcMint) => { - tx_host_env::write_temp(&src_key.to_string(), src_bal) - } - Address::Internal(InternalAddress::IbcBurn) => unreachable!(), - _ => tx_host_env::write(&src_key.to_string(), src_bal), - } - match dest { - Address::Internal(InternalAddress::IbcMint) => unreachable!(), - Address::Internal(InternalAddress::IbcBurn) => { - tx_host_env::write_temp(&dest_key.to_string(), dest_bal) - } - _ => tx_host_env::write(&dest_key.to_string(), dest_bal), - } - } - - fn get_height(&self) -> BlockHeight { - tx_host_env::get_block_height() - } - - fn get_header_time(&self) -> Rfc3339String { - tx_host_env::get_block_time() - } -} - -/// Initialize IBC VP by running a transaction. -pub fn init_ibc_vp_from_tx<'a>( +/// Validate an IBC transaction with IBC VP. +pub fn validate_ibc_vp_from_tx<'a>( tx_env: &'a TestTxEnv, tx: &'a Tx, -) -> (TestIbcVp<'a>, TempDir) { +) -> std::result::Result { let (verifiers, keys_changed) = tx_env .write_log .verifiers_and_changed_keys(&tx_env.verifiers); @@ -186,27 +123,30 @@ pub fn init_ibc_vp_from_tx<'a>( addr, verifiers ); } - let (vp_wasm_cache, vp_cache_dir) = + let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); let ctx = Ctx::new( + &ADDRESS, &tx_env.storage, &tx_env.write_log, tx, VpGasMeter::new(0), + &keys_changed, + &verifiers, vp_wasm_cache, ); let ibc = Ibc { ctx }; - (TestIbcVp { ibc, keys_changed }, vp_cache_dir) + TestIbcVp { ibc }.validate(tx.data.as_ref().unwrap()) } -/// Initialize the native token VP for the given address -pub fn init_token_vp_from_tx<'a>( +/// Validate the native token VP for the given address +pub fn validate_token_vp_from_tx<'a>( tx_env: &'a TestTxEnv, tx: &'a Tx, addr: &Address, -) -> (TestIbcTokenVp<'a>, TempDir) { +) -> std::result::Result { let (verifiers, keys_changed) = tx_env .write_log .verifiers_and_changed_keys(&tx_env.verifiers); @@ -217,26 +157,57 @@ pub fn init_token_vp_from_tx<'a>( addr, verifiers ); } - let (vp_wasm_cache, vp_cache_dir) = + let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); let ctx = Ctx::new( + &ADDRESS, &tx_env.storage, &tx_env.write_log, tx, VpGasMeter::new(0), + &keys_changed, + &verifiers, vp_wasm_cache, ); let token = IbcToken { ctx }; - ( - TestIbcTokenVp { - token, - keys_changed, - }, - vp_cache_dir, - ) -} + TestIbcTokenVp { token }.validate(tx.data.as_ref().unwrap()) +} + +// /// Initialize the native token VP for the given address +// pub fn init_token_vp_from_tx<'a>( +// tx_env: &'a TestTxEnv, +// tx: &'a Tx, +// addr: &Address, +// ) -> (TestIbcTokenVp<'a>, TempDir) { +// let (verifiers, keys_changed) = tx_env +// .write_log +// .verifiers_and_changed_keys(&tx_env.verifiers); +// if !verifiers.contains(addr) { +// panic!( +// "The given token address {} isn't part of the tx verifiers set: \ +// {:#?}", +// addr, verifiers +// ); +// } +// let (vp_wasm_cache, vp_cache_dir) = +// wasm::compilation_cache::common::testing::cache(); + +// let ctx = Ctx::new( +// &ADDRESS, +// &tx_env.storage, +// &tx_env.write_log, +// tx, +// VpGasMeter::new(0), +// &keys_changed, +// &verifiers, +// vp_wasm_cache, +// ); +// let token = IbcToken { ctx }; + +// (TestIbcTokenVp { token }, vp_cache_dir) +// } /// Initialize the test storage. Requires initialized [`tx_host_env::ENV`]. pub fn init_storage() -> (Address, Address) { @@ -251,17 +222,18 @@ pub fn init_storage() -> (Address, Address) { // initialize a token let code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); - let token = tx_host_env::init_account(code.clone()); + let token = tx::ctx().init_account(code.clone()).unwrap(); // initialize an account - let account = tx_host_env::init_account(code); + let account = tx::ctx().init_account(code).unwrap(); let key = token::balance_key(&token, &account); let init_bal = Amount::from(1_000_000_000u64); - tx_host_env::write(key.to_string(), init_bal); + tx::ctx().write(&key, init_bal).unwrap(); (token, account) } -pub fn prepare_client() -> (ClientId, AnyClientState, HashMap>) { +pub fn prepare_client() +-> (ClientId, AnyClientState, HashMap>) { let mut writes = HashMap::new(); let msg = msg_create_client(); @@ -292,7 +264,7 @@ pub fn prepare_client() -> (ClientId, AnyClientState, HashMap>) { pub fn prepare_opened_connection( client_id: &ClientId, -) -> (ConnectionId, HashMap>) { +) -> (ConnectionId, HashMap>) { let mut writes = HashMap::new(); let conn_id = connection_id(0); @@ -313,7 +285,7 @@ pub fn prepare_opened_connection( pub fn prepare_opened_channel( conn_id: &ConnectionId, is_ordered: bool, -) -> (PortId, ChannelId, HashMap>) { +) -> (PortId, ChannelId, HashMap>) { let mut writes = HashMap::new(); // port diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index ce547520f7..d539289533 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -26,6 +26,7 @@ mod tests { use namada::ledger::ibc::vp::{ get_dummy_header as tm_dummy_header, Error as IbcError, }; + use namada::ledger::tx_env::TxEnv; use namada::proto::{SignedTxData, Tx}; use namada::tendermint_proto::Protobuf; use namada::types::key::*; @@ -33,16 +34,14 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; - use namada_vm_env::tx_prelude::{ - BorshDeserialize, BorshSerialize, KeyValIterator, - }; - use namada_vm_env::vp_prelude::{PostKeyValIterator, PreKeyValIterator}; + use namada_tx_prelude::{BorshDeserialize, BorshSerialize}; + use namada_vp_prelude::VpEnv; use prost::Message; use test_log::test; - use super::ibc; - use super::tx::*; - use super::vp::*; + use super::{ibc, tx, vp}; + use crate::tx::{tx_host_env, TestTxEnv}; + use crate::vp::{vp_host_env, TestVpEnv}; // paths to the WASMs used for tests const VP_ALWAYS_TRUE_WASM: &str = "../wasm_for_tests/vp_always_true.wasm"; @@ -53,8 +52,8 @@ mod tests { // The environment must be initialized first tx_host_env::init(); - let key = "key"; - let read_value: Option = tx_host_env::read(key); + let key = storage::Key::parse("key").unwrap(); + let read_value: Option = tx::ctx().read(&key).unwrap(); assert_eq!( None, read_value, "Trying to read a key that doesn't exists shouldn't find any value" @@ -62,9 +61,9 @@ mod tests { // Write some value let value = "test".repeat(4); - tx_host_env::write(key, value.clone()); + tx::ctx().write(&key, value.clone()).unwrap(); - let read_value: Option = tx_host_env::read(key); + let read_value: Option = tx::ctx().read(&key).unwrap(); assert_eq!( Some(value), read_value, @@ -73,8 +72,8 @@ mod tests { ); let value = vec![1_u8; 1000]; - tx_host_env::write(key, value.clone()); - let read_value: Option> = tx_host_env::read(key); + tx::ctx().write(&key, value.clone()).unwrap(); + let read_value: Option> = tx::ctx().read(&key).unwrap(); assert_eq!( Some(value), read_value, @@ -87,18 +86,18 @@ mod tests { // The environment must be initialized first tx_host_env::init(); - let key = "key"; + let key = storage::Key::parse("key").unwrap(); assert!( - !tx_host_env::has_key(key), + !tx::ctx().has_key(&key).unwrap(), "Before a key-value is written, its key shouldn't be found" ); // Write some value let value = "test".to_string(); - tx_host_env::write(key, value); + tx::ctx().write(&key, value).unwrap(); assert!( - tx_host_env::has_key(key), + tx::ctx().has_key(&key).unwrap(), "After a key-value has been written, its key should be found" ); } @@ -112,28 +111,28 @@ mod tests { tx_host_env::set(env); // Trying to delete a key that doesn't exists should be a no-op - let key = "key"; - tx_host_env::delete(key); + let key = storage::Key::parse("key").unwrap(); + tx::ctx().delete(&key).unwrap(); let value = "test".to_string(); - tx_host_env::write(key, value); + tx::ctx().write(&key, value).unwrap(); assert!( - tx_host_env::has_key(key), + tx::ctx().has_key(&key).unwrap(), "After a key-value has been written, its key should be found" ); // Then delete it - tx_host_env::delete(key); + tx::ctx().delete(&key).unwrap(); assert!( - !tx_host_env::has_key(key), + !tx::ctx().has_key(&key).unwrap(), "After a key has been deleted, its key shouldn't be found" ); // Trying to delete a validity predicate should fail - let key = storage::Key::validity_predicate(&test_account).to_string(); + let key = storage::Key::validity_predicate(&test_account); assert!( - panic::catch_unwind(|| { tx_host_env::delete(key) }) + panic::catch_unwind(|| { tx::ctx().delete(&key).unwrap() }) .err() .map(|a| a.downcast_ref::().cloned().unwrap()) .unwrap() @@ -146,10 +145,10 @@ mod tests { // The environment must be initialized first tx_host_env::init(); - let iter: KeyValIterator> = tx_host_env::iter_prefix("empty"); - assert_eq!( - iter.count(), - 0, + let empty_key = storage::Key::parse("empty").unwrap(); + let mut iter = tx::ctx().iter_prefix(&empty_key).unwrap(); + assert!( + tx::ctx().iter_next(&mut iter).unwrap().is_none(), "Trying to iter a prefix that doesn't have any matching keys \ should yield an empty iterator." ); @@ -166,8 +165,14 @@ mod tests { }); // Then try to iterate over their prefix - let iter: KeyValIterator = - tx_host_env::iter_prefix(prefix.to_string()); + let iter = tx::ctx().iter_prefix(&prefix).unwrap(); + let iter = itertools::unfold(iter, |iter| { + if let Ok(Some((key, value))) = tx::ctx().iter_next(iter) { + let decoded_value = i32::try_from_slice(&value[..]).unwrap(); + return Some((key, decoded_value)); + } + None + }); let expected = (0..10).map(|i| (format!("{}/{}", prefix, i), i)); itertools::assert_equal(iter.sorted(), expected.sorted()); } @@ -182,7 +187,7 @@ mod tests { "pre-condition" ); let verifier = address::testing::established_address_1(); - tx_host_env::insert_verifier(&verifier); + tx::ctx().insert_verifier(&verifier).unwrap(); assert!( tx_host_env::with(|env| env.verifiers.contains(&verifier)), "The verifier should have been inserted" @@ -201,7 +206,7 @@ mod tests { tx_host_env::init(); let code = vec![]; - tx_host_env::init_account(code); + tx::ctx().init_account(code).unwrap(); } #[test] @@ -211,7 +216,7 @@ mod tests { let code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); - tx_host_env::init_account(code); + tx::ctx().init_account(code).unwrap(); } #[test] @@ -220,19 +225,19 @@ mod tests { tx_host_env::init(); assert_eq!( - tx_host_env::get_chain_id(), + tx::ctx().get_chain_id().unwrap(), tx_host_env::with(|env| env.storage.get_chain_id().0) ); assert_eq!( - tx_host_env::get_block_height(), + tx::ctx().get_block_height().unwrap(), tx_host_env::with(|env| env.storage.get_block_height().0) ); assert_eq!( - tx_host_env::get_block_hash(), + tx::ctx().get_block_hash().unwrap(), tx_host_env::with(|env| env.storage.get_block_hash().0) ); assert_eq!( - tx_host_env::get_block_epoch(), + tx::ctx().get_block_epoch().unwrap(), tx_host_env::with(|env| env.storage.get_current_epoch().0) ); } @@ -252,9 +257,9 @@ mod tests { env.write_log.write(&key, value_raw.clone()).unwrap() }); - let read_pre_value: Option = vp_host_env::read_pre(key_raw); + let read_pre_value: Option = vp::CTX.read_pre(&key).unwrap(); assert_eq!(None, read_pre_value); - let read_post_value: Option = vp_host_env::read_post(key_raw); + let read_post_value: Option = vp::CTX.read_post(&key).unwrap(); assert_eq!(Some(value), read_post_value); } @@ -268,7 +273,6 @@ mod tests { // Write some value to storage let existing_key = addr_key.join(&Key::parse("existing_key_raw").unwrap()); - let existing_key_raw = existing_key.to_string(); let existing_value = vec![2_u8; 1000]; // Values written to storage have to be encoded with Borsh let existing_value_encoded = existing_value.try_to_vec().unwrap(); @@ -280,25 +284,24 @@ mod tests { // In a transaction, write override the existing key's value and add // another key-value let override_value = "override".to_string(); - let new_key = - addr_key.join(&Key::parse("new_key").unwrap()).to_string(); + let new_key = addr_key.join(&Key::parse("new_key").unwrap()); let new_value = "vp".repeat(4); // Initialize the VP environment via a transaction vp_host_env::init_from_tx(addr, tx_env, |_addr| { // Override the existing key - tx_host_env::write(&existing_key_raw, &override_value); + tx::ctx().write(&existing_key, &override_value).unwrap(); // Write the new key-value - tx_host_env::write(&new_key, new_value.clone()); + tx::ctx().write(&new_key, new_value.clone()).unwrap(); }); assert!( - vp_host_env::has_key_pre(&existing_key_raw), + vp::CTX.has_key_pre(&existing_key).unwrap(), "The existing key before transaction should be found" ); let pre_existing_value: Option> = - vp_host_env::read_pre(&existing_key_raw); + vp::CTX.read_pre(&existing_key).unwrap(); assert_eq!( Some(existing_value), pre_existing_value, @@ -307,10 +310,11 @@ mod tests { ); assert!( - !vp_host_env::has_key_pre(&new_key), + !vp::CTX.has_key_pre(&new_key).unwrap(), "The new key before transaction shouldn't be found" ); - let pre_new_value: Option> = vp_host_env::read_pre(&new_key); + let pre_new_value: Option> = + vp::CTX.read_pre(&new_key).unwrap(); assert_eq!( None, pre_new_value, "The new value read from state before transaction shouldn't yet \ @@ -318,11 +322,11 @@ mod tests { ); assert!( - vp_host_env::has_key_post(&existing_key_raw), + vp::CTX.has_key_post(&existing_key).unwrap(), "The existing key after transaction should still be found" ); let post_existing_value: Option = - vp_host_env::read_post(&existing_key_raw); + vp::CTX.read_post(&existing_key).unwrap(); assert_eq!( Some(override_value), post_existing_value, @@ -331,10 +335,11 @@ mod tests { ); assert!( - vp_host_env::has_key_post(&new_key), + vp::CTX.has_key_post(&new_key).unwrap(), "The new key after transaction should be found" ); - let post_new_value: Option = vp_host_env::read_post(&new_key); + let post_new_value: Option = + vp::CTX.read_post(&new_key).unwrap(); assert_eq!( Some(new_value), post_new_value, @@ -362,26 +367,37 @@ mod tests { // In a transaction, write override the existing key's value and add // another key-value let existing_key = prefix.join(&Key::parse(5.to_string()).unwrap()); - let existing_key_raw = existing_key.to_string(); let new_key = prefix.join(&Key::parse(11.to_string()).unwrap()); - let new_key_raw = new_key.to_string(); // Initialize the VP environment via a transaction vp_host_env::init_from_tx(addr, tx_env, |_addr| { // Override one of the existing keys - tx_host_env::write(&existing_key_raw, 100_i32); + tx::ctx().write(&existing_key, 100_i32).unwrap(); // Write the new key-value under the same prefix - tx_host_env::write(&new_key_raw, 11.try_to_vec().unwrap()); + tx::ctx().write(&new_key, 11_i32).unwrap(); }); - let iter_pre: PreKeyValIterator = - vp_host_env::iter_prefix_pre(prefix.to_string()); + let iter_pre = vp::CTX.iter_prefix(&prefix).unwrap(); + let iter_pre = itertools::unfold(iter_pre, |iter| { + if let Ok(Some((key, value))) = vp::CTX.iter_pre_next(iter) { + if let Ok(decoded_value) = i32::try_from_slice(&value[..]) { + return Some((key, decoded_value)); + } + } + None + }); let expected_pre = (0..10).map(|i| (format!("{}/{}", prefix, i), i)); itertools::assert_equal(iter_pre.sorted(), expected_pre.sorted()); - let iter_post: PostKeyValIterator = - vp_host_env::iter_prefix_post(prefix.to_string()); + let iter_post = vp::CTX.iter_prefix(&prefix).unwrap(); + let iter_post = itertools::unfold(iter_post, |iter| { + if let Ok(Some((key, value))) = vp::CTX.iter_post_next(iter) { + let decoded_value = i32::try_from_slice(&value[..]).unwrap(); + return Some((key, decoded_value)); + } + None + }); let expected_post = (0..10).map(|i| { let val = if i == 5 { 100 } else { i }; (format!("{}/{}", prefix, i), val) @@ -421,13 +437,21 @@ mod tests { .expect("decoding signed data we just signed") }); assert_eq!(&signed_tx_data.data, data); - assert!(vp_host_env::verify_tx_signature(&pk, &signed_tx_data.sig)); + assert!( + vp::CTX + .verify_tx_signature(&pk, &signed_tx_data.sig) + .unwrap() + ); let other_keypair = key::testing::keypair_2(); - assert!(!vp_host_env::verify_tx_signature( - &other_keypair.ref_to(), - &signed_tx_data.sig - )); + assert!( + !vp::CTX + .verify_tx_signature( + &other_keypair.ref_to(), + &signed_tx_data.sig + ) + .unwrap() + ); } } @@ -437,19 +461,19 @@ mod tests { vp_host_env::init(); assert_eq!( - vp_host_env::get_chain_id(), + vp::CTX.get_chain_id().unwrap(), vp_host_env::with(|env| env.storage.get_chain_id().0) ); assert_eq!( - vp_host_env::get_block_height(), + vp::CTX.get_block_height().unwrap(), vp_host_env::with(|env| env.storage.get_block_height().0) ); assert_eq!( - vp_host_env::get_block_hash(), + vp::CTX.get_block_hash().unwrap(), vp_host_env::with(|env| env.storage.get_block_hash().0) ); assert_eq!( - vp_host_env::get_block_epoch(), + vp::CTX.get_block_epoch().unwrap(), vp_host_env::with(|env| env.storage.get_current_epoch().0) ); } @@ -462,14 +486,14 @@ mod tests { // evaluating without any code should fail let empty_code = vec![]; let input_data = vec![]; - let result = vp_host_env::eval(empty_code, input_data); + let result = vp::CTX.eval(empty_code, input_data).unwrap(); assert!(!result); // evaluating the VP template which always returns `true` should pass let code = std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); let input_data = vec![]; - let result = vp_host_env::eval(code, input_data); + let result = vp::CTX.eval(code, input_data).unwrap(); assert!(result); // evaluating the VP template which always returns `false` shouldn't @@ -477,7 +501,7 @@ mod tests { let code = std::fs::read(VP_ALWAYS_FALSE_WASM).expect("cannot load wasm"); let input_data = vec![]; - let result = vp_host_env::eval(code, input_data); + let result = vp::CTX.eval(code, input_data).unwrap(); assert!(!result); } @@ -503,25 +527,25 @@ mod tests { .sign(&key::testing::keypair_1()); // get and increment the connection counter let counter_key = ibc::client_counter_key(); - let counter = ibc::TestIbcActions + let counter = tx::ctx() .get_and_inc_counter(&counter_key) .expect("getting the counter failed"); let client_id = ibc::client_id(msg.client_state.client_type(), counter) .expect("invalid client ID"); // only insert a client type - let client_type_key = ibc::client_type_key(&client_id).to_string(); - tx_host_env::write( - &client_type_key, - msg.client_state.client_type().as_str().as_bytes(), - ); + let client_type_key = ibc::client_type_key(&client_id); + tx::ctx() + .write( + &client_type_key, + msg.client_state.client_type().as_str().as_bytes(), + ) + .unwrap(); // Check should fail due to no client state let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(matches!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect_err("validation succeeded unexpectedly"), + result.expect_err("validation succeeded unexpectedly"), IbcError::ClientError(_), )); // drop the transaction @@ -540,18 +564,14 @@ mod tests { .sign(&key::testing::keypair_1()); // create a client with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("creating a client failed"); // Check let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Commit env.commit_tx_and_block(); @@ -582,27 +602,28 @@ mod tests { let same_client_state = old_data.client_state.clone(); let height = same_client_state.latest_height(); let same_consensus_state = old_data.consensus_state; - let client_state_key = ibc::client_state_key(&client_id).to_string(); - tx_host_env::write_bytes( - &client_state_key, - same_client_state.encode_vec().unwrap(), - ); - let consensus_state_key = - ibc::consensus_state_key(&client_id, height).to_string(); - tx_host_env::write( - &consensus_state_key, - same_consensus_state.encode_vec().unwrap(), - ); + let client_state_key = ibc::client_state_key(&client_id); + tx::ctx() + .write_bytes( + &client_state_key, + same_client_state.encode_vec().unwrap(), + ) + .unwrap(); + let consensus_state_key = ibc::consensus_state_key(&client_id, height); + tx::ctx() + .write( + &consensus_state_key, + same_consensus_state.encode_vec().unwrap(), + ) + .unwrap(); let event = ibc::make_update_client_event(&client_id, &msg); - tx_host_env::emit_ibc_event(&event.try_into().unwrap()); + TxEnv::emit_ibc_event(tx::ctx(), &event.try_into().unwrap()).unwrap(); // Check should fail due to the invalid updating let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(matches!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect_err("validation succeeded unexpectedly"), + result.expect_err("validation succeeded unexpectedly"), IbcError::ClientError(_), )); // drop the transaction @@ -620,18 +641,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // update the client with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("updating the client failed"); // Check let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Commit env.commit_tx_and_block(); @@ -653,18 +670,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // upgrade the client with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("upgrading the client failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -696,25 +709,25 @@ mod tests { .sign(&key::testing::keypair_1()); // get and increment the connection counter let counter_key = ibc::connection_counter_key(); - let counter = ibc::TestIbcActions + let counter = tx::ctx() .get_and_inc_counter(&counter_key) .expect("getting the counter failed"); // insert a new opened connection let conn_id = ibc::connection_id(counter); - let conn_key = ibc::connection_key(&conn_id).to_string(); + let conn_key = ibc::connection_key(&conn_id); let mut connection = ibc::init_connection(&msg); ibc::open_connection(&mut connection); - tx_host_env::write_bytes(&conn_key, connection.encode_vec().unwrap()); + tx::ctx() + .write_bytes(&conn_key, connection.encode_vec().unwrap()) + .unwrap(); let event = ibc::make_open_init_connection_event(&conn_id, &msg); - tx_host_env::emit_ibc_event(&event.try_into().unwrap()); + TxEnv::emit_ibc_event(tx::ctx(), &event.try_into().unwrap()).unwrap(); // Check should fail due to directly opening a connection let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(matches!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect_err("validation succeeded unexpectedly"), + result.expect_err("validation succeeded unexpectedly"), IbcError::ConnectionError(_), )); // drop the transaction @@ -732,18 +745,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // init a connection with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("creating a connection failed"); // Check let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Commit env.commit_tx_and_block(); @@ -762,18 +771,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // open the connection with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("opening the connection failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -802,18 +807,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // open try a connection with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("creating a connection failed"); // Check let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Commit env.commit_tx_and_block(); @@ -833,18 +834,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // open the connection with the mssage - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("opening the connection failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -880,27 +877,24 @@ mod tests { // not bind a port // get and increment the channel counter let counter_key = ibc::channel_counter_key(); - let counter = ibc::TestIbcActions + let counter = tx::ctx() .get_and_inc_counter(&counter_key) .expect("getting the counter failed"); // channel let channel_id = ibc::channel_id(counter); let port_channel_id = ibc::port_channel_id(port_id, channel_id.clone()); - let channel_key = ibc::channel_key(&port_channel_id).to_string(); - tx_host_env::write_bytes( - &channel_key, - msg.channel.encode_vec().unwrap(), - ); + let channel_key = ibc::channel_key(&port_channel_id); + tx::ctx() + .write_bytes(&channel_key, msg.channel.encode_vec().unwrap()) + .unwrap(); let event = ibc::make_open_init_channel_event(&channel_id, &msg); - tx_host_env::emit_ibc_event(&event.try_into().unwrap()); + TxEnv::emit_ibc_event(tx::ctx(), &event.try_into().unwrap()).unwrap(); // Check should fail due to no port binding let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(matches!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect_err("validation succeeded unexpectedly"), + result.expect_err("validation succeeded unexpectedly"), IbcError::ChannelError(_), )); // drop the transaction @@ -922,32 +916,32 @@ mod tests { } .sign(&key::testing::keypair_1()); // bind a port - ibc::TestIbcActions + tx::ctx() .bind_port(&port_id) .expect("binding the port failed"); // get and increment the channel counter let counter_key = ibc::channel_counter_key(); - let counter = ibc::TestIbcActions + let counter = tx::ctx() .get_and_inc_counter(&counter_key) .expect("getting the counter failed"); // insert a opened channel let channel_id = ibc::channel_id(counter); let port_channel_id = ibc::port_channel_id(port_id, channel_id.clone()); - let channel_key = ibc::channel_key(&port_channel_id).to_string(); + let channel_key = ibc::channel_key(&port_channel_id); let mut channel = msg.channel.clone(); ibc::open_channel(&mut channel); - tx_host_env::write_bytes(&channel_key, channel.encode_vec().unwrap()); + tx::ctx() + .write_bytes(&channel_key, channel.encode_vec().unwrap()) + .unwrap(); let event = ibc::make_open_init_channel_event(&channel_id, &msg); - tx_host_env::emit_ibc_event(&event.try_into().unwrap()); + TxEnv::emit_ibc_event(tx::ctx(), &event.try_into().unwrap()).unwrap(); // Check should fail due to directly opening a channel let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); assert!(matches!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect_err("validation succeeded unexpectedly"), + result.expect_err("validation succeeded unexpectedly"), IbcError::ChannelError(_), )); // drop the transaction @@ -966,18 +960,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // init a channel with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("creating a channel failed"); // Check let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Commit env.commit_tx_and_block(); @@ -994,18 +984,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // open the channle with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("opening the channel failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -1036,18 +1022,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // try open a channel with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("creating a channel failed"); // Check let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Commit env.commit_tx_and_block(); @@ -1065,18 +1047,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // open a channel with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("opening the channel failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -1109,18 +1087,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // close the channel with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("closing the channel failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -1154,18 +1128,14 @@ mod tests { .sign(&key::testing::keypair_1()); // close the channel with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("closing the channel failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -1202,18 +1172,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // send the token and a packet with the data - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("sending a packet failed"); // Check let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Check if the token was escrowed let escrow = address::Address::Internal( address::InternalAddress::ibc_escrow_address( @@ -1221,12 +1187,9 @@ mod tests { msg.source_channel.to_string(), ), ); - let (token_vp, _) = ibc::init_token_vp_from_tx(&env, &tx, &escrow); - assert!( - token_vp - .validate(tx.data.as_ref().unwrap()) - .expect("token validation failed unexpectedly") - ); + let token_vp_result = + ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + assert!(token_vp_result.expect("token validation failed unexpectedly")); // Commit env.commit_tx_and_block(); @@ -1246,18 +1209,14 @@ mod tests { } .sign(&key::testing::keypair_1()); // ack the packet with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("the packet ack failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -1292,27 +1251,19 @@ mod tests { } .sign(&key::testing::keypair_1()); // send the token and a packet with the data - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("sending a packet failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Check if the token was burned let burn = address::Address::Internal(address::InternalAddress::IbcBurn); - let (token_vp, _) = ibc::init_token_vp_from_tx(&env, &tx, &burn); - assert!( - token_vp - .validate(tx.data.as_ref().unwrap()) - .expect("token validation failed unexpectedly") - ); + let result = ibc::validate_token_vp_from_tx(&env, &tx, &burn); + assert!(result.expect("token validation failed unexpectedly")); } #[test] @@ -1354,27 +1305,19 @@ mod tests { } .sign(&key::testing::keypair_1()); // receive a packet with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("receiving a packet failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Check if the token was minted let mint = address::Address::Internal(address::InternalAddress::IbcMint); - let (token_vp, _) = ibc::init_token_vp_from_tx(&env, &tx, &mint); - assert!( - token_vp - .validate(tx.data.as_ref().unwrap()) - .expect("token validation failed unexpectedly") - ); + let result = ibc::validate_token_vp_from_tx(&env, &tx, &mint); + assert!(result.expect("token validation failed unexpectedly")); } #[test] @@ -1436,25 +1379,17 @@ mod tests { } .sign(&key::testing::keypair_1()); // receive a packet with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("receiving a packet failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Check if the token was unescrowed - let (token_vp, _) = ibc::init_token_vp_from_tx(&env, &tx, &escrow); - assert!( - token_vp - .validate(tx.data.as_ref().unwrap()) - .expect("token validation failed unexpectedly") - ); + let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + assert!(result.expect("token validation failed unexpectedly")); } #[test] @@ -1491,20 +1426,16 @@ mod tests { } .sign(&key::testing::keypair_1()); // send a packet with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("sending a packet failed"); // the transaction does something before senging a packet // Check let mut env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Commit env.commit_tx_and_block(); @@ -1524,20 +1455,16 @@ mod tests { } .sign(&key::testing::keypair_1()); // ack the packet with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("the packet ack failed"); // the transaction does something after the ack // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -1579,20 +1506,16 @@ mod tests { } .sign(&key::testing::keypair_1()); // receive a packet with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("receiving a packet failed"); // the transaction does something according to the packet // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); } #[test] @@ -1624,8 +1547,8 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); // send a packet with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("sending apacket failed"); // Commit @@ -1646,18 +1569,14 @@ mod tests { .sign(&key::testing::keypair_1()); // close the channel with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("closing the channel failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Check if the token was refunded let escrow = address::Address::Internal( address::InternalAddress::ibc_escrow_address( @@ -1665,12 +1584,8 @@ mod tests { packet.source_channel.to_string(), ), ); - let (token_vp, _) = ibc::init_token_vp_from_tx(&env, &tx, &escrow); - assert!( - token_vp - .validate(tx.data.as_ref().unwrap()) - .expect("token validation failed unexpectedly") - ); + let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + assert!(result.expect("token validation failed unexpectedly")); } #[test] @@ -1701,8 +1616,8 @@ mod tests { .encode(&mut tx_data) .expect("encoding failed"); // send a packet with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("sending a packet failed"); // Commit @@ -1723,18 +1638,14 @@ mod tests { .sign(&key::testing::keypair_1()); // close the channel with the message - ibc::TestIbcActions - .dispatch(&tx_data) + tx::ctx() + .dispatch_ibc_action(&tx_data) .expect("closing the channel failed"); // Check let env = tx_host_env::take(); - let (ibc_vp, _) = ibc::init_ibc_vp_from_tx(&env, &tx); - assert!( - ibc_vp - .validate(tx.data.as_ref().unwrap()) - .expect("validation failed unexpectedly") - ); + let result = ibc::validate_ibc_vp_from_tx(&env, &tx); + assert!(result.expect("validation failed unexpectedly")); // Check if the token was refunded let escrow = address::Address::Internal( address::InternalAddress::ibc_escrow_address( @@ -1742,11 +1653,7 @@ mod tests { packet.source_channel.to_string(), ), ); - let (token_vp, _) = ibc::init_token_vp_from_tx(&env, &tx, &escrow); - assert!( - token_vp - .validate(tx.data.as_ref().unwrap()) - .expect("token validation failed unexpectedly") - ); + let result = ibc::validate_token_vp_from_tx(&env, &tx, &escrow); + assert!(result.expect("token validation failed unexpectedly")); } } diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 3a684e8382..346cb6bdd4 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -15,16 +15,25 @@ use namada::types::{key, token}; use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; -use namada_vm_env::tx_prelude::BorshSerialize; +use namada_tx_prelude::{BorshSerialize, Ctx}; use tempfile::TempDir; +/// Tx execution context provides access to host env functions +static mut CTX: Ctx = unsafe { Ctx::new() }; + +/// Tx execution context provides access to host env functions +pub fn ctx() -> &'static mut Ctx { + unsafe { &mut CTX } +} + /// This module combines the native host function implementations from /// `native_tx_host_env` with the functions exposed to the tx wasm /// that will call to the native functions, instead of interfacing via a /// wasm runtime. It can be used for host environment integration tests. pub mod tx_host_env { - pub use namada_vm_env::tx_prelude::*; + pub use namada_tx_prelude::*; + pub use super::ctx; pub use super::native_tx_host_env::*; } diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 61b87e1b3b..d849a11487 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -10,17 +10,27 @@ use namada::types::storage::{self, Key}; use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::{self, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; +use namada_vp_prelude::Ctx; use tempfile::TempDir; use crate::tx::{tx_host_env, TestTxEnv}; +/// VP execution context provides access to host env functions +pub static CTX: Ctx = unsafe { Ctx::new() }; + +/// VP execution context provides access to host env functions +pub fn ctx() -> &'static Ctx { + &CTX +} + /// This module combines the native host function implementations from /// `native_vp_host_env` with the functions exposed to the vp wasm /// that will call to the native functions, instead of interfacing via a /// wasm runtime. It can be used for host environment integration tests. pub mod vp_host_env { - pub use namada_vm_env::vp_prelude::*; + pub use namada_vp_prelude::*; + pub use super::ctx; pub use super::native_vp_host_env::*; } @@ -160,7 +170,7 @@ mod native_vp_host_env { /// Initialize the VP host environment in [`ENV`] by running a transaction. /// The transaction is expected to modify the storage sub-space of the given /// address `addr` or to add it to the set of verifiers using - /// [`tx_host_env::insert_verifier`]. + /// `ctx.insert_verifier`. pub fn init_from_tx( addr: Address, mut tx_env: TestTxEnv, From d7d0ef14ab15ac052abdbf8ef81bd0a385725b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 20 Jul 2022 21:03:18 +0200 Subject: [PATCH 217/373] wasm: update for VM API changes --- tests/src/vm_host_env/ibc.rs | 4 +- wasm/tx_template/Cargo.lock | 19 +- wasm/tx_template/src/lib.rs | 5 +- wasm/vp_template/Cargo.lock | 21 +- wasm/vp_template/src/lib.rs | 12 +- wasm/wasm_source/Cargo.lock | 13 +- wasm/wasm_source/src/tx_bond.rs | 8 +- wasm/wasm_source/src/tx_from_intent.rs | 16 +- wasm/wasm_source/src/tx_ibc.rs | 4 +- wasm/wasm_source/src/tx_init_account.rs | 7 +- wasm/wasm_source/src/tx_init_nft.rs | 5 +- wasm/wasm_source/src/tx_init_proposal.rs | 4 +- wasm/wasm_source/src/tx_init_validator.rs | 5 +- wasm/wasm_source/src/tx_mint_nft.rs | 4 +- wasm/wasm_source/src/tx_transfer.rs | 4 +- wasm/wasm_source/src/tx_unbond.rs | 12 +- wasm/wasm_source/src/tx_update_vp.rs | 4 +- wasm/wasm_source/src/tx_vote_proposal.rs | 4 +- wasm/wasm_source/src/tx_withdraw.rs | 6 +- wasm/wasm_source/src/vp_nft.rs | 389 +++++++++++++--------- wasm/wasm_source/src/vp_testnet_faucet.rs | 94 ++++-- wasm/wasm_source/src/vp_token.rs | 27 +- wasm/wasm_source/src/vp_user.rs | 225 +++++++++---- wasm_for_tests/wasm_source/Cargo.lock | 14 +- wasm_for_tests/wasm_source/Cargo.toml | 1 - wasm_for_tests/wasm_source/src/lib.rs | 90 ++--- 26 files changed, 620 insertions(+), 377 deletions(-) diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 0f94214a35..a838f37819 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -112,7 +112,7 @@ impl<'a> TestIbcTokenVp<'a> { pub fn validate_ibc_vp_from_tx<'a>( tx_env: &'a TestTxEnv, tx: &'a Tx, -) -> std::result::Result { +) -> std::result::Result { let (verifiers, keys_changed) = tx_env .write_log .verifiers_and_changed_keys(&tx_env.verifiers); @@ -146,7 +146,7 @@ pub fn validate_token_vp_from_tx<'a>( tx_env: &'a TestTxEnv, tx: &'a Tx, addr: &Address, -) -> std::result::Result { +) -> std::result::Result { let (verifiers, keys_changed) = tx_env .write_log .verifiers_and_changed_keys(&tx_env.verifiers); diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index bf9d222920..08a4aa8b12 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1421,7 +1421,8 @@ dependencies = [ "concat-idents", "derivative", "namada", - "namada_vm_env", + "namada_tx_prelude", + "namada_vp_prelude", "prost", "serde_json", "sha2 0.9.9", @@ -1435,8 +1436,12 @@ dependencies = [ name = "namada_tx_prelude" version = "0.7.0" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] @@ -1444,9 +1449,19 @@ name = "namada_vm_env" version = "0.7.0" dependencies = [ "borsh", - "hex", + "namada", +] + +[[package]] +name = "namada_vp_prelude" +version = "0.7.0" +dependencies = [ + "borsh", "namada", "namada_macros", + "namada_vm_env", + "sha2 0.10.2", + "thiserror", ] [[package]] diff --git a/wasm/tx_template/src/lib.rs b/wasm/tx_template/src/lib.rs index f507e90bed..473984aa31 100644 --- a/wasm/tx_template/src/lib.rs +++ b/wasm/tx_template/src/lib.rs @@ -1,8 +1,9 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(_ctx: &mut Ctx, tx_data: Vec) -> TxResult { log_string(format!("apply_tx called with data: {:#?}", tx_data)); + Ok(()) } #[cfg(test)] @@ -19,7 +20,7 @@ mod tests { tx_host_env::init(); let tx_data = vec![]; - apply_tx(tx_data); + apply_tx(ctx(), tx_data).unwrap(); let env = tx_host_env::take(); assert!(env.all_touched_storage_keys().is_empty()); diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 98f6c7f71c..91059c4434 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1421,7 +1421,8 @@ dependencies = [ "concat-idents", "derivative", "namada", - "namada_vm_env", + "namada_tx_prelude", + "namada_vp_prelude", "prost", "serde_json", "sha2 0.9.9", @@ -1432,21 +1433,35 @@ dependencies = [ ] [[package]] -name = "namada_vm_env" +name = "namada_tx_prelude" version = "0.7.0" dependencies = [ "borsh", - "hex", "namada", "namada_macros", + "namada_vm_env", + "sha2 0.10.2", + "thiserror", +] + +[[package]] +name = "namada_vm_env" +version = "0.7.0" +dependencies = [ + "borsh", + "namada", ] [[package]] name = "namada_vp_prelude" version = "0.7.0" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] diff --git a/wasm/vp_template/src/lib.rs b/wasm/vp_template/src/lib.rs index 7918072266..35cdabd1c5 100644 --- a/wasm/vp_template/src/lib.rs +++ b/wasm/vp_template/src/lib.rs @@ -2,25 +2,25 @@ use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( + ctx: &Ctx, tx_data: Vec, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, -) -> bool { +) -> VpResult { log_string(format!( "validate_tx called with addr: {}, key_changed: {:#?}, tx_data: \ {:#?}, verifiers: {:?}", addr, keys_changed, tx_data, verifiers )); - for key in keys_changed.iter() { - let key = key.to_string(); - let pre: Option = read_pre(&key); - let post: Option = read_post(&key); + for key in keys_changed { + let pre: Option = ctx.read_pre(&key)?; + let post: Option = ctx.read_post(&key)?; log_string(format!( "validate_tx key: {}, pre: {:#?}, post: {:#?}", key, pre, post, )); } - true + accept() } diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index b7185cc110..19a50588dc 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1421,7 +1421,8 @@ dependencies = [ "concat-idents", "derivative", "namada", - "namada_vm_env", + "namada_tx_prelude", + "namada_vp_prelude", "prost", "serde_json", "sha2 0.9.9", @@ -1435,8 +1436,12 @@ dependencies = [ name = "namada_tx_prelude" version = "0.7.0" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] @@ -1444,17 +1449,19 @@ name = "namada_vm_env" version = "0.7.0" dependencies = [ "borsh", - "hex", "namada", - "namada_macros", ] [[package]] name = "namada_vp_prelude" version = "0.7.0" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 9a5309f927..0d9d390226 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -1,19 +1,19 @@ //! A tx for a PoS bond that stakes tokens via a self-bond or delegation. -use namada_tx_prelude::proof_of_stake::bond_tokens; use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let bond = transaction::pos::Bond::try_from_slice(&signed.data.unwrap()[..]) .unwrap(); if let Err(err) = - bond_tokens(bond.source.as_ref(), &bond.validator, bond.amount) + ctx.bond_tokens(bond.source.as_ref(), &bond.validator, bond.amount) { - debug_log!("Bond failed with: {}", err); + debug_log!("Unbonding failed with: {}", err); panic!() } + Ok(()) } diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs index e39963fae7..9b5afb5ad0 100644 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ b/wasm/wasm_source/src/tx_from_intent.rs @@ -5,7 +5,7 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let tx_data = @@ -14,7 +14,7 @@ fn apply_tx(tx_data: Vec) { let tx_data = tx_data.unwrap(); // make sure that the matchmaker has to validate this tx - insert_verifier(&tx_data.source); + ctx.insert_verifier(&tx_data.source)?; for token::Transfer { source, @@ -23,13 +23,11 @@ fn apply_tx(tx_data: Vec) { amount, } in tx_data.matches.transfers { - token::transfer(&source, &target, &token, amount); + token::transfer(ctx, &source, &target, &token, amount)?; } - tx_data - .matches - .exchanges - .values() - .into_iter() - .for_each(intent::invalidate_exchange); + for intent in tx_data.matches.exchanges.values() { + intent::invalidate_exchange(ctx, intent)?; + } + Ok(()) } diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index e38aa2f856..188ec1ae8d 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -6,7 +6,7 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - Ibc.dispatch(&signed.data.unwrap()).unwrap() + ctx.dispatch_ibc_action(&signed.data.unwrap()) } diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index e976c38941..9d187d3c48 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -4,14 +4,15 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let tx_data = transaction::InitAccount::try_from_slice(&signed.data.unwrap()[..]) .unwrap(); debug_log!("apply_tx called to init a new established account"); - let address = init_account(&tx_data.vp_code); + let address = ctx.init_account(&tx_data.vp_code)?; let pk_key = key::pk_key(&address); - write(&pk_key.to_string(), &tx_data.public_key); + ctx.write(&pk_key, &tx_data.public_key)?; + Ok(()) } diff --git a/wasm/wasm_source/src/tx_init_nft.rs b/wasm/wasm_source/src/tx_init_nft.rs index e26d656b57..ee0db9310d 100644 --- a/wasm/wasm_source/src/tx_init_nft.rs +++ b/wasm/wasm_source/src/tx_init_nft.rs @@ -3,12 +3,13 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let tx_data = transaction::nft::CreateNft::try_from_slice(&signed.data.unwrap()[..]) .unwrap(); log_string("apply_tx called to create a new NFT"); - nft::init_nft(tx_data); + let _address = nft::init_nft(ctx, tx_data)?; + Ok(()) } diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index 3cb1c3d5de..48bd0fa0f5 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let tx_data = transaction::governance::InitProposalData::try_from_slice( &signed.data.unwrap()[..], @@ -11,5 +11,5 @@ fn apply_tx(tx_data: Vec) { .unwrap(); log_string("apply_tx called to create a new governance proposal"); - governance::init_proposal(tx_data); + governance::init_proposal(ctx, tx_data) } diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 79dfedad56..bae45fc533 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -5,14 +5,14 @@ use namada_tx_prelude::transaction::InitValidator; use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let init_validator = InitValidator::try_from_slice(&signed.data.unwrap()[..]).unwrap(); debug_log!("apply_tx called to init a new validator account"); // Register the validator in PoS - match proof_of_stake::init_validator(init_validator) { + match ctx.init_validator(init_validator) { Ok((validator_address, staking_reward_address)) => { debug_log!( "Created validator {} and staking reward account {}", @@ -25,4 +25,5 @@ fn apply_tx(tx_data: Vec) { panic!() } } + Ok(()) } diff --git a/wasm/wasm_source/src/tx_mint_nft.rs b/wasm/wasm_source/src/tx_mint_nft.rs index 692155432c..73c915d123 100644 --- a/wasm/wasm_source/src/tx_mint_nft.rs +++ b/wasm/wasm_source/src/tx_mint_nft.rs @@ -3,12 +3,12 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let tx_data = transaction::nft::MintNft::try_from_slice(&signed.data.unwrap()[..]) .unwrap(); log_string("apply_tx called to mint a new NFT tokens"); - nft::mint_tokens(tx_data); + nft::mint_tokens(ctx, tx_data) } diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index f0ab0ad2d0..e1dabcd851 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -5,7 +5,7 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let transfer = token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); @@ -16,5 +16,5 @@ fn apply_tx(tx_data: Vec) { token, amount, } = transfer; - token::transfer(&source, &target, &token, amount) + token::transfer(ctx, &source, &target, &token, amount) } diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 5d2662ed5c..c3eec798a5 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -1,20 +1,22 @@ //! A tx for a PoS unbond that removes staked tokens from a self-bond or a //! delegation to be withdrawn in or after unbonding epoch. -use namada_tx_prelude::proof_of_stake::unbond_tokens; use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let unbond = transaction::pos::Unbond::try_from_slice(&signed.data.unwrap()[..]) .unwrap(); - if let Err(err) = - unbond_tokens(unbond.source.as_ref(), &unbond.validator, unbond.amount) - { + if let Err(err) = ctx.unbond_tokens( + unbond.source.as_ref(), + &unbond.validator, + unbond.amount, + ) { debug_log!("Unbonding failed with: {}", err); panic!() } + Ok(()) } diff --git a/wasm/wasm_source/src/tx_update_vp.rs b/wasm/wasm_source/src/tx_update_vp.rs index 4b68f11170..d6fdb16a2e 100644 --- a/wasm/wasm_source/src/tx_update_vp.rs +++ b/wasm/wasm_source/src/tx_update_vp.rs @@ -5,11 +5,11 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let update_vp = transaction::UpdateVp::try_from_slice(&signed.data.unwrap()[..]) .unwrap(); debug_log!("update VP for: {:#?}", update_vp.addr); - update_validity_predicate(&update_vp.addr, update_vp.vp_code) + ctx.update_validity_predicate(&update_vp.addr, update_vp.vp_code) } diff --git a/wasm/wasm_source/src/tx_vote_proposal.rs b/wasm/wasm_source/src/tx_vote_proposal.rs index cae8c4ef33..30173d1b34 100644 --- a/wasm/wasm_source/src/tx_vote_proposal.rs +++ b/wasm/wasm_source/src/tx_vote_proposal.rs @@ -3,7 +3,7 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let tx_data = transaction::governance::VoteProposalData::try_from_slice( &signed.data.unwrap()[..], @@ -11,5 +11,5 @@ fn apply_tx(tx_data: Vec) { .unwrap(); log_string("apply_tx called to vote a governance proposal"); - governance::vote_proposal(tx_data); + governance::vote_proposal(ctx, tx_data) } diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index 27bd984a66..adab55ca5a 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -1,17 +1,16 @@ //! A tx for a PoS unbond that removes staked tokens from a self-bond or a //! delegation to be withdrawn in or after unbonding epoch. -use namada_tx_prelude::proof_of_stake::withdraw_tokens; use namada_tx_prelude::*; #[transaction] -fn apply_tx(tx_data: Vec) { +fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let withdraw = transaction::pos::Withdraw::try_from_slice(&signed.data.unwrap()[..]) .unwrap(); - match withdraw_tokens(withdraw.source.as_ref(), &withdraw.validator) { + match ctx.withdraw_tokens(withdraw.source.as_ref(), &withdraw.validator) { Ok(slashed) => { debug_log!("Withdrawal slashed for {}", slashed); } @@ -20,4 +19,5 @@ fn apply_tx(tx_data: Vec) { panic!() } } + Ok(()) } diff --git a/wasm/wasm_source/src/vp_nft.rs b/wasm/wasm_source/src/vp_nft.rs index f1e6dd587b..4ab7f232bd 100644 --- a/wasm/wasm_source/src/vp_nft.rs +++ b/wasm/wasm_source/src/vp_nft.rs @@ -4,33 +4,36 @@ use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( + ctx: &Ctx, tx_data: Vec, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, -) -> bool { +) -> VpResult { log_string(format!( "validate_tx called with token addr: {}, key_changed: {:#?}, \ verifiers: {:?}", addr, keys_changed, verifiers )); - if !is_tx_whitelisted() { - return false; + if !is_tx_whitelisted(ctx)? { + return reject(); } - let vp_check = - keys_changed - .iter() - .all(|key| match key.is_validity_predicate() { - Some(_) => { - let vp: Vec = read_bytes_post(key.to_string()).unwrap(); - is_vp_whitelisted(&vp) + let vp_check = keys_changed.iter().all(|key| { + if key.is_validity_predicate().is_some() { + match ctx.read_bytes_post(key) { + Ok(Some(vp)) => { + matches!(is_vp_whitelisted(ctx, &vp), Ok(true)) } - None => true, - }); - - vp_check && nft::vp(tx_data, &addr, &keys_changed, &verifiers) + _ => false, + } + } else { + true + } + }); + + Ok(vp_check && nft::vp(ctx, tx_data, &addr, &keys_changed, &verifiers)?) } #[cfg(test)] @@ -38,8 +41,9 @@ mod tests { use namada::types::nft::{self, NftToken}; use namada::types::transaction::nft::{CreateNft, MintNft}; use namada_tests::log::test; - use namada_tests::tx::{tx_host_env, TestTxEnv}; + use namada_tests::tx::{self, tx_host_env, TestTxEnv}; use namada_tests::vp::*; + use namada_tx_prelude::TxEnv; use super::*; @@ -59,21 +63,25 @@ mod tests { std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); tx_host_env::set(tx_env); - let nft_address = tx_host_env::nft::init_nft(CreateNft { - tag: "v1".to_string(), - creator: nft_creator.clone(), - vp_code, - keys: vec![], - opt_keys: vec![], - tokens: vec![], - }); + let nft_address = tx_host_env::nft::init_nft( + tx::ctx(), + CreateNft { + tag: "v1".to_string(), + creator: nft_creator.clone(), + vp_code, + keys: vec![], + opt_keys: vec![], + tokens: vec![], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.write_log.commit_tx(); vp_host_env::init_from_tx(nft_address.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::insert_verifier(address) + tx::ctx().insert_verifier(address).unwrap() }); let vp_env = vp_host_env::take(); @@ -82,7 +90,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= vp_env.get_verifiers(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, nft_address, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, nft_address, keys_changed, verifiers) + .unwrap() + ); } /// Test that you can create an nft without tokens @@ -98,26 +109,34 @@ mod tests { std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); tx_host_env::set(tx_env); - let nft_address = tx_host_env::nft::init_nft(CreateNft { - tag: "v1".to_string(), - creator: nft_creator.clone(), - vp_code, - keys: vec![], - opt_keys: vec![], - tokens: vec![], - }); + let nft_address = tx_host_env::nft::init_nft( + tx::ctx(), + CreateNft { + tag: "v1".to_string(), + creator: nft_creator.clone(), + vp_code, + keys: vec![], + opt_keys: vec![], + tokens: vec![], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.write_log.commit_tx(); vp_host_env::init_from_tx(nft_address.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::nft::mint_tokens(MintNft { - address: nft_address.clone(), - tokens: vec![], - creator: nft_creator.clone(), - }); - tx_host_env::insert_verifier(address) + tx_host_env::nft::mint_tokens( + tx::ctx(), + MintNft { + address: nft_address.clone(), + tokens: vec![], + creator: nft_creator.clone(), + }, + ) + .unwrap(); + tx::ctx().insert_verifier(address).unwrap() }); let vp_env = vp_host_env::take(); @@ -127,7 +146,10 @@ mod tests { let verifiers: BTreeSet
= vp_env.get_verifiers(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, nft_address, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, nft_address, keys_changed, verifiers) + .unwrap() + ); } /// Test that you can create an nft with tokens @@ -144,34 +166,42 @@ mod tests { std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); tx_host_env::set(tx_env); - let nft_address = tx_host_env::nft::init_nft(CreateNft { - tag: "v1".to_string(), - creator: nft_creator.clone(), - vp_code, - keys: vec![], - opt_keys: vec![], - tokens: vec![], - }); + let nft_address = tx_host_env::nft::init_nft( + tx::ctx(), + CreateNft { + tag: "v1".to_string(), + creator: nft_creator.clone(), + vp_code, + keys: vec![], + opt_keys: vec![], + tokens: vec![], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.commit_tx_and_block(); vp_host_env::init_from_tx(nft_address.clone(), tx_env, |_| { // Apply transfer in a transaction - tx_host_env::nft::mint_tokens(MintNft { - address: nft_address.clone(), - creator: nft_creator.clone(), - tokens: vec![NftToken { - id: 1, - values: vec![], - opt_values: vec![], - metadata: "".to_string(), - approvals: vec![], - current_owner: Some(nft_token_owner.clone()), - past_owners: vec![], - burnt: false, - }], - }); + tx_host_env::nft::mint_tokens( + tx::ctx(), + MintNft { + address: nft_address.clone(), + creator: nft_creator.clone(), + tokens: vec![NftToken { + id: 1, + values: vec![], + opt_values: vec![], + metadata: "".to_string(), + approvals: vec![], + current_owner: Some(nft_token_owner.clone()), + past_owners: vec![], + burnt: false, + }], + }, + ) + .unwrap(); }); let vp_env = vp_host_env::take(); @@ -181,7 +211,10 @@ mod tests { let verifiers: BTreeSet
= vp_env.get_verifiers(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, nft_address, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, nft_address, keys_changed, verifiers) + .unwrap() + ); } /// Test that only owner can mint new tokens @@ -198,34 +231,42 @@ mod tests { std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); tx_host_env::set(tx_env); - let nft_address = tx_host_env::nft::init_nft(CreateNft { - tag: "v1".to_string(), - creator: nft_creator.clone(), - vp_code, - keys: vec![], - opt_keys: vec![], - tokens: vec![], - }); + let nft_address = tx_host_env::nft::init_nft( + tx::ctx(), + CreateNft { + tag: "v1".to_string(), + creator: nft_creator.clone(), + vp_code, + keys: vec![], + opt_keys: vec![], + tokens: vec![], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.commit_tx_and_block(); vp_host_env::init_from_tx(nft_address.clone(), tx_env, |_| { // Apply transfer in a transaction - tx_host_env::nft::mint_tokens(MintNft { - address: nft_address.clone(), - creator: nft_token_owner.clone(), - tokens: vec![NftToken { - id: 1, - values: vec![], - opt_values: vec![], - metadata: "".to_string(), - approvals: vec![], - current_owner: Some(nft_token_owner.clone()), - past_owners: vec![], - burnt: false, - }], - }); + tx_host_env::nft::mint_tokens( + tx::ctx(), + MintNft { + address: nft_address.clone(), + creator: nft_token_owner.clone(), + tokens: vec![NftToken { + id: 1, + values: vec![], + opt_values: vec![], + metadata: "".to_string(), + approvals: vec![], + current_owner: Some(nft_token_owner.clone()), + past_owners: vec![], + burnt: false, + }], + }, + ) + .unwrap(); }); let vp_env = vp_host_env::take(); @@ -235,7 +276,10 @@ mod tests { let verifiers: BTreeSet
= vp_env.get_verifiers(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, nft_address, keys_changed, verifiers)); + assert!( + !validate_tx(&CTX, tx_data, nft_address, keys_changed, verifiers) + .unwrap() + ); } /// Test that an approval can add another approval @@ -259,45 +303,54 @@ mod tests { std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); tx_host_env::set(tx_env); - let nft_address = tx_host_env::nft::init_nft(CreateNft { - tag: "v1".to_string(), - creator: nft_creator.clone(), - vp_code, - keys: vec![], - opt_keys: vec![], - tokens: vec![], - }); + let nft_address = tx_host_env::nft::init_nft( + tx::ctx(), + CreateNft { + tag: "v1".to_string(), + creator: nft_creator.clone(), + vp_code, + keys: vec![], + opt_keys: vec![], + tokens: vec![], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.commit_tx_and_block(); tx_host_env::set(tx_env); - tx_host_env::nft::mint_tokens(MintNft { - address: nft_address.clone(), - creator: nft_creator.clone(), - tokens: vec![NftToken { - id: 1, - values: vec![], - opt_values: vec![], - metadata: "".to_string(), - approvals: vec![nft_token_approval.clone()], - current_owner: None, - past_owners: vec![], - burnt: false, - }], - }); + tx_host_env::nft::mint_tokens( + tx::ctx(), + MintNft { + address: nft_address.clone(), + creator: nft_creator.clone(), + tokens: vec![NftToken { + id: 1, + values: vec![], + opt_values: vec![], + metadata: "".to_string(), + approvals: vec![nft_token_approval.clone()], + current_owner: None, + past_owners: vec![], + burnt: false, + }], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.commit_tx_and_block(); vp_host_env::init_from_tx(nft_address.clone(), tx_env, |_| { - let approval_key = - nft::get_token_approval_key(&nft_address, "1").to_string(); - tx_host_env::write( - approval_key, - [&nft_token_approval_2, &nft_token_approval], - ); - tx_host_env::insert_verifier(&nft_token_approval); + let approval_key = nft::get_token_approval_key(&nft_address, "1"); + tx::ctx() + .write( + &approval_key, + [&nft_token_approval_2, &nft_token_approval], + ) + .unwrap(); + tx::ctx().insert_verifier(&nft_token_approval).unwrap(); }); let vp_env = vp_host_env::take(); @@ -307,7 +360,10 @@ mod tests { let verifiers: BTreeSet
= vp_env.get_verifiers(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, nft_address, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, nft_address, keys_changed, verifiers) + .unwrap() + ); } /// Test that an approval can add another approval @@ -331,45 +387,54 @@ mod tests { std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); tx_host_env::set(tx_env); - let nft_address = tx_host_env::nft::init_nft(CreateNft { - tag: "v1".to_string(), - creator: nft_creator.clone(), - vp_code, - keys: vec![], - opt_keys: vec![], - tokens: vec![], - }); + let nft_address = tx_host_env::nft::init_nft( + tx::ctx(), + CreateNft { + tag: "v1".to_string(), + creator: nft_creator.clone(), + vp_code, + keys: vec![], + opt_keys: vec![], + tokens: vec![], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.commit_tx_and_block(); tx_host_env::set(tx_env); - tx_host_env::nft::mint_tokens(MintNft { - address: nft_address.clone(), - creator: nft_creator.clone(), - tokens: vec![NftToken { - id: 1, - values: vec![], - opt_values: vec![], - metadata: "".to_string(), - approvals: vec![nft_token_approval.clone()], - current_owner: None, - past_owners: vec![], - burnt: false, - }], - }); + tx_host_env::nft::mint_tokens( + tx::ctx(), + MintNft { + address: nft_address.clone(), + creator: nft_creator.clone(), + tokens: vec![NftToken { + id: 1, + values: vec![], + opt_values: vec![], + metadata: "".to_string(), + approvals: vec![nft_token_approval.clone()], + current_owner: None, + past_owners: vec![], + burnt: false, + }], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.commit_tx_and_block(); vp_host_env::init_from_tx(nft_address.clone(), tx_env, |_| { - let approval_key = - nft::get_token_approval_key(&nft_address, "1").to_string(); - tx_host_env::write( - approval_key, - [&nft_token_approval_2, &nft_token_approval], - ); - tx_host_env::insert_verifier(&nft_token_approval_2); + let approval_key = nft::get_token_approval_key(&nft_address, "1"); + tx::ctx() + .write( + &approval_key, + [&nft_token_approval_2, &nft_token_approval], + ) + .unwrap(); + tx::ctx().insert_verifier(&nft_token_approval_2).unwrap(); }); let vp_env = vp_host_env::take(); @@ -379,7 +444,10 @@ mod tests { let verifiers: BTreeSet
= vp_env.get_verifiers(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, nft_address, keys_changed, verifiers)); + assert!( + !validate_tx(&CTX, tx_data, nft_address, keys_changed, verifiers) + .unwrap() + ); } /// Test nft address cannot be changed @@ -396,21 +464,25 @@ mod tests { std::fs::read(VP_ALWAYS_TRUE_WASM).expect("cannot load wasm"); tx_host_env::set(tx_env); - let nft_address = tx_host_env::nft::init_nft(CreateNft { - tag: "v1".to_string(), - creator: nft_owner.clone(), - vp_code, - keys: vec![], - opt_keys: vec![], - tokens: vec![], - }); + let nft_address = tx_host_env::nft::init_nft( + tx::ctx(), + CreateNft { + tag: "v1".to_string(), + creator: nft_owner.clone(), + vp_code, + keys: vec![], + opt_keys: vec![], + tokens: vec![], + }, + ) + .unwrap(); let mut tx_env = tx_host_env::take(); tx_env.commit_tx_and_block(); vp_host_env::init_from_tx(nft_address.clone(), tx_env, |_| { - let creator_key = nft::get_creator_key(&nft_address).to_string(); - tx_host_env::write(creator_key, &another_address); + let creator_key = nft::get_creator_key(&nft_address); + tx::ctx().write(&creator_key, &another_address).unwrap(); }); let vp_env = vp_host_env::take(); @@ -420,6 +492,9 @@ mod tests { let verifiers: BTreeSet
= vp_env.get_verifiers(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, nft_address, keys_changed, verifiers)); + assert!( + !validate_tx(&CTX, tx_data, nft_address, keys_changed, verifiers) + .unwrap() + ); } } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 553288926e..a3a5630ab7 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -13,11 +13,12 @@ pub const MAX_FREE_DEBIT: i128 = 1_000_000_000; // in micro units #[validity_predicate] fn validate_tx( + ctx: &Ctx, tx_data: Vec, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, -) -> bool { +) -> VpResult { debug_log!( "vp_testnet_faucet called with user addr: {}, key_changed: {:?}, \ verifiers: {:?}", @@ -31,26 +32,31 @@ fn validate_tx( let valid_sig = Lazy::new(|| match &*signed_tx_data { Ok(signed_tx_data) => { - let pk = key::get(&addr); + let pk = key::get(ctx, &addr); match pk { - Some(pk) => verify_tx_signature(&pk, &signed_tx_data.sig), - None => false, + Ok(Some(pk)) => { + matches!( + ctx.verify_tx_signature(&pk, &signed_tx_data.sig), + Ok(true) + ) + } + _ => false, } } _ => false, }); - if !is_tx_whitelisted() { - return false; + if !is_tx_whitelisted(ctx)? { + return reject(); } for key in keys_changed.iter() { let is_valid = if let Some(owner) = token::is_any_token_balance_key(key) { if owner == &addr { - let key = key.to_string(); - let pre: token::Amount = read_pre(&key).unwrap_or_default(); - let post: token::Amount = read_post(&key).unwrap_or_default(); + let pre: token::Amount = ctx.read_pre(key)?.unwrap_or_default(); + let post: token::Amount = + ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); // Debit over `MAX_FREE_DEBIT` has to signed, credit doesn't change >= -MAX_FREE_DEBIT || change >= 0 || *valid_sig @@ -59,18 +65,17 @@ fn validate_tx( true } } else if let Some(owner) = key.is_validity_predicate() { - let key = key.to_string(); - let has_post: bool = has_key_post(&key); + let has_post: bool = ctx.has_key_post(key)?; if owner == &addr { if has_post { - let vp: Vec = read_bytes_post(&key).unwrap(); - return *valid_sig && is_vp_whitelisted(&vp); + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + return Ok(*valid_sig && is_vp_whitelisted(ctx, &vp)?); } else { - return false; + return reject(); } } else { - let vp: Vec = read_bytes_post(&key).unwrap(); - return is_vp_whitelisted(&vp); + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + return is_vp_whitelisted(ctx, &vp); } } else { // Allow any other key change if authorized by a signature @@ -78,10 +83,10 @@ fn validate_tx( }; if !is_valid { debug_log!("key {} modification failed vp", key); - return false; + return reject(); } } - true + accept() } #[cfg(test)] @@ -89,9 +94,10 @@ mod tests { use address::testing::arb_non_internal_address; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; - use namada_tests::tx::{tx_host_env, TestTxEnv}; + use namada_tests::tx::{self, tx_host_env, TestTxEnv}; use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; + use namada_tx_prelude::TxEnv; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -112,7 +118,9 @@ mod tests { // The VP env must be initialized before calling `validate_tx` vp_host_env::init(); - assert!(validate_tx(tx_data, addr, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap() + ); } /// Test that a credit transfer is accepted. @@ -136,7 +144,14 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + tx_host_env::ctx(), + &source, + address, + &token, + amount, + ) + .unwrap(); }); let vp_env = vp_host_env::take(); @@ -145,7 +160,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a validity predicate update without a valid signature is @@ -165,7 +183,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction - tx_host_env::update_validity_predicate(address, &vp_code); + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); }); let vp_env = vp_host_env::take(); @@ -174,7 +194,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a validity predicate update with a valid signature is @@ -198,7 +221,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction - tx_host_env::update_validity_predicate(address, &vp_code); + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); }); let mut vp_env = vp_host_env::take(); @@ -210,7 +235,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } prop_compose! { @@ -251,7 +279,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, amount).unwrap(); }); let vp_env = vp_host_env::take(); @@ -260,7 +288,7 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } /// Test that a debit of less than or equal to [`MAX_FREE_DEBIT`] tokens without a valid signature is accepted. @@ -284,7 +312,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, amount).unwrap(); }); let vp_env = vp_host_env::take(); @@ -293,7 +321,7 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } /// Test that a signed tx that performs arbitrary storage writes or @@ -321,9 +349,9 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Write or delete some data in the transaction if let Some(value) = &storage_value { - tx_host_env::write(storage_key.to_string(), value); + tx::ctx().write(&storage_key, value).unwrap(); } else { - tx_host_env::delete(storage_key.to_string()); + tx::ctx().delete(&storage_key).unwrap(); } }); @@ -336,7 +364,7 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } } } diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 60513ce808..b9d3de8f7d 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -5,11 +5,12 @@ use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( + ctx: &Ctx, _tx_data: Vec, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, -) -> bool { +) -> VpResult { debug_log!( "validate_tx called with token addr: {}, key_changed: {:?}, \ verifiers: {:?}", @@ -18,20 +19,18 @@ fn validate_tx( verifiers ); - if !is_tx_whitelisted() { - return false; + if !is_tx_whitelisted(ctx)? { + return reject(); } - let vp_check = - keys_changed - .iter() - .all(|key| match key.is_validity_predicate() { - Some(_) => { - let vp: Vec = read_bytes_post(key.to_string()).unwrap(); - is_vp_whitelisted(&vp) - } - None => true, - }); + for key in keys_changed.iter() { + if key.is_validity_predicate().is_some() { + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + if !is_vp_whitelisted(ctx, &vp)? { + return reject(); + } + } + } - vp_check && token::vp(&addr, &keys_changed, &verifiers) + token::vp(ctx, &addr, &keys_changed, &verifiers) } diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..3b6ddcf65f 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -57,11 +57,12 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { #[validity_predicate] fn validate_tx( + ctx: &Ctx, tx_data: Vec, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, -) -> bool { +) -> VpResult { debug_log!( "vp_user called with user addr: {}, key_changed: {:?}, verifiers: {:?}", addr, @@ -74,22 +75,32 @@ fn validate_tx( let valid_sig = Lazy::new(|| match &*signed_tx_data { Ok(signed_tx_data) => { - let pk = key::get(&addr); + let pk = key::get(ctx, &addr); match pk { - Some(pk) => verify_tx_signature(&pk, &signed_tx_data.sig), - None => false, + Ok(Some(pk)) => { + matches!( + ctx.verify_tx_signature(&pk, &signed_tx_data.sig), + Ok(true) + ) + } + _ => false, } } _ => false, }); let valid_intent = Lazy::new(|| match &*signed_tx_data { - Ok(signed_tx_data) => check_intent_transfers(&addr, signed_tx_data), + Ok(signed_tx_data) => { + matches!( + check_intent_transfers(ctx, &addr, signed_tx_data), + Ok(true) + ) + } _ => false, }); - if !is_tx_whitelisted() { - return false; + if !is_tx_whitelisted(ctx)? { + return reject(); } for key in keys_changed.iter() { @@ -97,10 +108,10 @@ fn validate_tx( let is_valid = match key_type { KeyType::Token(owner) => { if owner == &addr { - let key = key.to_string(); - let pre: token::Amount = read_pre(&key).unwrap_or_default(); + let pre: token::Amount = + ctx.read_pre(key)?.unwrap_or_default(); let post: token::Amount = - read_post(&key).unwrap_or_default(); + ctx.read_post(key)?.unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't let valid = change >= 0 || *valid_sig || *valid_intent; @@ -150,11 +161,10 @@ fn validate_tx( } KeyType::InvalidIntentSet(owner) => { if owner == &addr { - let key = key.to_string(); let pre: HashSet = - read_pre(&key).unwrap_or_default(); + ctx.read_pre(key)?.unwrap_or_default(); let post: HashSet = - read_post(&key).unwrap_or_default(); + ctx.read_post(key)?.unwrap_or_default(); // A new invalid intent must have been added pre.len() + 1 == post.len() } else { @@ -184,18 +194,17 @@ fn validate_tx( } } KeyType::Vp(owner) => { - let key = key.to_string(); - let has_post: bool = has_key_post(&key); + let has_post: bool = ctx.has_key_post(key)?; if owner == &addr { if has_post { - let vp: Vec = read_bytes_post(&key).unwrap(); - return *valid_sig && is_vp_whitelisted(&vp); + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + *valid_sig && is_vp_whitelisted(ctx, &vp)? } else { - return false; + false } } else { - let vp: Vec = read_bytes_post(&key).unwrap(); - return is_vp_whitelisted(&vp); + let vp: Vec = ctx.read_bytes_post(key)?.unwrap(); + is_vp_whitelisted(ctx, &vp)? } } KeyType::Unknown => { @@ -211,24 +220,25 @@ fn validate_tx( }; if !is_valid { debug_log!("key {} modification failed vp", key); - return false; + return reject(); } } - true + accept() } fn check_intent_transfers( + ctx: &Ctx, addr: &Address, signed_tx_data: &SignedTxData, -) -> bool { +) -> EnvResult { if let Some((raw_intent_transfers, exchange, intent)) = try_decode_intent(addr, signed_tx_data) { log_string("check intent"); - return check_intent(addr, exchange, intent, raw_intent_transfers); + return check_intent(ctx, addr, exchange, intent, raw_intent_transfers); } - false + reject() } fn try_decode_intent( @@ -259,25 +269,26 @@ fn try_decode_intent( } fn check_intent( + ctx: &Ctx, addr: &Address, exchange: namada_vp_prelude::Signed, intent: namada_vp_prelude::Signed, raw_intent_transfers: Vec, -) -> bool { +) -> EnvResult { // verify signature - let pk = key::get(addr); + let pk = key::get(ctx, addr)?; if let Some(pk) = pk { if intent.verify(&pk).is_err() { log_string("invalid sig"); - return false; + return reject(); } } else { - return false; + return reject(); } // verify the intent have not been already used - if !intent::vp_exchange(&exchange) { - return false; + if !intent::vp_exchange(ctx, &exchange)? { + return reject(); } // verify the intent is fulfilled @@ -294,10 +305,10 @@ fn check_intent( debug_log!("vp is: {}", vp.is_some()); if let Some(code) = vp { - let eval_result = eval(code.to_vec(), raw_intent_transfers); + let eval_result = ctx.eval(code.to_vec(), raw_intent_transfers)?; debug_log!("eval result: {}", eval_result); if !eval_result { - return false; + return reject(); } } @@ -310,18 +321,19 @@ fn check_intent( rate_min.0 ); - let token_sell_key = token::balance_key(token_sell, addr).to_string(); + let token_sell_key = token::balance_key(token_sell, addr); let mut sell_difference: token::Amount = - read_pre(&token_sell_key).unwrap_or_default(); + ctx.read_pre(&token_sell_key)?.unwrap_or_default(); let sell_post: token::Amount = - read_post(token_sell_key).unwrap_or_default(); + ctx.read_post(&token_sell_key)?.unwrap_or_default(); sell_difference.spend(&sell_post); - let token_buy_key = token::balance_key(token_buy, addr).to_string(); - let buy_pre: token::Amount = read_pre(&token_buy_key).unwrap_or_default(); + let token_buy_key = token::balance_key(token_buy, addr); + let buy_pre: token::Amount = + ctx.read_pre(&token_buy_key)?.unwrap_or_default(); let mut buy_difference: token::Amount = - read_post(token_buy_key).unwrap_or_default(); + ctx.read_post(&token_buy_key)?.unwrap_or_default(); buy_difference.spend(&buy_pre); @@ -354,9 +366,9 @@ fn check_intent( min_buy.change(), buy_diff / sell_diff ); - false + reject() } else { - true + accept() } } @@ -365,9 +377,10 @@ mod tests { use address::testing::arb_non_internal_address; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; - use namada_tests::tx::{tx_host_env, TestTxEnv}; + use namada_tests::tx::{self, tx_host_env, TestTxEnv}; use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; + use namada_tx_prelude::TxEnv; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -388,7 +401,9 @@ mod tests { // The VP env must be initialized before calling `validate_tx` vp_host_env::init(); - assert!(validate_tx(tx_data, addr, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, addr, keys_changed, verifiers).unwrap() + ); } /// Test that a credit transfer is accepted. @@ -412,7 +427,14 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + tx::ctx(), + &source, + address, + &token, + amount, + ) + .unwrap(); }); let vp_env = vp_host_env::take(); @@ -421,7 +443,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a debit transfer without a valid signature is rejected. @@ -445,7 +470,14 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + tx::ctx(), + address, + &target, + &token, + amount, + ) + .unwrap(); }); let vp_env = vp_host_env::take(); @@ -454,7 +486,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a debit transfer with a valid signature is accepted. @@ -482,7 +517,14 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + tx::ctx(), + address, + &target, + &token, + amount, + ) + .unwrap(); }); let mut vp_env = vp_host_env::take(); @@ -494,7 +536,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a transfer on with accounts other than self is accepted. @@ -518,9 +563,16 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { - tx_host_env::insert_verifier(address); + tx::ctx().insert_verifier(address).unwrap(); // Apply transfer in a transaction - tx_host_env::token::transfer(&source, &target, &token, amount); + tx_host_env::token::transfer( + tx::ctx(), + &source, + &target, + &token, + amount, + ) + .unwrap(); }); let vp_env = vp_host_env::take(); @@ -529,7 +581,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } prop_compose! { @@ -569,9 +624,9 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Write or delete some data in the transaction if let Some(value) = &storage_value { - tx_host_env::write(storage_key.to_string(), value); + tx::ctx().write(&storage_key, value).unwrap(); } else { - tx_host_env::delete(storage_key.to_string()); + tx::ctx().delete(&storage_key).unwrap(); } }); @@ -581,7 +636,7 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!(!validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } } @@ -611,9 +666,9 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |_address| { // Write or delete some data in the transaction if let Some(value) = &storage_value { - tx_host_env::write(storage_key.to_string(), value); + tx::ctx().write(&storage_key, value).unwrap(); } else { - tx_host_env::delete(storage_key.to_string()); + tx::ctx().delete(&storage_key).unwrap(); } }); @@ -626,7 +681,7 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); } } @@ -647,7 +702,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction - tx_host_env::update_validity_predicate(address, &vp_code); + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); }); let vp_env = vp_host_env::take(); @@ -656,7 +713,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a validity predicate update with a valid signature is @@ -681,7 +741,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction - tx_host_env::update_validity_predicate(address, &vp_code); + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); }); let mut vp_env = vp_host_env::take(); @@ -693,7 +755,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a validity predicate update is rejected if not whitelisted @@ -717,7 +782,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction - tx_host_env::update_validity_predicate(address, &vp_code); + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); }); let mut vp_env = vp_host_env::take(); @@ -729,7 +796,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a validity predicate update is accepted if whitelisted @@ -755,7 +825,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction - tx_host_env::update_validity_predicate(address, &vp_code); + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); }); let mut vp_env = vp_host_env::take(); @@ -767,7 +839,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } /// Test that a tx is rejected if not whitelisted @@ -797,7 +872,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction - tx_host_env::update_validity_predicate(address, &vp_code); + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); }); let mut vp_env = vp_host_env::take(); @@ -809,7 +886,10 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(!validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } #[test] @@ -834,7 +914,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction - tx_host_env::update_validity_predicate(address, &vp_code); + tx::ctx() + .update_validity_predicate(address, &vp_code) + .unwrap(); }); let mut vp_env = vp_host_env::take(); @@ -846,6 +928,9 @@ mod tests { vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(tx_data, vp_owner, keys_changed, verifiers)); + assert!( + validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + .unwrap() + ); } } diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 39f70c1787..3e7bbbe365 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1432,7 +1432,8 @@ dependencies = [ "concat-idents", "derivative", "namada", - "namada_vm_env", + "namada_tx_prelude", + "namada_vp_prelude", "prost", "serde_json", "sha2 0.9.9", @@ -1446,8 +1447,12 @@ dependencies = [ name = "namada_tx_prelude" version = "0.7.0" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] @@ -1455,17 +1460,19 @@ name = "namada_vm_env" version = "0.7.0" dependencies = [ "borsh", - "hex", "namada", - "namada_macros", ] [[package]] name = "namada_vp_prelude" version = "0.7.0" dependencies = [ + "borsh", + "namada", + "namada_macros", "namada_vm_env", "sha2 0.10.2", + "thiserror", ] [[package]] @@ -1476,7 +1483,6 @@ dependencies = [ "getrandom", "namada_tests", "namada_tx_prelude", - "namada_vm_env", "namada_vp_prelude", "wee_alloc", ] diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index cdd56aaf89..8f3cc9cc36 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -26,7 +26,6 @@ tx_proposal_code = [] [dependencies] namada_tx_prelude = {path = "../../tx_prelude"} -namada_vm_env = {path = "../../vm_env"} namada_vp_prelude = {path = "../../vp_prelude"} borsh = "0.9.1" wee_alloc = "0.4.5" diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 674fb2a2d9..215a967846 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -1,66 +1,71 @@ /// A tx that doesn't do anything. #[cfg(feature = "tx_no_op")] pub mod main { - use namada_vm_env::tx_prelude::*; + use namada_tx_prelude::*; #[transaction] - fn apply_tx(_tx_data: Vec) {} + fn apply_tx(_ctx: &mut Ctx, _tx_data: Vec) -> TxResult { + Ok(()) + } } /// A tx that allocates a memory of size given from the `tx_data: usize`. #[cfg(feature = "tx_memory_limit")] pub mod main { - use namada_vm_env::tx_prelude::*; + use namada_tx_prelude::*; #[transaction] - fn apply_tx(tx_data: Vec) { + fn apply_tx(_ctx: &mut Ctx, tx_data: Vec) -> TxResult { let len = usize::try_from_slice(&tx_data[..]).unwrap(); log_string(format!("allocate len {}", len)); let bytes: Vec = vec![6_u8; len]; // use the variable to prevent it from compiler optimizing it away log_string(format!("{:?}", &bytes[..8])); + Ok(()) } } /// A tx to be used as proposal_code #[cfg(feature = "tx_proposal_code")] pub mod main { - use namada_vm_env::tx_prelude::*; + use namada_tx_prelude::*; #[transaction] - fn apply_tx(_tx_data: Vec) { + fn apply_tx(ctx: &mut Ctx, _tx_data: Vec) -> TxResult { // governance - let target_key = storage::get_min_proposal_grace_epoch_key(); - write(&target_key.to_string(), 9_u64); + let target_key = gov_storage::get_min_proposal_grace_epoch_key(); + ctx.write(&target_key, 9_u64)?; // treasury let target_key = treasury_storage::get_max_transferable_fund_key(); - write(&target_key.to_string(), token::Amount::whole(20_000)); + ctx.write(&target_key, token::Amount::whole(20_000))?; // parameters let target_key = parameters_storage::get_tx_whitelist_storage_key(); - write(&target_key.to_string(), vec!["hash"]); + ctx.write(&target_key, vec!["hash"])?; + Ok(()) } } /// A tx that attempts to read the given key from storage. #[cfg(feature = "tx_read_storage_key")] pub mod main { - use namada_vm_env::tx_prelude::*; + use namada_tx_prelude::*; #[transaction] - fn apply_tx(tx_data: Vec) { + fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { // Allocates a memory of size given from the `tx_data (usize)` - let key = Key::try_from_slice(&tx_data[..]).unwrap(); + let key = storage::Key::try_from_slice(&tx_data[..]).unwrap(); log_string(format!("key {}", key)); - let _result: Vec = read(key.to_string()).unwrap(); + let _result: Vec = ctx.read(&key)?.unwrap(); + Ok(()) } } /// A tx that attempts to write arbitrary data to the given key #[cfg(feature = "tx_write_storage_key")] pub mod main { - use namada_vm_env::tx_prelude::*; + use namada_tx_prelude::*; const TX_NAME: &str = "tx_write"; @@ -81,7 +86,7 @@ pub mod main { } #[transaction] - fn apply_tx(tx_data: Vec) { + fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = match SignedTxData::try_from_slice(&tx_data[..]) { Ok(signed) => { log("got signed data"); @@ -100,15 +105,15 @@ pub mod main { }; let key = match String::from_utf8(data) { Ok(key) => { + let key = storage::Key::parse(key).unwrap(); log(&format!("parsed key from data: {}", key)); key } Err(error) => fatal("getting key", error), }; - let val: Option> = read(key.as_str()); + let val: Option = ctx.read(&key)?; match val { Some(val) => { - let val = String::from_utf8(val).unwrap(); log(&format!("preexisting val is {}", val)); } None => { @@ -119,7 +124,7 @@ pub mod main { "attempting to write new value {} to key {}", ARBITRARY_VALUE, key )); - write(key.as_str(), ARBITRARY_VALUE); + ctx.write(&key, ARBITRARY_VALUE) } } @@ -128,10 +133,10 @@ pub mod main { /// token's VP. #[cfg(feature = "tx_mint_tokens")] pub mod main { - use namada_vm_env::tx_prelude::*; + use namada_tx_prelude::*; #[transaction] - fn apply_tx(tx_data: Vec) { + fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); let transfer = token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); @@ -144,41 +149,43 @@ pub mod main { } = transfer; let target_key = token::balance_key(&token, &target); let mut target_bal: token::Amount = - read(&target_key.to_string()).unwrap_or_default(); + ctx.read(&target_key)?.unwrap_or_default(); target_bal.receive(&amount); - write(&target_key.to_string(), target_bal); + ctx.write(&target_key, target_bal) } } /// A VP that always returns `true`. #[cfg(feature = "vp_always_true")] pub mod main { - use namada_vm_env::vp_prelude::*; + use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( + _ctx: &Ctx, _tx_data: Vec, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, - ) -> bool { - true + ) -> VpResult { + accept() } } /// A VP that always returns `false`. #[cfg(feature = "vp_always_false")] pub mod main { - use namada_vm_env::vp_prelude::*; + use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( + _ctx: &Ctx, _tx_data: Vec, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, - ) -> bool { - false + ) -> VpResult { + reject() } } @@ -186,19 +193,20 @@ pub mod main { /// of `eval`. #[cfg(feature = "vp_eval")] pub mod main { - use namada_vm_env::vp_prelude::*; + use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( + ctx: &Ctx, tx_data: Vec, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, - ) -> bool { + ) -> VpResult { use validity_predicate::EvalVp; let EvalVp { vp_code, input }: EvalVp = EvalVp::try_from_slice(&tx_data[..]).unwrap(); - eval(vp_code, input) + ctx.eval(vp_code, input) } } @@ -206,21 +214,22 @@ pub mod main { // Returns `true`, if the allocation is within memory limits. #[cfg(feature = "vp_memory_limit")] pub mod main { - use namada_vm_env::vp_prelude::*; + use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( + _ctx: &Ctx, tx_data: Vec, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, - ) -> bool { + ) -> VpResult { let len = usize::try_from_slice(&tx_data[..]).unwrap(); log_string(format!("allocate len {}", len)); let bytes: Vec = vec![6_u8; len]; // use the variable to prevent it from compiler optimizing it away log_string(format!("{:?}", &bytes[..8])); - true + accept() } } @@ -228,19 +237,20 @@ pub mod main { /// execution). Returns `true`, if the allocation is within memory limits. #[cfg(feature = "vp_read_storage_key")] pub mod main { - use namada_vm_env::vp_prelude::*; + use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( + ctx: &Ctx, tx_data: Vec, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, - ) -> bool { + ) -> VpResult { // Allocates a memory of size given from the `tx_data (usize)` - let key = Key::try_from_slice(&tx_data[..]).unwrap(); + let key = storage::Key::try_from_slice(&tx_data[..]).unwrap(); log_string(format!("key {}", key)); - let _result: Vec = read_pre(key.to_string()).unwrap(); - true + let _result: Vec = ctx.read_pre(&key)?.unwrap(); + accept() } } From bf604a4c3a7b9b6084d20284c4df2495f212feaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 20 Jul 2022 21:03:53 +0200 Subject: [PATCH 218/373] macros: add error handling to transaction and validity_predicate macros --- macros/src/lib.rs | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index afa49c66ab..33e729dca4 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -15,7 +15,10 @@ use syn::{parse_macro_input, DeriveInput, ItemFn}; /// This macro expects a function with signature: /// /// ```compiler_fail -/// fn apply_tx(tx_data: Vec) +/// fn apply_tx( +/// ctx: &mut Ctx, +/// tx_data: Vec +/// ) -> TxResult /// ``` #[proc_macro_attribute] pub fn transaction(_attr: TokenStream, input: TokenStream) -> TokenStream { @@ -38,7 +41,19 @@ pub fn transaction(_attr: TokenStream, input: TokenStream) -> TokenStream { ) }; let tx_data = slice.to_vec(); - #ident(tx_data); + + // The context on WASM side is only provided by the VM once its + // being executed (in here it's implicit). But because we want to + // have interface consistent with the VP interface, in which the + // context is explicit, in here we're just using an empty `Ctx` + // to "fake" it. + let mut ctx = unsafe { namada_tx_prelude::Ctx::new() }; + + if let Err(err) = #ident(&mut ctx, tx_data) { + namada_tx_prelude::debug_log!("Transaction error: {}", err); + // crash the transaction to abort + panic!(); + } } }; TokenStream::from(gen) @@ -50,11 +65,12 @@ pub fn transaction(_attr: TokenStream, input: TokenStream) -> TokenStream { /// /// ```compiler_fail /// fn validate_tx( +/// ctx: &Ctx, /// tx_data: Vec, /// addr: Address, /// keys_changed: BTreeSet, /// verifiers: BTreeSet
-/// ) -> bool +/// ) -> VpResult /// ``` #[proc_macro_attribute] pub fn validity_predicate( @@ -74,7 +90,6 @@ pub fn validity_predicate( #[no_mangle] extern "C" fn _validate_tx( // VP's account's address - // TODO Should the address be on demand (a call to host function?) addr_ptr: u64, addr_len: u64, tx_data_ptr: u64, @@ -113,11 +128,22 @@ pub fn validity_predicate( }; let verifiers: BTreeSet
= BTreeSet::try_from_slice(slice).unwrap(); + // The context on WASM side is only provided by the VM once its + // being executed (in here it's implicit). But because we want to + // have interface identical with the native VPs, in which the + // context is explicit, in here we're just using an empty `Ctx` + // to "fake" it. + let ctx = unsafe { namada_vp_prelude::Ctx::new() }; + // run validation with the concrete type(s) - if #ident(tx_data, addr, keys_changed, verifiers) { - 1 - } else { - 0 + match #ident(&ctx, tx_data, addr, keys_changed, verifiers) + { + Ok(true) => 1, + Ok(false) => 0, + Err(err) => { + namada_vp_prelude::debug_log!("Validity predicate error: {}", err); + 0 + }, } } }; From d868424682a67517b53cbda42bc9c1ed6c97d7e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 12:44:30 +0200 Subject: [PATCH 219/373] wasm: improve error handling --- wasm/wasm_source/src/tx_bond.rs | 17 ++++++----------- wasm/wasm_source/src/tx_from_intent.rs | 11 +++++------ wasm/wasm_source/src/tx_ibc.rs | 6 ++++-- wasm/wasm_source/src/tx_init_account.rs | 9 +++++---- wasm/wasm_source/src/tx_init_nft.rs | 9 +++++---- wasm/wasm_source/src/tx_init_proposal.rs | 11 ++++++----- wasm/wasm_source/src/tx_init_validator.rs | 8 +++++--- wasm/wasm_source/src/tx_mint_nft.rs | 9 +++++---- wasm/wasm_source/src/tx_transfer.rs | 8 +++++--- wasm/wasm_source/src/tx_unbond.rs | 19 ++++++------------- wasm/wasm_source/src/tx_update_vp.rs | 11 +++++++---- wasm/wasm_source/src/tx_vote_proposal.rs | 14 ++++++++------ wasm/wasm_source/src/tx_withdraw.rs | 21 +++++++++------------ wasm_for_tests/wasm_source/src/lib.rs | 12 ++++-------- 14 files changed, 80 insertions(+), 85 deletions(-) diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 0d9d390226..9b04e2a939 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -4,16 +4,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let bond = - transaction::pos::Bond::try_from_slice(&signed.data.unwrap()[..]) - .unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let bond = transaction::pos::Bond::try_from_slice(&data[..]) + .err_msg("failed to decode Bond")?; - if let Err(err) = - ctx.bond_tokens(bond.source.as_ref(), &bond.validator, bond.amount) - { - debug_log!("Unbonding failed with: {}", err); - panic!() - } - Ok(()) + ctx.bond_tokens(bond.source.as_ref(), &bond.validator, bond.amount) } diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs index 9b5afb5ad0..a299070393 100644 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ b/wasm/wasm_source/src/tx_from_intent.rs @@ -6,12 +6,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - - let tx_data = - intent::IntentTransfers::try_from_slice(&signed.data.unwrap()[..]); - - let tx_data = tx_data.unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let tx_data = intent::IntentTransfers::try_from_slice(&data[..]) + .err_msg("failed to decode IntentTransfers")?; // make sure that the matchmaker has to validate this tx ctx.insert_verifier(&tx_data.source)?; diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index 188ec1ae8d..08b3c60d60 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -7,6 +7,8 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - ctx.dispatch_ibc_action(&signed.data.unwrap()) + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + ctx.dispatch_ibc_action(&data) } diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index 9d187d3c48..05789751b2 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -5,10 +5,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let tx_data = - transaction::InitAccount::try_from_slice(&signed.data.unwrap()[..]) - .unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let tx_data = transaction::InitAccount::try_from_slice(&data[..]) + .err_msg("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); let address = ctx.init_account(&tx_data.vp_code)?; diff --git a/wasm/wasm_source/src/tx_init_nft.rs b/wasm/wasm_source/src/tx_init_nft.rs index ee0db9310d..ace54fc161 100644 --- a/wasm/wasm_source/src/tx_init_nft.rs +++ b/wasm/wasm_source/src/tx_init_nft.rs @@ -4,10 +4,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let tx_data = - transaction::nft::CreateNft::try_from_slice(&signed.data.unwrap()[..]) - .unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let tx_data = transaction::nft::CreateNft::try_from_slice(&data[..]) + .err_msg("failed to decode CreateNft")?; log_string("apply_tx called to create a new NFT"); let _address = nft::init_nft(ctx, tx_data)?; diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index 48bd0fa0f5..728d7613ae 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -4,11 +4,12 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let tx_data = transaction::governance::InitProposalData::try_from_slice( - &signed.data.unwrap()[..], - ) - .unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let tx_data = + transaction::governance::InitProposalData::try_from_slice(&data[..]) + .err_msg("failed to decode InitProposalData")?; log_string("apply_tx called to create a new governance proposal"); governance::init_proposal(ctx, tx_data) diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index bae45fc533..2bfed44fac 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -6,9 +6,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let init_validator = - InitValidator::try_from_slice(&signed.data.unwrap()[..]).unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let init_validator = InitValidator::try_from_slice(&data[..]) + .err_msg("failed to decode InitValidator")?; debug_log!("apply_tx called to init a new validator account"); // Register the validator in PoS diff --git a/wasm/wasm_source/src/tx_mint_nft.rs b/wasm/wasm_source/src/tx_mint_nft.rs index 73c915d123..f132b74158 100644 --- a/wasm/wasm_source/src/tx_mint_nft.rs +++ b/wasm/wasm_source/src/tx_mint_nft.rs @@ -4,10 +4,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let tx_data = - transaction::nft::MintNft::try_from_slice(&signed.data.unwrap()[..]) - .unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let tx_data = transaction::nft::MintNft::try_from_slice(&data[..]) + .err_msg("failed to decode MintNft")?; log_string("apply_tx called to mint a new NFT tokens"); nft::mint_tokens(ctx, tx_data) diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index e1dabcd851..5731612888 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -6,9 +6,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let transfer = - token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let transfer = token::Transfer::try_from_slice(&data[..]) + .err_msg("failed to decode token::Transfer")?; debug_log!("apply_tx called with transfer: {:#?}", transfer); let token::Transfer { source, diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index c3eec798a5..c5ffc1ab6e 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -5,18 +5,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let unbond = - transaction::pos::Unbond::try_from_slice(&signed.data.unwrap()[..]) - .unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let unbond = transaction::pos::Unbond::try_from_slice(&data[..]) + .err_msg("failed to decode Unbond")?; - if let Err(err) = ctx.unbond_tokens( - unbond.source.as_ref(), - &unbond.validator, - unbond.amount, - ) { - debug_log!("Unbonding failed with: {}", err); - panic!() - } - Ok(()) + ctx.unbond_tokens(unbond.source.as_ref(), &unbond.validator, unbond.amount) } diff --git a/wasm/wasm_source/src/tx_update_vp.rs b/wasm/wasm_source/src/tx_update_vp.rs index d6fdb16a2e..d0c41d3bd9 100644 --- a/wasm/wasm_source/src/tx_update_vp.rs +++ b/wasm/wasm_source/src/tx_update_vp.rs @@ -6,10 +6,13 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let update_vp = - transaction::UpdateVp::try_from_slice(&signed.data.unwrap()[..]) - .unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let update_vp = transaction::UpdateVp::try_from_slice(&data[..]) + .err_msg("failed to decode UpdateVp")?; + debug_log!("update VP for: {:#?}", update_vp.addr); + ctx.update_validity_predicate(&update_vp.addr, update_vp.vp_code) } diff --git a/wasm/wasm_source/src/tx_vote_proposal.rs b/wasm/wasm_source/src/tx_vote_proposal.rs index 30173d1b34..614e4a9fa1 100644 --- a/wasm/wasm_source/src/tx_vote_proposal.rs +++ b/wasm/wasm_source/src/tx_vote_proposal.rs @@ -4,12 +4,14 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let tx_data = transaction::governance::VoteProposalData::try_from_slice( - &signed.data.unwrap()[..], - ) - .unwrap(); - log_string("apply_tx called to vote a governance proposal"); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let tx_data = + transaction::governance::VoteProposalData::try_from_slice(&data[..]) + .err_msg("failed to decode VoteProposalData")?; + + debug_log!("apply_tx called to vote a governance proposal"); governance::vote_proposal(ctx, tx_data) } diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index adab55ca5a..bcb64b4af0 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -5,19 +5,16 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - let withdraw = - transaction::pos::Withdraw::try_from_slice(&signed.data.unwrap()[..]) - .unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; + let data = signed.data.ok_or_err_msg("Missing data")?; + let withdraw = transaction::pos::Withdraw::try_from_slice(&data[..]) + .err_msg("failed to decode Withdraw")?; - match ctx.withdraw_tokens(withdraw.source.as_ref(), &withdraw.validator) { - Ok(slashed) => { - debug_log!("Withdrawal slashed for {}", slashed); - } - Err(err) => { - debug_log!("Withdrawal failed with: {}", err); - panic!() - } + let slashed = + ctx.withdraw_tokens(withdraw.source.as_ref(), &withdraw.validator)?; + if slashed != token::Amount::default() { + debug_log!("Withdrawal slashed for {}", slashed); } Ok(()) } diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 215a967846..0e47437704 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -87,13 +87,8 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = match SignedTxData::try_from_slice(&tx_data[..]) { - Ok(signed) => { - log("got signed data"); - signed - } - Err(error) => fatal("getting signed data", error), - }; + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; let data = match signed.data { Some(data) => { log(&format!("got data ({} bytes)", data.len())); @@ -137,7 +132,8 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); + let signed = SignedTxData::try_from_slice(&tx_data[..]) + .err_msg("failed to decode SignedTxData")?; let transfer = token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); log_string(format!("apply_tx called to mint tokens: {:#?}", transfer)); From 221e9f2544cf2420b90bc78fd69328959103638d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 21 Jul 2022 15:55:09 +0200 Subject: [PATCH 220/373] changelog: add #1093 --- .../unreleased/improvements/1093-unify-native-and-wasm-vp.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/1093-unify-native-and-wasm-vp.md diff --git a/.changelog/unreleased/improvements/1093-unify-native-and-wasm-vp.md b/.changelog/unreleased/improvements/1093-unify-native-and-wasm-vp.md new file mode 100644 index 0000000000..e39308413f --- /dev/null +++ b/.changelog/unreleased/improvements/1093-unify-native-and-wasm-vp.md @@ -0,0 +1,3 @@ +- Added WASM transaction and validity predicate `Ctx` with methods for host + environment functions to unify the interface of native VPs and WASM VPs under + `trait VpEnv` ([#1093](https://github.com/anoma/anoma/pull/1093)) \ No newline at end of file From 8bce9152fdb14d927b9756646d9e8633712a9bc3 Mon Sep 17 00:00:00 2001 From: Tomas Zemanovic Date: Mon, 25 Jul 2022 16:08:26 +0200 Subject: [PATCH 221/373] Update shared/src/ledger/vp_env.rs --- shared/src/ledger/vp_env.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 1a77c38312..e2ffd72e13 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -21,11 +21,7 @@ pub trait VpEnv { /// Storage read prefix iterator type PrefixIter; - /// Host functions possible error. - /// - /// In a native VP this may be out-of-gas error, however, because WASM VP is - /// sandboxed and gas accounting is injected by and handled in the host, - /// in WASM VP this error is [`std::convert::Infallible`]. + /// Host functions possible errors, extensible with custom user errors. type Error; /// Storage read prior state Borsh encoded value (before tx execution). It From affa0b550ac045a6f5d1f3cc1cec9f4f24f2884e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 11 Aug 2022 15:56:07 +0200 Subject: [PATCH 222/373] wasm_for_tests: make --- wasm_for_tests/tx_memory_limit.wasm | Bin 135502 -> 135517 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 226185 -> 226159 bytes wasm_for_tests/tx_no_op.wasm | Bin 25027 -> 25030 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 214371 -> 213719 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152558 -> 152558 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 149083 -> 218648 bytes wasm_for_tests/vp_always_false.wasm | Bin 160359 -> 160766 bytes wasm_for_tests/vp_always_true.wasm | Bin 161066 -> 160766 bytes wasm_for_tests/vp_eval.wasm | Bin 161675 -> 160961 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 163364 -> 162919 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170912 -> 170873 bytes 11 files changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 88c8ef0ada51f31c8e0f25c2fa633b4ce20ad65b..c31821bdf45cc9da95e876de939171236563b048 100755 GIT binary patch delta 778 zcmY*XO-NKx6u#%a-*IN_F%FZ?KwY0?lRv04IQ|XhRVQKed6w>t)I$UcxA&T+cI^fzd1CdaJs}*+spX!h*e6abH4csr)Bt=O}h) zQ4#TV2HXr7H4;HQhKi`kO$llx>99oZ8+s z+*M&yB-?)1Pz%dYnh7z1S5NWkX;bDT<0V0nj-=tKcoJ!9)Sb@|Q+14s%Y}s8$Iz+f zoAapFCp1hCampp8i@%Yxpo>KG7`Vi>vMogi^%!ks3aVvqMJFYvywQ&Dvd2UExBo0} zEpF~UPl~SiB+S__;&0_6eEk^?puQG`Vkm&YfUjYME2y}Ux9{;fwcO&+40x>+vT&wY z^b+D0RjHx9kvVh35ssy9AMkfl=V4pe!xw6)h3Y9`Dw`U=Q!v#8wu}*#si*~qLdb4P z?tm;42BK&9mKaQ5qL2~G>Gu>)i;2ud2R>vT)m9NgI`GYnk5635O^vbfu^SBiCSu@U dLWswnANdO4v9qI^Ax3fybEVu`DF?4#{Rdr?&5Qs5 delta 766 zcmY*XTS!zv82Z`o3g7u0QCGa`thTtW!3Y*#IUbkTYY zNrpm@a515$iaLZnY*23zR9HR)8By3n5J*qbOEhQo#bM6(@&Dhq9!yeBE8306|nhMWndHKm=zKR7|3v z^)OnkV z7&7AND3iBjfWZa=99%!`DeqkrrG+68D;&?yht{<}4^}hF8xN!Q$HK=bqf8c;Qd}2v zWz{YQ0phU~j-5%dQg)6&T-5o~lI-XHlqSyxtWhfIe#KQJ&}A0|mYj6nRZeR7LG;%C zE@5g7fQ8&Q%6(%NcKg&5UQ>+ou7cEIbd*_?4T`78a}>!OSb2e1ZMg< z;R-Xw-_Qv(MJ&7z4Ux*>Y`9&wj0O^9hP@UZQJmaPH-4k02~5^2ix%yX5uCDLL|#)S z6UolYepi$$vZYe>FoiEk4_6uTm3i^BWtaVkyEP|FwsRZ{pi2sPER}KtxInDNuHknv z5}W8clOx8q;K{9Fu;PgS{U8g`%swV{@p^q~(xjyRUDvcSlbt6qxAi zIZq%ep7p#X5EB#evktAs@0ITafJXTxhK7bOBt{3=(7QMei5Hh&|k= Vd&G3YE3PDbH#3Q)Oafm%`49Xo&n5r> diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index c9a102773e28ced047f374606582082da3d839d0..d9c858690158ed1142676c5d1e90e1e37cf85e63 100755 GIT binary patch delta 67440 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 delta 67188 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 diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 3c753377ed91ac3086bd2c55079414182be5aa0b..4f19c38cdb3b54224d2cebd493660c22186e1c39 100755 GIT binary patch delta 65786 zcmcHC2Y}R6yZHaf%tdWs0DoP9^ zrvE1yi7Ve}ad13Uz*_Rde+7jlMxl|6#R>|I(~Jod3KK@GVX|c+XPB1d$5OeNx#NOI z^fQvrqCN%5lc$_~>Ws7Ev67>XKJBz=XR({He>mfe)`uT##7rYKzM!xu-LQAJu6?`g zJL)L2Ma#-e+id6VgLd70kLeK!&ET^0 ztI~rr^H-F|GU*jn6=rNic`QCCQ!%t)kXevTl^Ll_g;|hJRHcTN7p5}h1%omrnR2tm zwCZd_K8>m7AN*%bH;w9SgG?%0niv%4f0+`3u?}q^KydSc@+RXm-CS~-G&&p>{<0Fh@`H6frVeW`k_a}xVw@LCe5?f={{O(_vXWfASWVdc50qT}Sijdd zvF6r&mz9aNu)c|PO??yV`Z|{t*9+B{Z$BxU=8CR6t{46m>-G93*1PphtWWBjSYOt; ztf*eBM#NfLmjx)o<+aX9nQ~mKYZr)#b$5LeYg2s_Yjd5;lKGlaMjaYK?fLK3#6^5x zYGUqhRgoH)uZq;k+NzkWcQ9#o?}ab_dX8SNZ;s{N`sP?Zsc(+u>w3MS*n7LEpR@EXgqg2DmuBUokzT6w}) zH4SM0IBj?)(Iqi)Kw^YswvdX&8st?K(tOrrj~I|h$8+&qrqJv$o)$V`_UIpPkx6up zr?W}KhC1gvgG?rA_Ndn5%oJ7?Osy)CwVGrKrjFL{rK>ZAnWA*DENzi3AST;y6&=^kpi=DR+b$w3#XcWD4>pN(KrA=~CJH zgqj*Yg8FUt;Y+3=*9(Vb(3DRw&(R7{!1+ybs?3m)PGUN_(kxW>p#GVKvuCgucdcFZ zW@4t)A+zOdY)r4@e15U?k-OqEsquwoTo?Sj{-W=V{DJ~q@YcukKjnL}MzJnf|JVyb z%~#UXE7=#>lWYOkYpVV(R24|bB8kblb@54*ZuqnKS6_kUsMWY0czp((cLCl^+{&jZu?^mGg_Mm$OINWyY7s>mPBZXy(5u=cJZ% zj#5k36m-(8DQHq+ zX;NhsG<#;o+;O2FOE-w6%F0uWo6EHIr|lY1ULYgTf^1=YP%dN0h~3b-B25fK(1M}m zB`Im(XqdDqk^Y8@OFqW%!Id#W#`m<>G;6fPY2q2>T!ngV=!uighu(!=ie941kd}@P zK?AUI!ekpVMwh-w2I(6=kkc%s@YkW#N?p!jl2bckCrMH1IKJNCUABLOlmks=XL?6D z6e4lC%8t-Gl+q9L)qE!?-9;%SlFgM;GA*jBN-_RHlJhHmxdI&HYhIwpU8!E1PPTokF*ll8P!F!BDEGEatj2q{=e-r*9ie zmsikMzC=+IoBOZy)GCA9FT>oee_VzUpE>P@wKq07`wi=m+ws}vSh@)nO3xrGm9sZl zxoJsrFQ>V(BXLop*0q$xKZv*;^X2 z&hg!)g`@T)iz*Nv^o#6dciiJ1u< ze{lg;#rTG9j}An>6t6i7S#GdqeoNezS9dr#R*~W+F{C{-yRnx$>DmIp_D`r69GSWG zQ`FwvDfOiBzwl041G7iXSGUnfnJX`HT9^0FzWg(Np$r_+nk0C+BDy!8>otLzT&JSgBn_usm{;_ zy@%xU*cjEbW=pc2bTyYiEY*}!&d7dM$uCJ2r+cL+!2D8KP>}Am{)ADJj8vj3!8OL+ zMY@sm^yo>ww=wBL7g$`KZZ0Rfz|64C3W{hBN8ML*%!qZ&MHB;t=XAMjM9WPdN*g5m zPaD)i!srgCn>&|Yc8oDbW=)JnPKU%qW>C&cNIJXVFqV@p;0oOCJY!s7lse<%bK~)x z(_nG7vRE$lST>Q;G|%+dH1*`&ae@~jnG**qOZ}!`Rsrm zEsL}g%H)X*ls3)t_1F_;k<;hEU07#&|Kp5p&a3?g8ZWPJIG}}5l|SKpOZpNyM}IvR zoE+!7i+(eDIp18|(Rr_|RR^gsQjjqwlL!(sJxX5+IL3)FH-dB%C!EpRSm69NqbngF z8PU1t&bhKq8ReA!I;TS}NICm7?(|gKzLg8WT>8D#a;L?}&ZX7t5o5P>y4qQnIK*gH zbD~}uIk21dWpr2+M?$VuKIEE`i5{bz%d1|m=L{UxGhr8&G9%Vx{j@Q24WrokcI*Ux z4;gn$(SdQ9A&Wa}ukN&4)BIVLYO58Kv&tnSn=gAU1ri^)3b##Oy^)t-#PpM(^%x(a6pgjcT4j_0E)4xx#)&EY>1l|sho)i-q(EI z6~11?+D$ZJBi#0+%?0Z_A2i#D=f*hh!M~>>Th5G(IEtKy4sGuEhxAHcd!?L2DGBof z+M~E*AKJ7-zGj(QSU@jNBRIGqKSTE`QR;7D{GlC_OW7*b;Os;9NO^zB-gW#|tQ%)Q zm+nK8Q*-K9%U5XNWPBp^Jl%$7y{@#|rG7H>WaYclxOAu7lA5cXeJA$odk@EK=C4BY z8R~p`KvuR9qutH-xW24VqL-=pj1m zd*^s-n$U(Zu6Vx8UT`>Br{Ccn49}T#_>o4Hv*GZ=jCIbOBbpfPolZwg%BM5!Q(0-? zq+{U7wdb&mG}j~>k%V*45yu$oofb!C`91K+Bl&&pk)8PMAKAI7w&Zf(Q^J6#+U%4` zOij0UemioL(Zjj_9~1fA_^7iJJ<1Jd-Q=dutw)_|^jP0?@;D>kX2kPtM%>(bY5sDK zQz6bhx zS10Xd-Jn#`T(Wv08+zU5RZf?~ne88EWWoKoAzct9B91v9AAcmZcKxsuN{#sE7dr=> zILzqcEa7**_2G$+8Igx*0!GUeCruEBW)QOj7foMgCI>zzRK+8G&ti^;~D3tGmbC< z=a4hIlAd#BbAB&5)8zM?XHMSzk87xMUCoyk^Wht&vxLuk6Y2itI`3UzhFr^LXVbfi z4>xWGi}+j{Pxp6bo^{!u%?PtIe>Nk9=A(tR%}BemClyv@V`T--*JrnK9$q@od2V%* z+B*FPskvXAePlk}p*`zJYp2ze`dXK$tIerx&V}bZQob!Yw_in}7uvR5yAW_0yom_~Z zg{f84;X2F|SDJA%ktud|J+FuHmNWUh4yoe0)fPBcoLAY2`ys}417pqk=vQq<^Q5xK zB_#7+X7l{;FH_>YaNdyJrHSKqkxQ9gVOLI)zIU#P9d`Z4Dh z8OE;8LAeR3UoT<5YMTAExfU$ln)`s?YpetRl9!ruk<`H}=UhkK?tZ~Baq7?&7abdK z6LXr)?Pe4?gXa!vzkPx1Z425t*`iD!85CxmeMmRY58aBKTj%y_z4AY-=R|W`Hm|DJ z&hD0KUDLAO>}2NcVXSlxo7XnET$;}U=Z~BAO1cznx})RI8)ht(cC%D3StE;7 z(w5GI`F#`fKiuq?6T4T|G{JFm16{jW$o?@1-Sh$7{69Cr{V!=8|NH}I*^PT8cgh+a zoy#v7u3?%wpI%a$eVv7-Ja1?%HwF0?meHZ~7P14+eBk`Pu&49D+ilsgd8oS1)Z?lVVqmH=InmvSH1mG9H-eBQxWCXH0rr_j(V)68a!aZ$Yv$oKDD? zr65}(<;*QcI^jH??vYC+25}!!Zo~)4Dl+vbcTB7*3s_W2VX)xO=U$~$G^xRQ?^i^A zu~2T{Yn#fXM94Rl+ypjZ+DH0F8I>4oGU>t06kDfVcu_Vjcdt2q70d1GUpC8aTg_%m zX;Z20a=XgaQn%5@veD9iv(aLi+h?<8oZGgV4KZI*FudG=P5#__a&w$o+5P07yI=9= z?pF*gZ^W$`v1)c-9yHF)q%X=grX=+^<<`BfI11Rl7FU+3R(61WueaZYL(3b=P@#5f zvVj9rna0_4ra~rKd7?<^XNu)=mg2~y^OFTKOwTXlO6DF~a_OEK7{xO3_>$+MGOeCX zWSBFUT9tAhZPcg5j8^hirJsC45uVho}RJ3tnpB3>JMl#K35gb6f);7<*E&t znH$4-XI{8R%rN(z&YZc7-05SIvu2Rw5h>GJp-gHs+1w)*XB{RJ$Yf21imX&*M)~Z7 z*=QCIoTJ!uSu;_$=8Tlto@uhO*6ga&wrPto`54)R?nPY$&b36%Bxf)KMbZ2vT2`j< zi6a#vA2+{J!nvU7@ElXn#?%Z&LP=DW=2t3}IsJHs4{q-$y^68r@m75G-07SshJ`xs zKQ&vS^GH;ZYAMwst_-?Y9z)m79dIYla!yoowBEo=9pqU+u{;YXnwsT`%jEdv&O1{f zGvSHp>|?2!8^zcH&7$~POeajCe1R@tD&qy_2d2u>GJW2xQK_p_Dbk`$*(|+$dBRaK zdxo5D<`(#l?0`I3klstTZcq?3?2?ybuWP<9lMnymONI>2r3~1&cs3!$8)q-#`t+l) zn&T_X=VZ9-*c!p;>}2|>=J^4IQZ0p2^Ke5Z8m3m2);tf%>6z5onf&2%6^!7*)5A~x z_3)VvA#k$(;Y+|Gu8;~wA97jov|?86k>{V^@L+{Apoh<%>(=Slbmpaq?>LV&Z<5n& zDI1=<$jRWD4(pc65HBIMAb(g!&0*21$fj!!tFB~=rB*PD$G*#Vm;vGmQHqv`_K1R( zlF8SOdfQ<+s~ucZJ>mpUnZ+Z^OBf*ck;%w{!Ru)K(lQ%akV*BAA4CS{XhL4h?`KNo zF)pp%R30U;|J4+0rgSRNQ_h+eJ)2k5G|TzfMfQtpm_~U0H!V^|01Y zlbv)c#t@@*l1UD|N_I9=&?7c^SUJ5AlTES{v>eX5RxLBU>LKSlC08h|htWM2Gat^4 zDQ`xVq0*$BPg}L3TQ98a&JEedyMB`%pV%um2*(VnGzW=${0(h z1t~Upsen~#v!w}R{x{#cX(2adk2h9kWFYZI<=TTA$>dsLrjb-tK4iDbq&;p_UAt$@ zq6xF{e7VF2aa!V;3f5$Q<8prFfUC-CMrsL~IOpxw=QS>)aoQ52N07eQ$Z{c-Ij6Mg zVr+LFZa36ozI_5ZhE!u3EDfjWbKEx%F zFYRG8Ss5)MmOno)PKM0=>nkW#iK%9MB&UNp&*xwK}5s$wp5 z*&>;#Oe5N4nF?-1OVm^(^R;Bn%?C&LpV#a0=Zf?Llw6@q z8c>P3d@_wBO{wv#SSn1Zb5+&R5AGL3A7b5r}~M#9<9e!S7iDeBN= zFY-IbT_fK&(v>o#evuKz!YF!fZ4Ms2Vq2=WqL7ZKxaHe(WJWP&=I+5ALwC zq|ddBSTEmf^8LgJt~VY4ab+bl#n}=LiQ>^`0Sp`D`DV7v*}r4+X6$h1_y{ujn7{)8 zo|#m0`JUggpHbqh@7RF}wrw3dlQwqgau6L09lP}MT%=kcW2hAJkV1En!!nqPmn~)4 zkZib$V`a{aCj$(1dBcF-ngvyi`eLovry`oq(|1{%OVO6I`k1N&+sdCuCQW$0Dvx%V zLKqn9oQadHDNo(@UeXV7&=dnh0GT$Fm^@?S*Py92-F$(ZkKL>2Tsrd}oDBIXmx3JC2cS;Q=&PW4R8#kh_}nbiE%mrt|P7p@p1jtmh)bJUS5q6^xS@ zP3vKBwu@``&Uv#_x0d;`XTP#C>`o?#OJtfmr(Ng6y8W%xd0mN8FO=Z~J52G(V1hyI z1p0DkdFK(O?7wZu0hBnWrQ14QgOZ*c#3G}rxxC|7#q4^FqGjrjrkx;((Un}LB{e-| z$<%BEXLOg2t$F)FIwRiekzoxFXmZ15XrExvG%x7Vg{jYbyYwogeaaTedxQmr=I09> zqiefjc|^%!q@2!Od-9UppK32iC9K@uqHH+B7S!FCwyG-VH0(w-GrKncvi*J)@~iig8YXS*CfMZh0Nf zd(U~Z+brXEXF~VZJ@b7qH|R|maLOwqW)IqU%7R73w4c2B!6;KEg&IuFHg&G+zIT&` zOk0#xXPae`S^C3-oRo&nXWd)o=+xLi|9BG_LUQIPOJ0m&cqHRU?v;9s=OM0P{{E%B zB*3XkvKc$GcjuBGt#ie6-#ohH^UeY?Is0Yl3DXJzWNfC1y}3 zdB@ZKsy2@o;hNR9x^hR~pxS)?>%YD;_GEo;u0zdP<%~DZBnDr@5HRFv2ko<-ebe!K z7K~tMB-y+y$ip+)ZR)lh2T^&%nQ!amUBV=98nT03YTsPkQHOjKr>s|-oNi3tJpAj% zc>R?p-8?Tf`_|uPFJ22{XKUUPC8%pYMRt1XAPZP?}^Say`@7syZ4DXUPDNk1$s>|m(V~e6?b-& z%bypDyu0`Ld$naI_s4SXOZ2@}vxl5i8Ia1GrFqFImX|;Cl2a_NXXeFVMBi`Bt1y!* z(bod=R!>PwdrT+LB|awGgbRZHrTL2d=$bY4Y3*NnktWi5(v&x$ zDW^TpcPR2;Ki`{hy=tcX^wCUL(DU$R z?X4In)b&)W^bHl2M;o%1JZseH8|k_|d0dieEK57f#U3-S z{7|ZJEL9@QG*uoO{L6K(d=uTVdck!$XXL_cF^!(gxNmW0EZi%;<3q<=*lhRa*%*s@ z>(du`4k{1tc;Ii+(x$6YpBjIA0#!5bxbs8i9rsAi|464S|J9shgGo)C;ED;|xv`Ts zdgS(3Uozl>$>a(dG4S38H!0ad?L=6ZaK_l*cVjdo^PF0TD^hvUnn^_y=g38a zb)9xRS@RdQHZFJG;`a^C&x<Xntoa_6!u`}JKXFEG^15^t2J z__a^+Ry+$AK{x{wMaE@9$Jo|6rkIO%F=62OdFK*K+&TEXi6I_~7-?;UX z0Y6WkonIFBFQ`bR);POeJB+9H=U&_Um^Z$o#HETeiyo5J@}b@jjJ2b1R@CM)kXoJ3 zjhjts+f5mT&~DD-0e)TYM7#ONPxX4hAAfQhENRYo2e` zuRhX2Xf?aWv6t-Dyn^N|L$PzaJusFgrFkzq+n4lD>MnJ3T3jb}X!3Pkk~d43LTB>z zopzB(HM=jZL(T4+H9Nv}n%|4<*Y)rCul9mjOSy=ox?dwZX%^tmW)y;b+Kc{ac z=zcek)Gar5+?z{k9;rNdbE+n_y)N}jU8>bBwYhuLrpofu%~br_2S{h!(xmj#M1j1R z!T+4;=}Ko~gMk%^D(;o?MI(3Qj#05|ei^d~?3^weTybpkKkw&x3N3ebHF=vVj&1KZ zE~(jUe&hQ4&%ck74$xUq&?d(lQ~7B&#>5O32`p`MCPv@Jo0H54NM-#%n)@RemB?R1 zu?n?GUP{c!a|Y?XSgUt+mqaXYx~l|3!5Sw9&(W#N_z-#mD&ZirLL;%u+{)$5oou^i zP|Ab@m*}6S%h2~u2VcP!BJTL*eM??8PjK_fuZXuz*(h(FGg3=PFPSjAOJ6I!Ca+s| ziFc4c0V~jeyeB8mn&N}BU*=x0nEUB?=N8;LF@HoGql|Nl#k>tA;JjJbDo4M=Ge(ZO z!nEqjheIA*B7gmpe}j!%1xZ?bcE}%LMuR6ZRj~ny)5zkjPcy$(h9whNBFDf7FLI_S zNDhDr{={(J*VNMf_2Zh-9`%1x+`8jVlx&tu44zLxTKR&kDcgTtp7NHF{=vmRm1mmr zlr8?7^89zjDHHq7JB7{ueUV!BJN{GpS5^PJa?MxAzZWePR4Z>zYA-_!sq6U~C=Kv@ z?sjUr37+ADnhF`$0Tq>QrrwlX)O+m5u(?I2lqhV3#pN zez;oDKPKC?iU(-rV_uxfCzY);^*hN7RDtlVzJix%?dRNcpG;)=E>EoM zpX9nzOwFh}#d@0-wB2RuNZ{!BVY$W%m@yj(gKIbRr=vA%aJ9jz_SGJ1p*y)U2(JKDdPUnSH zt&%V8WbIN}+j!iuR#qlA{kdSREQsH-Q@(9n2Zl5hP`yZ!hpKghR} zPskDt6MrDT>gpeyWn?UojK;c+g+Iu##8-c>A9H?i=5U-psFGjni&DC8A2{|M?Gt;a zUU3GU-P`%-jz$%^QB|?Z40l!Os_cLHR$jCAtV6Hx_xq1OR=3D~z&}`E$}i4kRh&5= z`ZUI&+gZZg0W(u!K93w7qHB&kQHiBAOQ&h5Uvb`y;@&4ddInu`Y^b@d=4-hzaz`|J z28&}fH!I_b{Lnl9{%*oK)!QwlZ>-8M{uW`ach+Crcikbz2hMM64$tc2ALil<7!dNR z2UC|k1CnWUd6h!`wr>6GJ3lcB3Mdrk;x%VD3)ejvH%2&abZm4i7CXKq7HhyKyR82i{H}EYY5A$( zC(REp_x^po#l}lUW9RktZA+H2imYH=b@$=%Ut-RYcUSe0jn;0YQO%cY-T3btb5`Gd zp>dhh`<{LIecC+(+FUj(9owI^=Ce;N|Mu-B&X#-nme=2YQ>WnG^-ZTG(y{p8w%p8l z=H3A#=aZ8mXC~jl#WG`AdV>VR&C32-RYs2$R z>+efz)@#F0TW8|N0p(2!^AUI9v&!)vXySMqD~ySbzp-7z`7D!|n#bovC)(Jz;YOA= zL(lOk+nwn2yKm3| zOo^GHVi-6+W=4wP-~=uTPqsD!oJd!z7zs||ZDGYIP)!_#TqL=v1X7Jb(+H#(3r;4G zVjMVyK#K9;R06G&Koh`e1X4(ao=zae0bmA!6bFKt#8DgsW)VkmFn?wnJDotPL%?hT zDGmi^5a=!mG!dLhAjKqb7J(FpfwKvu@Igo*#b)p>ffSE`_lTo-6ueIykV_SAK_3uk zy##s;d`KY0>!ZhN$?4Q6i)$8ad0x8}AKNCptCisOwitXT60x8}CzY*wO3G_DjoymFy*Pvk#NRW)91oBh` zs1W(8ToM++Kv|4RP^c&p!PjoobcQSh*>pltC6ZJy2^WbxBa-J^h$Fkj6=BxUn0Vq)Ifd-;bwI|vOMXJ5gKFGRX zw!AMIglv_G1|wIs9~y!@)lf7H`8nlqI06Q$QD`&@Rb$Xt6sg9c@yL2W0!~2tBU^O< zIuN<4gV4dqQyqd1MZRhxnuLM}a@B+4bvgXcr*A&EB!9T+E`>H>5jd9LarbTRT& zbJ0Pp>8s|U`Q!zvOVA8l(xnY=)?2px+;)ivlO6sZ=Y6OrXhz-!SGiKkjB*XgNby2=~S4CJY9LZ>5N<)USL zF;Lx%Zb6}HIl2`^s@sqy0XIv)6=MnE{id5^--K=RnA_4C~_ewz326UAKRBc4}Nj%jiBbJLj2z})iG9O2Q>aiMy zswdEsX*T_{pi=HFTQ$3FYTC2Z@>So|C{TTez9%nK{eZ6K{F{;TNBA(A*5eXzC;AE5 zs-Mv>$W{G{enXz>ck~C^EGmrCKXS4I&5EOhR2fwPBHxTuNmPV#))Nx17?wa=RfUXi9eis`f;Ck!L+6+uR#n$Bx>nebDi&>8eaLm^HnpwEf=?TCD18 z<`8s=1XK-03nZXw7#fZu)d(~aSx-yAQD`)>Rb$Xt2$r*4a z3^a2#Is=8O9QqbTs<~)2vYwHE^U)e)t1dxzB3E@OT8li@0`v^>RSVIxC=lh6SHS0B zsF_!ybtqC@h3-Puvl7rj>yfQmgziSJ>S}Zk@>JKLdy%hNj5eS^buHS6LRD@Fybngo z>(KqkdQJjfj~+m_YAM=;T-6QeLFB1!L=PcfbrX6R1u7T$C{!&&n^B~?MGE*)Xgx0h zm!mDnR^5soL$2yJ^f>ZVE6@|jSFJ=_QJ`9doyi9FSI^cM0}Z=-impbF8uC{(?N-baz@1N0%XUXXwvp^uTR+L56D`2@Pk zPs#iYd8*IR7sywAiM~RC>N}+8Q1v6)NnWJ-34MdC7bW1&=oe(GenaOVSM@u}oeMqX zAMiZ1SyWgMi%lpXo@NCV*DCDcssCPju zX9miKun(D`su9{1MXJWAFS1^dfK5<8WUHE@-H@wlhWaB<)f^2#zN!V<9R;eEXb+SN zm95}F7^y1Jp2&Jt0=7nbAzRf3?TuVjTeJ`IRPE5d$X8|1AQY&w$V8#43Jpe4PT3yr z2d&p6UP@3(I`+IgN{X^>Nqq7MXKY`3CMa~BAkd$LiX$0|5U@N z(ACUo=w#%nPC=(4Uv(Oqjsn#TG!uoYS?F{Wsb-@ykoATHJQJOTY}MK59ONSUpL5}P z(9_KGQ4aYk3(Y}+>QZzW3RMfxQS@>d8)_I@-_ajv&U*wbEadK>kZ^)z93=|r ze_Uk&WCi{JLNb%62>GgFRDuFkDN3PGRfft@q-uaFkoCT7C5;*)Th$0PMy{#}YFbGD z<0+fL=4AS+7N{i(RIN}Y3RSI98x*P9qISsoKmuk^7TKyQ)E>F24yYsYRJ))~DCa9X z!!9sTbw%A!sOpY-ph(pd^+MK%60i?C8riB{(J{zX^+m@bPt^|{hkVs;XbK8)%Kq?p z7^()K6Huhu9i51*k0jt8=pGgm(ZwiG9fIbfQ01m$R2l-RmLUUKpGt(A5ih=)wkVgp z1ty@YnafcD@>I7X{-VzGRktCYW}1O&1u8vIWs7ixrT)q2zzxvINS6XdDx$-$=3 zSKf=7p+L0(HAkUpBWi&n)qSWXvc8aj_oG(GRy}|!k*nH-S|d;OAZmkr)kA6epSCbi zK1^mi6smlbL6K@R$|CDa3HS)ALbmEr)E>F2EvN(XRF9#K$X7jqI-x-IY?}V32Mm>N zV=lZq2&h_yrX%Ys33xM_fo#<+XeM%1%h4?4scuE5BVTnJnvDY03UmevRVz{MOc*Iw z!LyJRNx<9D*~nJifzCm$YBf3+d8#$&JmjnHMCYSGwHD=2sPd47BGo!HM>4IiCE#7~ z0%WV!qYII%x*J`DJk>quV&tptMRQT0+JNSvP_+@wN0I72bP2M)k$Cr`OYflnvy~6P z%gA(9o6rK}sUAd^BVY9pT8IMG!{`bWs(fUlNVOSViL7rW;3McNWUC%U&K>lBu5t@p zM5d>D3|)PfT&S>H*(r_goCRy~cbN3QA_v=rq$<+Jbx z=&PPXH=;oGJh};mssOntQoVqdA?tey_#(O)*{YY&Eyz{9jFux$^$NNb`8nmQ@HQBz zUPCKTsM>~BqDb{RT7|41B;Xt9c4VvGM0X%pwH>WSp6V^M2KlPD(VZyxLHoaV;93}J zW{5l#soq8FkoBVkd=K4)Y}NZ{J#tkapu3T$`VifNeAP$jUKFT4MjKH0qxOG0;6@l} z<|pVrWbKrIpQ8Jbt@;c-fLzt*XcO{OU!Vt(ulf=_gaXx9=wTG9BDAwH@euc4U&EiE z^^*kr2K|g|)wk#u(Co0QeBVUMAk17VJX^kgd8EeTZDuZ8`W6 z^pq>m$H-T$L_1KRT7^DAq3U+@DT-8gpwE!?n*>~qK1a4{4f+DPsyoq_$WyIto#TcC z`bv+?2nDKj=xY?J?n2+7NVOh)i>%)z;N9puWUKB$-y>IbFZuy_stxEzDsUAeXA?ptb_z?OX*{X-pA847Vuq|V)w#3t{I7%R2Re;Dh162|g zX;H9!@}Ri#lwiJSK}}JhYKEGlP}KspY%9-y zl&xSTnbsT$*c!D#wyG^^hg?+#Ws#?VP_;K(!0%ghEwk)CEPVuBcmE`XB28 z3D_O>Ak$X$M7@x!>W%s!Pqi!Ri+oi-v>OUk{m}pvs&+?vphz_k?TM@lCEi|WZYi{R16^T-9WBH1aOa$*RY| zW1+8^$Dt`GP#up>K%weHbP|eG)o3cR=E_#4p_7rVIt87IT-9l4I`ULA&`jjd-7N1P z%z~%GKr?5fGf=2H6P<-3)!FDAWX+R+=c4nFtvVmc)#R!yGzWRA3($qgS6zfIMggM# znG5H^P&4PFOHib`6kUd_`4Vsex*XZ6h3E?8s%&&6@>EwL2l=W+=xP**3U5U35Kgml zE?P!jq`Vp3f~-p<-g0y+vQ@XC706YsM5~aex*gqteAQ~S1_i1+(OML$JhTo)s=Fw$ z*m`JPDgp0C_aIw!FWP`y)kbt5@>KVu2avDYgdRkJ>LK(n3ROPZj3U(|=uu={Ch@k= z{=^=Gw(@Z@pFpl^D|!-ns;AJ?$X7jso<)J`IrKaVRRMYdMXDFkOUPOv0bfS1AY1h+ z?N97A=qk67`8x7cZ=g4kuiB2@LV@aS^bQJDA$k`@s`t?Q$huqtet+Nq zQ0^1xDL;juAz$@5`T_;2FVR;hR7L1(6sf*J-y&Wu=`9;gorRRhtkC>JUB zgnglPl?2=i^+UF5Z?qe7Rr{d+$W!f$1|VNG0u4feY9umIs2YU^qewLx?T4(KBLT<2 zA<$NhMMIIRnuPX8p6YOPAo5j5qJvSOItm?%Le+_AGKy3up`($tNa9taW01W_`=6=s zSm)kfr@NOd1thpcNQ;Qi<{WUC%P(~+y%gw`WZwH3`kzUoOd69uZ=Q*agx zl~1G7QKWhXJ&LR)5^x&|kga+hy?|WR8)!E2RBxg)kgwW-LKLVzLGPka^=S^C4I|}e z=p1BSCjmc4A0k_|6P<}%)lcXwR#687NR4i)NxwbsU<7BGnXhI^4i8=1c9Ve~o*R6cqGg{sZyO%$mfLEDjalLUMey@hPm7W6i9Rga-}kf(Ya zg(&AMpMdYeK(!UUheFkp=zSEao6yimYW4FM;|ZTUCJiAy*|^*$sKBBCvC{ndT zqmZ?nc)4UH91U$@vNalmTvZ!17I~_+XdLoY?a+7>s4{2*3RPLOKZ;aU=m2EhDiPYF z1Cg!Db$|y!SJ@FAj6BsY=n&+qI-x^Rpz4e!qEOWZO+t~XD>@8Ww@JWm=x}7Kx}zhI ztLl-1M?z286a54Es$S?Q6sRVnqfw|j22JIS!bo*2II8Hm-*Z(b zA^wS0(^F0DPWw9z`pT2Z+@CK7s#DN(6sk@|6C|MOG;{#6R!YDbXeL{+Ri~rb5>RzM z+Ltvwl^pIMnuIm5bojC{kU679;C+33x49f^5}w=z8R;mZBSwr@9f{gnX5Y zmZ3m(Gr9$Zs^#cb3dD?*x4{+2x)We~EKeQW)RQ=HaWZfh2a=XJlpsgH;_C&5~FSIxERQsTPk*^wrOcbaFqy12*8iIzR zNHq)%N7lU(a0D8OY}Ke791UIN7&I1ns&QyM@>LVi{wPo#fDS~V>L7G5id2W7Ly@&X z0!~DekgYll9gbYp5i98bj)b1_A7mbdeAQ%hGzwJ5pkq;}Iu1=ik?MGK0LPS8@>FxtJmjk`L%9VoP+ktF&^L!F8(m3Wq`C?@$huzwUV|1RTXij3f?U;g z=z8R-mZBSwueuT4gaVa|mdR0s%A4UWC{is)w<7BS33wY?fo#=EvLdPw30=mnIsm9N6rpsU)3UPqqld-MbHRoOqd?*Aa5 zstOH8p{hOF4@IgDXb7?%mVh16P-LriLBo)nQ+9&Gp{MGMMj&6+1&u_3sw*0WLRB|3 z8bzw^XbiG^3D^UTMYgIZ8i!m}FEk!`eoj{H4JSZfGy9Z~=qa0$+*{}?n~~f{7$}>Q+_xZyLv;&0NU|ekOG(O) z^_T>1MRKsvR#uYSPv|OJlN=)Slx;{375d7yB!>wDWjm6?g(1wP_$SN^{%iq9nw=#% zQfNIcfvZT4657i4Bu5KfWe1XDgr2e^$+1FTxeLj0!a&)HYi6iwolCKM` zCnWG)B;OF)%Jn4Q6uQd0Np2T<%6mw@CG?f|l6+ejC^wLNM;IzMk_?5>6H@<+?j!xK z*xD+A?8Lh^H=r+kd$7eZh8ILR-Cf$|BGUkO9yR+5o0Qa(xYYoYa& zv_C~pk^V+(KP7ycy0}!btf7$)AMQ z(-P=Kx&D6^+v=A{{vvdhrxhBp#r*JRi?a4~lGh4-v;)mliVP*l~9F zJ|eUN$-R)|qe5GG5y>q=S9vkX$M|9XSv{BZ}Y!@@b*1TtM;}p{u-{kUKxvZ$ z4Wadt1iqf+n?hT;l;n1ytGt2aTS8BHBgwagzA#tBKSdmSM;vIjOEMIO%4He}K3;+DO1wj|L**)xp9mx6 z?Ib@HTCYgpJ4k*ew3Vw#elB#CYe;?}^ptm!{8H#E*OL577(n`8k8~ssHG3V&uZ5BF zE|T8}tyd+m+k#7gAO0FrxQygxp{u-^t4Tg143uj~J}V4g)Bg8P z($9$_&0b6Ld7-sU0(&F_p{-m;@&%!*yo=+Mv~ivw(>rbuM1t}{UqNIddde#zA5yTn@DaK2FeFXz9kHm50QLZ z7`?9j@57|u5nFFa;6bMtvB~`K=aNE`$tl86 zIgI4-!bmy%6t4dh#MYY1e$&-YxaumsGp{E>8a;nf*jv+Zs7%0b*JXshj z$B{fm7%9h-JXL7rwoBj%q)!vu%Kb@B7rM#=NX`&?$^%Kx6#B}8NX`-l%7aOsE)122 zken@yl!uZ$LukDvaVMUVGh%0oZS^FQX9->9VI?N02;M7$}b>{+@kwC>Hy9#Y($tN6tH?gZOCD~o*DN`hS2z_N4$)3VMSx&N-FjO`m z*;^PXD@gVcTA>6^liXEkD;tvRE6lm-Mx^_RJ!NB(y9s?|6O#RffwC#d0m4w(jO6aZ zNZFj^9zyF~3EYC@K%uQ{Nper2t8A4cy_eWiSCZUY=qp>3+(#HF+mPH>7%JP693+gC z?IbBb)_W2-LvpatR%S`=Cv=5R7L@ZwSF!y2cLgLe{M0N^^DJJMW%ws2t4O!!hbP9d zj{NMxkE|=-lb=q1bLX06N7Q_`3+~EKH-5S=?$gRRXOH*E>H%y2MN9rbHP#f@{$q}{ zpAxoys2+XacE7>|Dt^ev{xcKr)qj`_|-|fY=dh^4( z+Pd3)`0RD?%qcTw_L_Beuj8i7UOc+B(Q@%;l}6JOVsSEyV)_rbT42=_TmlF1=SCT1o;P#6>s1Ql3T zTDehwimnJAEO>*uf+x6Mc&r?{ip#3Fx{4zD|JFN_7sM6!@Al_2NmqBj?&|8Q>gsyk z{oLM0Uqube@`tnjC^44fTpRvhSnV)RLxV5i@|6cXwGA$h%OCJHd8)mx`QDbFwU{%P z+OlGlPGMM1@MlqXU!=J3qq>XmbKpmm?v7s%{CeVN>{>6tz47a#rQ;t1YRVOn!kNmm z?QPBGiS5UDzQ|4DF%|l4orh7F+96DE{{-ElZhtL2u*kizrZ(WMtMvzrSP*tbxF29j zb2O{GLX;VR-$49|#eGTqPUkjcl>qz`e$=ULVs0{b540`_XA=P4grkd{@n45;0~~n) zO!>w>=Zf!>`ROQ~Q-=UseNJI2Wub35~d zDaJYnq4aoP!<0Z(pCOf%OR@8Jan(Aq)^)v+9;Wk9P z+L>qK0@yYo8H)IDZ-uYYt3Kv$s>rXaEzfK6*LeIjsIsoMqBh`a^fc5~+}{$eP>H%6 zBFa+vw24#|21)r1!~c5V298o%g%B&;F&zKL)HYO3@KZDGF&}}v%ka}VX{7i*m8bA! zB0i0$bTJ}I!!)cJ99!SMhEaRxrtxekNM$JyHly(ygWp*E{!=>fF8#*gHy*zUZ0*%EDZ?1ONHdOf{>(m1H%M(L6Wb|H6RdAi@)){!k`X-VdG4y&cuQcTKA3(-ih78(*_GUW(n-68g7@^03!x6qU4 z9D~HCnLNoX{eg%n99cXg`CYu;+eehxPr|JPg|4o7L?Ka{#iLSxoxI|PEZ&9REOut` z!qnlYPMU1(s8z?5`ev-hyPRW6hGoI}uDqI;i<+)HgI_OJcjfc>E#h2PK9sK$#ohQQ zo*-6q;|;-(JZ$WOjP#li=fDQ;*3fHWqT}w7tsv_)(dmdqSv~YBjdJ{x<}5ArniT2$ z8s*PWUEWB0F0`=KhOJ62Zi^t5>;jM#fj{OeNFi$>s#k4>`FaCQ2YBq(7O^ImM~W@k zJXXQ422T-wJVk#D@PCQlGOA0%?>59+@!JjHlJP6e;~AnchnwOb1V0yEI1=$N0usb6 zIlO^~i1Rr-A^6{r@_14ks%H^zMKv7b^m9}%N=c2fLga#ZLd27~|0OPsXi_H|9RX>@ zh0bUEMqH-l@wC*l5SO3P?f%EHjq~41hGmx6lm`|*C`|d>moen83SaY9j3cPu)hJj0}L3--X<#HabZTkOV{P(2IbonEfNWQ#fq zue9(^f8HXp3wUGjG|kFdD4Y-yZN6-f4nmg~+RXh7@MxFKe4dO7EsVY)#C!!A8d?}N z$!2-g01rRru$ik34_wyGX|6OBC<$I=vp2wamhK9be60;#VtiV^->Xb6$e6`c0&575$Q8V zq^A>s+-&jUF|4C&&axE@CI?G$XEP!xeYi;-=H$MK7? z4Y)!N#2NHdJ2Xdg+a;kH)N9f4Ty}9hFM=ksWEuKWxI4sYnPH&R*hjg|o`@3LP~$k6 zJIo&$hw8nK*0-?4Xl*}zI=3;$C<6zK{Ui>^S@#%{lt$WVmM25fF4Qc}O33LxG&(xf zX8zL9=IDNz*2fI8kbXs2N|3czL?feqXGNsxp(DVD^pKPE&=~;GK@reFL1x9^DGJC= z3djYBl#i2?PbmN>9w#ZDWr#!RM3B z_QnTjl>R747ZA{jGyomxM#Ocb*C5`8^lqh8ls^8>aE#L-Dlt03G9*W5N(k(VkOa#u z*=-4#e=>zwdl*V&_JBrXmRMS^YGM41A;}iIAtBvnj?bK)i z0cg-p4VDmqw(Qi_JOIDkjv^c8Gy~eBOXJK%hJKBYwOJz#%7*kEj;u9;UlyV2(UTJ# ziw)WI_*KZ$!|0h2j(;0wkkPm~$ay^;*hUXm1mm490EYk&w@!!X9H;o4!CD(;sX{a7NrxgeaC)pBgS2#b*v9sZB-uA0cgsDHBy|0L+Q{8DyDa69{{~WxLToA z3}c_*kU{8$9sR(3sNwKdZ zj_w5KzzrsweFh01fQg%VT=*G?SOEZKZm}#kES4s2=c&%sNGL&eX+enDUZ9UD;NoaY zbbcG$#c7JL+>_guFeKR57GgecC^GQFkakl`mk^U;jG2wPtaM1krKmFVeFl*&9h$7# zvA}a!2JfQUv6^ID+>o_yGjw!VCXZ##myoiEdO9)MAsOb=Qd~c0rc6DQmR+PkUyxA` zrL#gD@jdnSkxDBT^K(XwXN4#juW5&|_WVUHZ|nUA3>1VN&lr)M1tj+xTgVNDE0fS} zt%>lbP{A1jsJF~QOrVE}e}+ea7(ovzGyG(jQYP-@iI%SoIAk6S33Hq@B$UNNtwQIo zg?&>H39F??erIPDfD!=XhJfu5pGWa@-cd1n77_4EMo&9bE@JaA=VsKm237PZh;V*^ z_ztxl%Spr$>XpCa8nwpuQ|{Z&S2inQMdv7aA9}!xX!#ytQ*YlfJ>y;5I&u^h}A>*;IR2?wESXj-znZ7 z!mmXE&rm)I&XPStc~9;Wr-$+t!3TGtoCWVZUXR$52ayJudwm(@&`os0s@Io!N*;MC zd;RNzynp495yfID@BGmA&z;Y6P@Q_{bv`Pb$`(*r3m;1BfHs*%N}jb{aSK<=mr!|` zM_b2;l;ON9UnEB1-~#a&h`5KHMQz=Ki!F#axjf3TobK90yc=?>;j)bK zCA8(P+)TM5bp#(My&{BX1h3%P;x8k3cFl$Ek_dz8q!Wm0M+yF8iA?j9$%SOUg z=My)Mnl+@0RV3{ z#o2SQ0I_0HsJdcXBj5QQ1 zJYb5qe%?cL8pDTH$z2Q;ZnN5~>3s|d+pQ7yrEtLC7K7#pm`QsM;^hmYY9o>v0R(5 z08^s5BjVJ)62=|v=pJ(SeW zEi~>0ZJJ9BuCzLp@uw@v3RhXxKg^n*XcZ61v#l#65rkZG(0DEBFDBdZHNgeRILply6 zE~jGF&I&{1{SYqie+sIXfIV0QJiAvsF@ax?*UH06c}ej3ck!~VgNe({Tj9U{25u`pcAS&0q5$EH-6^Rq^TA=AL@H!*O zFQW0CClGIvIE&EZbkHg?N&t*lbdeXud4TFju*B0_0XB~Gh=Nan8eBD4|dasw|fx?KjmRuW$=;)jNgYFDqq!r#a(gE<5j3krCdui zEZvtipfAwqDUOx#;mP;mZL0l4j@d~KI@R{1<7Oc)hD_p7c>gwS5?;!ciKUZxbjbcc zqFyEH49urP)7aqhp=s{Z@SjU!|0Et?wOmO;-|nOizDXom1I)ez=ViP{Z*OlwlIg=| z)ue*HGzu*?{P~DzarIrWcTu$WOjHBsG?I4RhQmuTlD?*=F%vTRiRZ=O$$Y$Y_ARky zGEYv~115PGgX1W$ad~GqoEXChI|f?0eBE9E;+4rfc2v*(AOI*oq`;PlN|c~?_`}`5 zocH$3);xJzBhFfiyeT{}c)ePcyrZhEIVK4+da{=O(D!@f6bM5lhPH!%VC_GIyI=^| z4OZSzfU}UmQpx$(7F!=jeTH(XN)epcj z|2u;&BL}5WcMEk6r-2oI1%ve-y1xA5GW))n{dMea17Sh)>Dgfxp4IzSIp#&Qnr=z$XKQ7Ew{MXkXeF&=9a4G6z!3RLVeSV!+VRSGv2z-zx|7hNZqDzF%~T)&0npEO^ehKjMJmJ})BR~M34v3m z>D(022^WB!Q{yjVdAJ`W>{!K3-~S03AMs&Rxas9v)H35y{^o4(D`-w}*Oj=21pTEl zPp!qXL3#iWzdr<5g+bh?Pz{7F9<)RuCKZF8rqLkN3^d>IdTz31GWHXir}%RTYJ3>t zbewY|nxXjZvvDsAHG}xH$&hkzFU606Sd0tC#+JDLx?EFs%vpTlz^%*OqR=VU%+ty zXj^{ob}%P+Dt!i~(arneSRo(;fOi+p_5TDjDE$I}T% zRM|flf@j_VK$Qiq!<9u4`QfqXvjozFW2lCg;G68IDgr%JUNoE56hVK#4+ibZrBI}r zLLo!XfF+%|*s%hge=kgf%W^q0y#(uE$=?BVr5c==Riq0x;g7*cI%Q=rEvP$?iizf74A;4FN0dt{}|M(`3t5%_QCdGu{&mEwr z%BK0Kcg2Trb}ZtiM@QIp|)f&w1+8FD8Rma8yE(mxP0 z4SEWL5_Ntkm(Y0WRo=8Or)_c3TO01d#bjL%nHZGpsLa*eUI+3vOX( zDQ%q1{9f0#YSPMV)>jR1*K<6cIp6OF#v%%p+3ZbtdJW>;*2qbYJB?M9Zqv^i3Djz$++AAxvrqEhyS3cj9mF|U%xmGC=gMTmJd6;(<-!SAN3 zn{k&0T6YcRrYWnj+gXdR>ZPWSLwfae$$?@w2%wr%oI)j@| zc7PfKE~A@Ztlc5jda<(VB<}I@;^5GMcu)&tE2Av0K^^L0teGd-C!@g8NLWF|JT{y* zZ=3~y&bv@b@z}&h0d9%}%U-ac{qs(7_VH3RgmKdfu3-@V_k+bk9;&2vTxbf(s z4#Tr3t2+$CV=izQf|iQEFyb&em=%j`^v7&;W{q8%Z_N?kRr3|_^Iubg?XQoCmuvWf zT3pY-()x1g4ruDAJ$-PQDdwlNcfhlEVH`{i8*rx$6}gO?%*(*i6fc7v?uA+MqnbY0 zj|)a9j`V>67^~oTDxUz9=TjUOgK3cXQ!QS@xWt>aJe^|;el1Vtw~DyS;n3#8F`TeW z($3n<8#EP1>_IB~W&`~Hqq3l9S*E2^3r z{S{3;7ey|vs%2=|STsVadL?iyK>RJlodsp@%%Yg-jJ&F8&?H?GCKJ{ZwyCUZXe zu7_!s&gNwX64Ta{J_dE6ZcTB1fTW|)x>S6)A)8(dzgD1E2BDh+8FWtUiTAoxIyBKq za&3)gStRwA&|Ymtvx=~D$N1KgHtMKvEjtCBWCpaANsp}OL;l&-A#ZB;?FD}m~AeA7+b zUeD7fknf|<7c@c1WwRjWe}#>^1k0^Eu7S88)_^CUgi-n_UNut=FU5U6Z>s?8GfZ&O z7pPah2@DV>91VPkWBMO(qZ16h;$@CQ0;da;c%H!d!vP^zJqL%0*w?_*;;Oem^};9~ zgcq9k4}u{LRWZoJqplZUH*oCj7M*?YPu?f`_;_{)YOi4}rq{<^Fkr+LK0Ye=;u|;* zawQhrZ^P;d0&jkX%df)dnf*HAb;zjghb1Ec_i@vtMkpbQFX!?&ut5er9f2A*KMHGv zp043-+n|+oO!L*ashl3^%I$ZKVr(#o#JllW%LcGxtF;CXbfWcUO3{M+XV^p5fMM{c zh-H((w&5#zC(G4_gphw=Bb(2d;8HfSwR>n)yN81C`d<7g>uw;c2fl`9htGi7HJz|u z!#i1@Fy^kstYUv~3>Nfi=WLde@%p1E|0&P5Y&1-&q04y(3%;9%fc7akX!lD$!!z~| zLoC?_I5fz^EZ@4-()2fUJM6QM3<l-$={Js2EFu5PqeMy(eG9u_ zl!CuJOltrC$D<@_{X8}3xEmHHNz&8YI%}LpctZF^9%8xHz!Lm5^kAkTArzwMs4^xT zh;ZyN!Y`%3d|P8k;cswT`$s}Pu`z3%=oo}l zf9Ek{yayLcG~5&N)}^X8h|Ym>JC6DAbMztFWZ`nZyRfDKXGK2_C&RbkwpeI`=yh;} zp4d;^1e)b86pEe1aZpWyG0EiKFe{JzRjh92{er`{ z;-!lXuBXK&`{PjUhfpBmN>j)1djT8=5U0EvJOt=0Wi2t~+EWI=I{{IRqqAXDP7iU) z+cZn@1U&%{TxE)|RvYHg=!534wzF74l#G7f9Mk?V77~7b7>l((H?{;@;QqUFV_865 zKQ~70tAV?I&vRpWdbMcEYKb67?vHurT@1_|OkYeI1uW#F;@2!^rBTy}O84|*2|LQCl#CmYz zuu6RZD5M9$x^iCyg`s6?&mR(c+3H(}{qn(65(#hOT zI3LY5w-e`2Y>oqlHbeh}%FOL%nx_&hRlhLP05yc)O@)c?T6iw@re!XMhw+{@5Uw!1 zYTXP?@IyHk>>mKQ8!kCN*hPMiMuAdxAp0i_#G1n@z~rX6aNrVz!pgZ}1xx*Hj(HQl z85fG&Yq*`rzXB|mu$)KPiS#=F5ZUc#f`AT@On~HeBKau-KyD|I+X~z2BBXW_sk12W z(Of5yc`)Lkmqg+VDLhr1%oW^!UlJz-EuTVJ=^^1-u-W^Dgk(CfGr=Hn$#h`nqYEW& zdA?o)-X=lf78qbn;;=rq6~n**6{r>GF)V@`C9rc6w$Ic6Sb>}*tcU}M>mjiyB7)#; z$Vca%dM^^lm+0a1J5Wax2fa20pjThRI`0=Dx3&anU(Tr{pA zHg60fKnpI~4*Cr!5KFnV9dx#__c#OdP!GKnCXrM)i=a016KSq2F;;Geei)I@7 zn)*?aIe3$Sj7gIt%x8>boE&L6V;Jj>@VPjQW80DZF6|$g3qAF#k2r4ATc!Rd&a<|! zqaxuq)j?Hw9qp^ANx76NmXX6s4~exGFyG}Dz;zwkGiL)-htmeC?;z^_J<9$P;`=FH z!6Tie7^tHFate~|1BPK$j5x+A%b5+pYQl2;U7LA9(7;{f^QbkzzoXCNR#fXko8u42M<1%-|KI0~eq*9^`d>%7_WDVl~!RN7zifTTO+Yr}$ z9#<~IhKlRJ=ev=Xa0f4N9s+O(fD6L@xIqA1v>ls-{WxWkckj0gyX8$|g|r=;#JH8M zT(lqCC^Bv8a6TC1tcqfk{qJBM`Xs#~$;FV^EC7&Xdm-Y35f7h>ivdV!>LL6_Ym%81 zryfXo>Pk>)A6l?I!)n@eI;zvPDL$rtF>MOvIRy=`poUjkqwH@3y=?@{42^Wo0pj}r z=$w57@m9`01t9I+AXwYL$^T={X7Et7=WOc#Z{X}$s^ItJY*0uGZJb?zMs&_DLHs{( z_B<-8arPp_+c-Nu*zUTBggdcF`<=6aEf716J~V zkCEVmVU_`g2|cYB_BrMYejSg)KD>nMxNF8&Z{WJ~lW=T*jJZ+)c457u?D+V88+PBl zj_hw>iY5-me*g0TK7jEl-GrLuURkh6en8R;EOD4r5A#);gP6QL6V`VD+ec5d-%+7dOf*$(XQEi)v{nTI{Afv)1V>v?8!zrSctMQ{r( zp7b!g6FnvVc0C`B~wl^^EWOA%j#c*RV-n;iyV2LUHx zLl*+rPrx~DbEX40O2BTLa}12|GZgI-N-9v8~2RO8zIMCe9u@PP(|55MtjCeH9*-jRzvjqr9ERgEBO>$FmcUFez(@1 zxEW?LA0W1_!l57K6;buxnK$v*rHBq~q+r$mkZcYv2Ce3~(iZLg0Ngw* z+y%gzhTG`>&l1nQWxNm(`c;7QT7y4W7Hbx zPT*&hRIc=?Q4C%OcD4xbI$XiCPQ11bUiyf==hktHgbSwR^*lH1whW;Bip^?68@t88 z^>~ebXzv^A`BI68?wxcyFW}NAPl)R_z^5;X-5YoUZxS!#U-EH0Xf7RT&&PvSP%%XH z?8wgP_$vL6;=v6(+B~!iK8RD&AEhFt5ZC+f!hYoU5-Rhi7LF& zSfQ|4X(!DQt-_UvpEXoC>v6p8U%!dRn77@5-8WW|w28;}ab=;lsxb-)N)T5_V5MzJ zjg&J_&ACm_0SoNjju*Q>3BC#L*GD(uYXxHECZ5oDzg7{^=9FICS^+4$t6CA|6deObL-o4x-2E!ej%&3O&8!$ql?p`70zgzd<&^3pDt4Q$4@f0NhOrU zVmEZ22`69Rl@9LAp3tM=%;J#Ik~7lzZ!1v_k-uTeJ!lqQ^?kA=E&7%$1zKT#o(VkOL)W z#R2T8B>>MTT?eI^iN!(c^sC))NQ2dNB(tM%6bllh zn7vnDE{$Afp|&RsWMcbC9+gbD^(<_{b%+BhYCKKFjO-InU7fGO6Yw&tkWNckctx6h zTADGCE(-GFjzBlA3JeTr_SY`<7PE!0fUc~qZ>&q}-hBxhi6Rwsz6Nh4^MSL~jKbba zpdAmKmSP9DptY(dU%k7@v(Vk>X$trU%xdm~{ia1dKlg(G5#|g|Fnt5 z&!l)4y=1Cx@-1`|4X~S+sOf2JtZk@v``ZS>i6@ff- z>r8V4J?Zu}4ao2ZFcP#UMorU(25$PjFFzwWxeWh;Sb9u~j_(7J8L`My5tsy`jRO5V zxRR66g&cx^!ILF+li2))lq0>=M+6h(%f;cBrEsx7PwpIF%;;(ke{;RNyd~iEh3pY%a&3(IE@6kpFtOnGbfv;FA*7;g)^2@L5ERI+s-9SMQE-V?yRsZ+(qucc|( z4?ugUDEBH{!ro|wdO|QfpoW^)1A{+KB>h`TNJ|9`Fw`t!IQBdEeDj+d8SMyBey^vgqQ*_catA!+bzU}69V2!l&}sApF#HX;5p)x(mRoX|xbi_M znWu|01#+=C5+M%`&2ec$dr)}3k>(D66Vrpnk%hpRE;6e0xhuf%0fqPdj4W8NR6UJ+ zWu-3jpcT5m(7m9Ltrfc^*(DC|m+Uxmod2yfbu{gqZ}j;=+=dFbKL8I7F&`xO{zfC-FUh3L8#jTSCL%5anFSHh;!?ShX28Qc=);}S| zIHJ)Xe@jCJ!>%W>@k1%j@fDb-s&+_))zcu4BnF&AA^ z#hwKD4&nbvN)4s+K?^jP3&gPuB)S_tR1uW?8v&0_~{wRaN<+WtzJe z6*B>CSnsW8xQbNNJuGGLF+_?a6H6^*;_KZ~A08&=NOB>LldhKJX!}WYTwy16F9}iC zWZL5bo#bt5f)1zs&>Cwh6Uk!ntt1Z&>sbueX!6ts{Osf2dxyyK6WkGx5vf668LpBO z{dYq_#)1iVpW?B$waD_f_}M*TT8Qkz!WeQtL=4&`nZ?r~a!gzpG!H41O4b>)(5F9m zKS>lSW$fw^*$+w+c`k-3TjU;6=@foOt37}8RE)T z)*%IF6Hi*@&y~Hhc+k5@5rFr*iC4}@V@2*;QfU6|;MWRY!vZg67uuGA0U{%W-H&vY z4ZsBCdQt5G;e13&=t4dog<86(&^DtQPeWy$*Nt0_y-f?etgl!WDo4wBrz8d)md?uI z*-SjYOG=Tk&KAe+lWO|XTcuXfWc2p59V;og(q&ZoSgUqVY%UYyPD(SKBOzJCymfVq zZgucp!6t%KE-@JI^26wSy3${U{lql)^$a+v8+>@5_7ZjoZelrTABvIL%N!qvnu zh%m*np{THeDx|Fdq;*wufUdKn8Lhcbs~S&l)rioRZ-CitAYaA#9W;YZR@ABUSs+(Ry&HvFkr{6IQ7wVP{balNE?my! zeMM)d+$l&mPFDGv7J8a04XT+od%4|B)ovedZ=Q>mw0UF+dl}<`o2|z;gIp6KqiRNB zZ)n^wbdF|H#UkpB&M6aTZFK|gET_qnj*@#D$lP-ii5MNt5Dz=$bh!>(fBGqDmyE3t zq@st@>^U^@eN+^_@Xg2WIR`}Uz zLq{=bTEGRW&cG>5ar@H{@byu0Y(5>jP#A`k6TM9)bFR6uQMI=)e%19J7LA81Jpm7U zO8hNK&X8{cTi<@!L{psPdC-QG8idq%k zExG_kF$n~y^|60Id3&0gJT2@hHxo5`q`9I;f}GK16Df5vacONpHZI-k0IC|e^KHep zUJ{lG#Os?O#ZY*+2wS3@%`-&LM7bdKPv8&#{MtskcOINax2U$-9b{LrLE?r)xvPmh zgu=B?n$uxYG@O!jjrb&QfVo6Uk{lOHI~)}?K~iuIMh|Xi2mNm)F`MtG2A#^h{^q&> z)>;RX~u(Ldv+r|FQa%AK}P;^qj6R54wlv5}s=f#gmX+hc= zq)ip7^`eRpoex64(n5)+$e7OL@kZ#A+ZIMnlecOBnUekbiH^@mo%j~9B~>0aV_+@4D5%B~OtrRDyqefL@J*9< z0qcjS)!X^qRUU9qCEEZN(yaqxAljhbjw=(&CSL`8Ex<31rpZ~ZQ4oHGkJY6Jqup%EVnv;yW(Zfh zJY@h~0i}8rsONO0r81zZ4UM^g@aD3QjWF3MM_38w<7g$L8+%#1Y{HS$2zvF!FO z4(Q%9$JM7Nj38|07Y!du@sao{xW01MXmdJWZt%4@gQY|udWVXIKWpL zbSo&42gHFca$gAQxh`^CSK16+?}2%uA{zw~`0)V)FYAEG0h6P;LdLzC;_zN6I`mC) z6HsTaS8LpoAt%ak*^^z7DBj7Coqg#Tn-5k^6&AX>Pk_!T5MO60Q+c1R#f-MDk`saL zM2kK@!<|DSctXk$qcY`T5LyM_S2N|Y@pKI+7^$MBxnaIv#WTm%qnLeABzk1Ysf9Bp z0A=#gXyzXnN`nM9X>>2tP9y2z#3MKr;e8YPqmhzk2|oHp!FnmjfPz* zG3-i6kH5Len^v%-jZv42C3z6OQn4{l9+|KT)dgx4qkwJz^j-ndK*xeX6U6yEIW>3{ zIB-H!C8INq+B^$FR@E-TBA}kp5yLuI6vRo3y-nbhgP3$0d=2gfvViC=Rh8pa{-qb1 z;9B28_~Vo@=nZ|P>3#Bsff1DAs!AeG7F+V=y-{?0Q?ndeCx9Yxd4b$7;X2|kwa;`Z zCanWB@6;c}(*<%$#A%Ezt(xglE2U& zrK%ED+!&0`>w)vJm?k-f+pz&(7op9sO2<{b_3?P|eUaR2Oa^GKErHyMO+*)4Dduxk zZ4=CM#dzL2f>;|>Dn?{6s;{hF!02u+#i^jqQFFwV-Q`Tjy&zU&v%iMn0%39D&r(0R zySgxoNpNGGGY{;ljz$qi;}{fT1ID;YB=wNTOg+}RUM|Ff*@uCj(5TF=bZa@xdB3l| z)dHex%N1WGMgzU{v)4g|u@i<38S5?`GJe!B_pk|5#!qI)u=L;hf>gopYV`>X4Wqj% zm9Yj7Hi}(6<@k>NHoV|c7$uJOl#@Eq>po4ZtISo~UGz_pH;6fJNTH6)fs{|h&nfox zlDiM0^Rhs*jZ_stasli#P!>jlpOt}hWi%Y48Y=3bSg}?+3X!O&Z)Erch46eJP3m9F z0%TNq1JGBnQR$8^;L1J%Z7SgeXsT74+(1GK`x-Jjs-Xe;80TEX4$wQDuKH{hOLXRA z$i8vbVc32mUVcQ% z6?OgOSQ+2qpw7?iG7bb%`E@h)dO%kxB24$F(Ezw>z=Q05VeT(q!JiO|`pfC_#^r+0SUxwQ3MmH)9pX#()IMY9F5hA-wXt z6f?SN596Rp*gN1BEZN`*WwdueUA7Mpr32(yDK02ijha}f+|%ZE$9ffSz(vA7sk}Sw z#vv&oLxAmq461$rzrrPS?8!KPlCg8Lmv9e+O&crD43yKwmVqE-t=KbA?gs}4lKOG` X-t1!eVYBo|i}-S&oF?QE^1S~C@TqkL delta 65827 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 z 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 AsQ>@~ delta 33651 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 KH{+#Kmj45i)%)lG diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 89b669f6af9c1826ece9718b94c1767b2b95d1f3..a7db45733eeb9ef5731e1d1a1e640d1d9ffb4b07 100755 GIT binary patch literal 218648 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�?n*8Vpa_=>(yHeK|a);3}E> z7)Ao+DQi-7Vmc1A7@0t$079q=Y#0vkW0>-s*+SlkeSFYh7IY1rmfxUCFmWO7$F50a zHV?ugKr6|zLsZIB+Z+cs^+tSIjIG_UK!B_;w_@TC%t*w$N8x%y%k+N64!}5zT;JV8$h;7ELRfS#n z9UXlkL510)HmDE)DjWy^J|cCAf(Z!-C&SzU&}KA4q9^#gNTWU*cZhpW)+sanx#2IB zs%!@Vv5}{+B6ZRr>W7gZWrcx+l;e2r4Vw*{Br%H*px_sCpz%n67aLvKy10$Eu-hee zdb3}O0?B$Kh(W9$`<*23$2jWieu;)C(GfbaIAp9u5(6LjZfIyNLmQ%=V9&zX#HQJ2 zJ`5m*2EsgO2b&-i7Kr#Xi8Xek!5CmfJro>qH|&&g2|_sZkiAS8!B~^Hnj_LXv0}B} zB_Zu^!%Ndu(=RJ`L77d!N3iHY6872hV${hFcq8=6Fpc+Q&kNDnMqP1b_ia%|%$YE& zECq%h=6ZB?nw{Mf5<+jV9_Dc${uF%FJ!v;*tmd%RMvgYgHh)H^*IH)4XFiQ#+|imO z=xlXUF2`;mu9*1xvjkz?%C4>`)H8t5WjI_)GD0ohyQ+c)x?AT-Jg}_RaOM*OktBe^ z**eis41+5oPEw0FMnj3RAH*UG#7N|_`=&4fRTo-Ev=6FV4MgMljNSu;4g;qmZi@f& zj3twnu?vUwVhvV?EDYiNwD~e@d21zDo^{B<^YJx7(*`df3wAL_348#qrhy+}ta2N- zTYY0Yrlc#O#{^O^0Lonqc{4^m3diQw;WDpio*O~mc)g)gDN$!f-9efJ5axzfjF)+0 z6+hWXI@lRW<{AgG1>}R!DL<2-c!4S{jC56Av76vRiF>@Rb;3?JvvY(mDCrYYSe0Rc z4wLvZr#p_a0Mexh*5yH^QG=4w5)9ivyGUFZEu(%AqH7&)=_Cm=!P`o09FDJd;YWB1 z^uqyBSpnfu4GQc^h;Fpuow$tHfFNcaP|9}!vZN+8EgO^7g&DyY3-=ee8TR$q0y8EN zy%>R6GwJUl1q;E?1E}d*Y z=&-Y6l*QSo7^JqP#@r>2^CLuDZS2ERQ%=xpfi#wuxWKax+tVJf4Q;HgOPoG2V};_8 z+z=xncSHC;;`}yV!(;g@>jGfDYWt#Gf!HZoRajBr=VQ1mKO)2q9$1Znp3OS6F={6x zpp@?qx@g$&Plk_0zwV4(i{N&eNRde5ady<(kS#mRiXwv!4QQGJxaEpKT%82hRs`*k z&`6RN(x?bE3z4%y9ws=0nz9p!;#ggPge&9WQ0oH5NP+|oVbe+TK~}N|-1aHv(04LI z)SjorSL9fGg-pc|SEo->CRU=55rep=BUKFNivzdcPl?4HF3`9q`vC%lIOM2o92CqW z)J(V!_HoA}X2Gec3ElEjEzP)@Hl$AY>=ctsH^e-HSMfMJ7;o8D{$)84-EV3d=@crrgNp3o_dq>7JfpH_g1~Ubk|IC@M7nbEP{3{t22yh%k zDTx+xztucetFst8nz>vd`61mLAUs{DE}$uZV+W zUZoh%iB-mYfDX_dcy{~*D?8dyx`G7*`yN)l02Ma0Pc4Ouw$h~vyU{*S11dtC?QlQC z1{j5{mN3R4NtpNuGXlIx9v2~L7dpcq)f(!N>;XIOImCrxUa!JDs0g;|>i(q~k@d0= z3e!YfVk^A3l@kXk%u6TEptiUexC1ZUMfQt^Qc~z<@Y1I4aFr7}meD9DVyqq*B13%Z z8Lt-X-HKZ%^JP1P(v0~0b<2*#T3{l>sTUo5L*g%xWft8V8b7}vc9XU{09$dAsrbN& zPd4~~bx$IXW$7UGnj&2t<*vNS?$xIF5pK|kyeh2Yw5V;H*bbPw`;<=)HzH=wlwqJ5|5a!5WB%j5AnM#r&41myQ!CN_KXl z%0ehUky;wm6(JUJHptQht_p8c6qs%&*oP#ROY#P6W)k3GXNZ7l?B{r`fGybKOl$QF znSIICn%}>)y?SWNhTP`506IuI#iot=A5-X*`3IBr!zAiu*HL zWrnw~>5kUs?UwQyS7DL>Kyrct`WE6)Hhq9*@T{)TA(Y4j(I_M2Kp{x`}SXf!HFaz7p7Bc?oT8!e@dAlSeAb zsWeAG)TL9`*A;TKs|+5~=?L*7gk=$yk)rU&A*J2gF~VD)2uS1{#8()7g6VVtlMN#s zWgO`9;v)ONAd1Ys9KG!gNTPld}en7lU=nTC*1796E26n+)P z38x0F#GGb#+mXE@AC8YikL9#epk{M`?<~@jumffQ?=a#vF$y?k6;ZA8(h^HJk~|sY zWgd`35s0A+anvf_X2QuxK?ok^{xV<%=enMWahEeD-Iz7*GA?j$7Y2KPEMP0wDj5Cm zI1h@{W^1LJ0g7$@FquYKOxSoEN0q&6D3g(c9OLa>Cbb60DZ?lNUO+%hNP45fz`v|? z)tVG@lv4>4)5Y!#S$BG-*+Rp1kWft64wVp@_F~e)C==&Fl6PQ1oKk`{G9DA-n()8F z{SLKD0Wz?OI710t8J8tSX&+REQEDbV8J-UK!4MS)BI)9K5|!uMs97j!dB162oD%mz z^6(*#;09kLP9s{(@WiAcQ9DDW%wSd$^qjK%$VEa>jp|l>;hu#WFL3&iW9?F(fPiEW zw|dKHO;p&->g6R<8Q{ECl6#HRG-LsDhQdze6jSa)lC6BIDVONPf!K)AehpImbe-uo zm=ce54(8?J;#lyt2~65Y2l}|`qKiT!R^or%s)7A84o8&CM(JbH$}KK7Y2~z>C*p}< zS|FZfeH=Q;Jy^8hQgK#O#zg$0LTs4^ zZq4@u9VKq{)B<%~kI=f|x8H?bhO_CLVwYhfuk&%lC1G3v$tvkPIh@?NhJ)7%;bg4b ztt7>kkSN}`T2OTNRC%fbNOd*Zr}KSKO9X+uS=OK_lWkg31yU zV;b0Y9~m+Kk6Py@$=7IPIR|U!yjOK|NQOudKFS?rA=2`>6GStnCQKaREt97V zE-VY!Ksve-dQkeECT}@resc~{keEcC;#jb`B+0nzcR)*U3cyrGgHsX-8E6-f9Tw#r z9?TLAgPT7#Kh^c&RwepKBR@FpWc7~Z96VYQ#T8*=Idpm0TE>&bJxk-E4Srp>%tMZ(3T>w~2a(4V2K>yz@sg}&<$#m4@e z4fEDmN%Bmcf+JmXRz#g(mZW2y6vt_7J)|IT zyWEloCPW*H61c30<|XrPHK?5Fn91K_G>#gDR=0zh2i_<~3+w=-^_EIs((nkZKIBh} z(TVwFf^chWOIyB^s9g{-Dv+cQ07ZQqm}2eNNE)nS34}=Sj<#k(+!2*PZ}d8TE7F-A^(*;)e>8GXYLe-k8Uabg((Wt0KtXGaVmI3nwlE;=a* zsO)qda588qZavC}Qz{N{u|{Gi%Q6$4xL<yt3{ zpvc6(99Ldad%L4DwNa%084hq~Q^QS%C^~osa$jmt6EJ?3ctx6(W`sgl3>(=eO|N!u zZ^meaP=*c^Pm>Oef*u`Xm6+l_G?F|z8-(OZ2#KFSt4fYqXP8|?BONq40enPZv+*g| zc^sx?Fk*IL6j+%|Ev}B@wd+(Xl3zoGboRRTVF{Ec0tFvY93+VU%!!qA$~TPcP@WmR zZw$#nuIM976+Dv`d?GZ(ID!W_2UU2&IW=fvq&fbet0hC>U6rgbl;8s602b#c1`wes zfrcU+MdQm7bei)Wdi5zY^syrDE5O+%gliY;UDL;z$e|72LmB~vN%q$v%qn-m%)tB6 zjDVbAxc8#>%(1nY@)MRoblBjXcT;CC{gaK6ZJ~7k!jL9~qTNA4+ zA$<;se{i&}t5gemh7Pf!$(4WyN{OFRR}z0Y10uqzZ<0nSl(#2UQi_NO3|t&kgG(e? z_;e{vFxzKIr1t1R3gM0uV5Ln-F-3S>;a1gRGqU>VFDaCfk`r?g$^KA&_I06)-tZzI zW4P#sK2lVMm=hStwU&A2NcTobl1k=^k`OWW5!}k0{>_B?#ygOFVuS~C6bfttx(p5F zikQY!4r6?!qa2+!syRg%T7l(Y#PqlTR~s ziaE@w+kp&n>h}`y3P_-&DMDos;zJ7~-waYKu}sILb=B!B;F55x?vuta$g?VR3Mt~)I^je3x%Od_ zOgRZB#5_jo5i(Gr25D*)oT?oy#>z$-2gf;DSPj3yOYuj*^NNO&SP<7QRmlVrIg40~ zSq~pKJl!zBkp;nNw5DTPmT8}>Yo=ObjASn{hS;HDA5s7?dWtUJ zryXsF2VK20PPn|&x&eFzI5Px!37`6I$E|#NFY07iI^>m{-JJxc1~n{UHG`z-gHTaF z6uVRJnY9&;ZB!neWLhK!2SYqLJxGP5zn`$`W)A0&Y|^CAgC~RYMcF8Z3DppdDB;*Z zV5`2PEb%>koXSEDm&?gj>2?AnHim0ohq%k0Bhf8cVeG_KT9!_kNr~yY;LzsU zIZ=(m2a=d6#6^v~AGYuiR%-W1a=&@t5|Z2#_+Ag z>9#;kYI?#6x>^#>l9UX10}iL)hzpK{YMLjyD;TWxu$;W~iaL9(gL$7eg&Riv(cc`WT$$*)EQa?Rqn8^tN(Yko@32!)nq>g-*k z*kFa^gizx1i=+f|=k%}bWk|SxO3ovAc9IFF=0nj_v=U_a-G?Mk2Ir=0D>&6fqT7^d z!Jh3G#Cefb4Q05Q7VXA0)~v0DkKJL|xil7Ko9GcSD1z@e8dt0UG@YvLGtk9pI%=h$ zgpce5_=ce(<7nE{85CC*nE60}8B=l*$cwWs*>s6)!cODVs&}2+t3Rn8|X;Lfz%;l zf&5b4*n!v@@J{2E3O6!*8i+XwgxJYGXV9gnL?Il}1X!cl)9@)TpTPyA(t%@S&cf^) z$SDQnN(mh>!jSKf$7M&NUHIhNA@2@HXAr_R483J8^fN-p^g(aUF@MZ+N~$5OICyEv zsY5LgW}YWHin5aX==C{3lo*pF(0!Y*aK;83li;xxY&>kyB{xf%lm9l!@!K5u&frbj zE#tMby){XrRE9Z=E+-`?IckW#QHxsWOk+8*=K(9`qCQc2$1%rVL?(4Yzi`zeO`L{& zMbsSukq}bR4jH zBIr$rY~ZxT*+rlTGb^|Z6Twhno$D$JU=*>pYK=1=q#@;Zv~x)K(IQaO_IjxGY)1% zh6rB{4H61tc3-TC#2yeC<Z0Z542}zJe?v+L zt3ww>%nnXG5?!JOBzEJI)s&3nQ*!Mll2A+}Lc`Y+YmAE$LA7{}kRHx$Bd~@ky*e69 zpKZ!65z{S67e$<3U-nhqoZUIG`DisPxlJ@-CvRA^D;n+KY{U!jrQ^ z5-0Vi->C+n)3S!q)>l0yUuL1h{z9A#)?Y08roaTDVP-9)i18=`I3Gt6Id%xl_H|1p z0G{OxrjT$=I!02Cid$ojLjp^n>wN*wh+%ed*kgBM zVo9Yg?#Ing?5LhZMuk2Y zDXmDwq_t**BBDAwob^|sYOBbDt)W;$BFmHoVE4wKkb%8G46Yg1!t@YM=cEEpJnH%1$+Eiskracn5KN z63;{nMq8bU0UdFFP^#^*u}kU{aXfzd5{U?!UVgmFHk^zYsDL?PH6l}u=>BtOJS-!u z5*O@F@!w{L(*aBzexMjE82ikrVy2DCPdC?BmbW+_wtWWGfN)Jtu_kz|GeAAjOr$it z;o{sB6*U_tQKry>IO1e+Ldl)NmdUx8fH4F-G>>HBV7nI=f~ILxaBPShDo`g7-$9;N z$SN32$n(i#)8JTPF>plB|@UOU3~p9M8<*gm`0= z;g}-BCumu^fXR`ALpZFFupG{#Z0ee6O2^CL^nuLexJi-clISU1$TVDIshzyE+h>x< zP!JfAa5xY0CW$J2r+F5sC+duGvCKF#m6NiFyhU+5+_QFM4Rz%e@gaoRa}XL_i77CF zt()C{T+G(QkP;xmd3@x%h{(*Wnlc)Fy`FDj`C7%h2lMmkD&OEpsxi*x4Bz5n{cZi7 zC|f(qEt%8(R9t3ECXkqbD`KKcGW3`^&;&qJ;t}cwMvl#oQk$?)8Nw*yNRkt@!hxu& zD_2yRYcmCQIcF@wH@)PbE)?X>^eSd%{5b+Rijlx`W0g#%%2+JvZnPGk6N5sshT-0y z6ZPkSQ>(OgD7MjNO6W7uW_U5-4@TgPU!oXlmEEo8%_x*bV1ZjhWeQ(Sw2SJTUOjCe z(#TkS$rQ*ji#YId@)llIPV+6vn6Um)#dL9~~W5uWqeb3vSCTNCNg<&q5G3V>Z@$0*F zo3Z&!&)Q7HRML#%#iG-akRXV_d=MckBpaNr(>e#GDwNpf zZ@~~iGDaLduwS7-(-lo1dov{lgTqW&nz-0xgAy%^Gg^?YIunD|2MlTDE|II*m<0KC z;sco@yhoUij=`ZQDHtRm zla)y6wCj$UFbOsJkpn#W98$xfj%XO=Fvw2ge6H538RJnsw(j*)}oVsm_ z>j`0sMwwoPM#G`Johl(r0v8_CgDAP2Mu=A!*@cHQdMoAH;vy(spDEkQvceHxgx;k1 zN09c=HvkRKFxI;Zm$c`$B%)2_J5jj9;GnKdiTK9%4gDb6MvZ^7arBQ>=ahbN1GAcV zCSazPbeC-1_>O5v7Z=(OR2+mz^TxTWWP8IJ?u?r|c{4a`WMY-rFlLFQC{R11btu6l zJiD7!HxunqRns`49mK(XP@?~fp`{HNnbh4HM&;lgj?;yWCRcbAngLXJPkxm1_tnj>or+wf@Flnke>n28Bul?YI}^vMJD^>0_@;j zN$HC4iEajVQ#bLl>Nc#J$Q{MRs zk5uH(;-VPYhqf`3DQ$7lCh#$&j~O5Gk7#j7=Bq1yIKg-^;4LFjD&&cW6O!z*WL>jE zqBl6kx#oiiv>-k$N1o)^h`2Q!?Wr6(uRzETX<0bVl2igIDU!-S4SH9zpJsH%+Ghm)k}l@TF$5)p)bP?xU9g223y?N3-%dk|<%hg2lu;-XI0sIPbU7;5^L$W)ea^f&iTqB)!I4TO0o_+{RFEs;CG9|>@zNnyN3dc8P$XYrkIHH5) z2>zc8as8BZSq_+%6SPmfsv`W7p<2#aWH3O_0xw>2S910vX`K}M7$U^lGh25(4E1^= zuIgD58?C+LS)yxNOH$Y&23q2!*W@LYaEw%pfdD%RDiXZi#j3#cAw1(`f2&7I~ zjbz65A8FhFXpR9!jT~_J4iqK18aaF`9Rab!F??f>_^d*bm2zr)Dq$lKf2T{krTgvE zu4G-|@QAJk^-(}@+A!>z){!O%kNt`OiJY_IyG|3V&^j!5C(Em35fNJovLXkSC0*P} z2oZ71?x@@%NEuZH99l`FF8-8F&u8NHGPd4AB><-xFkCo{lPQOjNF)bx1Iso;6vthV zq_>S;3^|A8iH#B0=n^OXSQnVK)mtqE$&L{6NpD=m*qI|;P$+K8B^wO~fh1@Bz;((t zTAvPCtP^yxc!3jnXCP>*OZ)te&BXWwN9=9Z(iPDRMU1MFDUwhQUGD@DSG$j9a zA}Mb0N_K!w!a|8t;h+JDQ{gCMb+{Mj5s|0bE!?0&;&*+phI#A*XMr^!_ISZTPDN&pD>``MoDs>=cWIn>@S`0~{ zudAAX6f@~P0#2{NnS*>zoI8n+lhbyW!1}Ho!=&2Yv9XX&c11b68J`2Fa05Hhx%~WW zOfJ7PM>gJCs^mbAiU1dOFX&ZOks-=&Aj~vDW9hHY39bXiP)_8=BCBCejOm{-k+X0m zqbCitQtC$)>^>A9w^-cyOuaP~*ug5wjS*fAils7+wh=a`_qyg)CX zv$Anwbp|n>Vjh5oDIp*`-R zrVSG)j98=CXk?*sG!a^Pj=kk1bc_*d`bRd&F>y=EN!7$Ip{6E>5`frZ@Go<^-`b%j zqo5D40n3ai*PpVMRqWh z98@dh#wwdoJXhtW98YBQbc7X?4XeVii6$L<(Jc$Lb#aW~I#xz^zf(beTA$0vW8Qv1 z>2_rTw6W*@W5y@aJ;p{XP4bBjE<{pOVzKW4lXYBxfQ9(S5j{*U7u8n1U%*vZi$q5! zzo@0dc5RnB_}xq}Uey>Ratu8sK_qU|8m~q}u_O&S&O2oGB!LP(FXrP(*f7f(F6fEz za3g9EgB!+rGcdb%sUlXHRDtAV5_1HJ`q~3Lfo;R*R7Y7$`pGW2q+y9kwmMw89b48_ z(mu?qq?Aw$IrGb*A-FM!%O0!|T5whUC0&lA8;oeRaXaTQ+qA-f)b2~> zGkiRefgl_{CxI6Mv3b$ z7R-zzYhrwuJ5KxB(X~0w9Yh04KA!%7JV)xo3EiLBxmJWrQnA7JCpa20r%1U9aHf~0 zUXfaD)nrj48L8w6dq&8>#bi#y4k4R9L4X}`8i`nA1k$)eFlLHsk2Z3QELGyK*VFk? zIN=V{8tV(=I&LLX2WVUhTF#i2=!A%ejx(u*v>d;Q^t_YITJEm$(D9(J;9(v6ycNk%~CcI(6!q zY2DNgY=9z6R6-;;5$?m|hM4QSns;2@j>=AMisfFRRS+hu? zWEgMIM^CHKPwE?^yl7&RdP*CIOC!l+F+ru6vB*@{z}S`Lvr)?QOSU2u8hHH?W7+~} zmcta^*_Nuez1T!6C-X}tA6y1dw{vE0kT~si`+QkEvEu}`B_pr|j6lxQRmUcG7*>w4 za^wtbHTwr_ekD>f7Ma+wXU7suL*Nxme=~;-H|2-g*}c@} zEk-iw!WQFsAc2SxM>sU4cD+)X*@w*HIgK+WVNV!%U7*aLKFVw;0V6$+Loy_yQYPHY z6u~w6e;$kDPy`bJA(-3=K*z-wldv;}8pT7g`a}v9#45N>axFOO#^Wy}MNrl$q{lTF~1A>V&;uBK>Mjf;Q z0~((mb4DeS2yGwqael$&i=;Gc^S&-L179^YMTqcKobjGvB!u-8Z)6MD*WL@7u~J=e z6R}w9bAiPix8I83MnM6OzkO7O82Mmd%9S8uAj;dOE+()YSyv z(M3)_C8iIf91egXvR*Ub8eMiUF>F^ zbm*x~K}>^$-Ml@1{=^BfIG!JBVF$={oP*oMEjp-+b|^Z=1TY2=*mt+?VNtEDoH%hm z|LTD}dDl@4Q72BQ&EYJPmbugcF(*!p1Vx=VVWOhORMYTvSE9CA=Wk3yMXZ#wM@=); zbPZ38&*;i`V~StgjCW<)l=Igm2OV8L(BU-DDtgKcrdf+3uAVp{dMc0?EHevCuvdM} zkJznr+|`toD(k>5-P=3Q-+hO(S}zFLtY|Zhd`P4E%d0t#h=wokpTGM~>CjzA|CIc^ z>1g=Ani`OyL_9W5oG=$XiE5K!sQLR7Cr0`E%Y+=&3ai}?sG^0^nTx|{jT_@3OK66> z%r>Bm&-f=!Sd;ZhPdNdN{Y1`-IR|ls5?1Rgc~R}~m9|3MeTT+1wW@73n{6y|uu&uG z{i2jR=_&DwTG>_Pm~Lf7RQpG1v0HGiRiw#AHU5)Rt0l+30~<5f4qwpXyWxk}np9E| z!>ZqXhk6F9R$nos`r+DOfceDS2$k25Yjcdbmj;_l)HLR*<*7xz;DvJS13lEv6v&E4 z-Z*jMwDR9m`x$r-t5n#YV7otY0)u*QbzAn8N7hy!;o|N;wVl3?HLpY%L39-(A%g6Q z6OV74$7EY=JX0BX+I~28^E~Ovh&Xd)+E*Vtb<)#bCFYwUI^Ou%86=Yk`B^_@%xC>n z7W~fN8swCZYn)ftHEydrQODh_Kho&Hv@EaHQ=>h18JNa<_--@I=I5;|YyEr$RBoM> zqd181AGS;!Q@)et2=N^c1wE}eKvgT1tk}(IU)KJwQ$7;pobpvkpHBH=>;=&4+MwqEYGiMrqe8EMF@_*!LSJD@{O~?I-CZbMjxJEE!bDbqhK9XWfr5X zBG~Xv5TsI3949@zJB%Epe_wy}jVDd>QC^=ofm1{!4L#}U zl?3G^__m)ZIw2;Ulb+e0%;z=?+I~oz?4{-b%y-5|%f2(Ih3#!C>vHo@^9Ke(OWL;I z*y_EhAhgkP0-#k=qPO7-lh5Kc8{tSZoHdyws_hL9vnoSY#Oyk!q^Vtz`myu`{un^I~~xjJ#ZT_afTh z$ST9_A1tpZnWo{6j36T(L4xlieDy^OEAUWeSY=Ak_MGwY8S`nhB z6?T(lvC)NQNXk$>Cx?VS_<5k|ReR zUo*_hNl!KfJ&V)6nhH(KAuc?3Q;}`Y5}1(+S_R!I(9;V$&}8owzM#(D%XHTI!dYO- zwm6}Strj#xF`@)#Lsf@Bn4uc;UN?VARi{>q6?T|ss(Ps09*^{6=&LD!>n|+}FrlyY zckZrrhin06FRyR(dG&_Cw3Pc_QovGE6N>>@OFER?@?$fwZ?!^REwZ)sEo>EN&(!Zko?zH2HFGhw zA!%fvxD&PZtKpebV|Fpb@LZ++3SFc;F*se6_}$f+xHryZ+SZI2~_4PX1x3V#7c z0D3&e-3f1ZOZ*HM(Z-)ISf{QDg-kY{mMLw- za;U(I3RZK~L!7vJ3k9-Dpo>8{G}SfAwfqt*hb zNr0lAhV_-Ol{+Y`UaU$ZyLT$f0KOPo8*3{)y!oHSJ73>D$c^#@lo$d~O-5FwJvCHL zsb%yS-jD8X(+xFf4{f=&EzYbsR8I~^M+%bfsfz)RJ5E5 z#a?ALPPr0a1O1EXTrO}pZ+S`aNVY0@rQJBJmlO}QjR*C$Y;iijZ{yk^C8T?*sBtHi zXB&Vgl%%e;*XINkb*EyfanD_2qg~OF^v!du?PFdUPb|Q$H>X>vLVT}s0cr>`h@b43OG zZvmTO-pR9S%2$qC6<Wxt+_L=ohUNgM3K4g<5Y&4s&M^WmH;UgKP39noPvaCo8f$E3uBB&o0 zm}`1#94US`E|9tkW58L-&#!IMm50=?a<9FOeRkS65=3IbIn907=~M50p!e{-kG=hY z$410hK-P}DV%%`b)j9BA?*gWsu0phKSnJ_Xfl*h0y4aXkLxra~hK_oT>Rs?oP5fxM z!i?s2Xh&>bG;Vm!Gxk#lH=5_Y-UeCBg{kRnF5#N4c0HI$W^p*8am-1*W2WFXx;*M9 zJ8i0!Pwcc0#CsNenGLJP*Wa2^1*-L=sbSpX@gT>{wgNonc>{j(ODbXS-A={1z9)H# zv`ePCa&mKcz23^oGv|6c(70aed^nBHwLY9m>-h3`k#C$?JHEk@&9Keqk8f@i$Isx@ zz1?3d*3P}ruSlB5ac^ZmtH^E=9_Qiu@=8Ts&27AX2{L+PXVG8mc2vQY<^JNv=J9(* zzgi^c1$idZ_>IMx?ZxB;s+SYMoiUuof%PKi!>NPqemH%wqZekd(@5=Rq-Ipu$j=Kh z=Rx3()YP#LW*vL?!|7umi1g8W#&;l3ZG!vZ)aiFWoZbZAYxE^iEaws7d;B)L%^e#m>}HB-+#5|83#iDc`tpFHyUg7t$q7R7(Va4j8F6l zSl${#q09Q+=e>Pz_y!Uq* zrb@-X!9uAZbno|1(p`~NoTSm-e_LeKMtPqWOK}0b$1lB;9w}8v?MtNa?8FHhr26X& ztcs%X&M{rNP1aRvK* z4AukB<;B)%oX4#O(kt#&D7clwY>8eluhWY(zWzce%@g77sK zzVIGxYjhVsX`Ndiq}LH--X~cxE#)Gwln%+Po1x`%ADy&*VBQUoe4RKT?;{U+3vW;_ ztv=!+tzK4GKI#3JQ=aFz>wC9n&P0o+w;E|J77c4-Uj)va|L1LrXn98-Vuof8Gk<3c zSyeF^BJ}FcPj>qM_9xZ$QU^PEKYzRD@=c$)(~=-Rd)|BXBkW{rm`hdK1QqkXM8b|0 zLJg>EJZAhwc2r2s4C>Fn$F?K(2et4%SZ%E7#rDH5vrUKts37gz*k1L%>NWOh$Y%5T zdfN`zZ2oDzQAN@7-oIss6K%Fo!js<3?7m7F&89j=>HLnjGai>PCh&h8r@4++{M%So za%AX~0sA`ItCZ`FhT%Ktw00{+6+T#g&ij$Djp>)d=G&~Rf4b^R?-MgV&cs=kddk3B4aB@LvCKeHX)K<*Ws>SFtfJ*ApDFG5%3A`QYeDFq)b6 zmLBvxPNg`zz3%-wqoqbvxR`$h-!d+%$uYhk&3)s&MiVygLyUnr^Vj=oCbgCCop+=sptI!?hH23YOEHLj{q^`|;Ya1^S6cLgU-us4E z(P(`@mzMycImX2MKVWM_)x5OPO62B!gnpXsjt%aAAVsP@wls<>51OE8qIFjp=e1`Sfg&mHS*-LPfWCOxpCOT6@HGbXm|(QuKc-->ER^dD0)VN~W5v;AC=*zx;VV59j|ygz0X+J;!8 z|CxX?jxWMV?=476vnCSzNX-i}INrCjfogXbvJ!Bd*&ttD@xJ3WyGm2`*dJx~Iv#Rw~7l-Y+7u*b@M~zXC5L zgSF{-==b!v1;PK{-nqv}RbO%Zuoh9VlteT_Bal+mDvFC#7i&i;53{fjJG0_aY-@;AjaX8vwLWNTYiX^BiYY-+8)L1nQb}uR#CNsT7^)?uQPJ4X`91Ev zb7yw;!tNSu_(Npb`}p18@0{;>pMxU*YD_KiHi&Ew{e^a8F+b$ZxKBdbFhvwFO|(@m zft==R^_^6;DIeK>M=}-79!K8N78=5(c@BYEc@BnC&dW;&Z)$5QwB#D|vS}||d1X-G zs_@7$OV)%|0_IstcH*r-4#@ZT3UIPETGnDZ6U((DMb6P?%Gu29hDHqGQ#f)T=E=+P zEW&1S$^$Uyfjq@%-_R0*?ZyP{mpsKORbGPv)!d5>bcD^0qfIhT17~R`zC`Lc#>9P1 zk4Dq70B#&$>2Sh?tmlDDm6mnTo8};Deyi$k3`vtlh{|F46|$<=W^v{I#9)!ysg1G> z=<}hF%Mf_|r`iU2zHXYj2R$RHnDC5B8dggF$jO>qUzvNbx>g!x4vz}_^tf*i zR9hBzrQ82ZwPSK{r*ZLb3p6SCTehoqyX8Dgvs`JLxlZ%dU_Ramt!zK#1?xFx>4B&R$SCD!G-chlGVHMG&tvMuS| z)RzMeMxf~g?gE(;SB%jrQ*t57kx%^9rb7fjip+$F0^b)op|@k9>Np1(ih>1gbg5q$znLNK>f6 zO*;~k%A{|an1I~ON9e&x7VSOkQ2vIsNR6^?Y|)gQ9r#``?@r)0vR?8H%}uf~CJ{?G z#97$Jyz`9PFD(Z<@R%4UnxOVj0Y2j>$N(0vg<3?xI%iTc2(cGQ>ea@pvJl8&R@<_Mj>d@N_(}{;;+4(mT&_))LxGm* zFwvg{A!pi20dhW;r~O6s=~%@(AWxMNTK*@i(bdD~lFnn0SH52KdU=>v!)7r*1A6}b zLv=G6C)-v)(h4{#!SX>)gzoQ+FIUEuxqb6PJ9dshj@|8~J(W{(7Ym6N5Xw;?S(#+9 zk_rKnFjn%fd)-ia2^ja;kDBd^>T|mjhQ^O|Gg^+eK9~FWi%!>+YH~3U5t{LQGWycnH`#e z<4$?JlSve&g)l;~xvfDauoRuRBg3XbNtyy`N)sNj_lAgNpRyd^+AJzI0gpyi(yuF0 z-V)Z`tm(-uR|LFc$}?1H4x#PQUgqBumTBw+P#c>}AP3c~xzHl_vnBRK$>I2la@v7r znH4(BBiFC&TRSuG5^X|4QgzanYnN6WYARPBL}>Lzu1NX8OfBFDzByD>^V0JoW(&r_ zWEGgr*ZN z8sHt!8usxW8Lj`r&y`BaE{)Md+Cj?*Ow*!Jq}&NMMO9~gT;OQwf1B<&xWk2xDZ}b5t2^5Jq?+;vEP67F5-jNu&Hnl}J!8 zDpfi5zCQ<-tU|{&!Kyh;1sRR}kLPSz_s0<#a6wBo)ezW&I5lHq<;MOnh5|Tk4-!>t zkZjGM8B@nPTMP2-(tHNbnSe5n-kaF1>I=cw!*>oHDsXJC30g2g*6gNVj3aV1b&qqY4 zp50%JrWKE~*xo(%<6My2@`(;LjbHq!v=950vb8Ba@GabBQeeXyozmFw=Rx$zds5Xo z1vR3xrDXU_JprS2OFfjy-<2|dZf#i;?3D|TrF#UO)LN66zUovk)4kJR&v z9*4uNh-XM{+Y?t%gn>}-3^(RgQDhe_XvT4v+LG8MiBiU*# zYT_qG5o?iEM`uRe8^II)?|tCcYdlYLHn0vY9hUHmGh8L^8d-o=y%j+sH{BdxfV*-eyN_y`Wjq9`v52 z9XZBw=H)5Q$Ip~X`yjPUg3WCTjGvP(Kf!v6w2zQ)Aiz|WbGGyX$LdMyKWQ;@vF7eWm2KmL-6)i%dN#U5xNBKh6l9xvsJDv{=%^B z%G6Tf9+pWw4}`f2Id%3!7Vo%aM&28Bbhmq2Ra|%r_mUeUBkOrm?+l(wlW6Zi@>9cN ztp7Bl(6TrM6g>;j#=cFtS^*5C(-8znTTtkb+h7%D^?9wl8hSHQPF{qU)M;Z-_wz7D zj9u)(KFLy*wB&|f??|Qj;tz51>OScX&E?TyPQ#n433jp-cJ~|tIGYsQ8i(;AJ-ooK zXyK+j#aHL@^15Y=2w!(Y7o)(C`Yo3>%xR=`we9*uW=A+ zyi477Fb%!3kX<6b0f=m&xzrF&jmYk>|JlW04xVJ=>TThAP3^3p_@&`Oo9F=>R=Dn| zp_ZrQF@y_FY0vycbm{CKTcyB2tkPM}tF zd!WSAQmb=e&R*O6d zEe95|e2hRMl|mN!-=`PlI@=pnC}@+4_n=nd@(rz8_&E$s)YeGq5KMU$jGkjNg?@oz z7WIAw{qR$n)glV7_^+D*CHlB(rD_ut4ayVA4QRET=JhnY2QbCZcOSOI>C0j>Y*g(^FVbJ|&;9QVn&> zM9_JkC!?R*ZQi@OG*%!i?8Z+3m|j7SQWhp#;y~^TlK*rhUlMv$lawLv$@n)^Q;U>? ze(TM^D!J8?az57NUS z9xMFRlnsu6eh&Kqc;z zL9qqsnrbslK+-^}Y;cjY(&Nf!IE$zDzvp2Lg+dtU^hhw13J)9-a2)Z=cV8sHM zx4qEf4lRbawH9$w@&Ka9M>5sTu%2_U;>08h1)0KY%6zCsfMhdUYK0^$R4q-nAkXDV*p7HrA&W$I4mErJ- zs@@`djdLk^9uFa=p4HEGBZf%Wqcf0<2S?0Hsm zxrk1(E*Yj<17b9ql7j)V`__&1KUI^4znc@(Y@dEjHrVBKHY0@xqFhIyOjZLeQOC5} zNB0^_HQIw8DK4k;UzO>O$@5&RiCC*&JsiauvT6kI)4%ekzx66mKyP&ysnj}_A4?hr zm$~(??$+|x`v)%VD!TRFgLT2jEfx9o;Ub#`t4iV8AhtI(n@U_;EP^YRfZ=A?X(WSM`3 zmX>QWxh&;YUrM8^y&&?lF3O+IBhkmEmr;P4mun-}>N3i2`b+jJDz>yWD&ZjAT|*NU zrcme_w;q<@LS6jXNuH=HKgw^Q<;3ZJo7_pb&D?ybd2bJ2qAPp=O@vR;E9JaIcjggEQ{wXKhvBVPv4Et1H1cGTk= zU-?}gqfUZ%`M>{X%kv98WqY14rt16obpWf5jb_JhDdB}5`YQqkEPm=(R%h~6Oa{e` zmj5g5gaJw(Do9>r)^l)pk3!a<(1~2nmgudt7Cn^AjKIsB6GL%___sQ?XC>{{U~G>ni{N delta 44569 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 OzWtw0eLH;V&;BpQ@Uwyd delta 16941 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 KZ-&qQ;r|CUUg!w` diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 15b9ad7d670bc22bb78a7dd5d3417a429f53272c..58dff40f048c99f93b6798bf1de26963f302c4ba 100755 GIT binary patch delta 17271 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 E|Lk=?sQ>@~ delta 17810 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 M@2`A0eB$5!f2HuVasU7T diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 9d445b88fde2864b07ad9d1c7916123ee0c96f67..3e476d5ec4aa9dcad450d17a744e092d10a0ad1b 100755 GIT binary patch delta 18096 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 delta 18652 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 D&)3*0 delta 38737 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 Date: Thu, 11 Aug 2022 16:46:24 +0200 Subject: [PATCH 223/373] update wasm checksums --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5d670022b7..0e7c2970e3 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.71acaf1ff3a4ac1ce6550c4ce594cf70d684213769f45641be5cdb4b7321184e.wasm", - "tx_from_intent.wasm": "tx_from_intent.e18907b1fb0e1c4e4946228c0ec57838a783a5d88c6b3129d44935386693cee8.wasm", - "tx_ibc.wasm": "tx_ibc.38ee97db63b765f1639ee24dd3e7f264139f146a56f7c06edafde63eaecedade.wasm", - "tx_init_account.wasm": "tx_init_account.6be05f8ca240d9e978dfe6a1566b3860e07fa3aee334003fc5d85a5a1ae99cf7.wasm", - "tx_init_nft.wasm": "tx_init_nft.5f710b641c38cbe7822c2359bf7a764e864e952f4660ccff9b58b97fbaa4b3a2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.25666fc8fbf251bd4ad478cb968f1d50a8100a0a7aae0e3ee5e50e28304e8e3e.wasm", - "tx_init_validator.wasm": "tx_init_validator.2b1a4c947242ddddf94a491b8c571185f649913606bfa7d37f668ddc86fe1049.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9af8eea9219db09161b98405c18ea3c82db03b982f69a66ce1d117e64a715c3c.wasm", - "tx_transfer.wasm": "tx_transfer.87e4fba9d2476388e0f2b1d4bc00c396ac7b8b7b38d5079735fe53bc5984b583.wasm", - "tx_unbond.wasm": "tx_unbond.61f2ac46e7680ec35aba367ec908ac63aa43d6765cd07da932aa9ea45d77be46.wasm", - "tx_update_vp.wasm": "tx_update_vp.00d828bdf510d92d971ca59015945c8c00f285a802a655451cc5ee87c5ee99bc.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.92ed628aca7856e2b0fe461c0a0a65c6f7c1f585ac542c10feceef0e4b9ce611.wasm", - "tx_withdraw.wasm": "tx_withdraw.fb54d108a6fe7c6db16e573110c4a9f52158ec993e04e5a06e1425b8dd676c54.wasm", - "vp_nft.wasm": "vp_nft.3421840146f8d2e6c3ce3a0d94bbb0dad6ef296c50e9d00744179d290f32745b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1b37ab6939280fbb54512918053362fae34e3db74f029ef31c9f08e767539afb.wasm", - "vp_token.wasm": "vp_token.08dcec2d3ecb0942d0a244bbd176f9a42424ec09544120db1b30c0d97ed7dbb1.wasm", - "vp_user.wasm": "vp_user.404f089f0c73fcf906f7d6059d74c6bab3e2e3d3a108394503bbc22588ff95b9.wasm" + "tx_bond.wasm": "tx_bond.d1d6c4f26fce547f8513a541824a4935edcd33493eff8b1b7a90d2ec93ff2e3e.wasm", + "tx_from_intent.wasm": "tx_from_intent.8c191f8c888143f1b839a4db9b6f0befbb088394785434da146b633f5f30b1b8.wasm", + "tx_ibc.wasm": "tx_ibc.0137abdd701591e3f12bee5dbf24aac5b616febbfd93cfdf40f68715493e46a0.wasm", + "tx_init_account.wasm": "tx_init_account.f9765f3fc3e93ddab2981d69196e896db20cd47bfd378df73a7d1f28bd18b917.wasm", + "tx_init_nft.wasm": "tx_init_nft.b268527f6b8d3aa99c07d50cb003a3be4de38dbbaa7ae5ee846b4e4bc43d12e9.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8588577db37a9b55f6700b761a0dd39ffe16a69a818da875397e48074775eea9.wasm", + "tx_init_validator.wasm": "tx_init_validator.74f67dbceb2d3d07d4e722487887d151c88caa44d600ad9e2d9b316568430a0d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.b0cd25ce674765a2fc79a69b3863da66971317b7e0168a21a4bed41a3211a7a3.wasm", + "tx_transfer.wasm": "tx_transfer.71adf67c4b597f79ee85284eb144513ece312929c0c7629d2d3cffce1446b871.wasm", + "tx_unbond.wasm": "tx_unbond.549c267003e98a3807a67c95e790bfa8cd35ae5da1344cb776a3c67ed3e7df7a.wasm", + "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.7c0b48866b9b31e913f338d0afaf41605f51c0cddf2e3a3f0d231e57e68c72f2.wasm", + "tx_withdraw.wasm": "tx_withdraw.34f64072aacd5b4fc3815a12999fbaf0dd64cd2c0772e302a634c3fbe7cfe7ac.wasm", + "vp_nft.wasm": "vp_nft.ea4754591552e715ffdb125b649b3d1bb4b45ca93c3c808a51fa5ccaa9787d5c.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.8a2c7be6d3e5afc4fec1e001c95f7134a37e64df661bc54e9fe04c6a38e166ce.wasm", + "vp_token.wasm": "vp_token.5e619a0471b160712711bf44de9db1950f12900d1405dbcca0a2cea41d16a8a1.wasm", + "vp_user.wasm": "vp_user.7667570c56cdadea9fa8eb303e31cbb4b6661406cb277dcb59a9293702f6dd91.wasm" } \ No newline at end of file From 18526da1a51ba4851d89862d9c9b5afb02b73ba2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Aug 2022 17:20:07 +0000 Subject: [PATCH 224/373] [ci skip] wasm checksums update --- wasm/checksums.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 0e7c2970e3..b8e98c6cf2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.d1d6c4f26fce547f8513a541824a4935edcd33493eff8b1b7a90d2ec93ff2e3e.wasm", - "tx_from_intent.wasm": "tx_from_intent.8c191f8c888143f1b839a4db9b6f0befbb088394785434da146b633f5f30b1b8.wasm", - "tx_ibc.wasm": "tx_ibc.0137abdd701591e3f12bee5dbf24aac5b616febbfd93cfdf40f68715493e46a0.wasm", - "tx_init_account.wasm": "tx_init_account.f9765f3fc3e93ddab2981d69196e896db20cd47bfd378df73a7d1f28bd18b917.wasm", - "tx_init_nft.wasm": "tx_init_nft.b268527f6b8d3aa99c07d50cb003a3be4de38dbbaa7ae5ee846b4e4bc43d12e9.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.8588577db37a9b55f6700b761a0dd39ffe16a69a818da875397e48074775eea9.wasm", - "tx_init_validator.wasm": "tx_init_validator.74f67dbceb2d3d07d4e722487887d151c88caa44d600ad9e2d9b316568430a0d.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.b0cd25ce674765a2fc79a69b3863da66971317b7e0168a21a4bed41a3211a7a3.wasm", - "tx_transfer.wasm": "tx_transfer.71adf67c4b597f79ee85284eb144513ece312929c0c7629d2d3cffce1446b871.wasm", - "tx_unbond.wasm": "tx_unbond.549c267003e98a3807a67c95e790bfa8cd35ae5da1344cb776a3c67ed3e7df7a.wasm", + "tx_bond.wasm": "tx_bond.091bf7885488fe9d01643d448236a3c5a8496fc72b1992d98633fa9954a79505.wasm", + "tx_from_intent.wasm": "tx_from_intent.65511fd08da70dc102f8c9f2c4c96fa611d7d2c9b680bcd323da242b1ce6d563.wasm", + "tx_ibc.wasm": "tx_ibc.bfc87f17efbd799cfa73487b7e080f845a6004cdc5f9b26c74cee93587f9d3c4.wasm", + "tx_init_account.wasm": "tx_init_account.ca95fc31bafe84f9bccc4e01336d2d993f4652fef9ea3d0bcfc10edbd9ef30d8.wasm", + "tx_init_nft.wasm": "tx_init_nft.683188dc9eceaf55263d4021bb0fe6c733a74e79998370fc54994eff0d4fd061.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c4612a75320aa5ceb23cd61da9b43cf89b15feadad9fc28886eadfdeac38bbca.wasm", + "tx_init_validator.wasm": "tx_init_validator.05b94e3de1ff997c830ec0393df6d51fdcbe0410b28e3b9b4cc585c7132141b7.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.e29e4510c1bb9df5b98671ab2b5b5c728d21812924398e810f8d4a00885d9b98.wasm", + "tx_transfer.wasm": "tx_transfer.38739d844a43275e1e4bc18f79a225d1abece7bf302a49763073c1a5188f54d9.wasm", + "tx_unbond.wasm": "tx_unbond.c943d4a1759b2912dc1762e9bce55f16226db12b081afce912e3fa1a28459ba2.wasm", "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.7c0b48866b9b31e913f338d0afaf41605f51c0cddf2e3a3f0d231e57e68c72f2.wasm", - "tx_withdraw.wasm": "tx_withdraw.34f64072aacd5b4fc3815a12999fbaf0dd64cd2c0772e302a634c3fbe7cfe7ac.wasm", + "tx_withdraw.wasm": "tx_withdraw.1ec566946f3178c7a7e639e56eb4d1e26ac24e4d5cb856efd1a81bbe59390a3e.wasm", "vp_nft.wasm": "vp_nft.ea4754591552e715ffdb125b649b3d1bb4b45ca93c3c808a51fa5ccaa9787d5c.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.8a2c7be6d3e5afc4fec1e001c95f7134a37e64df661bc54e9fe04c6a38e166ce.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.70588d919e1c3d7a9c2ec37c67c5587c835ab0b9b485b73cec25b9413c9ccfd8.wasm", "vp_token.wasm": "vp_token.5e619a0471b160712711bf44de9db1950f12900d1405dbcca0a2cea41d16a8a1.wasm", - "vp_user.wasm": "vp_user.7667570c56cdadea9fa8eb303e31cbb4b6661406cb277dcb59a9293702f6dd91.wasm" + "vp_user.wasm": "vp_user.3712b862e606effd77fc8c82058d960012ea5aef09580b8521de827484af27d1.wasm" } \ No newline at end of file From 52ef18a1af0a886c56dde7c6a6bee742431d31b4 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 4 Aug 2022 14:14:22 +0200 Subject: [PATCH 225/373] [feat]: Integrated in new merkle tree for ibc. Proofs are not correct yet --- Cargo.lock | 1145 +++++++++---------- apps/Cargo.toml | 2 +- apps/src/lib/node/ledger/storage/mod.rs | 10 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 12 +- shared/Cargo.toml | 3 +- shared/src/ledger/storage/merkle_tree.rs | 425 ++++--- shared/src/ledger/storage/mockdb.rs | 12 +- shared/src/types/hash.rs | 36 + shared/src/types/storage.rs | 12 + wasm/wasm_source/Cargo.lock | 2 +- 10 files changed, 922 insertions(+), 737 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9c1414272..d1af6bd240 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli 0.26.2", ] [[package]] @@ -23,7 +23,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -58,7 +58,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "once_cell", "version_check 0.9.4", ] @@ -83,9 +83,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" [[package]] name = "ark-bls12-381" @@ -286,14 +286,14 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.4" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", "num_cpus", @@ -328,15 +328,6 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-mutex" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" -dependencies = [ - "event-listener", -] - [[package]] name = "async-process" version = "1.4.0" @@ -356,16 +347,16 @@ dependencies = [ [[package]] name = "async-std" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-channel", "async-global-executor", "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.11", "futures-channel", "futures-core", "futures-io", @@ -374,7 +365,6 @@ dependencies = [ "kv-log-macro", "log 0.4.17", "memchr", - "num_cpus", "once_cell", "pin-project-lite 0.2.9", "pin-utils", @@ -419,15 +409,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -456,7 +446,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-sink", "futures-util", "memchr", @@ -480,12 +470,12 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "atty" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ + "hermit-abi", "libc", - "termion", "winapi 0.3.9", ] @@ -506,16 +496,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object", + "object 0.29.0", "rustc-demangle", ] @@ -562,7 +552,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.137", + "serde 1.0.142", ] [[package]] @@ -574,7 +564,7 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "lazy_static 1.4.0", + "lazy_static", "lazycell", "peeking_take_while", "proc-macro2", @@ -586,9 +576,9 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] @@ -679,7 +669,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -688,7 +678,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -773,7 +763,7 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "memchr", "regex-automata", ] @@ -801,9 +791,9 @@ dependencies = [ [[package]] name = "bytecheck" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -811,9 +801,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", @@ -844,9 +834,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bzip2-sys" @@ -865,12 +855,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "camino" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" + [[package]] name = "cargo-watch" -version = "7.7.0" +version = "7.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4cf2908216028d1d97f49ed180367f009fdb3cd07550d0ef2db42bd6c739f" +checksum = "45fee6e0b2e10066aee5060c0dc74826f9a6447987fc535ca7719ae8883abd8e" dependencies = [ + "camino", "clap 2.34.0", "log 0.4.17", "shell-escape", @@ -880,9 +877,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -922,9 +919,9 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", "cipher", @@ -969,7 +966,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -993,7 +990,6 @@ dependencies = [ "atty", "bitflags", "strsim 0.8.0", - "term_size", "textwrap 0.11.0", "unicode-width", "vec_map", @@ -1007,7 +1003,7 @@ dependencies = [ "atty", "bitflags", "indexmap", - "lazy_static 1.4.0", + "lazy_static", "os_str_bytes", "strsim 0.10.0", "termcolor", @@ -1016,6 +1012,19 @@ dependencies = [ "vec_map", ] +[[package]] +name = "clearscreen" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c969a6b6dadff9f3349b1f783f553e2411104763ca4789e1c6ca6a41f46a57b0" +dependencies = [ + "nix 0.24.2", + "terminfo", + "thiserror", + "which", + "winapi 0.3.9", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -1053,10 +1062,20 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors", - "tracing-core 0.1.27", + "tracing-core 0.1.29", "tracing-error", ] +[[package]] +name = "command-group" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a8a86f409b4a59df3a3e4bee2de0b83f1755fdd2a25e3a9684c396fc4bed2c" +dependencies = [ + "nix 0.22.3", + "winapi 0.3.9", +] + [[package]] name = "concat-idents" version = "1.1.3" @@ -1069,9 +1088,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" dependencies = [ "cache-padded", ] @@ -1082,10 +1101,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.137", + "serde 1.0.142", "serde-hjson", "serde_json", "toml", @@ -1154,7 +1173,7 @@ dependencies = [ "gimli 0.25.0", "log 0.4.17", "regalloc", - "smallvec 1.8.0", + "smallvec 1.9.0", "target-lexicon", ] @@ -1188,7 +1207,7 @@ checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", "log 0.4.17", - "smallvec 1.8.0", + "smallvec 1.9.0", "target-lexicon", ] @@ -1203,36 +1222,36 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.11", ] [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.11", ] [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", - "lazy_static 1.4.0", + "crossbeam-utils 0.8.11", "memoffset", + "once_cell", "scopeguard", ] @@ -1244,17 +1263,17 @@ checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.1.0", "cfg-if 0.1.10", - "lazy_static 1.4.0", + "lazy_static", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if 1.0.0", - "lazy_static 1.4.0", + "once_cell", ] [[package]] @@ -1265,11 +1284,11 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "typenum", ] @@ -1289,7 +1308,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] @@ -1310,9 +1329,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" dependencies = [ "quote", "syn", @@ -1346,9 +1365,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ "curl-sys", "libc", @@ -1361,9 +1380,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.55+curl-7.83.1" +version = "0.4.56+curl-7.83.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" +checksum = "6093e169dd4de29e468fa649fbae11cdcd5551c81fe5bf1b0677adad7ef3d26f" dependencies = [ "cc", "libc", @@ -1530,9 +1549,9 @@ dependencies = [ [[package]] name = "diff" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" @@ -1555,7 +1574,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -1578,6 +1597,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -1619,7 +1648,7 @@ checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", - "lazy_static 1.4.0", + "lazy_static", "proc-macro-error", "proc-macro2", "quote", @@ -1643,7 +1672,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde 1.0.137", + "serde 1.0.142", "signature", ] @@ -1656,7 +1685,7 @@ dependencies = [ "curve25519-dalek-ng", "hex", "rand_core 0.6.3", - "serde 1.0.137", + "serde 1.0.142", "sha2 0.9.9", "thiserror", "zeroize", @@ -1672,7 +1701,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.137", + "serde 1.0.142", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1680,22 +1709,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "embed-resource" -version = "1.7.2" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" -dependencies = [ - "cc", - "rustc_version 0.4.0", - "toml", - "vswhom", - "winreg 0.10.1", -] +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "encoding_rs" @@ -1759,15 +1775,6 @@ dependencies = [ "syn", ] -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "log 0.4.17", -] - [[package]] name = "escargot" version = "0.5.7" @@ -1776,15 +1783,15 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.137", + "serde 1.0.142", "serde_json", ] [[package]] name = "event-listener" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "expectrl" @@ -1822,9 +1829,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1858,7 +1865,7 @@ dependencies = [ "num", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.142", "serde_bytes", "serde_json", "subproductdomain", @@ -1875,20 +1882,20 @@ dependencies = [ "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.137", + "serde 1.0.142", "serde_bytes", ] [[package]] name = "file-lock" -version = "2.1.4" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d68ace7c2694114c6d6b1047f87f8422cb84ab21e3166728c1af5a77e2e5325" +checksum = "0815fc2a1924e651b71ae6d13df07b356a671a09ecaf857dbd344a2ba937a496" dependencies = [ "cc", "libc", "mktemp", - "nix 0.24.1", + "nix 0.24.2", ] [[package]] @@ -1904,14 +1911,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", - "winapi 0.3.9", + "redox_syscall 0.2.16", + "windows-sys", ] [[package]] @@ -1922,9 +1929,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" @@ -2164,9 +2171,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check 0.9.4", @@ -2185,13 +2192,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -2217,9 +2224,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "git2" @@ -2244,9 +2251,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" dependencies = [ "aho-corasick", "bstr", @@ -2280,7 +2287,7 @@ dependencies = [ "ark-serialize", "ark-std", "blake2b_simd", - "chacha20 0.8.1", + "chacha20 0.8.2", "hex", "itertools 0.10.3", "miracl_core", @@ -2317,7 +2324,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "fnv", "futures-core", "futures-sink", @@ -2327,7 +2334,7 @@ dependencies = [ "slab", "tokio", "tokio-util 0.7.3", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -2341,9 +2348,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] @@ -2354,11 +2361,7 @@ version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" dependencies = [ - "base64 0.13.0", "byteorder", - "crossbeam-channel", - "flate2", - "nom 7.1.1", "num-traits 0.2.15", ] @@ -2370,7 +2373,7 @@ checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" dependencies = [ "base64 0.13.0", "bitflags", - "bytes 1.1.0", + "bytes 1.2.1", "headers-core", "http", "httpdate", @@ -2461,7 +2464,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "fnv", "itoa", ] @@ -2472,7 +2475,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "http", "pin-project-lite 0.2.9", ] @@ -2510,11 +2513,11 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.19" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-channel", "futures-core", "futures-util", @@ -2528,7 +2531,7 @@ dependencies = [ "socket2 0.4.4", "tokio", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", "want", ] @@ -2538,11 +2541,11 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures 0.3.21", "headers", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-rustls", "rustls-native-certs", "tokio", @@ -2559,7 +2562,7 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.19", + "hyper 0.14.20", "log 0.4.17", "rustls", "rustls-native-certs", @@ -2575,7 +2578,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.19", + "hyper 0.14.20", "pin-project-lite 0.2.9", "tokio", "tokio-io-timeout", @@ -2587,8 +2590,8 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.1.0", - "hyper 0.14.19", + "bytes 1.2.1", + "hyper 0.14.20", "native-tls", "tokio", "tokio-native-tls", @@ -2599,7 +2602,7 @@ name = "ibc" version = "0.12.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "derive_more", "flex-error", "ibc-proto", @@ -2608,7 +2611,7 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.137", + "serde 1.0.142", "serde_derive", "serde_json", "sha2 0.10.2", @@ -2617,8 +2620,8 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.9", - "tracing 0.1.35", + "time 0.3.12", + "tracing 0.1.36", ] [[package]] @@ -2626,10 +2629,10 @@ name = "ibc-proto" version = "0.16.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.142", "tendermint-proto", "tonic", ] @@ -2641,7 +2644,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" dependencies = [ "anyhow", - "bytes 1.1.0", + "bytes 1.2.1", "hex", "prost 0.9.0", "ripemd160", @@ -2723,13 +2726,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.11.2", - "serde 1.0.137", + "hashbrown 0.12.3", + "serde 1.0.142", ] [[package]] @@ -2758,7 +2761,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", ] [[package]] @@ -2775,9 +2778,9 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "iovec" @@ -2826,9 +2829,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jobserver" @@ -2841,9 +2844,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] @@ -2855,7 +2858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" dependencies = [ "log 0.4.17", - "serde 1.0.137", + "serde 1.0.142", "serde_json", ] @@ -2890,12 +2893,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -[[package]] -name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" - [[package]] name = "lazy_static" version = "1.4.0" @@ -2929,9 +2926,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.121" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" [[package]] name = "libgit2-sys" @@ -2963,9 +2960,9 @@ version = "0.38.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "atomic", - "bytes 1.1.0", + "bytes 1.2.1", "futures 0.3.21", - "lazy_static 1.4.0", + "lazy_static", "libp2p-core", "libp2p-deflate", "libp2p-dns", @@ -2990,8 +2987,8 @@ dependencies = [ "libp2p-yamux", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.10", - "smallvec 1.8.0", + "pin-project 1.0.11", + "smallvec 1.9.0", "wasm-timer", ] @@ -3007,21 +3004,21 @@ dependencies = [ "fnv", "futures 0.3.21", "futures-timer", - "lazy_static 1.4.0", + "lazy_static", "libsecp256k1", "log 0.4.17", "multihash", "multistream-select", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.10", + "pin-project 1.0.11", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", "ring", "rw-stream-sink", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.9.0", "thiserror", "unsigned-varint 0.7.1", "void", @@ -3047,7 +3044,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "log 0.4.17", - "smallvec 1.8.0", + "smallvec 1.9.0", "trust-dns-resolver", ] @@ -3065,7 +3062,7 @@ dependencies = [ "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", ] [[package]] @@ -3076,7 +3073,7 @@ dependencies = [ "asynchronous-codec", "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "fnv", "futures 0.3.21", "hex_fmt", @@ -3088,7 +3085,7 @@ dependencies = [ "rand 0.7.3", "regex", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.9.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3104,7 +3101,7 @@ dependencies = [ "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", - "smallvec 1.8.0", + "smallvec 1.9.0", "wasm-timer", ] @@ -3115,7 +3112,7 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "arrayvec 0.5.2", "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "either", "fnv", "futures 0.3.21", @@ -3126,7 +3123,7 @@ dependencies = [ "prost-build 0.7.0", "rand 0.7.3", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.9.0", "uint", "unsigned-varint 0.7.1", "void", @@ -3143,12 +3140,12 @@ dependencies = [ "dns-parser", "futures 0.3.21", "if-watch", - "lazy_static 1.4.0", + "lazy_static", "libp2p-core", "libp2p-swarm", "log 0.4.17", "rand 0.8.5", - "smallvec 1.8.0", + "smallvec 1.9.0", "socket2 0.4.4", "void", ] @@ -3159,14 +3156,14 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "futures 0.3.21", "libp2p-core", "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", "unsigned-varint 0.7.1", ] @@ -3175,10 +3172,10 @@ name = "libp2p-noise" version = "0.31.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "curve25519-dalek", "futures 0.3.21", - "lazy_static 1.4.0", + "lazy_static", "libp2p-core", "log 0.4.17", "prost 0.7.0", @@ -3211,7 +3208,7 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "futures 0.3.21", "libp2p-core", "log 0.4.17", @@ -3228,7 +3225,7 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "futures 0.3.21", "log 0.4.17", - "pin-project 1.0.10", + "pin-project 1.0.11", "rand 0.7.3", "salsa20", "sha3", @@ -3240,17 +3237,17 @@ version = "0.2.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "futures 0.3.21", "futures-timer", "libp2p-core", "libp2p-swarm", "log 0.4.17", - "pin-project 1.0.10", + "pin-project 1.0.11", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", "unsigned-varint 0.7.1", "void", "wasm-timer", @@ -3262,7 +3259,7 @@ version = "0.11.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.2.1", "futures 0.3.21", "libp2p-core", "libp2p-swarm", @@ -3270,7 +3267,7 @@ dependencies = [ "lru", "minicbor", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3285,7 +3282,7 @@ dependencies = [ "libp2p-core", "log 0.4.17", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.9.0", "void", "wasm-timer", ] @@ -3427,12 +3424,11 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" dependencies = [ - "serde 1.0.137", - "serde_test", + "serde 1.0.142", ] [[package]] @@ -3530,7 +3526,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.137", + "serde 1.0.142", "serde_derive", "serde_yaml", ] @@ -3580,9 +3576,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" +checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" dependencies = [ "libc", ] @@ -3615,12 +3611,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.11", "integer-encoding", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "mio 0.7.14", - "serde 1.0.137", + "serde 1.0.142", "strum", "tungstenite 0.16.0", "url 2.2.2", @@ -3720,9 +3716,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log 0.4.17", @@ -3791,7 +3787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" dependencies = [ "digest 0.9.0", - "generic-array 0.14.5", + "generic-array 0.14.6", "multihash-derive", "sha2 0.9.9", "unsigned-varint 0.5.1", @@ -3803,7 +3799,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 1.2.0", "proc-macro-error", "proc-macro2", "quote", @@ -3822,11 +3818,11 @@ name = "multistream-select" version = "0.10.3" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures 0.3.21", "log 0.4.17", - "pin-project 1.0.10", - "smallvec 1.8.0", + "pin-project 1.0.11", + "smallvec 1.9.0", "unsigned-varint 0.7.1", ] @@ -3864,7 +3860,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.3", "rust_decimal", - "serde 1.0.137", + "serde 1.0.142", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -3874,8 +3870,8 @@ dependencies = [ "test-log", "thiserror", "tonic-build", - "tracing 0.1.35", - "tracing-subscriber 0.3.11", + "tracing 0.1.36", + "tracing-subscriber 0.3.15", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -3940,7 +3936,7 @@ dependencies = [ "rlimit", "rocksdb", "rpassword", - "serde 1.0.137", + "serde 1.0.142", "serde_bytes", "serde_json", "serde_regex", @@ -3963,9 +3959,9 @@ dependencies = [ "tonic-build", "tower", "tower-abci", - "tracing 0.1.35", + "tracing 0.1.36", "tracing-log", - "tracing-subscriber 0.3.11", + "tracing-subscriber 0.3.15", "websocket", "winapi 0.3.9", ] @@ -3976,7 +3972,7 @@ version = "0.7.0" dependencies = [ "borsh", "itertools 0.10.3", - "lazy_static 1.4.0", + "lazy_static", "madato", "namada", ] @@ -4028,8 +4024,8 @@ dependencies = [ "tempfile", "test-log", "toml", - "tracing 0.1.35", - "tracing-subscriber 0.3.11", + "tracing 0.1.36", + "tracing-subscriber 0.3.15", ] [[package]] @@ -4064,7 +4060,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "libc", "log 0.4.17", "openssl", @@ -4089,9 +4085,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.20.2" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" dependencies = [ "bitflags", "cc", @@ -4102,9 +4098,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" dependencies = [ "bitflags", "cc", @@ -4128,9 +4124,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4218,9 +4214,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits 0.2.15", ] @@ -4259,9 +4255,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg 1.1.0", "num-bigint", @@ -4306,12 +4302,6 @@ dependencies = [ "libc", ] -[[package]] -name = "numtoa" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - [[package]] name = "object" version = "0.28.4" @@ -4324,11 +4314,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "opaque-debug" @@ -4344,9 +4343,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.40" +version = "0.10.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4376,9 +4375,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.74" +version = "0.9.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" dependencies = [ "autocfg 1.1.0", "cc", @@ -4394,7 +4393,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.6", + "getrandom 0.2.7", "subtle 2.4.1", "zeroize", ] @@ -4431,7 +4430,7 @@ dependencies = [ "data-encoding", "multihash", "percent-encoding 2.1.0", - "serde 1.0.137", + "serde 1.0.142", "static_assertions", "unsigned-varint 0.7.1", "url 2.2.2", @@ -4511,8 +4510,8 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", + "redox_syscall 0.2.16", + "smallvec 1.9.0", "winapi 0.3.9", ] @@ -4524,16 +4523,16 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", + "redox_syscall 0.2.16", + "smallvec 1.9.0", "windows-sys", ] [[package]] name = "paste" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" [[package]] name = "pathdiff" @@ -4611,33 +4610,71 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset 0.4.1", + "fixedbitset 0.4.2", "indexmap", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" +checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" dependencies = [ - "pin-project-internal 0.4.29", + "pin-project-internal 0.4.30", ] [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" dependencies = [ - "pin-project-internal 1.0.10", + "pin-project-internal 1.0.11", ] [[package]] name = "pin-project-internal" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" +checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" dependencies = [ "proc-macro2", "quote", @@ -4646,9 +4683,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" dependencies = [ "proc-macro2", "quote", @@ -4771,10 +4808,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "26d50bfb8c23f23915855a00d98b5a35ef2e0b871bb52937bacadb798fbb66c8" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -4805,9 +4843,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] @@ -4820,7 +4858,7 @@ dependencies = [ "bit-set", "bitflags", "byteorder", - "lazy_static 1.4.0", + "lazy_static", "num-traits 0.2.15", "quick-error 2.0.1", "rand 0.8.5", @@ -4837,7 +4875,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost-derive 0.7.0", ] @@ -4847,7 +4885,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost-derive 0.9.0", ] @@ -4857,7 +4895,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "heck 0.3.3", "itertools 0.9.0", "log 0.4.17", @@ -4875,10 +4913,10 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "heck 0.3.3", "itertools 0.10.3", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "multimap", "petgraph 0.6.2", @@ -4921,7 +4959,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.7.0", ] @@ -4931,7 +4969,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.9.0", ] @@ -5000,9 +5038,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -5021,7 +5059,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift 0.1.1", "winapi 0.3.9", ] @@ -5037,6 +5075,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", + "rand_pcg 0.2.1", ] [[package]] @@ -5110,7 +5149,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -5175,6 +5214,15 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xorshift" version = "0.1.1" @@ -5213,7 +5261,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.11", "num_cpus", ] @@ -5234,30 +5282,21 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] -[[package]] -name = "redox_termios" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" -dependencies = [ - "redox_syscall 0.2.13", -] - [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", - "redox_syscall 0.2.13", + "getrandom 0.2.7", + "redox_syscall 0.2.16", "thiserror", ] @@ -5269,14 +5308,14 @@ checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ "log 0.4.17", "rustc-hash", - "smallvec 1.8.0", + "smallvec 1.9.0", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -5294,9 +5333,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -5330,33 +5369,34 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" dependencies = [ "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.2.1", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-tls", "ipnet", "js-sys", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "mime 0.3.16", "native-tls", "percent-encoding 2.1.0", "pin-project-lite 0.2.9", - "serde 1.0.137", + "serde 1.0.142", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", + "tower-service", "url 2.2.2", "wasm-bindgen", "wasm-bindgen-futures", @@ -5402,12 +5442,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.38" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517a3034eb2b1499714e9d1e49b2367ad567e07639b69776d35e259d9c27cca6" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown 0.12.1", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -5416,9 +5456,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.38" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505c209ee04111a006431abf39696e640838364d67a107c559ababaf6fd8c9dd" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -5462,13 +5502,13 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" +checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.142", ] [[package]] @@ -5501,15 +5541,6 @@ dependencies = [ "semver 0.11.0", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.10", -] - [[package]] name = "rustls" version = "0.19.1" @@ -5537,9 +5568,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rusty-fork" @@ -5560,15 +5591,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ "futures 0.3.21", - "pin-project 0.4.29", + "pin-project 0.4.30", "static_assertions", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-proc-macro2" @@ -5647,7 +5678,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "windows-sys", ] @@ -5714,12 +5745,6 @@ dependencies = [ "semver-parser 0.10.2", ] -[[package]] -name = "semver" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" - [[package]] name = "semver-parser" version = "0.7.0" @@ -5743,9 +5768,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" dependencies = [ "serde_derive", ] @@ -5756,7 +5781,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "num-traits 0.1.43", "regex", "serde 0.8.23", @@ -5764,18 +5789,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ - "serde 1.0.137", + "serde 1.0.142", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" dependencies = [ "proc-macro2", "quote", @@ -5784,14 +5809,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ "indexmap", "itoa", "ryu", - "serde 1.0.137", + "serde 1.0.142", ] [[package]] @@ -5801,29 +5826,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.137", + "serde 1.0.142", ] [[package]] name = "serde_repr" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "serde_test" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe196827aea34242c314d2f0dd49ed00a129225e80dda71b0dbf65d54d25628d" -dependencies = [ - "serde 1.0.137", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -5833,7 +5849,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.137", + "serde 1.0.142", ] [[package]] @@ -5844,7 +5860,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.137", + "serde 1.0.142", "yaml-rust", ] @@ -5938,7 +5954,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", ] [[package]] @@ -5984,11 +6000,20 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "smallvec" @@ -6001,9 +6026,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "snow" @@ -6069,7 +6094,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=yuji/prost-0.9#be4b2293558361df2f452c60a3e90c6b5e52e225" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#2b127dec1cbe26792d2b661e1fffc381b78a92fb" dependencies = [ "blake2b-rs", "borsh", @@ -6098,15 +6123,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stderrlog" -version = "0.4.3" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" +checksum = "af95cb8a5f79db5b2af2a46f44da7594b5adbcbb65cbf87b8da0959bfdd82460" dependencies = [ "atty", "chrono", "log 0.4.17", "termcolor", - "thread_local 0.3.4", + "thread_local", ] [[package]] @@ -6123,18 +6148,18 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -6185,9 +6210,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -6246,7 +6271,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.16", "remove_dir_all", "winapi 0.3.9", ] @@ -6257,7 +6282,7 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.2.1", "ed25519", "ed25519-dalek", "flex-error", @@ -6266,7 +6291,7 @@ dependencies = [ "once_cell", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.142", "serde_bytes", "serde_json", "serde_repr", @@ -6275,7 +6300,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto", - "time 0.3.9", + "time 0.3.12", "zeroize", ] @@ -6285,7 +6310,7 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.137", + "serde 1.0.142", "serde_json", "tendermint", "toml", @@ -6299,10 +6324,10 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.137", + "serde 1.0.142", "tendermint", "tendermint-rpc", - "time 0.3.9", + "time 0.3.12", ] [[package]] @@ -6310,16 +6335,16 @@ name = "tendermint-proto" version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "flex-error", "num-derive", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.142", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.12", ] [[package]] @@ -6329,17 +6354,17 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.1.0", + "bytes 1.2.1", "flex-error", "futures 0.3.21", - "getrandom 0.2.6", + "getrandom 0.2.7", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", - "serde 1.0.137", + "pin-project 1.0.11", + "serde 1.0.142", "serde_bytes", "serde_json", "subtle-encoding", @@ -6347,9 +6372,9 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.9", + "time 0.3.12", "tokio", - "tracing 0.1.35", + "tracing 0.1.36", "url 2.2.2", "uuid", "walkdir", @@ -6362,22 +6387,12 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.137", + "serde 1.0.142", "serde_json", "simple-error", "tempfile", "tendermint", - "time 0.3.9", -] - -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi 0.3.9", + "time 0.3.12", ] [[package]] @@ -6390,15 +6405,16 @@ dependencies = [ ] [[package]] -name = "termion" -version = "1.5.6" +name = "terminfo" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e" dependencies = [ - "libc", - "numtoa", - "redox_syscall 0.2.13", - "redox_termios", + "dirs", + "fnv", + "nom 5.1.2", + "phf", + "phf_codegen", ] [[package]] @@ -6409,9 +6425,9 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-log" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" +checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", @@ -6424,7 +6440,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "term_size", "unicode-width", ] @@ -6457,16 +6472,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" -dependencies = [ - "lazy_static 0.2.11", - "unreachable", -] - [[package]] name = "thread_local" version = "1.1.4" @@ -6489,11 +6494,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f" dependencies = [ "itoa", + "js-sys", "libc", "num_threads", "time-macros", @@ -6514,7 +6520,7 @@ dependencies = [ "ascii", "chunked_transfer", "log 0.4.17", - "time 0.3.9", + "time 0.3.12", "url 2.2.2", ] @@ -6535,14 +6541,15 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ - "bytes 1.1.0", + "autocfg 1.1.0", + "bytes 1.2.1", "libc", "memchr", - "mio 0.8.3", + "mio 0.8.4", "num_cpus", "once_cell", "parking_lot 0.12.1", @@ -6624,7 +6631,7 @@ checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" dependencies = [ "crossbeam-utils 0.7.2", "futures 0.1.31", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "mio 0.6.23", "num_cpus", @@ -6688,7 +6695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "tokio", "tokio-stream", @@ -6711,7 +6718,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-sink", "log 0.4.17", @@ -6725,12 +6732,12 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-sink", "pin-project-lite 0.2.9", "tokio", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -6739,7 +6746,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.137", + "serde 1.0.142", ] [[package]] @@ -6751,16 +6758,16 @@ dependencies = [ "async-stream", "async-trait", "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-timeout", "percent-encoding 2.1.0", - "pin-project 1.0.10", + "pin-project 1.0.11", "prost 0.9.0", "prost-derive 0.9.0", "tokio", @@ -6769,7 +6776,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -6787,15 +6794,15 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", "hdrhistogram", "indexmap", - "pin-project 1.0.10", + "pin-project 1.0.11", "pin-project-lite 0.2.9", "rand 0.8.5", "slab", @@ -6803,7 +6810,7 @@ dependencies = [ "tokio-util 0.7.3", "tower-layer", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -6811,9 +6818,9 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures 0.3.21", - "pin-project 1.0.10", + "pin-project 1.0.11", "prost 0.9.0", "tendermint-proto", "tokio", @@ -6841,9 +6848,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" @@ -6858,15 +6865,15 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log 0.4.17", "pin-project-lite 0.2.9", - "tracing-attributes 0.1.21", - "tracing-core 0.1.27", + "tracing-attributes 0.1.22", + "tracing-core 0.1.29", ] [[package]] @@ -6881,9 +6888,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -6895,14 +6902,14 @@ name = "tracing-core" version = "0.1.22" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", ] [[package]] name = "tracing-core" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -6914,7 +6921,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ - "tracing 0.1.35", + "tracing 0.1.36", "tracing-subscriber 0.2.25", ] @@ -6924,8 +6931,8 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", - "tracing 0.1.35", + "pin-project 1.0.11", + "tracing 0.1.36", ] [[package]] @@ -6943,9 +6950,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", - "tracing-core 0.1.27", + "tracing-core 0.1.29", ] [[package]] @@ -6955,25 +6962,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", - "thread_local 1.1.4", - "tracing-core 0.1.27", + "thread_local", + "tracing-core 0.1.29", ] [[package]] name = "tracing-subscriber" -version = "0.3.11" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ "ansi_term", - "lazy_static 1.4.0", "matchers", + "once_cell", "regex", "sharded-slab", - "smallvec 1.8.0", - "thread_local 1.1.4", - "tracing 0.1.35", - "tracing-core 0.1.27", + "smallvec 1.9.0", + "thread_local", + "tracing 0.1.36", + "tracing-core 0.1.29", "tracing-log", ] @@ -7012,10 +7019,10 @@ dependencies = [ "futures-util", "idna 0.2.3", "ipnet", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "rand 0.8.5", - "smallvec 1.8.0", + "smallvec 1.9.0", "thiserror", "tinyvec", "url 2.2.2", @@ -7030,12 +7037,12 @@ dependencies = [ "cfg-if 1.0.0", "futures-util", "ipconfig", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "lru-cache", "parking_lot 0.11.2", "resolv-conf", - "smallvec 1.8.0", + "smallvec 1.9.0", "thiserror", "trust-dns-proto", ] @@ -7054,7 +7061,7 @@ checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "http", "httparse", "input_buffer", @@ -7073,7 +7080,7 @@ checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "http", "httparse", "log 0.4.17", @@ -7098,9 +7105,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" [[package]] name = "uint" @@ -7140,15 +7147,15 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] @@ -7177,19 +7184,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] - [[package]] name = "unsigned-varint" version = "0.5.1" @@ -7203,7 +7201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "futures-io", "futures-util", ] @@ -7255,7 +7253,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -7304,26 +7302,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "wait-timeout" version = "0.2.0" @@ -7380,9 +7358,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7390,13 +7368,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", - "lazy_static 1.4.0", "log 0.4.17", + "once_cell", "proc-macro2", "quote", "syn", @@ -7405,9 +7383,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7417,9 +7395,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7427,9 +7405,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -7440,15 +7418,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" [[package]] name = "wasm-encoder" -version = "0.13.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" +checksum = "8905fd25fdadeb0e7e8bf43a9f46f9f972d6291ad0c7a32573b88dd13a6cfa6b" dependencies = [ "leb128", ] @@ -7515,9 +7493,9 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.137", + "serde 1.0.142", "serde_bytes", - "smallvec 1.8.0", + "smallvec 1.9.0", "target-lexicon", "thiserror", "wasmer-types", @@ -7538,9 +7516,9 @@ dependencies = [ "loupe", "more-asserts", "rayon", - "smallvec 1.8.0", + "smallvec 1.9.0", "target-lexicon", - "tracing 0.1.35", + "tracing 0.1.36", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7555,11 +7533,11 @@ dependencies = [ "byteorder", "dynasm", "dynasmrt", - "lazy_static 1.4.0", + "lazy_static", "loupe", "more-asserts", "rayon", - "smallvec 1.8.0", + "smallvec 1.9.0", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7585,12 +7563,12 @@ checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" dependencies = [ "backtrace", "enumset", - "lazy_static 1.4.0", + "lazy_static", "loupe", "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.137", + "serde 1.0.142", "serde_bytes", "target-lexicon", "thiserror", @@ -7611,11 +7589,11 @@ dependencies = [ "leb128", "libloading", "loupe", - "object", + "object 0.28.4", "rkyv", - "serde 1.0.137", + "serde 1.0.142", "tempfile", - "tracing 0.1.35", + "tracing 0.1.36", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -7650,7 +7628,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object", + "object 0.28.4", "thiserror", "wasmer-compiler", "wasmer-types", @@ -7665,7 +7643,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.137", + "serde 1.0.142", "thiserror", ] @@ -7686,7 +7664,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.137", + "serde 1.0.142", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -7706,9 +7684,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "42.0.0" +version = "45.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" +checksum = "186c474c4f9bb92756b566d592a16591b4526b1a4841171caa3f31d7fe330d96" dependencies = [ "leb128", "memchr", @@ -7718,28 +7696,27 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.44" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" +checksum = "c2d4bc4724b4f02a482c8cab053dac5ef26410f264c06ce914958f9a42813556" dependencies = [ "wast", ] [[package]] name = "watchexec" -version = "1.15.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0fba7f2b9f4240dadf1c2eb4cf15359ffeb19d4f1841cb2d85a7bb4f82755f" +checksum = "c52e0868bc57765fa91593a173323f464855e53b27f779e1110ba0fb4cb6b406" dependencies = [ - "clap 2.34.0", + "clearscreen", + "command-group", "derive_builder", - "embed-resource", - "env_logger", "glob", "globset", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", - "nix 0.20.2", + "nix 0.22.3", "notify", "walkdir", "winapi 0.3.9", @@ -7747,9 +7724,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -7776,9 +7753,9 @@ dependencies = [ [[package]] name = "websocket" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e2836502b48713d4e391e7e016df529d46e269878fe5d961b15a1fd6417f1a" +checksum = "92aacab060eea423e4036820ddd28f3f9003b2c4d8048cbda985e5a14e18038d" dependencies = [ "bytes 0.4.12", "futures 0.1.31", @@ -7797,9 +7774,9 @@ dependencies = [ [[package]] name = "websocket-base" -version = "0.26.2" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f3fd505ff930da84156389639932955fb09705b3dccd1a3d60c8e7ff62776" +checksum = "49aec794b07318993d1db16156d5a9c750120597a5ee40c6b928d416186cb138" dependencies = [ "base64 0.10.1", "bitflags", @@ -7831,7 +7808,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", - "lazy_static 1.4.0", + "lazy_static", "libc", ] diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 7a4dffac76..95f2ab5ed9 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -49,6 +49,7 @@ testing = ["dev"] namada = {path = "../shared", features = ["wasm-runtime", "ferveo-tpke", "rand"]} ark-serialize = "0.3.0" ark-std = "0.3.0" +arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "bat/arse-merkle-tree", features = ["std", "borsh"]} async-std = {version = "1.9.0", features = ["unstable"]} async-trait = "0.1.51" base64 = "0.13.0" @@ -100,7 +101,6 @@ serde_json = {version = "1.0.62", features = ["raw_value"]} serde_regex = "1.1.0" sha2 = "0.9.3" signal-hook = "0.3.9" -sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/prost-0.9", features = ["borsh"]} # sysinfo with disabled multithread feature sysinfo = {version = "=0.21.1", default-features = false} tar = "0.4.37" diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 25929ba17e..2876236bca 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -5,11 +5,11 @@ mod rocksdb; use std::fmt; +use arse_merkle_tree::blake2b::Blake2bHasher; +use arse_merkle_tree::traits::Hasher; +use arse_merkle_tree::H256; use blake2b_rs::{Blake2b, Blake2bBuilder}; use namada::ledger::storage::{Storage, StorageHasher}; -use sparse_merkle_tree::blake2b::Blake2bHasher; -use sparse_merkle_tree::traits::Hasher; -use sparse_merkle_tree::H256; #[derive(Default)] pub struct PersistentStorageHasher(Blake2bHasher); @@ -19,8 +19,8 @@ pub type PersistentDB = rocksdb::RocksDB; pub type PersistentStorage = Storage; impl Hasher for PersistentStorageHasher { - fn write_h256(&mut self, h: &H256) { - self.0.write_h256(h) + fn write_bytes(&mut self, h: &[u8]) { + self.0.write_bytes(h) } fn finish(self) -> H256 { diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 71ed305ea5..6af0011dcc 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -348,11 +348,8 @@ impl DB for RocksDB { types::decode(bytes) .map_err(Error::CodingError)?, ), - Some(&"store") => merkle_tree_stores.set_store( - &st, - types::decode(bytes) - .map_err(Error::CodingError)?, - ), + Some(&"store") => merkle_tree_stores + .set_store(st.decode_store(bytes)?), _ => unknown_key_error(path)?, } } @@ -484,7 +481,7 @@ impl DB for RocksDB { .map_err(Error::KeyError)?; batch.put( store_key.to_string(), - types::encode(merkle_tree_stores.store(st)), + merkle_tree_stores.store(st).encode(), ); } } @@ -593,8 +590,7 @@ impl DB for RocksDB { .map_err(|e| Error::DBError(e.into_string()))?; match bytes { Some(b) => { - let store = types::decode(b).map_err(Error::CodingError)?; - merkle_tree_stores.set_store(st, store); + merkle_tree_stores.set_store(st.decode_store(b)?); } None => return Ok(None), } diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 241fd034da..03b76942f6 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -47,6 +47,8 @@ namada_proof_of_stake = {path = "../proof_of_stake"} ark-bls12-381 = {version = "0.3"} ark-ec = {version = "0.3", optional = true} ark-serialize = "0.3" +# We switch off "blake2b" because it cannot be compiled to wasm +arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "bat/arse-merkle-tree", default-features = false, features = ["std", "borsh"]} bech32 = "0.8.0" borsh = "0.9.0" chrono = "0.4.19" @@ -78,7 +80,6 @@ serde = {version = "1.0.125", features = ["derive"]} serde_json = "1.0.62" sha2 = "0.9.3" # We switch off "blake2b" because it cannot be compiled to wasm -sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/prost-0.9", default-features = false, features = ["std", "borsh"]} tempfile = {version = "3.2.0", optional = true} # temporarily using fork work-around for https://github.com/informalsystems/tendermint-rs/issues/971 tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9"} diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index fa0f4a011f..8b98f47316 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -1,43 +1,56 @@ //! The merkle tree in the storage - -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use std::fmt; +use std::marker::PhantomData; use std::str::FromStr; +use arse_merkle_tree::default_store::DefaultStore; +use arse_merkle_tree::error::Error as MtError; +use arse_merkle_tree::traits::Hasher; +use arse_merkle_tree::{PaddedKey, SparseMerkleTree as ArseMerkleTree, H256}; use borsh::{BorshDeserialize, BorshSerialize}; use ics23::commitment_proof::Proof as Ics23Proof; use ics23::{ CommitmentProof, ExistenceProof, HashOp, LeafOp, LengthOp, NonExistenceProof, ProofSpec, }; +use itertools::Either; use prost::Message; use sha2::{Digest, Sha256}; -use sparse_merkle_tree::default_store::DefaultStore; -use sparse_merkle_tree::error::Error as SmtError; -use sparse_merkle_tree::traits::Hasher; -use sparse_merkle_tree::{SparseMerkleTree, H256}; use tendermint::merkle::proof::{Proof, ProofOp}; use thiserror::Error; use crate::bytes::ByteBuf; +use crate::ledger::storage::types; use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::{DbKeySeg, Error as StorageError, Key}; +use crate::types::hash::Hash; +use crate::types::storage::{DbKeySeg, Error as StorageError, Key, MerkleKey}; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { #[error("Invalid key: {0}")] InvalidKey(StorageError), + #[error("Invalid key for {0}-type merkle tree")] + InvalidMerkleKey(String), #[error("Empty Key: {0}")] EmptyKey(String), - #[error("SMT error: {0}")] - Smt(SmtError), + #[error("Merkle Tree error: {0}")] + MerkleTree(MtError), #[error("Invalid store type: {0}")] StoreType(String), } /// Result for functions that may fail type Result = std::result::Result; +/// The maximum size of an IBC key (in bytes) allowed in merkle-ized storage +pub const IBC_KEY_LIMIT: usize = 120; + +/// Type aliases for the different merkle trees and backing stores +pub type SmtStore = DefaultStore; +pub type AmtStore = DefaultStore; +pub type Smt = ArseMerkleTree; +pub type Amt = ArseMerkleTree; /// Store types for the merkle tree #[derive( @@ -62,6 +75,62 @@ pub enum StoreType { PoS, } +/// Backing storage for merkle trees +pub enum Store { + /// Base tree, which has roots of the subtrees + Base(SmtStore), + /// For Account and other data + Account(SmtStore), + /// For IBC-related data + Ibc(AmtStore), + /// For PoS-related data + PoS(SmtStore), +} + +impl Store { + pub fn as_ref(&self) -> StoreRef { + match self { + Self::Base(store) => StoreRef::Base(store), + Self::Account(store) => StoreRef::Account(store), + Self::Ibc(store) => StoreRef::Ibc(store), + Self::PoS(store) => StoreRef::PoS(store), + } + } +} + +/// Pointer to backing storage of merkle tree +pub enum StoreRef<'a> { + /// Base tree, which has roots of the subtrees + Base(&'a SmtStore), + /// For Account and other data + Account(&'a SmtStore), + /// For IBC-related data + Ibc(&'a AmtStore), + /// For PoS-related data + PoS(&'a SmtStore), +} + +impl<'a> StoreRef<'a> { + pub fn to_owned(&self) -> Store { + match *self { + Self::Base(store) => Store::Base(store.to_owned()), + Self::Account(store) => Store::Account(store.to_owned()), + Self::Ibc(store) => Store::Ibc(store.to_owned()), + Self::PoS(store) => Store::PoS(store.to_owned()), + } + } + + pub fn encode(&self) -> Vec { + match self { + Self::Base(store) => store.try_to_vec(), + Self::Account(store) => store.try_to_vec(), + Self::Ibc(store) => store.try_to_vec(), + Self::PoS(store) => store.try_to_vec(), + } + .expect("Serialization failed") + } +} + impl StoreType { /// Get an iterator for the base tree and subtrees pub fn iter() -> std::slice::Iter<'static, Self> { @@ -95,6 +164,28 @@ impl StoreType { _ => Ok((StoreType::Account, key.clone())), } } + + /// Decode the backing store from bytes and tag its type correctly + pub fn decode_store>( + &self, + bytes: T, + ) -> std::result::Result { + use super::Error; + match self { + Self::Base => Ok(Store::Base( + types::decode(bytes).map_err(Error::CodingError)?, + )), + Self::Account => Ok(Store::Account( + types::decode(bytes).map_err(Error::CodingError)?, + )), + Self::Ibc => Ok(Store::Ibc( + types::decode(bytes).map_err(Error::CodingError)?, + )), + Self::PoS => Ok(Store::PoS( + types::decode(bytes).map_err(Error::CodingError)?, + )), + } + } } impl FromStr for StoreType { @@ -125,10 +216,10 @@ impl fmt::Display for StoreType { /// Merkle tree storage #[derive(Default)] pub struct MerkleTree { - base: SparseMerkleTree>, - account: SparseMerkleTree>, - ibc: SparseMerkleTree>, - pos: SparseMerkleTree>, + base: Smt, + account: Smt, + ibc: Amt, + pos: Smt, } impl core::fmt::Debug for MerkleTree { @@ -143,10 +234,10 @@ impl core::fmt::Debug for MerkleTree { impl MerkleTree { /// Restore the tree from the stores pub fn new(stores: MerkleTreeStoresRead) -> Self { - let base = SparseMerkleTree::new(stores.base.0, stores.base.1); - let account = SparseMerkleTree::new(stores.account.0, stores.account.1); - let ibc = SparseMerkleTree::new(stores.ibc.0, stores.ibc.1); - let pos = SparseMerkleTree::new(stores.pos.0, stores.pos.1); + let base = Smt::new(stores.base.0.into(), stores.base.1); + let account = Smt::new(stores.account.0.into(), stores.account.1); + let ibc = Amt::new(stores.ibc.0.into(), stores.ibc.1); + let pos = Smt::new(stores.pos.0.into(), stores.pos.1); Self { base, @@ -156,37 +247,42 @@ impl MerkleTree { } } - fn tree( - &self, - store_type: &StoreType, - ) -> &SparseMerkleTree> { + fn tree(&self, store_type: &StoreType) -> Either<&Smt, &Amt> { match store_type { - StoreType::Base => &self.base, - StoreType::Account => &self.account, - StoreType::Ibc => &self.ibc, - StoreType::PoS => &self.pos, + StoreType::Base => Either::Left(&self.base), + StoreType::Account => Either::Left(&self.account), + StoreType::Ibc => Either::Right(&self.ibc), + StoreType::PoS => Either::Left(&self.pos), } } fn update_tree( &mut self, store_type: &StoreType, - key: H256, - value: H256, + key: MerkleKey, + value: Hash, ) -> Result<()> { - let tree = match store_type { - StoreType::Account => &mut self.account, - StoreType::Ibc => &mut self.ibc, - StoreType::PoS => &mut self.pos, + let sub_root = match store_type { + StoreType::Account => self + .account + .update(key.try_into()?, value) + .map_err(Error::MerkleTree)?, + StoreType::Ibc => self + .ibc + .update(key.try_into()?, value) + .map_err(Error::MerkleTree)?, + StoreType::PoS => self + .pos + .update(key.try_into()?, value) + .map_err(Error::MerkleTree)?, // base tree should not be directly updated StoreType::Base => unreachable!(), }; - let sub_root = tree.update(key, value).map_err(Error::Smt)?; // update the base tree with the updated sub root without hashing if *store_type != StoreType::Base { let base_key = H::hash(&store_type.to_string()); - self.base.update(base_key, *sub_root)?; + self.base.update(base_key.into(), Hash::from(sub_root))?; } Ok(()) } @@ -194,43 +290,47 @@ impl MerkleTree { /// Check if the key exists in the tree pub fn has_key(&self, key: &Key) -> Result { let (store_type, sub_key) = StoreType::sub_key(key)?; - let subtree = self.tree(&store_type); - let value = subtree.get(&H::hash(sub_key.to_string()))?; + let value = match self.tree(&store_type) { + Either::Left(smt) => { + smt.get(&H::hash(sub_key.to_string()).into())? + } + Either::Right(amt) => { + let key: PaddedKey = sub_key + .to_string() + .try_into() + .map_err(Error::MerkleTree)?; + amt.get(&key)? + } + }; Ok(!value.is_zero()) } /// Update the tree with the given key and value pub fn update(&mut self, key: &Key, value: impl AsRef<[u8]>) -> Result<()> { - let (store_type, sub_key) = StoreType::sub_key(key)?; - self.update_tree( - &store_type, - H::hash(sub_key.to_string()), - H::hash(value), - ) + let sub_key = StoreType::sub_key(key)?; + let store_type = sub_key.0; + self.update_tree(&store_type, sub_key.into(), H::hash(value).into()) } /// Delete the value corresponding to the given key pub fn delete(&mut self, key: &Key) -> Result<()> { - let (store_type, sub_key) = StoreType::sub_key(key)?; - self.update_tree( - &store_type, - H::hash(sub_key.to_string()), - H256::zero(), - ) + let sub_key = StoreType::sub_key(key)?; + let store_type = sub_key.0; + self.update_tree(&store_type, sub_key.into(), H256::zero().into()) } /// Get the root pub fn root(&self) -> MerkleRoot { - (*self.base.root()).into() + self.base.root().into() } /// Get the stores of the base and sub trees pub fn stores(&self) -> MerkleTreeStoresWrite { MerkleTreeStoresWrite { - base: (self.base.root(), self.base.store()), - account: (self.account.root(), self.account.store()), - ibc: (self.ibc.root(), self.ibc.store()), - pos: (self.pos.root(), self.pos.store()), + base: (self.base.root().into(), self.base.store()), + account: (self.account.root().into(), self.account.store()), + ibc: (self.ibc.root().into(), self.ibc.store()), + pos: (self.pos.root().into(), self.pos.store()), } } @@ -241,23 +341,31 @@ impl MerkleTree { value: Vec, ) -> Result { let (store_type, sub_key) = StoreType::sub_key(key)?; - let subtree = self.tree(&store_type); - - // Get a proof of the sub tree - let hashed_sub_key = H::hash(&sub_key.to_string()); - let cp = subtree.membership_proof(&hashed_sub_key)?; - // Replace the values and the leaf op for the verification - let sub_proof = match cp.proof.expect("The proof should exist") { - Ics23Proof::Exist(ep) => CommitmentProof { - proof: Some(Ics23Proof::Exist(ExistenceProof { - key: sub_key.to_string().as_bytes().to_vec(), - value, - leaf: Some(self.leaf_spec()), - ..ep - })), - }, - // the proof should have an ExistenceProof - _ => unreachable!(), + let sub_proof = match self.tree(&store_type) { + Either::Left(smt) => { + let cp = smt + .membership_proof(&H::hash(&sub_key.to_string()).into())?; + // Replace the values and the leaf op for the verification + match cp.proof.expect("The proof should exist") { + Ics23Proof::Exist(ep) => CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + key: sub_key.to_string().as_bytes().to_vec(), + value, + leaf: Some(self.leaf_spec()), + ..ep + })), + }, + // the proof should have an ExistenceProof + _ => unreachable!(), + } + } + Either::Right(amt) => { + let key = sub_key + .to_string() + .try_into() + .map_err(Error::MerkleTree)?; + amt.membership_proof(&key)? + } }; self.get_proof(key, sub_proof) } @@ -265,22 +373,31 @@ impl MerkleTree { /// Get the non-existence proof pub fn get_non_existence_proof(&self, key: &Key) -> Result { let (store_type, sub_key) = StoreType::sub_key(key)?; - let subtree = self.tree(&store_type); - - // Get a proof of the sub tree - let hashed_sub_key = H::hash(&sub_key.to_string()); - let cp = subtree.non_membership_proof(&hashed_sub_key)?; - // Replace the key with the non-hashed key for the verification - let sub_proof = match cp.proof.expect("The proof should exist") { - Ics23Proof::Nonexist(nep) => CommitmentProof { - proof: Some(Ics23Proof::Nonexist(NonExistenceProof { - key: sub_key.to_string().as_bytes().to_vec(), - ..nep - })), - }, - // the proof should have a NonExistenceProof - _ => unreachable!(), + let sub_proof = match self.tree(&store_type) { + Either::Left(smt) => { + let hashed_sub_key = H::hash(&sub_key.to_string()).into(); + let cp = smt.non_membership_proof(&hashed_sub_key)?; + // Replace the key with the non-hashed key for the verification + match cp.proof.expect("The proof should exist") { + Ics23Proof::Nonexist(nep) => CommitmentProof { + proof: Some(Ics23Proof::Nonexist(NonExistenceProof { + key: sub_key.to_string().as_bytes().to_vec(), + ..nep + })), + }, + // the proof should have a NonExistenceProof + _ => unreachable!(), + } + } + Either::Right(amt) => { + let key = sub_key + .to_string() + .try_into() + .map_err(Error::MerkleTree)?; + amt.non_membership_proof(&key)? + } }; + // Get a proof of the sub tree self.get_proof(key, sub_proof) } @@ -304,7 +421,7 @@ impl MerkleTree { // exist let (store_type, _) = StoreType::sub_key(key)?; let base_key = store_type.to_string(); - let cp = self.base.membership_proof(&H::hash(&base_key))?; + let cp = self.base.membership_proof(&H::hash(&base_key).into())?; // Replace the values and the leaf op for the verification let base_proof = match cp.proof.expect("The proof should exist") { Ics23Proof::Exist(ep) => CommitmentProof { @@ -336,7 +453,7 @@ impl MerkleTree { /// Get the proof specs pub fn proof_specs(&self) -> Vec { - let spec = sparse_merkle_tree::proof_ics23::get_spec(H::hash_op()); + let spec = arse_merkle_tree::proof_ics23::get_spec(H::hash_op()); let sub_tree_spec = ProofSpec { leaf_spec: Some(self.leaf_spec()), ..spec.clone() @@ -353,7 +470,7 @@ impl MerkleTree { fn base_leaf_spec(&self) -> LeafOp { LeafOp { hash: H::hash_op().into(), - prehash_key: H::hash_op().into(), + prehash_key: HashOp::NoHash.into(), prehash_value: HashOp::NoHash.into(), length: LengthOp::NoPrefix.into(), prefix: H256::zero().as_slice().to_vec(), @@ -366,8 +483,8 @@ impl MerkleTree { fn leaf_spec(&self) -> LeafOp { LeafOp { hash: H::hash_op().into(), - prehash_key: H::hash_op().into(), - prehash_value: H::hash_op().into(), + prehash_key: HashOp::NoHash.into(), + prehash_value: HashOp::NoHash.into(), length: LengthOp::NoPrefix.into(), prefix: H256::zero().as_slice().to_vec(), } @@ -383,24 +500,66 @@ impl From for MerkleRoot { } } +impl From<&H256> for MerkleRoot { + fn from(root: &H256) -> Self { + let root = *root; + Self(root.as_slice().to_vec()) + } +} + impl fmt::Display for MerkleRoot { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", ByteBuf(&self.0)) } } +impl From<(StoreType, Key)> for MerkleKey { + fn from((store, key): (StoreType, Key)) -> Self { + match store { + StoreType::Base => MerkleKey::Sha256(key, PhantomData), + StoreType::Account => MerkleKey::Sha256(key, PhantomData), + StoreType::PoS => MerkleKey::Sha256(key, PhantomData), + StoreType::Ibc => MerkleKey::Raw(key), + } + } +} + +impl TryFrom> for PaddedKey<32> { + type Error = Error; + + fn try_from(value: MerkleKey) -> Result { + match value { + MerkleKey::Sha256(key, _) => Ok(H::hash(key.to_string()).into()), + _ => Err(Error::InvalidMerkleKey("SMT".into())), + } + } +} + +impl TryFrom> for PaddedKey { + type Error = Error; + + fn try_from(value: MerkleKey) -> Result { + match value { + MerkleKey::Raw(key) => { + key.to_string().try_into().map_err(Error::MerkleTree) + } + _ => Err(Error::InvalidMerkleKey("AMT".into())), + } + } +} + /// The root and store pairs to restore the trees #[derive(Default)] pub struct MerkleTreeStoresRead { - base: (H256, DefaultStore), - account: (H256, DefaultStore), - ibc: (H256, DefaultStore), - pos: (H256, DefaultStore), + base: (Hash, SmtStore), + account: (Hash, SmtStore), + ibc: (Hash, AmtStore), + pos: (Hash, SmtStore), } impl MerkleTreeStoresRead { /// Set the root of the given store type - pub fn set_root(&mut self, store_type: &StoreType, root: H256) { + pub fn set_root(&mut self, store_type: &StoreType, root: Hash) { match store_type { StoreType::Base => self.base.0 = root, StoreType::Account => self.account.0 = root, @@ -410,46 +569,42 @@ impl MerkleTreeStoresRead { } /// Set the store of the given store type - pub fn set_store( - &mut self, - store_type: &StoreType, - store: DefaultStore, - ) { + pub fn set_store(&mut self, store_type: Store) { match store_type { - StoreType::Base => self.base.1 = store, - StoreType::Account => self.account.1 = store, - StoreType::Ibc => self.ibc.1 = store, - StoreType::PoS => self.pos.1 = store, + Store::Base(store) => self.base.1 = store, + Store::Account(store) => self.account.1 = store, + Store::Ibc(store) => self.ibc.1 = store, + Store::PoS(store) => self.pos.1 = store, } } } /// The root and store pairs to be persistent pub struct MerkleTreeStoresWrite<'a> { - base: (&'a H256, &'a DefaultStore), - account: (&'a H256, &'a DefaultStore), - ibc: (&'a H256, &'a DefaultStore), - pos: (&'a H256, &'a DefaultStore), + base: (Hash, &'a SmtStore), + account: (Hash, &'a SmtStore), + ibc: (Hash, &'a AmtStore), + pos: (Hash, &'a SmtStore), } impl<'a> MerkleTreeStoresWrite<'a> { /// Get the root of the given store type - pub fn root(&self, store_type: &StoreType) -> &H256 { + pub fn root(&self, store_type: &StoreType) -> &Hash { match store_type { - StoreType::Base => self.base.0, - StoreType::Account => self.account.0, - StoreType::Ibc => self.ibc.0, - StoreType::PoS => self.pos.0, + StoreType::Base => &self.base.0, + StoreType::Account => &self.account.0, + StoreType::Ibc => &self.ibc.0, + StoreType::PoS => &self.pos.0, } } /// Get the store of the given store type - pub fn store(&self, store_type: &StoreType) -> &DefaultStore { + pub fn store(&self, store_type: &StoreType) -> StoreRef { match store_type { - StoreType::Base => self.base.1, - StoreType::Account => self.account.1, - StoreType::Ibc => self.ibc.1, - StoreType::PoS => self.pos.1, + StoreType::Base => StoreRef::Base(self.base.1), + StoreType::Account => StoreRef::Account(self.account.1), + StoreType::Ibc => StoreRef::Ibc(self.ibc.1), + StoreType::PoS => StoreRef::PoS(self.pos.1), } } } @@ -462,19 +617,24 @@ pub trait StorageHasher: Hasher + Default { /// The storage hasher used for the merkle tree. #[derive(Default)] -pub struct Sha256Hasher(sparse_merkle_tree::sha256::Sha256Hasher); +pub struct Sha256Hasher(Sha256); impl Hasher for Sha256Hasher { - fn write_h256(&mut self, h: &H256) { - self.0.write_h256(h) + fn write_bytes(&mut self, h: &[u8]) { + self.0.update(h) } - fn finish(self) -> H256 { - self.0.finish() + fn finish(self) -> arse_merkle_tree::H256 { + let hash = self.0.finalize(); + let bytes: [u8; 32] = hash + .as_slice() + .try_into() + .expect("Sha256 output conversion to fixed array shouldn't fail"); + bytes.into() } fn hash_op() -> ics23::HashOp { - sparse_merkle_tree::sha256::Sha256Hasher::hash_op() + ics23::HashOp::Sha256 } } @@ -503,9 +663,9 @@ impl From for Error { } } -impl From for Error { - fn from(error: SmtError) -> Self { - Error::Smt(error) +impl From for Error { + fn from(error: MtError) -> Self { + Error::MerkleTree(error) } } @@ -558,8 +718,8 @@ mod test { let stores_write = tree.stores(); let mut stores_read = MerkleTreeStoresRead::default(); for st in StoreType::iter() { - stores_read.set_root(st, *stores_write.root(st)); - stores_read.set_store(st, stores_write.store(st).clone()); + stores_read.set_root(st, stores_write.root(st).clone()); + stores_read.set_store(stores_write.store(st).to_owned()); } let restored_tree = MerkleTree::::new(stores_read); assert!(restored_tree.has_key(&ibc_key).unwrap()); @@ -589,6 +749,7 @@ mod test { let paths = vec![sub_key.to_string(), store_type.to_string()]; let mut sub_root = ibc_val.clone(); let mut value = ibc_val; + let mut second = false; // First, the sub proof is verified. Next the base proof is verified // with the sub root for ((p, spec), key) in @@ -602,13 +763,19 @@ mod test { }; sub_root = ics23::calculate_existence_root(&existence_proof).unwrap(); + let hashed = if !second { + Sha256Hasher::hash(&value).as_slice().to_vec() + } else { + value + }; assert!(ics23::verify_membership( &commitment_proof, spec, &sub_root, key.as_bytes(), - &value, + hashed.as_slice(), )); + second = true; // for the verification of the base tree value = sub_root.clone(); } diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 3cc7e8863c..99260d8333 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -105,11 +105,8 @@ impl DB for MockDB { types::decode(bytes) .map_err(Error::CodingError)?, ), - Some(&"store") => merkle_tree_stores.set_store( - &st, - types::decode(bytes) - .map_err(Error::CodingError)?, - ), + Some(&"store") => merkle_tree_stores + .set_store(st.decode_store(bytes)?), _ => unknown_key_error(path)?, } } @@ -218,7 +215,7 @@ impl DB for MockDB { .map_err(Error::KeyError)?; self.0.borrow_mut().insert( store_key.to_string(), - types::encode(merkle_tree_stores.store(st)), + merkle_tree_stores.store(st).encode(), ); } } @@ -322,8 +319,7 @@ impl DB for MockDB { let bytes = self.0.borrow().get(&store_key.to_string()).cloned(); match bytes { Some(b) => { - let store = types::decode(b).map_err(Error::CodingError)?; - merkle_tree_stores.set_store(st, store); + merkle_tree_stores.set_store(st.decode_store(b)?); } None => return Ok(None), } diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index e2fed30379..bf4b0f84f4 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -3,6 +3,8 @@ use std::fmt::{self, Display}; use std::ops::Deref; +use arse_merkle_tree::traits::Value; +use arse_merkle_tree::H256; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -95,6 +97,11 @@ impl Hash { let digest = Sha256::digest(data.as_ref()); Self(*digest.as_ref()) } + + /// Check if the hash is all zeros + pub fn is_zero(&self) -> bool { + self == &Self::zero() + } } impl From for TmHash { @@ -102,3 +109,32 @@ impl From for TmHash { TmHash::Sha256(hash.0) } } + +impl From for Hash { + fn from(hash: H256) -> Self { + Hash(hash.into()) + } +} + +impl From<&H256> for Hash { + fn from(hash: &H256) -> Self { + let hash = *hash; + Hash(hash.into()) + } +} + +impl From for H256 { + fn from(hash: Hash) -> H256 { + hash.to_h256() + } +} + +impl Value for Hash { + fn to_h256(&self) -> H256 { + self.0.into() + } + + fn zero() -> Self { + Hash([0u8; 32]) + } +} diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index fc87bc8d51..2b2f9b2198 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -1,6 +1,7 @@ //! Storage types use std::convert::{TryFrom, TryInto}; use std::fmt::Display; +use std::marker::PhantomData; use std::num::ParseIntError; use std::ops::{Add, Div, Mul, Rem, Sub}; use std::str::FromStr; @@ -12,6 +13,7 @@ use thiserror::Error; #[cfg(feature = "ferveo-tpke")] use super::transaction::WrapperTx; use crate::bytes::ByteBuf; +use crate::ledger::storage::StorageHasher; use crate::types::address::{self, Address}; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -222,6 +224,16 @@ impl FromStr for Key { } } +/// A type for converting an Anoma storage key +/// to that of the right type for the different +/// merkle trees used. +pub enum MerkleKey { + /// A key that needs to be hashed + Sha256(Key, PhantomData), + /// A key that can be given as raw bytes + Raw(Key), +} + impl Key { /// Parses string and returns a key pub fn parse(string: impl AsRef) -> Result { diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index b7185cc110..e538da6e7d 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -2308,7 +2308,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=yuji/prost-0.9#be4b2293558361df2f452c60a3e90c6b5e52e225" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#b03d8df11a6a0cfbd47a1a33cda5b73353bb715d" dependencies = [ "borsh", "cfg-if 1.0.0", From a2b3939ba611daa9b1e755324ee264c39edd6632 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Thu, 4 Aug 2022 14:29:27 +0200 Subject: [PATCH 226/373] [fix]: Fixed the proof specs. Tests now passing --- shared/src/ledger/storage/merkle_tree.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 8b98f47316..7b3cab5b1e 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -421,19 +421,8 @@ impl MerkleTree { // exist let (store_type, _) = StoreType::sub_key(key)?; let base_key = store_type.to_string(); - let cp = self.base.membership_proof(&H::hash(&base_key).into())?; - // Replace the values and the leaf op for the verification - let base_proof = match cp.proof.expect("The proof should exist") { - Ics23Proof::Exist(ep) => CommitmentProof { - proof: Some(Ics23Proof::Exist(ExistenceProof { - key: base_key.as_bytes().to_vec(), - leaf: Some(self.base_leaf_spec()), - ..ep - })), - }, - // the proof should have an ExistenceProof - _ => unreachable!(), - }; + let base_proof = + self.base.membership_proof(&H::hash(&base_key).into())?; let mut data = vec![]; base_proof @@ -746,7 +735,12 @@ mod test { let proof = tree.get_existence_proof(&ibc_key, ibc_val.clone()).unwrap(); let (store_type, sub_key) = StoreType::sub_key(&ibc_key).unwrap(); - let paths = vec![sub_key.to_string(), store_type.to_string()]; + let paths = vec![ + sub_key.to_string().as_bytes().to_vec(), + Sha256Hasher::hash(&store_type.to_string()) + .as_slice() + .to_vec(), + ]; let mut sub_root = ibc_val.clone(); let mut value = ibc_val; let mut second = false; @@ -772,7 +766,7 @@ mod test { &commitment_proof, spec, &sub_root, - key.as_bytes(), + key.as_slice(), hashed.as_slice(), )); second = true; From 10218d37c09c59462c56737ce4138d549c576226 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Fri, 5 Aug 2022 11:18:57 +0200 Subject: [PATCH 227/373] [fix]: Fixed new merkle trees to be compatible with existing unit tests (instead of changing the tests) --- shared/src/ledger/storage/merkle_tree.rs | 130 +++++++++++++++++++---- 1 file changed, 109 insertions(+), 21 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 7b3cab5b1e..b39ea9d0ad 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -364,7 +364,19 @@ impl MerkleTree { .to_string() .try_into() .map_err(Error::MerkleTree)?; - amt.membership_proof(&key)? + let cp = amt.membership_proof(&key)?; + + // Replace the values and the leaf op for the verification + match cp.proof.expect("The proof should exist") { + Ics23Proof::Exist(ep) => CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + value, + leaf:Some(self.ibc_leaf_spec()), + ..ep + })), + }, + _ => unreachable!(), + } } }; self.get_proof(key, sub_proof) @@ -421,8 +433,19 @@ impl MerkleTree { // exist let (store_type, _) = StoreType::sub_key(key)?; let base_key = store_type.to_string(); - let base_proof = - self.base.membership_proof(&H::hash(&base_key).into())?; + let cp = self.base.membership_proof(&H::hash(&base_key).into())?; + // Replace the values and the leaf op for the verification + let base_proof = match cp.proof.expect("The proof should exist") { + Ics23Proof::Exist(ep) => CommitmentProof { + proof: Some(Ics23Proof::Exist(ExistenceProof { + key: base_key.as_bytes().to_vec(), + leaf: Some(self.base_leaf_spec()), + ..ep + })), + }, + // the proof should have an ExistenceProof + _ => unreachable!(), + }; let mut data = vec![]; base_proof @@ -454,12 +477,26 @@ impl MerkleTree { vec![sub_tree_spec, base_tree_spec] } + /// Get the proof specs for ibc + pub fn ibc_proof_specs(&self) -> Vec { + let spec = arse_merkle_tree::proof_ics23::get_spec(H::hash_op()); + let sub_tree_spec = ProofSpec { + leaf_spec: Some(self.ibc_leaf_spec()), + ..spec.clone() + }; + let base_tree_spec = ProofSpec { + leaf_spec: Some(self.base_leaf_spec()), + ..spec + }; + vec![sub_tree_spec, base_tree_spec] + } + /// Get the leaf spec for the base tree. The key is stored after hashing, /// but the stored value is the subtree's root without hashing. fn base_leaf_spec(&self) -> LeafOp { LeafOp { hash: H::hash_op().into(), - prehash_key: HashOp::NoHash.into(), + prehash_key: H::hash_op().into(), prehash_value: HashOp::NoHash.into(), length: LengthOp::NoPrefix.into(), prefix: H256::zero().as_slice().to_vec(), @@ -470,10 +507,23 @@ impl MerkleTree { /// verification with this spec because a subtree stores the key-value pairs /// after hashing. fn leaf_spec(&self) -> LeafOp { + LeafOp { + hash: H::hash_op().into(), + prehash_key: H::hash_op().into(), + prehash_value: H::hash_op().into(), + length: LengthOp::NoPrefix.into(), + prefix: H256::zero().as_slice().to_vec(), + } + } + + /// Get the leaf spec for the ibc subtree. Non-hashed values are used for the + /// verification with this spec because a subtree stores the key-value pairs + /// after hashing. However, keys are also not hashed in the backing store. + fn ibc_leaf_spec(&self) -> LeafOp { LeafOp { hash: H::hash_op().into(), prehash_key: HashOp::NoHash.into(), - prehash_value: HashOp::NoHash.into(), + prehash_value: H::hash_op().into(), length: LengthOp::NoPrefix.into(), prefix: H256::zero().as_slice().to_vec(), } @@ -716,7 +766,7 @@ mod test { } #[test] - fn test_proof() { + fn test_ibc_existence_proof() { let mut tree = MerkleTree::::default(); let key_prefix: Key = @@ -731,19 +781,13 @@ mod test { let pos_val = [2u8; 8].to_vec(); tree.update(&pos_key, pos_val).unwrap(); - let specs = tree.proof_specs(); + let specs = tree.ibc_proof_specs(); let proof = tree.get_existence_proof(&ibc_key, ibc_val.clone()).unwrap(); let (store_type, sub_key) = StoreType::sub_key(&ibc_key).unwrap(); - let paths = vec![ - sub_key.to_string().as_bytes().to_vec(), - Sha256Hasher::hash(&store_type.to_string()) - .as_slice() - .to_vec(), - ]; + let paths = vec![sub_key.to_string(), store_type.to_string()]; let mut sub_root = ibc_val.clone(); let mut value = ibc_val; - let mut second = false; // First, the sub proof is verified. Next the base proof is verified // with the sub root for ((p, spec), key) in @@ -757,19 +801,63 @@ mod test { }; sub_root = ics23::calculate_existence_root(&existence_proof).unwrap(); - let hashed = if !second { - Sha256Hasher::hash(&value).as_slice().to_vec() - } else { - value + assert!(ics23::verify_membership( + &commitment_proof, + spec, + &sub_root, + key.as_bytes(), + &value, + )); + // for the verification of the base tree + value = sub_root.clone(); + } + // Check the base root + assert_eq!(sub_root, tree.root().0); + } + + #[test] + fn test_non_ibc_existence_proof() { + let mut tree = MerkleTree::::default(); + + let key_prefix: Key = + Address::Internal(InternalAddress::Ibc).to_db_key().into(); + let ibc_key = key_prefix.push(&"test".to_string()).unwrap(); + let key_prefix: Key = + Address::Internal(InternalAddress::PoS).to_db_key().into(); + let pos_key = key_prefix.push(&"test".to_string()).unwrap(); + + let ibc_val = [1u8; 8].to_vec(); + tree.update(&ibc_key, ibc_val).unwrap(); + let pos_val = [2u8; 8].to_vec(); + tree.update(&pos_key, pos_val.clone()).unwrap(); + + let specs = tree.proof_specs(); + let proof = + tree.get_existence_proof(&pos_key, pos_val.clone()).unwrap(); + let (store_type, sub_key) = StoreType::sub_key(&pos_key).unwrap(); + let paths = vec![sub_key.to_string(), store_type.to_string()]; + let mut sub_root = pos_val.clone(); + let mut value = pos_val; + // First, the sub proof is verified. Next the base proof is verified + // with the sub root + for ((p, spec), key) in + proof.ops.iter().zip(specs.iter()).zip(paths.iter()) + { + let commitment_proof = CommitmentProof::decode(&*p.data).unwrap(); + let existence_proof = match commitment_proof.clone().proof.unwrap() + { + Ics23Proof::Exist(ep) => ep, + _ => unreachable!(), }; + sub_root = + ics23::calculate_existence_root(&existence_proof).unwrap(); assert!(ics23::verify_membership( &commitment_proof, spec, &sub_root, - key.as_slice(), - hashed.as_slice(), + key.as_bytes(), + &value, )); - second = true; // for the verification of the base tree value = sub_root.clone(); } From 6a8cfe9d042434c8e166ec1786264e8dcc34ad35 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Sat, 6 Aug 2022 03:17:30 +0200 Subject: [PATCH 228/373] [fix]: Fixed map of ibc keys into fixed keyspace and fixed tests --- Cargo.lock | 13 +- shared/src/ledger/storage/merkle_tree.rs | 198 ++++++++++++++++++----- shared/src/ledger/storage/mod.rs | 2 + shared/src/types/hash.rs | 8 +- shared/src/types/storage.rs | 30 +++- 5 files changed, 198 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1af6bd240..d720427b71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -943,14 +943,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "6127248204b9aba09a362f6c930ef6a78f2c1b2215f8a7b398c06e1083f17af0" dependencies = [ - "libc", + "js-sys", "num-integer", "num-traits 0.2.15", "time 0.1.44", + "wasm-bindgen", "winapi 0.3.9", ] @@ -5502,9 +5503,9 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.25.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34a3bb58e85333f1ab191bf979104b586ebd77475bc6681882825f4532dfe87c" +checksum = "8fc129ab6000ab4037e7718703cdeab82a12c4ee23a238658f55372d80ef2b05" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", @@ -6094,7 +6095,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#2b127dec1cbe26792d2b661e1fffc381b78a92fb" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#d8182ea53db451e22a7de38386e3858f2b8c27ae" dependencies = [ "blake2b-rs", "borsh", diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index b39ea9d0ad..f51bb6f566 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -7,7 +7,9 @@ use std::str::FromStr; use arse_merkle_tree::default_store::DefaultStore; use arse_merkle_tree::error::Error as MtError; use arse_merkle_tree::traits::Hasher; -use arse_merkle_tree::{PaddedKey, SparseMerkleTree as ArseMerkleTree, H256}; +use arse_merkle_tree::{ + Key as Keyable, PaddedKey, SparseMerkleTree as ArseMerkleTree, H256, +}; use borsh::{BorshDeserialize, BorshSerialize}; use ics23::commitment_proof::Proof as Ics23Proof; use ics23::{ @@ -20,18 +22,21 @@ use sha2::{Digest, Sha256}; use tendermint::merkle::proof::{Proof, ProofOp}; use thiserror::Error; +use super::IBC_KEY_LIMIT; use crate::bytes::ByteBuf; use crate::ledger::storage::types; use crate::types::address::{Address, InternalAddress}; use crate::types::hash::Hash; -use crate::types::storage::{DbKeySeg, Error as StorageError, Key, MerkleKey}; +use crate::types::storage::{ + DbKeySeg, Error as StorageError, Key, MerkleKey, StringKey, +}; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { #[error("Invalid key: {0}")] InvalidKey(StorageError), - #[error("Invalid key for {0}-type merkle tree")] + #[error("Invalid key for merkle tree: {0}")] InvalidMerkleKey(String), #[error("Empty Key: {0}")] EmptyKey(String), @@ -39,18 +44,18 @@ pub enum Error { MerkleTree(MtError), #[error("Invalid store type: {0}")] StoreType(String), + #[error("Non-existence proofs not supported for store type: {0}")] + NonExistenceProof(String), } /// Result for functions that may fail type Result = std::result::Result; -/// The maximum size of an IBC key (in bytes) allowed in merkle-ized storage -pub const IBC_KEY_LIMIT: usize = 120; /// Type aliases for the different merkle trees and backing stores -pub type SmtStore = DefaultStore; -pub type AmtStore = DefaultStore; -pub type Smt = ArseMerkleTree; -pub type Amt = ArseMerkleTree; +pub type SmtStore = DefaultStore, Hash, 32>; +pub type AmtStore = DefaultStore; +pub type Smt = ArseMerkleTree, Hash, SmtStore, 32>; +pub type Amt = ArseMerkleTree; /// Store types for the merkle tree #[derive( @@ -295,10 +300,8 @@ impl MerkleTree { smt.get(&H::hash(sub_key.to_string()).into())? } Either::Right(amt) => { - let key: PaddedKey = sub_key - .to_string() - .try_into() - .map_err(Error::MerkleTree)?; + let key = + StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; amt.get(&key)? } }; @@ -360,10 +363,8 @@ impl MerkleTree { } } Either::Right(amt) => { - let key = sub_key - .to_string() - .try_into() - .map_err(Error::MerkleTree)?; + let key = + StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; let cp = amt.membership_proof(&key)?; // Replace the values and the leaf op for the verification @@ -371,7 +372,7 @@ impl MerkleTree { Ics23Proof::Exist(ep) => CommitmentProof { proof: Some(Ics23Proof::Exist(ExistenceProof { value, - leaf:Some(self.ibc_leaf_spec()), + leaf: Some(self.ibc_leaf_spec()), ..ep })), }, @@ -386,27 +387,33 @@ impl MerkleTree { pub fn get_non_existence_proof(&self, key: &Key) -> Result { let (store_type, sub_key) = StoreType::sub_key(key)?; let sub_proof = match self.tree(&store_type) { - Either::Left(smt) => { - let hashed_sub_key = H::hash(&sub_key.to_string()).into(); - let cp = smt.non_membership_proof(&hashed_sub_key)?; - // Replace the key with the non-hashed key for the verification - match cp.proof.expect("The proof should exist") { - Ics23Proof::Nonexist(nep) => CommitmentProof { - proof: Some(Ics23Proof::Nonexist(NonExistenceProof { - key: sub_key.to_string().as_bytes().to_vec(), - ..nep - })), - }, - // the proof should have a NonExistenceProof - _ => unreachable!(), - } + Either::Left(_) => { + return Err(Error::NonExistenceProof(store_type.to_string())); } Either::Right(amt) => { - let key = sub_key - .to_string() - .try_into() - .map_err(Error::MerkleTree)?; - amt.non_membership_proof(&key)? + let key = + StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; + let mut nep = amt.non_membership_proof(&key)?; + let mut spec = self.ibc_leaf_spec(); + spec.prehash_value = HashOp::NoHash.into(); + // Replace the values and the leaf op for the verification + if let Some(ref mut nep) = nep.proof { + match nep { + Ics23Proof::Nonexist(ref mut ep) => { + let NonExistenceProof { + ref mut left, + ref mut right, + .. + } = ep; + let ep = left.as_mut().or(right.as_mut()).expect( + "A left or right existence proof should exist.", + ); + ep.leaf = Some(spec); + } + _ => unreachable!(), + } + } + nep } }; // Get a proof of the sub tree @@ -516,9 +523,10 @@ impl MerkleTree { } } - /// Get the leaf spec for the ibc subtree. Non-hashed values are used for the - /// verification with this spec because a subtree stores the key-value pairs - /// after hashing. However, keys are also not hashed in the backing store. + /// Get the leaf spec for the ibc subtree. Non-hashed values are used for + /// the verification with this spec because a subtree stores the + /// key-value pairs after hashing. However, keys are also not hashed in + /// the backing store. fn ibc_leaf_spec(&self) -> LeafOp { LeafOp { hash: H::hash_op().into(), @@ -569,20 +577,24 @@ impl TryFrom> for PaddedKey<32> { fn try_from(value: MerkleKey) -> Result { match value { MerkleKey::Sha256(key, _) => Ok(H::hash(key.to_string()).into()), - _ => Err(Error::InvalidMerkleKey("SMT".into())), + _ => Err(Error::InvalidMerkleKey( + "This key is for a sparse merkle tree".into(), + )), } } } -impl TryFrom> for PaddedKey { +impl TryFrom> for StringKey { type Error = Error; fn try_from(value: MerkleKey) -> Result { match value { MerkleKey::Raw(key) => { - key.to_string().try_into().map_err(Error::MerkleTree) + Self::try_from_bytes(key.to_string().as_bytes()) } - _ => Err(Error::InvalidMerkleKey("AMT".into())), + _ => Err(Error::InvalidMerkleKey( + "This is not an key for the IBC subtree".into(), + )), } } } @@ -648,6 +660,37 @@ impl<'a> MerkleTreeStoresWrite<'a> { } } +impl Keyable for StringKey { + type Error = Error; + + fn to_vec(&self) -> Vec { + let array: [u8; IBC_KEY_LIMIT] = self.inner.into(); + let mut dec = [0u8; IBC_KEY_LIMIT]; + for (i, byte) in array.iter().enumerate() { + dec[i] = byte.wrapping_sub(1); + } + dec[..self.length].to_vec() + } + + fn try_from_bytes(bytes: &[u8]) -> Result { + let mut array = [0u8; IBC_KEY_LIMIT]; + let mut length = 0; + for (i, byte) in bytes.iter().enumerate() { + if i >= IBC_KEY_LIMIT { + return Err(Error::InvalidMerkleKey( + "Input IBC key is too large".into(), + )); + } + array[i] = byte.wrapping_add(1); + length += 1; + } + Ok(Self { + inner: array.into(), + length, + }) + } +} + /// The storage hasher used for the merkle tree. pub trait StorageHasher: Hasher + Default { /// Hash the value to store @@ -864,4 +907,71 @@ mod test { // Check the base root assert_eq!(sub_root, tree.root().0); } + + #[test] + fn test_ibc_non_existence_proof() { + let mut tree = MerkleTree::::default(); + + let key_prefix: Key = + Address::Internal(InternalAddress::Ibc).to_db_key().into(); + let ibc_non_key = + key_prefix.push(&"test".to_string()).expect("Test failed"); + let key_prefix: Key = + Address::Internal(InternalAddress::Ibc).to_db_key().into(); + let ibc_key = + key_prefix.push(&"test2".to_string()).expect("Test failed"); + let ibc_val = [2u8; 8].to_vec(); + tree.update(&ibc_key, ibc_val).expect("Test failed"); + + let nep = tree + .get_non_existence_proof(&ibc_non_key) + .expect("Test failed"); + let subtree_nep = nep.ops.get(0).expect("Test failed"); + let nep_commitment_proof = + CommitmentProof::decode(&*subtree_nep.data).expect("Test failed"); + let non_existence_proof = + match nep_commitment_proof.clone().proof.expect("Test failed") { + Ics23Proof::Nonexist(nep) => nep, + _ => unreachable!(), + }; + let subtree_root = if let Some(left) = &non_existence_proof.left { + ics23::calculate_existence_root(left).unwrap() + } else if let Some(right) = &non_existence_proof.right { + ics23::calculate_existence_root(right).unwrap() + } else { + unreachable!() + }; + let (store_type, sub_key) = + StoreType::sub_key(&ibc_non_key).expect("Test failed"); + + let specs = tree.ibc_proof_specs(); + let mut spec = specs[0].clone(); + spec.leaf_spec.as_mut().unwrap().prehash_value = HashOp::NoHash.into(); + + let nep_verification_res = ics23::verify_non_membership( + &nep_commitment_proof, + &spec, + &subtree_root, + sub_key.to_string().as_bytes(), + ); + assert!(nep_verification_res); + let basetree_ep = nep.ops.get(1).unwrap(); + let basetree_ep_commitment_proof = + CommitmentProof::decode(&*basetree_ep.data).unwrap(); + let basetree_ics23_ep = + match basetree_ep_commitment_proof.clone().proof.unwrap() { + Ics23Proof::Exist(ep) => ep, + _ => unreachable!(), + }; + let basetree_root = + ics23::calculate_existence_root(&basetree_ics23_ep).unwrap(); + let basetree_verification_res = ics23::verify_membership( + &basetree_ep_commitment_proof, + &specs[1], + &basetree_root, + store_type.to_string().as_bytes(), + &subtree_root, + ); + assert!(basetree_verification_res); + } } diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0b3d19a742..40af20ec69 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -34,6 +34,8 @@ use crate::types::time::DateTimeUtc; /// A result of a function that may fail pub type Result = std::result::Result; +/// The maximum size of an IBC key (in bytes) allowed in merkle-ized storage +pub const IBC_KEY_LIMIT: usize = 120; /// The storage data #[derive(Debug)] diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index bf4b0f84f4..6abe85136e 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display}; use std::ops::Deref; use arse_merkle_tree::traits::Value; -use arse_merkle_tree::H256; +use arse_merkle_tree::{PaddedKey, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -138,3 +138,9 @@ impl Value for Hash { Hash([0u8; 32]) } } + +impl From for PaddedKey<32> { + fn from(hash: Hash) -> Self { + Self::from(hash.0) + } +} diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 2b2f9b2198..feab593589 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -3,9 +3,10 @@ use std::convert::{TryFrom, TryInto}; use std::fmt::Display; use std::marker::PhantomData; use std::num::ParseIntError; -use std::ops::{Add, Div, Mul, Rem, Sub}; +use std::ops::{Add, Deref, DerefMut, Div, Mul, Rem, Sub}; use std::str::FromStr; +use arse_merkle_tree::TreeKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -13,7 +14,7 @@ use thiserror::Error; #[cfg(feature = "ferveo-tpke")] use super::transaction::WrapperTx; use crate::bytes::ByteBuf; -use crate::ledger::storage::StorageHasher; +use crate::ledger::storage::{StorageHasher, IBC_KEY_LIMIT}; use crate::types::address::{self, Address}; use crate::types::hash::Hash; use crate::types::time::DateTimeUtc; @@ -234,6 +235,31 @@ pub enum MerkleKey { Raw(Key), } +/// Storage keys that are utf8 encoded strings +#[derive( + Eq, PartialEq, Copy, Clone, Hash, BorshSerialize, BorshDeserialize, +)] +pub struct StringKey { + /// The utf8 bytes representation + pub inner: TreeKey, + /// The length of the input (without the padding) + pub length: usize, +} + +impl Deref for StringKey { + type Target = TreeKey; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for StringKey { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + impl Key { /// Parses string and returns a key pub fn parse(string: impl AsRef) -> Result { From a20f6a2955924581a69b3fee2118a73aa65fa9bb Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 8 Aug 2022 11:07:40 +0200 Subject: [PATCH 229/373] [feat]: Downstreaming changes from arse-merkle-tree. That library needed professionalization and I made a change to minimize heap allocations and vector copies --- shared/src/ledger/storage/merkle_tree.rs | 28 ++++++++--------- shared/src/types/hash.rs | 13 ++++---- shared/src/types/storage.rs | 40 ++++++++++++++++++------ 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index f51bb6f566..dacb978d17 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -8,7 +8,7 @@ use arse_merkle_tree::default_store::DefaultStore; use arse_merkle_tree::error::Error as MtError; use arse_merkle_tree::traits::Hasher; use arse_merkle_tree::{ - Key as Keyable, PaddedKey, SparseMerkleTree as ArseMerkleTree, H256, + Hash as SmtHash, Key as TreeKey, SparseMerkleTree as ArseMerkleTree, H256, }; use borsh::{BorshDeserialize, BorshSerialize}; use ics23::commitment_proof::Proof as Ics23Proof; @@ -52,9 +52,9 @@ pub enum Error { type Result = std::result::Result; /// Type aliases for the different merkle trees and backing stores -pub type SmtStore = DefaultStore, Hash, 32>; +pub type SmtStore = DefaultStore; pub type AmtStore = DefaultStore; -pub type Smt = ArseMerkleTree, Hash, SmtStore, 32>; +pub type Smt = ArseMerkleTree; pub type Amt = ArseMerkleTree; /// Store types for the merkle tree @@ -571,7 +571,7 @@ impl From<(StoreType, Key)> for MerkleKey { } } -impl TryFrom> for PaddedKey<32> { +impl TryFrom> for SmtHash { type Error = Error; fn try_from(value: MerkleKey) -> Result { @@ -660,20 +660,16 @@ impl<'a> MerkleTreeStoresWrite<'a> { } } -impl Keyable for StringKey { +impl TreeKey for StringKey { type Error = Error; - fn to_vec(&self) -> Vec { - let array: [u8; IBC_KEY_LIMIT] = self.inner.into(); - let mut dec = [0u8; IBC_KEY_LIMIT]; - for (i, byte) in array.iter().enumerate() { - dec[i] = byte.wrapping_sub(1); - } - dec[..self.length].to_vec() + fn as_slice(&self) -> &[u8] { + &self.original.as_slice()[..self.length] } fn try_from_bytes(bytes: &[u8]) -> Result { - let mut array = [0u8; IBC_KEY_LIMIT]; + let mut tree_key = [0u8; IBC_KEY_LIMIT]; + let mut original = [0u8; IBC_KEY_LIMIT]; let mut length = 0; for (i, byte) in bytes.iter().enumerate() { if i >= IBC_KEY_LIMIT { @@ -681,11 +677,13 @@ impl Keyable for StringKey { "Input IBC key is too large".into(), )); } - array[i] = byte.wrapping_add(1); + original[i] = *byte; + tree_key[i] = byte.wrapping_add(1); length += 1; } Ok(Self { - inner: array.into(), + original, + tree_key: tree_key.into(), length, }) } diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 6abe85136e..7a20a3f179 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display}; use std::ops::Deref; use arse_merkle_tree::traits::Value; -use arse_merkle_tree::{PaddedKey, H256}; +use arse_merkle_tree::{H256, Hash as TreeHash}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -129,6 +129,12 @@ impl From for H256 { } } +impl From for TreeHash { + fn from(hash: Hash) -> Self { + Self::from(hash.0) + } +} + impl Value for Hash { fn to_h256(&self) -> H256 { self.0.into() @@ -139,8 +145,3 @@ impl Value for Hash { } } -impl From for PaddedKey<32> { - fn from(hash: Hash) -> Self { - Self::from(hash.0) - } -} diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index feab593589..547cf8a9ef 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -1,12 +1,13 @@ //! Storage types use std::convert::{TryFrom, TryInto}; use std::fmt::Display; +use std::io::Write; use std::marker::PhantomData; use std::num::ParseIntError; -use std::ops::{Add, Deref, DerefMut, Div, Mul, Rem, Sub}; +use std::ops::{Add, Deref, Div, Mul, Rem, Sub}; use std::str::FromStr; -use arse_merkle_tree::TreeKey; +use arse_merkle_tree::InternalKey; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -237,26 +238,45 @@ pub enum MerkleKey { /// Storage keys that are utf8 encoded strings #[derive( - Eq, PartialEq, Copy, Clone, Hash, BorshSerialize, BorshDeserialize, + Eq, PartialEq, Copy, Clone, Hash, )] pub struct StringKey { - /// The utf8 bytes representation - pub inner: TreeKey, + /// The original key string, in bytes + pub original: [u8; IBC_KEY_LIMIT], + /// The utf8 bytes representation of the key to be + /// used internally in the merkle tree + pub tree_key: InternalKey, /// The length of the input (without the padding) pub length: usize, } impl Deref for StringKey { - type Target = TreeKey; + type Target = InternalKey; fn deref(&self) -> &Self::Target { - &self.inner + &self.tree_key } } -impl DerefMut for StringKey { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner +impl BorshSerialize for StringKey { + fn serialize(&self, writer: &mut W) -> std::io::Result<()> { + let to_serialize = (self.original.to_vec(), self.tree_key, self.length); + BorshSerialize::serialize(&to_serialize, writer) + } +} + +impl BorshDeserialize for StringKey { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + use std::io::ErrorKind; + let (original, tree_key, length): (Vec, InternalKey, usize) = BorshDeserialize::deserialize(buf)?; + let original: [u8; IBC_KEY_LIMIT] = original.try_into().map_err(|_| { + std::io::Error::new(ErrorKind::InvalidData, "Input byte vector is too large") + })?; + Ok(Self { + original, + tree_key, + length, + }) } } From 7d0a526614318db89e2a240c4adcaf9c068aee43 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 8 Aug 2022 11:20:00 +0200 Subject: [PATCH 230/373] [fix]: Updating the .lock file --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d720427b71..60aca9972f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,9 +83,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.59" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" +checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142" [[package]] name = "ark-bls12-381" @@ -5503,9 +5503,9 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.26.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc129ab6000ab4037e7718703cdeab82a12c4ee23a238658f55372d80ef2b05" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", @@ -6095,7 +6095,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#d8182ea53db451e22a7de38386e3858f2b8c27ae" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#5eca4885a556e0330770a5532495f2e1d9840f79" dependencies = [ "blake2b-rs", "borsh", @@ -6158,9 +6158,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.0", "proc-macro2", From ebcb1a02295b2c145a5a788dd651468baad33f00 Mon Sep 17 00:00:00 2001 From: R2D2 Date: Mon, 8 Aug 2022 16:50:54 +0200 Subject: [PATCH 231/373] [feat]: Change the ibc-leaf-spec to be the same for both existence and non-existence proofs --- Cargo.lock | 12 ++--- shared/src/ledger/storage/merkle_tree.rs | 65 +++++++++++++++--------- shared/src/types/hash.rs | 9 ++-- shared/src/types/storage.rs | 49 +++++++++++++++--- 4 files changed, 92 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 60aca9972f..ed1e3ad29c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,9 +261,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-channel" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "4b31b87a3367ed04dbcbc252bce3f2a8172fef861d47177524c503c908dff2c6" dependencies = [ "concurrent-queue", "event-listener", @@ -3800,7 +3800,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ - "proc-macro-crate 1.2.0", + "proc-macro-crate 1.2.1", "proc-macro-error", "proc-macro2", "quote", @@ -4809,9 +4809,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d50bfb8c23f23915855a00d98b5a35ef2e0b871bb52937bacadb798fbb66c8" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ "once_cell", "thiserror", @@ -6095,7 +6095,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#5eca4885a556e0330770a5532495f2e1d9840f79" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#b03d8df11a6a0cfbd47a1a33cda5b73353bb715d" dependencies = [ "blake2b-rs", "borsh", diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index dacb978d17..02cfa560a0 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use arse_merkle_tree::default_store::DefaultStore; use arse_merkle_tree::error::Error as MtError; -use arse_merkle_tree::traits::Hasher; +use arse_merkle_tree::traits::{Hasher, Value}; use arse_merkle_tree::{ Hash as SmtHash, Key as TreeKey, SparseMerkleTree as ArseMerkleTree, H256, }; @@ -28,7 +28,7 @@ use crate::ledger::storage::types; use crate::types::address::{Address, InternalAddress}; use crate::types::hash::Hash; use crate::types::storage::{ - DbKeySeg, Error as StorageError, Key, MerkleKey, StringKey, + DbKeySeg, Error as StorageError, Key, MerkleKey, StringKey, TreeBytes, }; #[allow(missing_docs)] @@ -53,9 +53,10 @@ type Result = std::result::Result; /// Type aliases for the different merkle trees and backing stores pub type SmtStore = DefaultStore; -pub type AmtStore = DefaultStore; +pub type AmtStore = DefaultStore; pub type Smt = ArseMerkleTree; -pub type Amt = ArseMerkleTree; +pub type Amt = + ArseMerkleTree; /// Store types for the merkle tree #[derive( @@ -265,20 +266,20 @@ impl MerkleTree { &mut self, store_type: &StoreType, key: MerkleKey, - value: Hash, + value: Either, ) -> Result<()> { let sub_root = match store_type { StoreType::Account => self .account - .update(key.try_into()?, value) + .update(key.try_into()?, value.unwrap_left()) .map_err(Error::MerkleTree)?, StoreType::Ibc => self .ibc - .update(key.try_into()?, value) + .update(key.try_into()?, value.unwrap_right()) .map_err(Error::MerkleTree)?, StoreType::PoS => self .pos - .update(key.try_into()?, value) + .update(key.try_into()?, value.unwrap_left()) .map_err(Error::MerkleTree)?, // base tree should not be directly updated StoreType::Base => unreachable!(), @@ -297,29 +298,39 @@ impl MerkleTree { let (store_type, sub_key) = StoreType::sub_key(key)?; let value = match self.tree(&store_type) { Either::Left(smt) => { - smt.get(&H::hash(sub_key.to_string()).into())? + smt.get(&H::hash(sub_key.to_string()).into())?.is_zero() } Either::Right(amt) => { let key = StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; - amt.get(&key)? + amt.get(&key)?.is_zero() } }; - Ok(!value.is_zero()) + Ok(!value) } /// Update the tree with the given key and value pub fn update(&mut self, key: &Key, value: impl AsRef<[u8]>) -> Result<()> { let sub_key = StoreType::sub_key(key)?; let store_type = sub_key.0; - self.update_tree(&store_type, sub_key.into(), H::hash(value).into()) + let value = match store_type { + StoreType::Ibc => { + Either::Right(TreeBytes::from(value.as_ref().to_vec())) + } + _ => Either::Left(H::hash(value).into()), + }; + self.update_tree(&store_type, sub_key.into(), value) } /// Delete the value corresponding to the given key pub fn delete(&mut self, key: &Key) -> Result<()> { let sub_key = StoreType::sub_key(key)?; let store_type = sub_key.0; - self.update_tree(&store_type, sub_key.into(), H256::zero().into()) + let value = match store_type { + StoreType::Ibc => Either::Right(TreeBytes::zero()), + _ => Either::Left(H256::zero().into()), + }; + self.update_tree(&store_type, sub_key.into(), value) } /// Get the root @@ -353,7 +364,6 @@ impl MerkleTree { Ics23Proof::Exist(ep) => CommitmentProof { proof: Some(Ics23Proof::Exist(ExistenceProof { key: sub_key.to_string().as_bytes().to_vec(), - value, leaf: Some(self.leaf_spec()), ..ep })), @@ -394,8 +404,6 @@ impl MerkleTree { let key = StringKey::try_from_bytes(sub_key.to_string().as_bytes())?; let mut nep = amt.non_membership_proof(&key)?; - let mut spec = self.ibc_leaf_spec(); - spec.prehash_value = HashOp::NoHash.into(); // Replace the values and the leaf op for the verification if let Some(ref mut nep) = nep.proof { match nep { @@ -408,7 +416,7 @@ impl MerkleTree { let ep = left.as_mut().or(right.as_mut()).expect( "A left or right existence proof should exist.", ); - ep.leaf = Some(spec); + ep.leaf = Some(self.ibc_leaf_spec()); } _ => unreachable!(), } @@ -531,7 +539,7 @@ impl MerkleTree { LeafOp { hash: H::hash_op().into(), prehash_key: HashOp::NoHash.into(), - prehash_value: H::hash_op().into(), + prehash_value: HashOp::NoHash.into(), length: LengthOp::NoPrefix.into(), prefix: H256::zero().as_slice().to_vec(), } @@ -563,9 +571,9 @@ impl fmt::Display for MerkleRoot { impl From<(StoreType, Key)> for MerkleKey { fn from((store, key): (StoreType, Key)) -> Self { match store { - StoreType::Base => MerkleKey::Sha256(key, PhantomData), - StoreType::Account => MerkleKey::Sha256(key, PhantomData), - StoreType::PoS => MerkleKey::Sha256(key, PhantomData), + StoreType::Base | StoreType::Account | StoreType::PoS => { + MerkleKey::Sha256(key, PhantomData) + } StoreType::Ibc => MerkleKey::Raw(key), } } @@ -689,6 +697,16 @@ impl TreeKey for StringKey { } } +impl Value for TreeBytes { + fn as_slice(&self) -> &[u8] { + self.0.as_slice() + } + + fn zero() -> Self { + TreeBytes::zero() + } +} + /// The storage hasher used for the merkle tree. pub trait StorageHasher: Hasher + Default { /// Hash the value to store @@ -941,14 +959,11 @@ mod test { }; let (store_type, sub_key) = StoreType::sub_key(&ibc_non_key).expect("Test failed"); - let specs = tree.ibc_proof_specs(); - let mut spec = specs[0].clone(); - spec.leaf_spec.as_mut().unwrap().prehash_value = HashOp::NoHash.into(); let nep_verification_res = ics23::verify_non_membership( &nep_commitment_proof, - &spec, + &specs[0], &subtree_root, sub_key.to_string().as_bytes(), ); diff --git a/shared/src/types/hash.rs b/shared/src/types/hash.rs index 7a20a3f179..0e960ec01f 100644 --- a/shared/src/types/hash.rs +++ b/shared/src/types/hash.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Display}; use std::ops::Deref; use arse_merkle_tree::traits::Value; -use arse_merkle_tree::{H256, Hash as TreeHash}; +use arse_merkle_tree::{Hash as TreeHash, H256}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -125,7 +125,7 @@ impl From<&H256> for Hash { impl From for H256 { fn from(hash: Hash) -> H256 { - hash.to_h256() + hash.0.into() } } @@ -136,12 +136,11 @@ impl From for TreeHash { } impl Value for Hash { - fn to_h256(&self) -> H256 { - self.0.into() + fn as_slice(&self) -> &[u8] { + self.0.as_slice() } fn zero() -> Self { Hash([0u8; 32]) } } - diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 547cf8a9ef..419c2408ae 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -237,9 +237,7 @@ pub enum MerkleKey { } /// Storage keys that are utf8 encoded strings -#[derive( - Eq, PartialEq, Copy, Clone, Hash, -)] +#[derive(Eq, PartialEq, Copy, Clone, Hash)] pub struct StringKey { /// The original key string, in bytes pub original: [u8; IBC_KEY_LIMIT], @@ -268,10 +266,18 @@ impl BorshSerialize for StringKey { impl BorshDeserialize for StringKey { fn deserialize(buf: &mut &[u8]) -> std::io::Result { use std::io::ErrorKind; - let (original, tree_key, length): (Vec, InternalKey, usize) = BorshDeserialize::deserialize(buf)?; - let original: [u8; IBC_KEY_LIMIT] = original.try_into().map_err(|_| { - std::io::Error::new(ErrorKind::InvalidData, "Input byte vector is too large") - })?; + let (original, tree_key, length): ( + Vec, + InternalKey, + usize, + ) = BorshDeserialize::deserialize(buf)?; + let original: [u8; IBC_KEY_LIMIT] = + original.try_into().map_err(|_| { + std::io::Error::new( + ErrorKind::InvalidData, + "Input byte vector is too large", + ) + })?; Ok(Self { original, tree_key, @@ -280,6 +286,35 @@ impl BorshDeserialize for StringKey { } } +/// A wrapper around raw bytes to be stored as values +/// in a merkle tree +#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct TreeBytes(pub Vec); + +impl TreeBytes { + /// The value indicating that a leaf should be deleted + pub fn zero() -> Self { + Self(vec![]) + } + + /// Check if an instance is the zero value + pub fn is_zero(&self) -> bool { + self.0.is_empty() + } +} + +impl From> for TreeBytes { + fn from(bytes: Vec) -> Self { + Self(bytes) + } +} + +impl From for Vec { + fn from(bytes: TreeBytes) -> Self { + bytes.0 + } +} + impl Key { /// Parses string and returns a key pub fn parse(string: impl AsRef) -> Result { From d92aec650d1e52498b8062993865730f322fd57f Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 9 Aug 2022 19:54:06 +0200 Subject: [PATCH 232/373] [fix]: Updated Cargo.lock files --- Cargo.lock | 4 +- wasm/tx_template/Cargo.lock | 696 +++++++++++++++----------- wasm/vp_template/Cargo.lock | 696 +++++++++++++++----------- wasm/wasm_source/Cargo.lock | 4 +- wasm_for_tests/wasm_source/Cargo.lock | 6 +- 5 files changed, 789 insertions(+), 617 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed1e3ad29c..58b4796eae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,9 +83,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.60" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c794e162a5eff65c72ef524dfe393eb923c354e350bb78b9c7383df13f3bc142" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" [[package]] name = "ark-bls12-381" diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index bf9d222920..98a2bbf6ad 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -8,7 +8,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli 0.26.2", ] [[package]] @@ -37,11 +37,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" -version = "1.0.55" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" [[package]] name = "ark-bls12-381" @@ -154,9 +163,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -164,9 +173,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", @@ -175,9 +184,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -192,16 +201,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.27.1", + "object 0.29.0", "rustc-demangle", ] @@ -219,9 +228,9 @@ checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" [[package]] name = "bit-set" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] @@ -240,9 +249,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake3" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", "arrayvec", @@ -264,9 +273,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ "generic-array", ] @@ -283,7 +292,7 @@ version = "0.9.4" source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" dependencies = [ "borsh-derive", - "hashbrown", + "hashbrown 0.11.2", ] [[package]] @@ -320,15 +329,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "bytecheck" -version = "0.6.7" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -336,9 +345,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.7" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", @@ -353,9 +362,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" @@ -377,14 +386,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -409,11 +420,27 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] @@ -489,9 +516,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -499,9 +526,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -510,32 +537,33 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "lazy_static", "memoffset", + "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if 1.0.0", - "lazy_static", + "once_cell", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -543,9 +571,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" dependencies = [ "byteorder", "digest 0.9.0", @@ -569,9 +597,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -579,23 +607,22 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", @@ -639,16 +666,16 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.0", + "block-buffer 0.10.2", "crypto-common", "subtle", ] [[package]] name = "dynasm" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", @@ -661,9 +688,9 @@ dependencies = [ [[package]] name = "dynasmrt" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" dependencies = [ "byteorder", "dynasm", @@ -672,18 +699,18 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.4.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ "signature", ] [[package]] name = "ed25519-consensus" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542217a53411d471743362251a5a1770a667cb0cc0384c9be2c0952bd70a7275" +checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" dependencies = [ "curve25519-dalek-ng", "hex", @@ -708,9 +735,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "enum-iterator" @@ -734,18 +761,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" +checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" dependencies = [ "darling", "proc-macro2", @@ -755,9 +782,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9289ed2c0440a6536e65119725cf91fc2c6b5e513bfd2e36e1134d7cca6ca12f" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ "indenter", "once_cell", @@ -771,9 +798,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -793,9 +820,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flex-error" @@ -886,9 +913,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -896,13 +923,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -918,24 +945,24 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "gumdrop" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" dependencies = [ "gumdrop_derive", ] [[package]] name = "gumdrop_derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", @@ -944,9 +971,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ "bytes", "fnv", @@ -957,7 +984,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.3", "tracing", ] @@ -970,6 +997,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.3.3" @@ -996,9 +1032,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", @@ -1007,9 +1043,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -1018,9 +1054,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" @@ -1030,9 +1066,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.17" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ "bytes", "futures-channel", @@ -1064,6 +1100,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "iana-time-zone" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1779539f58004e5dba1c1f093d44325ebeb244bfc04b791acdc0aaeca9c04570" +dependencies = [ + "android_system_properties", + "core-foundation", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.12.0" @@ -1087,7 +1136,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.7", + "time 0.3.13", "tracing", ] @@ -1145,12 +1194,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] @@ -1174,24 +1223,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" [[package]] name = "lazy_static" @@ -1207,15 +1256,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" [[package]] name = "libloading" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -1223,9 +1272,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] @@ -1277,15 +1326,15 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" dependencies = [ "libc", ] @@ -1307,34 +1356,23 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", - "autocfg", ] [[package]] name = "mio" -version = "0.8.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", ] [[package]] @@ -1449,15 +1487,6 @@ dependencies = [ "namada_macros", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -1482,9 +1511,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1492,9 +1521,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] @@ -1511,39 +1540,39 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] [[package]] name = "object" -version = "0.27.1" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ + "crc32fast", + "hashbrown 0.11.2", + "indexmap", "memchr", ] [[package]] name = "object" -version = "0.28.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ - "crc32fast", - "hashbrown", - "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "opaque-debug" @@ -1559,9 +1588,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "paste" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" [[package]] name = "peg" @@ -1598,18 +1627,19 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", "indexmap", @@ -1617,18 +1647,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" dependencies = [ "proc-macro2", "quote", @@ -1637,9 +1667,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -1688,11 +1718,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1812,9 +1842,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.15" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -1866,9 +1896,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", @@ -1878,22 +1908,21 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -1911,9 +1940,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -1931,9 +1960,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -1978,12 +2007,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.29" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a37de5dfc60bae2d94961dacd03c7b80e426b66a99fa1b17799570dbdd8f96" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -1992,9 +2021,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.29" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719d447dd0e84b23cee6cb5b32d97e21efb112a3e3c636c8da36647b938475a1" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -2003,9 +2032,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.22.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", "num-traits", @@ -2035,9 +2064,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rusty-fork" @@ -2053,9 +2082,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-proc-macro2" @@ -2145,27 +2174,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" dependencies = [ "proc-macro2", "quote", @@ -2174,9 +2203,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ "itoa", "ryu", @@ -2185,9 +2214,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", @@ -2253,15 +2282,18 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "socket2" @@ -2282,7 +2314,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=yuji/prost-0.9#be4b2293558361df2f452c60a3e90c6b5e52e225" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#b03d8df11a6a0cfbd47a1a33cda5b73353bb715d" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2296,12 +2328,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "subtle" version = "2.4.1" @@ -2325,13 +2351,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2348,9 +2374,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" @@ -2390,7 +2416,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.7", + "time 0.3.13", "zeroize", ] @@ -2417,7 +2443,7 @@ dependencies = [ "serde", "tendermint", "tendermint-rpc", - "time 0.3.7", + "time 0.3.13", ] [[package]] @@ -2434,7 +2460,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time 0.3.13", ] [[package]] @@ -2455,7 +2481,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.7", + "time 0.3.13", "url", "uuid", "walkdir", @@ -2473,14 +2499,14 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.7", + "time 0.3.13", ] [[package]] name = "test-log" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb78caec569a40f42c078c798c0e35b922d9054ec28e166f0d6ac447563d91a4" +checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", @@ -2489,18 +2515,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -2523,15 +2549,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "time" -version = "0.3.7" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" dependencies = [ "libc", "num_threads", @@ -2540,15 +2566,15 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -2561,14 +2587,16 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", + "once_cell", "pin-project-lite", "socket2", "tokio-macros", @@ -2587,9 +2615,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -2598,9 +2626,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", "pin-project-lite", @@ -2609,9 +2637,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ "bytes", "futures-core", @@ -2623,23 +2651,23 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -2667,7 +2695,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2689,9 +2717,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", @@ -2701,7 +2729,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.0", + "tokio-util 0.7.3", "tower-layer", "tower-service", "tracing", @@ -2715,15 +2743,15 @@ checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -2734,9 +2762,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -2745,12 +2773,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ - "lazy_static", - "valuable", + "once_cell", ] [[package]] @@ -2765,12 +2792,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.9" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ - "lazy_static", "matchers", + "once_cell", "regex", "sharded-slab", "thread_local", @@ -2803,21 +2830,27 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] @@ -2836,9 +2869,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "url" @@ -2858,12 +2891,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "version_check" version = "0.9.4" @@ -2906,11 +2933,17 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2918,13 +2951,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -2933,9 +2966,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2943,9 +2976,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -2956,9 +2989,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "wasm-encoder" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "8905fd25fdadeb0e7e8bf43a9f46f9f972d6291ad0c7a32573b88dd13a6cfa6b" +dependencies = [ + "leb128", +] [[package]] name = "wasmer" @@ -3103,7 +3145,7 @@ dependencies = [ "leb128", "libloading", "loupe", - "object 0.28.3", + "object 0.28.4", "rkyv", "serde", "tempfile", @@ -3142,7 +3184,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object 0.28.3", + "object 0.28.4", "thiserror", "wasmer-compiler", "wasmer-types", @@ -3198,20 +3240,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "39.0.0" +version = "45.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" +checksum = "186c474c4f9bb92756b566d592a16591b4526b1a4841171caa3f31d7fe330d96" dependencies = [ "leb128", "memchr", "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.41" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" +checksum = "c2d4bc4724b4f02a482c8cab053dac5ef26410f264c06ce914958f9a42813556" dependencies = [ "wast", ] @@ -3230,9 +3273,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.4" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -3270,11 +3313,54 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "zeroize" -version = "1.5.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" dependencies = [ "zeroize_derive", ] diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 98f6c7f71c..a993a3ebe2 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -8,7 +8,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli 0.26.2", ] [[package]] @@ -37,11 +37,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" -version = "1.0.55" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" [[package]] name = "ark-bls12-381" @@ -154,9 +163,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -164,9 +173,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", @@ -175,9 +184,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.52" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -192,16 +201,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.27.1", + "object 0.29.0", "rustc-demangle", ] @@ -219,9 +228,9 @@ checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" [[package]] name = "bit-set" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] @@ -240,9 +249,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake3" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882e99e4a0cb2ae6cb6e442102e8e6b7131718d94110e64c3e6a34ea9b106f37" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", "arrayvec", @@ -264,9 +273,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ "generic-array", ] @@ -283,7 +292,7 @@ version = "0.9.4" source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c54cf8259b7ec5ec4073a#cd5223e5103c4f139e0c54cf8259b7ec5ec4073a" dependencies = [ "borsh-derive", - "hashbrown", + "hashbrown 0.11.2", ] [[package]] @@ -320,15 +329,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "bytecheck" -version = "0.6.7" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -336,9 +345,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.7" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", @@ -353,9 +362,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" @@ -377,14 +386,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -409,11 +420,27 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] @@ -489,9 +516,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -499,9 +526,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -510,32 +537,33 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "lazy_static", "memoffset", + "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if 1.0.0", - "lazy_static", + "once_cell", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", @@ -543,9 +571,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" dependencies = [ "byteorder", "digest 0.9.0", @@ -569,9 +597,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -579,23 +607,22 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.13.1" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", @@ -639,16 +666,16 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.0", + "block-buffer 0.10.2", "crypto-common", "subtle", ] [[package]] name = "dynasm" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b1801e630bd336d0bbbdbf814de6cc749c9a400c7e3d995e6adfd455d0c83c" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", @@ -661,9 +688,9 @@ dependencies = [ [[package]] name = "dynasmrt" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d428afc93ad288f6dffc1fa5f4a78201ad2eec33c5a522e51c181009eb09061" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" dependencies = [ "byteorder", "dynasm", @@ -672,18 +699,18 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.4.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed12bbf7b5312f8da1c2722bc06d8c6b12c2d86a7fb35a194c7f3e6fc2bbe39" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ "signature", ] [[package]] name = "ed25519-consensus" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542217a53411d471743362251a5a1770a667cb0cc0384c9be2c0952bd70a7275" +checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" dependencies = [ "curve25519-dalek-ng", "hex", @@ -708,9 +735,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "enum-iterator" @@ -734,18 +761,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" +checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" dependencies = [ "darling", "proc-macro2", @@ -755,9 +782,9 @@ dependencies = [ [[package]] name = "eyre" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9289ed2c0440a6536e65119725cf91fc2c6b5e513bfd2e36e1134d7cca6ca12f" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" dependencies = [ "indenter", "once_cell", @@ -771,9 +798,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -793,9 +820,9 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flex-error" @@ -886,9 +913,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -896,13 +923,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -918,24 +945,24 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "gumdrop" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" dependencies = [ "gumdrop_derive", ] [[package]] name = "gumdrop_derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", @@ -944,9 +971,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ "bytes", "fnv", @@ -957,7 +984,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.6.9", + "tokio-util 0.7.3", "tracing", ] @@ -970,6 +997,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.3.3" @@ -996,9 +1032,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", @@ -1007,9 +1043,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -1018,9 +1054,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" @@ -1030,9 +1066,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.17" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ "bytes", "futures-channel", @@ -1064,6 +1100,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "iana-time-zone" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1779539f58004e5dba1c1f093d44325ebeb244bfc04b791acdc0aaeca9c04570" +dependencies = [ + "android_system_properties", + "core-foundation", + "js-sys", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.12.0" @@ -1087,7 +1136,7 @@ dependencies = [ "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.7", + "time 0.3.13", "tracing", ] @@ -1145,12 +1194,12 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] @@ -1174,24 +1223,24 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "js-sys" -version = "0.3.56" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" [[package]] name = "lazy_static" @@ -1207,15 +1256,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" [[package]] name = "libloading" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe203d669ec979b7128619bae5a63b7b42e9203c1b29146079ee05e2f604b52" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -1223,9 +1272,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.14" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", ] @@ -1277,15 +1326,15 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" dependencies = [ "libc", ] @@ -1307,34 +1356,23 @@ checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", - "autocfg", ] [[package]] name = "mio" -version = "0.8.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", ] [[package]] @@ -1449,15 +1487,6 @@ dependencies = [ "sha2 0.10.2", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.3" @@ -1482,9 +1511,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1492,9 +1521,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] @@ -1511,39 +1540,39 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ "libc", ] [[package]] name = "object" -version = "0.27.1" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ + "crc32fast", + "hashbrown 0.11.2", + "indexmap", "memchr", ] [[package]] name = "object" -version = "0.28.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ - "crc32fast", - "hashbrown", - "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.9.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "opaque-debug" @@ -1559,9 +1588,9 @@ checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" [[package]] name = "paste" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" [[package]] name = "peg" @@ -1598,18 +1627,19 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", "indexmap", @@ -1617,18 +1647,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" dependencies = [ "proc-macro2", "quote", @@ -1637,9 +1667,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -1688,11 +1718,11 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -1812,9 +1842,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quote" -version = "1.0.15" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -1866,9 +1896,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.1" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", @@ -1878,22 +1908,21 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -1911,9 +1940,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -1931,9 +1960,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -1978,12 +2007,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.29" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a37de5dfc60bae2d94961dacd03c7b80e426b66a99fa1b17799570dbdd8f96" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -1992,9 +2021,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.29" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719d447dd0e84b23cee6cb5b32d97e21efb112a3e3c636c8da36647b938475a1" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -2003,9 +2032,9 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.22.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37baa70cf8662d2ba1c1868c5983dda16ef32b105cce41fb5c47e72936a90b3" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec", "num-traits", @@ -2035,9 +2064,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rusty-fork" @@ -2053,9 +2082,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-proc-macro2" @@ -2145,27 +2174,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.136" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" dependencies = [ "proc-macro2", "quote", @@ -2174,9 +2203,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ "itoa", "ryu", @@ -2185,9 +2214,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", @@ -2253,15 +2282,18 @@ checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" [[package]] name = "slab" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "socket2" @@ -2282,7 +2314,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=yuji/prost-0.9#be4b2293558361df2f452c60a3e90c6b5e52e225" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#b03d8df11a6a0cfbd47a1a33cda5b73353bb715d" dependencies = [ "borsh", "cfg-if 1.0.0", @@ -2296,12 +2328,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "subtle" version = "2.4.1" @@ -2325,13 +2351,13 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.86" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -2348,9 +2374,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.2" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bffcddbc2458fa3e6058414599e3c838a022abae82e5c67b4f7f80298d5bff" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" @@ -2390,7 +2416,7 @@ dependencies = [ "subtle", "subtle-encoding", "tendermint-proto", - "time 0.3.7", + "time 0.3.13", "zeroize", ] @@ -2417,7 +2443,7 @@ dependencies = [ "serde", "tendermint", "tendermint-rpc", - "time 0.3.7", + "time 0.3.13", ] [[package]] @@ -2434,7 +2460,7 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", - "time 0.3.7", + "time 0.3.13", ] [[package]] @@ -2455,7 +2481,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.7", + "time 0.3.13", "url", "uuid", "walkdir", @@ -2473,14 +2499,14 @@ dependencies = [ "simple-error", "tempfile", "tendermint", - "time 0.3.7", + "time 0.3.13", ] [[package]] name = "test-log" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb78caec569a40f42c078c798c0e35b922d9054ec28e166f0d6ac447563d91a4" +checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", @@ -2489,18 +2515,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -2523,15 +2549,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] [[package]] name = "time" -version = "0.3.7" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" dependencies = [ "libc", "num_threads", @@ -2540,15 +2566,15 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -2561,14 +2587,16 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", + "once_cell", "pin-project-lite", "socket2", "tokio-macros", @@ -2587,9 +2615,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -2598,9 +2626,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", "pin-project-lite", @@ -2609,9 +2637,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ "bytes", "futures-core", @@ -2623,23 +2651,23 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes", "futures-core", "futures-sink", - "log", "pin-project-lite", "tokio", + "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -2667,7 +2695,7 @@ dependencies = [ "prost-derive", "tokio", "tokio-stream", - "tokio-util 0.6.9", + "tokio-util 0.6.10", "tower", "tower-layer", "tower-service", @@ -2689,9 +2717,9 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", @@ -2701,7 +2729,7 @@ dependencies = [ "rand", "slab", "tokio", - "tokio-util 0.7.0", + "tokio-util 0.7.3", "tower-layer", "tower-service", "tracing", @@ -2715,15 +2743,15 @@ checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.31" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", @@ -2734,9 +2762,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -2745,12 +2773,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.22" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ - "lazy_static", - "valuable", + "once_cell", ] [[package]] @@ -2765,12 +2792,12 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.9" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ - "lazy_static", "matchers", + "once_cell", "regex", "sharded-slab", "thread_local", @@ -2792,21 +2819,27 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" [[package]] name = "unicode-bidi" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] @@ -2825,9 +2858,9 @@ checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "url" @@ -2847,12 +2880,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "version_check" version = "0.9.4" @@ -2906,11 +2933,17 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" -version = "0.2.79" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -2918,13 +2951,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.79" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -2933,9 +2966,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.79" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2943,9 +2976,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.79" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -2956,9 +2989,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.79" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "wasm-encoder" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" +checksum = "8905fd25fdadeb0e7e8bf43a9f46f9f972d6291ad0c7a32573b88dd13a6cfa6b" +dependencies = [ + "leb128", +] [[package]] name = "wasmer" @@ -3103,7 +3145,7 @@ dependencies = [ "leb128", "libloading", "loupe", - "object 0.28.3", + "object 0.28.4", "rkyv", "serde", "tempfile", @@ -3142,7 +3184,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object 0.28.3", + "object 0.28.4", "thiserror", "wasmer-compiler", "wasmer-types", @@ -3198,20 +3240,21 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "39.0.0" +version = "45.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" +checksum = "186c474c4f9bb92756b566d592a16591b4526b1a4841171caa3f31d7fe330d96" dependencies = [ "leb128", "memchr", "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.41" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" +checksum = "c2d4bc4724b4f02a482c8cab053dac5ef26410f264c06ce914958f9a42813556" dependencies = [ "wast", ] @@ -3230,9 +3273,9 @@ dependencies = [ [[package]] name = "which" -version = "4.2.4" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a5a7e487e921cf220206864a94a89b6c6905bfc19f1057fa26a4cb360e5c1d2" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ "either", "lazy_static", @@ -3270,11 +3313,54 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "zeroize" -version = "1.5.3" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50344758e2f40e3a1fcfc8f6f91aa57b5f8ebd8d27919fe6451f15aaaf9ee608" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" dependencies = [ "zeroize_derive", ] diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index e538da6e7d..8c6679dedf 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.55" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" [[package]] name = "ark-bls12-381" diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 39f70c1787..8261b7e790 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.56" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" [[package]] name = "ark-bls12-381" @@ -2314,7 +2314,7 @@ checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" [[package]] name = "sparse-merkle-tree" version = "0.3.1-pre" -source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=yuji/prost-0.9#be4b2293558361df2f452c60a3e90c6b5e52e225" +source = "git+https://github.com/heliaxdev/sparse-merkle-tree?branch=bat/arse-merkle-tree#b03d8df11a6a0cfbd47a1a33cda5b73353bb715d" dependencies = [ "borsh", "cfg-if 1.0.0", From 0b00cac5ba89a06dd9319effe3803e6ca9fdc55d Mon Sep 17 00:00:00 2001 From: R2D2 Date: Tue, 9 Aug 2022 20:21:48 +0200 Subject: [PATCH 233/373] [fix]: Fixed a small bug in existence proofs --- shared/src/ledger/storage/merkle_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/merkle_tree.rs b/shared/src/ledger/storage/merkle_tree.rs index 02cfa560a0..c7fec10126 100644 --- a/shared/src/ledger/storage/merkle_tree.rs +++ b/shared/src/ledger/storage/merkle_tree.rs @@ -364,6 +364,7 @@ impl MerkleTree { Ics23Proof::Exist(ep) => CommitmentProof { proof: Some(Ics23Proof::Exist(ExistenceProof { key: sub_key.to_string().as_bytes().to_vec(), + value, leaf: Some(self.leaf_spec()), ..ep })), @@ -381,7 +382,6 @@ impl MerkleTree { match cp.proof.expect("The proof should exist") { Ics23Proof::Exist(ep) => CommitmentProof { proof: Some(Ics23Proof::Exist(ExistenceProof { - value, leaf: Some(self.ibc_leaf_spec()), ..ep })), From de46fbe5833929f740ca80fb48149d78f3518539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 11 Aug 2022 14:31:34 +0200 Subject: [PATCH 234/373] update checksums wasm --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5d670022b7..b860b4b845 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.71acaf1ff3a4ac1ce6550c4ce594cf70d684213769f45641be5cdb4b7321184e.wasm", - "tx_from_intent.wasm": "tx_from_intent.e18907b1fb0e1c4e4946228c0ec57838a783a5d88c6b3129d44935386693cee8.wasm", - "tx_ibc.wasm": "tx_ibc.38ee97db63b765f1639ee24dd3e7f264139f146a56f7c06edafde63eaecedade.wasm", - "tx_init_account.wasm": "tx_init_account.6be05f8ca240d9e978dfe6a1566b3860e07fa3aee334003fc5d85a5a1ae99cf7.wasm", - "tx_init_nft.wasm": "tx_init_nft.5f710b641c38cbe7822c2359bf7a764e864e952f4660ccff9b58b97fbaa4b3a2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.25666fc8fbf251bd4ad478cb968f1d50a8100a0a7aae0e3ee5e50e28304e8e3e.wasm", - "tx_init_validator.wasm": "tx_init_validator.2b1a4c947242ddddf94a491b8c571185f649913606bfa7d37f668ddc86fe1049.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9af8eea9219db09161b98405c18ea3c82db03b982f69a66ce1d117e64a715c3c.wasm", - "tx_transfer.wasm": "tx_transfer.87e4fba9d2476388e0f2b1d4bc00c396ac7b8b7b38d5079735fe53bc5984b583.wasm", - "tx_unbond.wasm": "tx_unbond.61f2ac46e7680ec35aba367ec908ac63aa43d6765cd07da932aa9ea45d77be46.wasm", - "tx_update_vp.wasm": "tx_update_vp.00d828bdf510d92d971ca59015945c8c00f285a802a655451cc5ee87c5ee99bc.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.92ed628aca7856e2b0fe461c0a0a65c6f7c1f585ac542c10feceef0e4b9ce611.wasm", - "tx_withdraw.wasm": "tx_withdraw.fb54d108a6fe7c6db16e573110c4a9f52158ec993e04e5a06e1425b8dd676c54.wasm", - "vp_nft.wasm": "vp_nft.3421840146f8d2e6c3ce3a0d94bbb0dad6ef296c50e9d00744179d290f32745b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1b37ab6939280fbb54512918053362fae34e3db74f029ef31c9f08e767539afb.wasm", - "vp_token.wasm": "vp_token.08dcec2d3ecb0942d0a244bbd176f9a42424ec09544120db1b30c0d97ed7dbb1.wasm", - "vp_user.wasm": "vp_user.404f089f0c73fcf906f7d6059d74c6bab3e2e3d3a108394503bbc22588ff95b9.wasm" + "tx_bond.wasm": "tx_bond.37baeec509de65c2259e2cbca3228af86f9bc149c10cefb5f005a63756396ebe.wasm", + "tx_from_intent.wasm": "tx_from_intent.dcc35cf46210e2226e68bc4a009eafdf2b608b6a12f8094c1d5336af567c7e45.wasm", + "tx_ibc.wasm": "tx_ibc.b34af88366e63f3defa115bda62be08157874fa3c12880ebc4a90ab9cba3bfce.wasm", + "tx_init_account.wasm": "tx_init_account.3c239513ead338800a6cc15df131537bbd8d87d1b538a71d425182fdff785db1.wasm", + "tx_init_nft.wasm": "tx_init_nft.62a8022dff3e8290525f55b4203add3eea52108c93ba4e1af2ed12f39a1a4c81.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d4f3d31973504ddea787e9bea91bbd8f833eee47359efa3a379c58205ec9977f.wasm", + "tx_init_validator.wasm": "tx_init_validator.2b332ec1347a514f17892616f3ac3ecb1a9d5c541d1fdd60e424ff7120e5b0ba.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.95c3be44ff37fbfa394dc20eca1bad828870412600f60b161b396e76b97088ec.wasm", + "tx_transfer.wasm": "tx_transfer.1f5b4841aa0605cd0f313e446741bebe6b7a2cad69da885a324e9cd161196646.wasm", + "tx_unbond.wasm": "tx_unbond.19f094dfac76896b525c317415cd09b58f0830f9aad2c4e73ddaa8f45bdba6eb.wasm", + "tx_update_vp.wasm": "tx_update_vp.7d669bbb7667cbf7a746cc384fc8e4b12506bcec8c00f70ca0e8599bb0a5deff.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.546a52b12c8c479e8226648ab765f0b19fd8fc708c805e1e087fd0d3d5be2733.wasm", + "tx_withdraw.wasm": "tx_withdraw.5024116e75fd5ddc3f8ede41f14344c5d4ed6f02e5789aff0b17bce03a4112e6.wasm", + "vp_nft.wasm": "vp_nft.4f2fe686e158c3086256377c0cac468ffa2b761e1cb1f2db17d230f79d545c90.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.0ac2db6f608ef1be9fdf6eb9e063f222cf86d802a02caeffd2b74ebf837252cf.wasm", + "vp_token.wasm": "vp_token.8dec11893c01ffd43a74f3ba8228ac9a9f8c1af908bf1d9a528d1cab9d3c76f0.wasm", + "vp_user.wasm": "vp_user.1413afa390753e2ae5f6470b5314f037884729bc6e31b61fe4519b198dd23a9c.wasm" } \ No newline at end of file From b6b64911605c0780ac6a00ac6865ae0a6829bfb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 19 Aug 2022 10:58:58 +0200 Subject: [PATCH 235/373] apps/dev-deps: remove unused cargo-watch --- Cargo.lock | 366 ++---------------------------------------------- apps/Cargo.toml | 1 - 2 files changed, 8 insertions(+), 359 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58b4796eae..9ef4b83247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -855,26 +855,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" -[[package]] -name = "camino" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" - -[[package]] -name = "cargo-watch" -version = "7.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fee6e0b2e10066aee5060c0dc74826f9a6447987fc535ca7719ae8883abd8e" -dependencies = [ - "camino", - "clap 2.34.0", - "log 0.4.17", - "shell-escape", - "stderrlog", - "watchexec", -] - [[package]] name = "cc" version = "1.0.73" @@ -981,21 +961,6 @@ dependencies = [ "libloading", ] -[[package]] -name = "clap" -version = "2.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "ansi_term", - "atty", - "bitflags", - "strsim 0.8.0", - "textwrap 0.11.0", - "unicode-width", - "vec_map", -] - [[package]] name = "clap" version = "3.0.0-beta.2" @@ -1006,26 +971,13 @@ dependencies = [ "indexmap", "lazy_static", "os_str_bytes", - "strsim 0.10.0", + "strsim", "termcolor", - "textwrap 0.12.1", + "textwrap", "unicode-width", "vec_map", ] -[[package]] -name = "clearscreen" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c969a6b6dadff9f3349b1f783f553e2411104763ca4789e1c6ca6a41f46a57b0" -dependencies = [ - "nix 0.24.2", - "terminfo", - "thiserror", - "which", - "winapi 0.3.9", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -1067,16 +1019,6 @@ dependencies = [ "tracing-error", ] -[[package]] -name = "command-group" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7a8a86f409b4a59df3a3e4bee2de0b83f1755fdd2a25e3a9684c396fc4bed2c" -dependencies = [ - "nix 0.22.3", - "winapi 0.3.9", -] - [[package]] name = "concat-idents" version = "1.1.3" @@ -1420,38 +1362,14 @@ dependencies = [ "zeroize", ] -[[package]] -name = "darling" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" -dependencies = [ - "darling_core 0.12.4", - "darling_macro 0.12.4", -] - [[package]] name = "darling" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", -] - -[[package]] -name = "darling_core" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn", + "darling_core", + "darling_macro", ] [[package]] @@ -1467,24 +1385,13 @@ dependencies = [ "syn", ] -[[package]] -name = "darling_macro" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" -dependencies = [ - "darling_core 0.12.4", - "quote", - "syn", -] - [[package]] name = "darling_macro" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core 0.13.4", + "darling_core", "quote", "syn", ] @@ -1506,37 +1413,6 @@ dependencies = [ "syn", ] -[[package]] -name = "derive_builder" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5" -dependencies = [ - "darling 0.12.4", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_builder_macro" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73" -dependencies = [ - "derive_builder_core", - "syn", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -1598,16 +1474,6 @@ dependencies = [ "dirs-sys", ] -[[package]] -name = "dirs" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" -dependencies = [ - "cfg-if 0.1.10", - "dirs-sys", -] - [[package]] name = "dirs-sys" version = "0.3.7" @@ -1770,7 +1636,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" dependencies = [ - "darling 0.13.4", + "darling", "proc-macro2", "quote", "syn", @@ -1992,25 +1858,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" -[[package]] -name = "fsevent" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6" -dependencies = [ - "bitflags", - "fsevent-sys", -] - -[[package]] -name = "fsevent-sys" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0" -dependencies = [ - "libc", -] - [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -2250,19 +2097,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" -[[package]] -name = "globset" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" -dependencies = [ - "aho-corasick", - "bstr", - "fnv", - "log 0.4.17", - "regex", -] - [[package]] name = "gloo-timers" version = "0.2.4" @@ -2736,26 +2570,6 @@ dependencies = [ "serde 1.0.142", ] -[[package]] -name = "inotify" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" -dependencies = [ - "bitflags", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - [[package]] name = "input_buffer" version = "0.4.0" @@ -3727,18 +3541,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "mio-extras" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" -dependencies = [ - "lazycell", - "log 0.4.17", - "mio 0.6.23", - "slab", -] - [[package]] name = "miow" version = "0.2.2" @@ -3897,8 +3699,7 @@ dependencies = [ "borsh", "byte-unit", "byteorder", - "cargo-watch", - "clap 3.0.0-beta.2", + "clap", "color-eyre", "config", "curl", @@ -4097,19 +3898,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "nix" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" -dependencies = [ - "bitflags", - "cc", - "cfg-if 1.0.0", - "libc", - "memoffset", -] - [[package]] name = "nix" version = "0.23.1" @@ -4161,24 +3949,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "notify" -version = "4.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257" -dependencies = [ - "bitflags", - "filetime", - "fsevent", - "fsevent-sys", - "inotify", - "libc", - "mio 0.6.23", - "mio-extras", - "walkdir", - "winapi 0.3.9", -] - [[package]] name = "ntapi" version = "0.3.7" @@ -4615,44 +4385,6 @@ dependencies = [ "indexmap", ] -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared", - "rand 0.7.3", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" version = "0.4.30" @@ -5060,7 +4792,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg 0.1.2", + "rand_pcg", "rand_xorshift 0.1.1", "winapi 0.3.9", ] @@ -5076,7 +4808,6 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", - "rand_pcg 0.2.1", ] [[package]] @@ -5215,15 +4946,6 @@ dependencies = [ "rand_core 0.4.2", ] -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rand_xorshift" version = "0.1.1" @@ -5958,12 +5680,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "shell-escape" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" - [[package]] name = "shlex" version = "1.1.0" @@ -6001,12 +5717,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" -[[package]] -name = "siphasher" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" - [[package]] name = "slab" version = "0.4.7" @@ -6122,25 +5832,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stderrlog" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af95cb8a5f79db5b2af2a46f44da7594b5adbcbb65cbf87b8da0959bfdd82460" -dependencies = [ - "atty", - "chrono", - "log 0.4.17", - "termcolor", - "thread_local", -] - -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.10.0" @@ -6405,19 +6096,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminfo" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e" -dependencies = [ - "dirs", - "fnv", - "nom 5.1.2", - "phf", - "phf_codegen", -] - [[package]] name = "termtree" version = "0.2.4" @@ -6435,15 +6113,6 @@ dependencies = [ "syn", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "textwrap" version = "0.12.1" @@ -7704,25 +7373,6 @@ dependencies = [ "wast", ] -[[package]] -name = "watchexec" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52e0868bc57765fa91593a173323f464855e53b27f779e1110ba0fb4cb6b406" -dependencies = [ - "clearscreen", - "command-group", - "derive_builder", - "glob", - "globset", - "lazy_static", - "log 0.4.17", - "nix 0.22.3", - "notify", - "walkdir", - "winapi 0.3.9", -] - [[package]] name = "web-sys" version = "0.3.59" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 95f2ab5ed9..629cb601d8 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -125,7 +125,6 @@ winapi = "0.3.9" [dev-dependencies] namada = {path = "../shared", features = ["testing", "wasm-runtime"]} -cargo-watch = "7.5.0" bit-set = "0.5.2" # A fork with state machime testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} From 8a9fc4c2e2db714cc3e1849bccf0cf84e09f6325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 19 Aug 2022 12:07:58 +0200 Subject: [PATCH 236/373] changelog: add #279 --- .changelog/unreleased/bug-fixes/279-new-merkle-tree.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/279-new-merkle-tree.md diff --git a/.changelog/unreleased/bug-fixes/279-new-merkle-tree.md b/.changelog/unreleased/bug-fixes/279-new-merkle-tree.md new file mode 100644 index 0000000000..e9c2b7c688 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/279-new-merkle-tree.md @@ -0,0 +1,3 @@ +- Switch to a alternative sparse merkle tree implementation for IBC sub-tree + to be able to support proofs compatible with the current version of ICS23 + ([#279](https://github.com/anoma/namada/pull/279)) \ No newline at end of file From 7a708c5b605c7e287b809e40f5d76b1ae23ebb8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sat, 20 Aug 2022 14:30:32 +0200 Subject: [PATCH 237/373] join-network: allow to skip pre-fetching wasm --- apps/src/lib/cli.rs | 7 +++++++ apps/src/lib/client/utils.rs | 5 ++++- tests/src/e2e/ledger_tests.rs | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index ec716a1385..de919297ba 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1440,6 +1440,7 @@ pub mod args { const FEE_TOKEN: ArgDefaultFromCtx = arg_default_from_ctx("fee-token", DefaultFn(|| "XAN".into())); const FORCE: ArgFlag = flag("force"); + const DONT_PREFETCH_WASM: ArgFlag = flag("dont-prefetch-wasm"); const GAS_LIMIT: ArgDefault = arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); const GENESIS_PATH: Arg = arg("genesis-path"); @@ -2927,6 +2928,7 @@ pub mod args { pub chain_id: ChainId, pub genesis_validator: Option, pub pre_genesis_path: Option, + pub dont_prefetch_wasm: bool, } impl Args for JoinNetwork { @@ -2934,10 +2936,12 @@ pub mod args { let chain_id = CHAIN_ID.parse(matches); let genesis_validator = GENESIS_VALIDATOR.parse(matches); let pre_genesis_path = PRE_GENESIS_PATH.parse(matches); + let dont_prefetch_wasm = DONT_PREFETCH_WASM.parse(matches); Self { chain_id, genesis_validator, pre_genesis_path, + dont_prefetch_wasm, } } @@ -2945,6 +2949,9 @@ pub mod args { app.arg(CHAIN_ID.def().about("The chain ID. The chain must be known in the https://github.com/heliaxdev/anoma-network-config repository.")) .arg(GENESIS_VALIDATOR.def().about("The alias of the genesis validator that you want to set up as, if any.")) .arg(PRE_GENESIS_PATH.def().about("The path to the pre-genesis directory for genesis validator, if any. Defaults to \"{base-dir}/pre-genesis/{genesis-validator}\".")) + .arg(DONT_PREFETCH_WASM.def().about( + "Do not pre-fetch WASM.", + )) } } diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 697387fd15..c1bf239e6a 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -53,6 +53,7 @@ pub async fn join_network( chain_id, genesis_validator, pre_genesis_path, + dont_prefetch_wasm, }: args::JoinNetwork, ) { use tokio::fs; @@ -343,7 +344,9 @@ pub async fn join_network( .await .unwrap(); } - fetch_wasms_aux(&base_dir, &chain_id).await; + if !dont_prefetch_wasm { + fetch_wasms_aux(&base_dir, &chain_id).await; + } println!("Successfully configured for chain ID {}", chain_id); } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 552e675e67..78dc58616d 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1726,6 +1726,7 @@ fn test_genesis_validators() -> Result<()> { chain_id.as_str(), "--pre-genesis-path", pre_genesis_path.as_ref(), + "--dont-prefetch-wasm", ], Some(5) )?; @@ -1743,6 +1744,7 @@ fn test_genesis_validators() -> Result<()> { chain_id.as_str(), "--pre-genesis-path", pre_genesis_path.as_ref(), + "--dont-prefetch-wasm", ], Some(5) )?; From 08ae9ca862719d95ecf70407ee6959cd6499990c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 6 Jul 2022 12:25:57 +0200 Subject: [PATCH 238/373] pos/validation: refactor accumulation of changes --- proof_of_stake/src/validation.rs | 2579 +++++++++++++++++------------- 1 file changed, 1480 insertions(+), 1099 deletions(-) diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 58c6b0c26c..38fc3cc20a 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::fmt::{Debug, Display}; use std::hash::Hash; +use std::marker::PhantomData; use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -14,7 +15,7 @@ use crate::btree_set::BTreeSetShims; use crate::epoched::DynEpochOffset; use crate::parameters::PosParams; use crate::types::{ - BondId, Bonds, Epoch, Slashes, TotalVotingPowers, Unbonds, + BondId, Bonds, Epoch, Slash, Slashes, TotalVotingPowers, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorState, ValidatorStates, ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, WeightedValidator, @@ -313,6 +314,16 @@ pub struct NewValidator { voting_power: VotingPower, } +/// Validation constants +#[derive(Clone, Debug)] +struct Constants { + current_epoch: Epoch, + pipeline_epoch: Epoch, + unbonding_epoch: Epoch, + pipeline_offset: u64, + unbonding_offset: u64, +} + /// Validate the given list of PoS data `changes`. Returns empty list, if all /// the changes are valid. #[must_use] @@ -375,1031 +386,186 @@ where + BorshSchema + PartialEq, { - let current_epoch = current_epoch.into(); - use DataUpdate::*; - use ValidatorUpdate::*; - + let current_epoch: Epoch = current_epoch.into(); let pipeline_offset = DynEpochOffset::PipelineLen.value(params); let unbonding_offset = DynEpochOffset::UnbondingLen.value(params); let pipeline_epoch = current_epoch + pipeline_offset; let unbonding_epoch = current_epoch + unbonding_offset; + let constants = Constants { + current_epoch, + pipeline_epoch, + unbonding_epoch, + pipeline_offset, + unbonding_offset, + }; let mut errors = vec![]; - let mut balance_delta = TokenChange::default(); - // Changes of validators' bonds - let mut bond_delta: HashMap = HashMap::default(); - // Changes of validators' unbonds - let mut unbond_delta: HashMap = HashMap::default(); - - // Changes of all validator total deltas (up to `unbonding_epoch`) - let mut total_deltas: HashMap = HashMap::default(); - // Accumulative stake calculated from validator total deltas for each epoch - // in which it has changed (the tuple of values are in pre and post state) - let mut total_stake_by_epoch: HashMap< - Epoch, - HashMap, - > = HashMap::default(); - // Total voting power delta calculated from validators' total deltas - let mut expected_total_voting_power_delta_by_epoch: HashMap< - Epoch, - VotingPowerDelta, - > = HashMap::default(); - // Changes of validators' voting power data - let mut voting_power_by_epoch: HashMap< - Epoch, - HashMap, - > = HashMap::default(); - - let mut validator_set_pre: Option> = None; - let mut validator_set_post: Option> = None; - - let mut total_voting_power_delta_by_epoch: HashMap< - Epoch, - VotingPowerDelta, - > = HashMap::default(); - - let mut new_validators: HashMap = HashMap::default(); - - for change in changes { - match change { - Validator { address, update } => match update { - State(data) => match (data.pre, data.post) { - (None, Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - // Before pipeline epoch, the state must be `Pending` - for epoch in - Epoch::iter_range(current_epoch, pipeline_offset) - { - match post.get(epoch) { - Some(ValidatorState::Pending) => {} - _ => errors.push( - Error::InvalidNewValidatorState( - epoch.into(), - ), - ), - } - } - // At pipeline epoch, the state must be `Candidate` - match post.get(pipeline_epoch) { - Some(ValidatorState::Candidate) => {} - _ => errors.push(Error::InvalidNewValidatorState( - pipeline_epoch.into(), - )), - } - let validator = - new_validators.entry(address.clone()).or_default(); - validator.has_state = true; - } - (Some(pre), Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - use ValidatorState::*; - // Before pipeline epoch, the only allowed state change - // is from `Inactive` to `Pending` - for epoch in - Epoch::iter_range(current_epoch, pipeline_offset) - { - match (pre.get(epoch), post.get(epoch)) { - (Some(Inactive), Some(Pending)) => {} - (Some(state_pre), Some(state_post)) - if state_pre == state_post => {} - _ => errors.push( - Error::InvalidValidatorStateUpdate( - epoch.into(), - ), - ), - } - } - // Check allowed state changes at pipeline epoch - match ( - pre.get(pipeline_epoch), - post.get(pipeline_epoch), - ) { - ( - Some(Pending), - Some(Candidate) | Some(Inactive), - ) - | (Some(Candidate), Some(Inactive)) - | ( - Some(Inactive), - Some(Candidate) | Some(Pending), - ) => {} - _ => errors.push(Error::InvalidNewValidatorState( - pipeline_epoch.into(), - )), - } - } - (Some(_), None) => errors - .push(Error::ValidatorStateIsRequired(address.clone())), - (None, None) => continue, - }, - ConsensusKey(data) => match (data.pre, data.post) { - (None, Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - // The value must be known at pipeline epoch - match post.get(pipeline_epoch) { - Some(_) => {} - _ => errors.push( - Error::MissingNewValidatorConsensusKey( - pipeline_epoch.into(), - ), - ), - } - let validator = - new_validators.entry(address.clone()).or_default(); - validator.has_consensus_key = true; - } - (Some(pre), Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - // Before pipeline epoch, the key must not change - for epoch in - Epoch::iter_range(current_epoch, pipeline_offset) - { - match (pre.get(epoch), post.get(epoch)) { - (Some(key_pre), Some(key_post)) - if key_pre == key_post => - { - continue; - } - _ => errors.push( - Error::InvalidValidatorConsensusKeyUpdate( - epoch.into(), - ), - ), - } - } - } - (Some(_), None) => errors - .push(Error::ValidatorStateIsRequired(address.clone())), - (None, None) => continue, - }, - StakingRewardAddress(data) => match (data.pre, data.post) { - (Some(_), Some(post)) => { - if post == address { - errors.push( - Error::StakingRewardAddressEqValidator( - address.clone(), - ), - ); - } - } - (None, Some(post)) => { - if post == address { - errors.push( - Error::StakingRewardAddressEqValidator( - address.clone(), - ), - ); - } - let validator = - new_validators.entry(address.clone()).or_default(); - validator.has_staking_reward_address = true; - } - _ => errors.push(Error::StakingRewardAddressIsRequired( - address.clone(), - )), - }, - TotalDeltas(data) => match (data.pre, data.post) { - (Some(pre), Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - // Changes of all total deltas (up to `unbonding_epoch`) - let mut deltas = TokenChange::default(); - // Sum of pre total deltas - let mut pre_deltas_sum = TokenChange::default(); - // Sum of post total deltas - let mut post_deltas_sum = TokenChange::default(); - // Iter from the first epoch to the last epoch of `post` - for epoch in Epoch::iter_range( - current_epoch, - unbonding_offset + 1, - ) { - // Changes of all total deltas (up to - // `unbonding_epoch`) - let mut delta = TokenChange::default(); - // Find the delta in `pre` - if let Some(change) = { - if epoch == current_epoch { - // On the first epoch, we have to get the - // sum of all deltas at and before that - // epoch as the `pre` could have been set in - // an older epoch - pre.get(epoch) - } else { - pre.get_delta_at_epoch(epoch).copied() - } - } { - delta -= change; - pre_deltas_sum += change; - } - // Find the delta in `post` - if let Some(change) = post.get_delta_at_epoch(epoch) - { - delta += *change; - post_deltas_sum += *change; - let stake_pre: i128 = - Into::into(pre_deltas_sum); - let stake_post: i128 = - Into::into(post_deltas_sum); - match ( - u64::try_from(stake_pre), - u64::try_from(stake_post), - ) { - (Ok(stake_pre), Ok(stake_post)) => { - let stake_pre = - TokenAmount::from(stake_pre); - let stake_post = - TokenAmount::from(stake_post); - total_stake_by_epoch - .entry(epoch) - .or_insert_with(HashMap::default) - .insert( - address.clone(), - (stake_pre, stake_post), - ); - } - _ => errors.push( - Error::InvalidValidatorTotalDeltas( - address.clone(), - stake_post, - ), - ), - } - } - deltas += delta; - // A total delta can only be increased at - // `pipeline_offset` from bonds and decreased at - // `unbonding_offset` from unbonding - if delta > TokenChange::default() - && epoch != pipeline_epoch - { - errors.push(Error::EpochedDataWrongEpoch { - got: epoch.into(), - expected: vec![pipeline_epoch.into()], - }) - } - if delta < TokenChange::default() - && epoch != unbonding_epoch - { - errors.push(Error::EpochedDataWrongEpoch { - got: epoch.into(), - expected: vec![unbonding_epoch.into()], - }) - } - } - if post_deltas_sum < TokenChange::default() { - errors.push(Error::NegativeValidatorTotalDeltasSum( - address.clone(), - )) - } - if deltas != TokenChange::default() { - let deltas_entry = total_deltas - .entry(address.clone()) - .or_default(); - *deltas_entry += deltas; - } - } - (None, Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - // Changes of all total deltas (up to `unbonding_epoch`) - let mut deltas = TokenChange::default(); - for epoch in Epoch::iter_range( - current_epoch, - unbonding_offset + 1, - ) { - if let Some(change) = post.get_delta_at_epoch(epoch) - { - // A new total delta can only be initialized - // at `pipeline_offset` (from bonds) and updated - // at `unbonding_offset` (from unbonding) - if epoch != pipeline_epoch - && epoch != unbonding_epoch - { - errors.push(Error::EpochedDataWrongEpoch { - got: epoch.into(), - expected: vec![pipeline_epoch.into()], - }) - } - deltas += *change; - let stake: i128 = Into::into(deltas); - match u64::try_from(stake) { - Ok(stake) => { - let stake = TokenAmount::from(stake); - total_stake_by_epoch - .entry(epoch) - .or_insert_with(HashMap::default) - .insert( - address.clone(), - (0.into(), stake), - ); - } - Err(_) => errors.push( - Error::InvalidValidatorTotalDeltas( - address.clone(), - stake, - ), - ), - } - } - } - if deltas < TokenChange::default() { - errors.push(Error::NegativeValidatorTotalDeltasSum( - address.clone(), - )) - } - if deltas != TokenChange::default() { - let deltas_entry = total_deltas - .entry(address.clone()) - .or_default(); - *deltas_entry += deltas; - } - let validator = - new_validators.entry(address.clone()).or_default(); - validator.has_total_deltas = true; - } - (Some(_), None) => { - errors.push(Error::MissingValidatorTotalDeltas(address)) - } - (None, None) => continue, - }, - VotingPowerUpdate(data) => match (&data.pre, data.post) { - (Some(_), Some(post)) | (None, Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - let mut voting_power = VotingPowerDelta::default(); - // Iter from the current epoch to the last epoch of - // `post` - for epoch in Epoch::iter_range( - current_epoch, - unbonding_offset + 1, - ) { - if let Some(delta_post) = - post.get_delta_at_epoch(epoch) - { - voting_power += *delta_post; + let Accumulator { + balance_delta, + bond_delta, + unbond_delta, + total_deltas, + total_stake_by_epoch, + expected_total_voting_power_delta_by_epoch, + voting_power_by_epoch, + validator_set_pre, + validator_set_post, + total_voting_power_delta_by_epoch, + new_validators, + } = Validate::::accumulate_changes( + changes, params, &constants, &mut errors + ); - // If the delta is not the same as in pre-state, - // accumulate the expected total voting power - // change - let delta_pre = data - .pre - .as_ref() - .and_then(|data| { - if epoch == current_epoch { - // On the first epoch, we have to - // get the sum of all deltas at and - // before that epoch as the `pre` - // could have been set in an older - // epoch - data.get(epoch) - } else { - data.get_delta_at_epoch(epoch) - .copied() - } - }) - .unwrap_or_default(); - if delta_pre != *delta_post { - let current_delta = - expected_total_voting_power_delta_by_epoch - .entry(epoch) - .or_insert_with(Default::default); - *current_delta += *delta_post - delta_pre; - } + // Check total deltas against bonds + for (validator, total_delta) in total_deltas.iter() { + let bond_delta = bond_delta.get(validator).copied().unwrap_or_default(); + let total_delta = *total_delta; + if total_delta != bond_delta { + errors.push(Error::InvalidValidatorTotalDeltasSum { + address: validator.clone(), + total_delta, + bond_delta, + }) + } + } + // Check that all bonds also have a total deltas update + for validator in bond_delta.keys() { + if !total_deltas.contains_key(validator) { + errors.push(Error::MissingValidatorTotalDeltas(validator.clone())) + } + } + // Check that all positive unbond deltas also have a total deltas update. + // Negative unbond delta is from withdrawing, which removes tokens from + // unbond, but doesn't affect total deltas. + for (validator, delta) in &unbond_delta { + if *delta > TokenChange::default() + && !total_deltas.contains_key(validator) + { + errors.push(Error::MissingValidatorTotalDeltas(validator.clone())); + } + } - let vp: i64 = Into::into(voting_power); - match u64::try_from(vp) { - Ok(vp) => { - let vp = VotingPower::from(vp); - voting_power_by_epoch - .entry(epoch) - .or_insert_with(HashMap::default) - .insert(address.clone(), vp); - } - Err(_) => errors.push( - Error::InvalidValidatorVotingPower( - address.clone(), - vp, - ), - ), - } - } - } - if data.pre.is_none() { - let validator = new_validators - .entry(address.clone()) - .or_default(); - validator.has_voting_power = true; - validator.voting_power = post - .get_at_offset( - current_epoch, - DynEpochOffset::PipelineLen, - params, - ) - .unwrap_or_default() - .try_into() - .unwrap_or_default() - } - } - (Some(_), None) => errors.push( - Error::MissingValidatorVotingPower(address.clone()), - ), - (None, None) => continue, - }, - }, - Balance(data) => match (data.pre, data.post) { - (None, Some(post)) => balance_delta += TokenChange::from(post), - (Some(pre), Some(post)) => { - balance_delta -= TokenChange::from(pre); - balance_delta += TokenChange::from(post); + // Check validator sets against validator total stakes. + // Iter from the first epoch to the last epoch of `validator_set_post` + if let Some(post) = &validator_set_post { + for epoch in Epoch::iter_range(current_epoch, unbonding_offset + 1) { + if let Some(post) = post.get_at_epoch(epoch) { + // Check that active validators length is not over the limit + if post.active.len() > params.max_validator_slots as usize { + errors.push(Error::TooManyActiveValidators) } - (Some(_), None) => errors.push(Error::MissingBalance), - (None, None) => continue, - }, - Bond { id, data, slashes } => match (data.pre, data.post) { - // Bond may be updated from newly bonded tokens and unbonding - (Some(pre), Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) + // Check that all active have voting power >= any inactive + if let ( + Some(max_inactive_validator), + Some(min_active_validator), + ) = (post.inactive.last_shim(), post.active.first_shim()) + { + if max_inactive_validator.voting_power + > min_active_validator.voting_power + { + errors.push(Error::ValidatorSetOutOfOrder( + max_inactive_validator.clone(), + min_active_validator.clone(), + )); } - let pre_offset: u64 = - match current_epoch.checked_sub(pre.last_update()) { - Some(offset) => offset.into(), - None => { - // If the last_update > current_epoch, the check - // above must have failed with - // `Error::InvalidLastUpdate` - continue; - } - }; + } - // Pre-bonds keyed by their `start_epoch` - let mut pre_bonds: HashMap = - HashMap::default(); - // We have to slash only the difference between post and - // pre, not both pre and post to avoid rounding errors - let mut slashed_deltas: HashMap = - HashMap::default(); - let mut neg_deltas: HashMap = - Default::default(); - // Iter from the first epoch of `pre` to the last epoch of - // `post` - for epoch in Epoch::iter_range( - pre.last_update(), - pre_offset + unbonding_offset + 1, - ) { - if let Some(bond) = pre.get_delta_at_epoch(epoch) { - for (start_epoch, delta) in bond.pos_deltas.iter() { - let delta = TokenChange::from(*delta); - slashed_deltas.insert(*start_epoch, -delta); - pre_bonds.insert(*start_epoch, delta); - } - let ins_epoch = if epoch <= current_epoch { - current_epoch - } else { - epoch - }; - let entry = - neg_deltas.entry(ins_epoch).or_default(); - *entry -= TokenChange::from(bond.neg_deltas); - } - if let Some(bond) = post.get_delta_at_epoch(epoch) { - for (start_epoch, delta) in bond.pos_deltas.iter() { - // An empty bond must be deleted - if *delta == TokenAmount::default() { - errors.push(Error::EmptyBond(id.clone())) - } - // On the current epoch, all bond's - // `start_epoch`s must be equal or lower than - // `current_epoch`. For all others, the - // `start_epoch` must be equal - // to the `epoch` at which it's set. - if (epoch == current_epoch - && *start_epoch > current_epoch) - || (epoch != current_epoch - && *start_epoch != epoch) - { - errors.push(Error::InvalidBondStartEpoch { - id: id.clone(), - got: (*start_epoch).into(), - expected: epoch.into(), - }) - } - let delta = TokenChange::from(*delta); - match slashed_deltas.get_mut(start_epoch) { - Some(pre_delta) => { - if *pre_delta + delta == 0_i128.into() { - slashed_deltas.remove(start_epoch); - } else { - *pre_delta += delta; - } - } - None => { - slashed_deltas - .insert(*start_epoch, delta); + match validator_set_pre.as_ref().and_then(|pre| pre.get(epoch)) + { + Some(pre) => { + let total_stakes = total_stake_by_epoch + .get(&epoch) + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(HashMap::default())); + // Check active validators + for validator in &post.active { + match total_stakes.get(&validator.address) { + Some((_stake_pre, stake_post)) => { + let voting_power = VotingPower::from_tokens( + *stake_post, + params, + ); + // Any validator who's total deltas changed, + // should + // be up-to-date + if validator.voting_power != voting_power { + errors.push( + Error::InvalidActiveValidator( + validator.clone(), + ), + ) } } + None => { + // Others must be have the same voting power + // as in pre (active or inactive), or be a + // newly added validator + if !pre.active.contains(validator) + && !pre.inactive.contains(validator) + && !new_validators + .contains_key(&validator.address) + { + let mut is_valid = false; - // Anywhere other than at `pipeline_offset` - // where new bonds are added, check against the - // data in `pre_bonds` to ensure that no new - // bond has been added and that the deltas are - // equal or lower to `pre_bonds` deltas. - // Note that any bonds from any epoch can be - // unbonded, even if they are not yet active. - if epoch != pipeline_epoch { - match pre_bonds.get(start_epoch) { - Some(pre_delta) => { - if &delta != pre_delta { - errors.push( - Error::InvalidNewBondEpoch { - id: id.clone(), - got: epoch.into(), - expected: pipeline_epoch - .into(), - }); + // It's also possible that for this + // validator there has been no change in + // this epoch, but in an earlier epoch. + // We attempt to search for it below and + // if the voting power matches the + // stake, this is valid. + let mut search_epoch = + u64::from(epoch) - 1; + while search_epoch + >= current_epoch.into() + { + if let Some(( + _take_pre, + last_total_stake, + )) = total_stake_by_epoch + .get(&search_epoch.into()) + .and_then(|stakes| { + stakes + .get(&validator.address) + }) + { + let voting_power = + VotingPower::from_tokens( + *last_total_stake, + params, + ); + is_valid = validator + .voting_power + == voting_power; + break; + } else { + search_epoch -= 1; } } - None => { + if !is_valid { errors.push( - Error::InvalidNewBondEpoch { - id: id.clone(), - got: epoch.into(), - expected: (current_epoch - + pipeline_offset) - .into(), - }, - ); + Error::InvalidActiveValidator( + validator.clone(), + ), + ) } } } } - if epoch != unbonding_epoch { - match neg_deltas.get(&epoch) { - Some(deltas) => { - if -*deltas - != TokenChange::from( - bond.neg_deltas, - ) - { - errors.push( - Error::InvalidNegDeltaEpoch { - id: id.clone(), - got: epoch.into(), - expected: unbonding_epoch - .into(), - }, - ) - } - } - None => { - if bond.neg_deltas != 0.into() { - errors.push( - Error::InvalidNegDeltaEpoch { - id: id.clone(), - got: epoch.into(), - expected: unbonding_epoch - .into(), - }, - ) - } - } - } - } - let entry = neg_deltas.entry(epoch).or_default(); - *entry += TokenChange::from(bond.neg_deltas); - } - } - // Check slashes - for (start_epoch, delta) in slashed_deltas.iter_mut() { - for slash in &slashes { - if slash.epoch >= *start_epoch { - let raw_delta: i128 = (*delta).into(); - let current_slashed = - TokenChange::from(slash.rate * raw_delta); - *delta -= current_slashed; - } - } - } - let total = slashed_deltas - .values() - .fold(TokenChange::default(), |acc, delta| { - acc + *delta - }) - - neg_deltas - .values() - .fold(TokenChange::default(), |acc, delta| { - acc + *delta - }); - - if total != TokenChange::default() { - let bond_entry = - bond_delta.entry(id.validator).or_default(); - *bond_entry += total; - } - } - // Bond may be created from newly bonded tokens only - (None, Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - let mut total_delta = TokenChange::default(); - for epoch in - Epoch::iter_range(current_epoch, unbonding_offset + 1) - { - if let Some(bond) = post.get_delta_at_epoch(epoch) { - // A new bond must be initialized at - // `pipeline_offset` - if epoch != pipeline_epoch - && !bond.pos_deltas.is_empty() - { - dbg!(&bond.pos_deltas); - errors.push(Error::EpochedDataWrongEpoch { - got: epoch.into(), - expected: vec![pipeline_epoch.into()], - }) - } - if epoch != unbonding_epoch - && bond.neg_deltas != 0.into() - { - errors.push(Error::InvalidNegDeltaEpoch { - id: id.clone(), - got: epoch.into(), - expected: unbonding_epoch.into(), - }) - } - for (start_epoch, delta) in bond.pos_deltas.iter() { - if *start_epoch != epoch { - errors.push(Error::InvalidBondStartEpoch { - id: id.clone(), - got: (*start_epoch).into(), - expected: epoch.into(), - }) - } - let mut delta = *delta; - // Check slashes - for slash in &slashes { - if slash.epoch >= *start_epoch { - let raw_delta: u64 = delta.into(); - let current_slashed = TokenAmount::from( - slash.rate * raw_delta, - ); - delta -= current_slashed; - } - } - let delta = TokenChange::from(delta); - total_delta += delta - } - total_delta -= TokenChange::from(bond.neg_deltas) - } - } - // An empty bond must be deleted - if total_delta == TokenChange::default() { - errors.push(Error::EmptyBond(id.clone())) - } - let bond_entry = - bond_delta.entry(id.validator).or_default(); - *bond_entry += total_delta; - } - // Bond may be deleted when all the tokens are unbonded - (Some(pre), None) => { - let mut total_delta = TokenChange::default(); - for index in 0..pipeline_offset + 1 { - let index = index as usize; - let epoch = pre.last_update() + index; - if let Some(bond) = pre.get_delta_at_epoch(epoch) { - for (start_epoch, delta) in &bond.pos_deltas { - let mut delta = *delta; - // Check slashes - for slash in &slashes { - if slash.epoch >= *start_epoch { - let raw_delta: u64 = delta.into(); - let current_slashed = TokenAmount::from( - slash.rate * raw_delta, - ); - delta -= current_slashed; - } - } - let delta = TokenChange::from(delta); - total_delta -= delta - } - total_delta += TokenChange::from(bond.neg_deltas) - } - } - let bond_entry = - bond_delta.entry(id.validator).or_default(); - *bond_entry += total_delta; - } - (None, None) => continue, - }, - Unbond { id, data, slashes } => match (data.pre, data.post) { - // Unbond may be updated from newly unbonded tokens - (Some(pre), Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - let pre_offset: u64 = - match current_epoch.checked_sub(pre.last_update()) { - Some(offset) => offset.into(), - None => { - // If the last_update > current_epoch, the check - // above must have failed with - // `Error::InvalidLastUpdate` - continue; - } - }; - - // We have to slash only the difference between post and - // pre, not both pre and post to avoid rounding errors - let mut slashed_deltas: HashMap< - (Epoch, Epoch), - TokenChange, - > = HashMap::default(); - // Iter from the first epoch of `pre` to the last epoch of - // `post` - for epoch in Epoch::iter_range( - pre.last_update(), - pre_offset + unbonding_offset + 1, - ) { - if let Some(unbond) = pre.get_delta_at_epoch(epoch) { - for ((start_epoch, end_epoch), delta) in - unbond.deltas.iter() - { - let delta = TokenChange::from(*delta); - slashed_deltas - .insert((*start_epoch, *end_epoch), -delta); - } - } - if let Some(unbond) = post.get_delta_at_epoch(epoch) { - for ((start_epoch, end_epoch), delta) in - unbond.deltas.iter() - { - let delta = TokenChange::from(*delta); - let key = (*start_epoch, *end_epoch); - match slashed_deltas.get_mut(&key) { - Some(pre_delta) => { - if *pre_delta + delta == 0_i128.into() { - slashed_deltas.remove(&key); - } else { - *pre_delta += delta; - } - } - None => { - slashed_deltas.insert(key, delta); - } - } - } } - } - // Check slashes - for ((start_epoch, end_epoch), delta) in - slashed_deltas.iter_mut() - { - for slash in &slashes { - if slash.epoch >= *start_epoch - && slash.epoch <= *end_epoch - { - let raw_delta: i128 = (*delta).into(); - let current_slashed = - TokenChange::from(slash.rate * raw_delta); - *delta -= current_slashed; - } - } - } - let total = slashed_deltas - .values() - .fold(TokenChange::default(), |acc, delta| { - acc + *delta - }); - if total != TokenChange::default() { - let unbond_entry = - unbond_delta.entry(id.validator).or_default(); - *unbond_entry += total; - } - } - // Unbond may be created from a bond - (None, Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - let mut total_delta = TokenChange::default(); - for epoch in Epoch::iter_range( - post.last_update(), - unbonding_offset + 1, - ) { - if let Some(unbond) = post.get_delta_at_epoch(epoch) { - for ((start_epoch, end_epoch), delta) in - unbond.deltas.iter() - { - let mut delta = *delta; - // Check and apply slashes, if any - for slash in &slashes { - if slash.epoch >= *start_epoch - && slash.epoch <= *end_epoch - { - let raw_delta: u64 = delta.into(); - let current_slashed = TokenAmount::from( - slash.rate * raw_delta, - ); - delta -= current_slashed; - } - } - let delta = TokenChange::from(delta); - total_delta += delta; - } - } - } - let unbond_entry = - unbond_delta.entry(id.validator).or_default(); - *unbond_entry += total_delta; - } - // Unbond may be deleted when all the tokens are withdrawn - (Some(pre), None) => { - let mut total_delta = TokenChange::default(); - for epoch in Epoch::iter_range( - pre.last_update(), - unbonding_offset + 1, - ) { - if let Some(unbond) = pre.get_delta_at_epoch(epoch) { - for ((start_epoch, end_epoch), delta) in - unbond.deltas.iter() - { - let mut delta = *delta; - // Check and apply slashes, if any - for slash in &slashes { - if slash.epoch >= *start_epoch - && slash.epoch <= *end_epoch - { - let raw_delta: u64 = delta.into(); - let current_slashed = TokenAmount::from( - slash.rate * raw_delta, - ); - delta -= current_slashed; - } - } - let delta = TokenChange::from(delta); - total_delta -= delta; - } - } - } - let unbond_entry = - unbond_delta.entry(id.validator).or_default(); - *unbond_entry += total_delta; - } - (None, None) => continue, - }, - ValidatorSet(data) => match (data.pre, data.post) { - (Some(pre), Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - validator_set_pre = Some(pre); - validator_set_post = Some(post); - } - _ => errors.push(Error::MissingValidatorSet), - }, - TotalVotingPower(data) => match (data.pre, data.post) { - (Some(pre), Some(post)) => { - if post.last_update() != current_epoch { - errors.push(Error::InvalidLastUpdate) - } - // Iter from the first epoch to the last epoch of `post` - for epoch in Epoch::iter_range( - post.last_update(), - unbonding_offset + 1, - ) { - // Find the delta in `pre` - let delta_pre = (if epoch == post.last_update() { - // On the first epoch, we have to get the - // sum of all deltas at and before that - // epoch as the `pre` could have been set in - // an older epoch - pre.get(epoch) - } else { - pre.get_delta_at_epoch(epoch).copied() - }) - .unwrap_or_default(); - // Find the delta in `post` - let delta_post = post - .get_delta_at_epoch(epoch) - .copied() - .unwrap_or_default(); - if delta_pre != delta_post { - total_voting_power_delta_by_epoch - .insert(epoch, delta_post - delta_pre); - } - } - } - _ => errors.push(Error::MissingTotalVotingPower), - }, - ValidatorAddressRawHash { raw_hash, data } => { - match (data.pre, data.post) { - (None, Some((address, expected_raw_hash))) => { - if raw_hash != expected_raw_hash { - errors.push(Error::InvalidAddressRawHash( - raw_hash, - expected_raw_hash, - )) - } - let validator = - new_validators.entry(address.clone()).or_default(); - validator.has_address_raw_hash = true; - } - (pre, post) if pre != post => { - errors.push(Error::InvalidRawHashUpdate) - } - _ => continue, - } - } - } - } - - // Check total deltas against bonds - for (validator, total_delta) in total_deltas.iter() { - let bond_delta = bond_delta.get(validator).copied().unwrap_or_default(); - let total_delta = *total_delta; - if total_delta != bond_delta { - errors.push(Error::InvalidValidatorTotalDeltasSum { - address: validator.clone(), - total_delta, - bond_delta, - }) - } - } - // Check that all bonds also have a total deltas update - for validator in bond_delta.keys() { - if !total_deltas.contains_key(validator) { - errors.push(Error::MissingValidatorTotalDeltas(validator.clone())) - } - } - // Check that all positive unbond deltas also have a total deltas update. - // Negative unbond delta is from withdrawing, which removes tokens from - // unbond, but doesn't affect total deltas. - for (validator, delta) in &unbond_delta { - if *delta > TokenChange::default() - && !total_deltas.contains_key(validator) - { - errors.push(Error::MissingValidatorTotalDeltas(validator.clone())); - } - } - - // Check validator sets against validator total stakes. - // Iter from the first epoch to the last epoch of `validator_set_post` - if let Some(post) = &validator_set_post { - for epoch in Epoch::iter_range(current_epoch, unbonding_offset + 1) { - if let Some(post) = post.get_at_epoch(epoch) { - // Check that active validators length is not over the limit - if post.active.len() > params.max_validator_slots as usize { - errors.push(Error::TooManyActiveValidators) - } - // Check that all active have voting power >= any inactive - if let ( - Some(max_inactive_validator), - Some(min_active_validator), - ) = (post.inactive.last_shim(), post.active.first_shim()) - { - if max_inactive_validator.voting_power - > min_active_validator.voting_power - { - errors.push(Error::ValidatorSetOutOfOrder( - max_inactive_validator.clone(), - min_active_validator.clone(), - )); - } - } - - match validator_set_pre.as_ref().and_then(|pre| pre.get(epoch)) - { - Some(pre) => { - let total_stakes = total_stake_by_epoch - .get(&epoch) - .map(Cow::Borrowed) - .unwrap_or_else(|| Cow::Owned(HashMap::default())); - // Check active validators - for validator in &post.active { + // Check inactive validators + for validator in &post.inactive { + // Any validator who's total deltas changed, should + // be up-to-date match total_stakes.get(&validator.address) { Some((_stake_pre, stake_post)) => { let voting_power = VotingPower::from_tokens( *stake_post, params, ); - // Any validator who's total deltas changed, - // should - // be up-to-date if validator.voting_power != voting_power { errors.push( - Error::InvalidActiveValidator( + Error::InvalidInactiveValidator( validator.clone(), ), ) @@ -1450,9 +616,10 @@ where search_epoch -= 1; } } + if !is_valid { errors.push( - Error::InvalidActiveValidator( + Error::InvalidInactiveValidator( validator.clone(), ), ) @@ -1461,109 +628,34 @@ where } } } - // Check inactive validators - for validator in &post.inactive { - // Any validator who's total deltas changed, should - // be up-to-date - match total_stakes.get(&validator.address) { - Some((_stake_pre, stake_post)) => { - let voting_power = VotingPower::from_tokens( - *stake_post, - params, - ); - if validator.voting_power != voting_power { - errors.push( - Error::InvalidInactiveValidator( - validator.clone(), - ), - ) - } - } - None => { - // Others must be have the same voting power - // as in pre (active or inactive), or be a - // newly added validator - if !pre.active.contains(validator) - && !pre.inactive.contains(validator) - && !new_validators - .contains_key(&validator.address) - { - let mut is_valid = false; - - // It's also possible that for this - // validator there has been no change in - // this epoch, but in an earlier epoch. - // We attempt to search for it below and - // if the voting power matches the - // stake, this is valid. - let mut search_epoch = - u64::from(epoch) - 1; - while search_epoch - >= current_epoch.into() - { - if let Some(( - _take_pre, - last_total_stake, - )) = total_stake_by_epoch - .get(&search_epoch.into()) - .and_then(|stakes| { - stakes - .get(&validator.address) - }) - { - let voting_power = - VotingPower::from_tokens( - *last_total_stake, - params, - ); - is_valid = validator - .voting_power - == voting_power; - break; - } else { - search_epoch -= 1; - } - } - - if !is_valid { - errors.push( - Error::InvalidInactiveValidator( - validator.clone(), - ), - ) - } - } - } - } - } - } - None => errors.push(Error::MissingValidatorSet), - } - } else if let Some(total_stake) = total_stake_by_epoch.get(&epoch) { - // When there's some total delta change for this epoch, - // check that it wouldn't have affected the validator set - // (i.e. the validator's voting power is unchanged). - match post.get(epoch) { - Some(post) => { - for (validator, (_stake_pre, tokens_at_epoch)) in - total_stake - { - let voting_power = VotingPower::from_tokens( - *tokens_at_epoch, - params, - ); - let weighted_validator = WeightedValidator { - voting_power, - address: validator.clone(), - }; - if !post.active.contains(&weighted_validator) { - if !post.inactive.contains(&weighted_validator) - { - errors.push( - Error::WeightedValidatorNotFound( - weighted_validator, - epoch.into(), - ), + } + None => errors.push(Error::MissingValidatorSet), + } + } else if let Some(total_stake) = total_stake_by_epoch.get(&epoch) { + // When there's some total delta change for this epoch, + // check that it wouldn't have affected the validator set + // (i.e. the validator's voting power is unchanged). + match post.get(epoch) { + Some(post) => { + for (validator, (_stake_pre, tokens_at_epoch)) in + total_stake + { + let voting_power = VotingPower::from_tokens( + *tokens_at_epoch, + params, + ); + let weighted_validator = WeightedValidator { + voting_power, + address: validator.clone(), + }; + if !post.active.contains(&weighted_validator) { + if !post.inactive.contains(&weighted_validator) + { + errors.push( + Error::WeightedValidatorNotFound( + weighted_validator, + epoch.into(), + ), ); } } else if post @@ -1769,3 +861,1292 @@ where errors } + +#[derive(Clone, Debug)] +struct Accumulator +where + Address: Display + + Debug + + Clone + + PartialEq + + Eq + + PartialOrd + + Ord + + Hash + + BorshDeserialize + + BorshSerialize + + BorshSchema, + TokenAmount: Display + + Clone + + Copy + + Debug + + Default + + Eq + + Add + + Sub + + AddAssign + + SubAssign + + Into + + From + + BorshDeserialize + + BorshSerialize + + BorshSchema, + TokenChange: Display + + Debug + + Default + + Clone + + Copy + + Add + + Sub + + Neg + + SubAssign + + AddAssign + + From + + Into + + From + + PartialEq + + Eq + + PartialOrd + + Ord + + BorshDeserialize + + BorshSerialize + + BorshSchema, +{ + balance_delta: TokenChange, + /// Changes of validators' bonds + bond_delta: HashMap, + /// Changes of validators' unbonds + unbond_delta: HashMap, + + /// Changes of all validator total deltas (up to `unbonding_epoch`) + total_deltas: HashMap, + /// Stake calculated from validator total deltas for each epoch + /// in which it has changed (the tuple of values are in pre and post state) + total_stake_by_epoch: + HashMap>, + /// Total voting power delta calculated from validators' total deltas + expected_total_voting_power_delta_by_epoch: + HashMap, + /// Changes of validators' voting power data + voting_power_by_epoch: HashMap>, + validator_set_pre: Option>, + validator_set_post: Option>, + total_voting_power_delta_by_epoch: HashMap, + new_validators: HashMap, +} + +/// Accumulator of storage changes +impl Default + for Accumulator +where + Address: Display + + Debug + + Clone + + PartialEq + + Eq + + PartialOrd + + Ord + + Hash + + BorshDeserialize + + BorshSerialize + + BorshSchema, + TokenAmount: Display + + Clone + + Copy + + Debug + + Default + + Eq + + Add + + Sub + + AddAssign + + SubAssign + + Into + + From + + BorshDeserialize + + BorshSerialize + + BorshSchema, + TokenChange: Display + + Debug + + Default + + Clone + + Copy + + Add + + Sub + + Neg + + SubAssign + + AddAssign + + From + + Into + + From + + PartialEq + + Eq + + PartialOrd + + Ord + + BorshDeserialize + + BorshSerialize + + BorshSchema, +{ + fn default() -> Self { + Self { + balance_delta: Default::default(), + bond_delta: Default::default(), + unbond_delta: Default::default(), + total_deltas: Default::default(), + total_stake_by_epoch: Default::default(), + expected_total_voting_power_delta_by_epoch: Default::default(), + voting_power_by_epoch: Default::default(), + validator_set_pre: Default::default(), + validator_set_post: Default::default(), + total_voting_power_delta_by_epoch: Default::default(), + new_validators: Default::default(), + } + } +} + +/// An empty local type to re-use trait bounds for the functions associated with +/// `Validate` in the `impl` below +struct Validate { + address: PhantomData
, + token_amount: PhantomData, + token_change: PhantomData, + public_key: PhantomData, +} + +impl + Validate +where + Address: Display + + Debug + + Clone + + PartialEq + + Eq + + PartialOrd + + Ord + + Hash + + BorshDeserialize + + BorshSerialize + + BorshSchema, + TokenAmount: Display + + Clone + + Copy + + Debug + + Default + + Eq + + Add + + Sub + + AddAssign + + SubAssign + + Into + + From + + BorshDeserialize + + BorshSerialize + + BorshSchema, + TokenChange: Display + + Debug + + Default + + Clone + + Copy + + Add + + Sub + + Neg + + SubAssign + + AddAssign + + From + + Into + + From + + PartialEq + + Eq + + PartialOrd + + Ord + + BorshDeserialize + + BorshSerialize + + BorshSchema, + PublicKey: Debug + + Clone + + BorshDeserialize + + BorshSerialize + + BorshSchema + + PartialEq, +{ + fn accumulate_changes( + changes: Vec>, + params: &PosParams, + constants: &Constants, + errors: &mut Vec>, + ) -> Accumulator { + use DataUpdate::*; + use ValidatorUpdate::*; + + let mut accumulator = Accumulator::default(); + let Accumulator { + balance_delta, + bond_delta, + unbond_delta, + total_deltas, + total_stake_by_epoch, + expected_total_voting_power_delta_by_epoch, + voting_power_by_epoch, + validator_set_pre, + validator_set_post, + total_voting_power_delta_by_epoch, + new_validators, + } = &mut accumulator; + + for change in changes { + match change { + Validator { address, update } => match update { + State(data) => Self::validator_state( + constants, + errors, + new_validators, + address, + data, + ), + ConsensusKey(data) => Self::validator_consensus_key( + constants, + errors, + new_validators, + address, + data, + ), + StakingRewardAddress(data) => { + Self::validator_staking_reward_address( + errors, + new_validators, + address, + data, + ) + } + + TotalDeltas(data) => Self::validator_total_deltas( + constants, + errors, + total_deltas, + total_stake_by_epoch, + new_validators, + address, + data, + ), + VotingPowerUpdate(data) => Self::validator_voting_power( + params, + constants, + errors, + voting_power_by_epoch, + expected_total_voting_power_delta_by_epoch, + new_validators, + address, + data, + ), + }, + Balance(data) => Self::balance(errors, balance_delta, data), + Bond { id, data, slashes } => { + Self::bond(constants, errors, bond_delta, id, data, slashes) + } + Unbond { id, data, slashes } => Self::unbond( + constants, + errors, + unbond_delta, + id, + data, + slashes, + ), + ValidatorSet(data) => Self::validator_set( + constants, + errors, + validator_set_pre, + validator_set_post, + data, + ), + TotalVotingPower(data) => Self::total_voting_power( + constants, + errors, + total_voting_power_delta_by_epoch, + data, + ), + ValidatorAddressRawHash { raw_hash, data } => { + Self::validator_address_raw_hash( + errors, + new_validators, + raw_hash, + data, + ) + } + } + } + + accumulator + } + + fn validator_state( + constants: &Constants, + errors: &mut Vec>, + new_validators: &mut HashMap, + address: Address, + data: Data, + ) { + match (data.pre, data.post) { + (None, Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + // Before pipeline epoch, the state must be `Pending` + for epoch in Epoch::iter_range( + constants.current_epoch, + constants.pipeline_offset, + ) { + match post.get(epoch) { + Some(ValidatorState::Pending) => {} + _ => errors.push(Error::InvalidNewValidatorState( + epoch.into(), + )), + } + } + // At pipeline epoch, the state must be `Candidate` + match post.get(constants.pipeline_epoch) { + Some(ValidatorState::Candidate) => {} + _ => errors.push(Error::InvalidNewValidatorState( + constants.pipeline_epoch.into(), + )), + } + // Add the validator to the accumulator + let validator = new_validators.entry(address).or_default(); + validator.has_state = true; + } + (Some(pre), Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + use ValidatorState::*; + // Before pipeline epoch, the only allowed state change + // is from `Inactive` to `Pending` + for epoch in Epoch::iter_range( + constants.current_epoch, + constants.pipeline_offset, + ) { + match (pre.get(epoch), post.get(epoch)) { + (Some(Inactive), Some(Pending)) => {} + (Some(state_pre), Some(state_post)) + if state_pre == state_post => {} + _ => errors.push(Error::InvalidValidatorStateUpdate( + epoch.into(), + )), + } + } + // Check allowed state changes at pipeline epoch + match ( + pre.get(constants.pipeline_epoch), + post.get(constants.pipeline_epoch), + ) { + (Some(Pending), Some(Candidate) | Some(Inactive)) + | (Some(Candidate), Some(Inactive)) + | (Some(Inactive), Some(Candidate) | Some(Pending)) => {} + _ => errors.push(Error::InvalidNewValidatorState( + constants.pipeline_epoch.into(), + )), + } + } + (Some(_), None) => { + errors.push(Error::ValidatorStateIsRequired(address)) + } + (None, None) => {} + } + } + + fn validator_consensus_key( + constants: &Constants, + errors: &mut Vec>, + new_validators: &mut HashMap, + address: Address, + data: Data>, + ) { + match (data.pre, data.post) { + (None, Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + // The value must be known at pipeline epoch + match post.get(constants.pipeline_epoch) { + Some(_) => {} + _ => errors.push(Error::MissingNewValidatorConsensusKey( + constants.pipeline_epoch.into(), + )), + } + let validator = new_validators.entry(address).or_default(); + validator.has_consensus_key = true; + } + (Some(pre), Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + // Before pipeline epoch, the key must not change + for epoch in Epoch::iter_range( + constants.current_epoch, + constants.pipeline_offset, + ) { + match (pre.get(epoch), post.get(epoch)) { + (Some(key_pre), Some(key_post)) + if key_pre == key_post => + { + continue; + } + _ => errors.push( + Error::InvalidValidatorConsensusKeyUpdate( + epoch.into(), + ), + ), + } + } + } + (Some(_), None) => { + errors.push(Error::ValidatorStateIsRequired(address)) + } + (None, None) => {} + } + } + + fn validator_staking_reward_address( + errors: &mut Vec>, + new_validators: &mut HashMap, + address: Address, + data: Data
, + ) { + match (data.pre, data.post) { + (Some(_), Some(post)) => { + if post == address { + errors + .push(Error::StakingRewardAddressEqValidator(address)); + } + } + (None, Some(post)) => { + if post == address { + errors.push(Error::StakingRewardAddressEqValidator( + address.clone(), + )); + } + let validator = new_validators.entry(address).or_default(); + validator.has_staking_reward_address = true; + } + _ => errors.push(Error::StakingRewardAddressIsRequired(address)), + } + } + + fn validator_total_deltas( + constants: &Constants, + errors: &mut Vec>, + total_deltas: &mut HashMap, + total_stake_by_epoch: &mut HashMap< + Epoch, + HashMap, + >, + new_validators: &mut HashMap, + address: Address, + data: Data>, + ) { + match (data.pre, data.post) { + (Some(pre), Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + // Changes of all total deltas (up to `unbonding_epoch`) + let mut deltas = TokenChange::default(); + // Sum of pre total deltas + let mut pre_deltas_sum = TokenChange::default(); + // Sum of post total deltas + let mut post_deltas_sum = TokenChange::default(); + // Iter from the first epoch to the last epoch of `post` + for epoch in Epoch::iter_range( + constants.current_epoch, + constants.unbonding_offset + 1, + ) { + // Changes of all total deltas (up to + // `unbonding_epoch`) + let mut delta = TokenChange::default(); + // Find the delta in `pre` + if let Some(change) = { + if epoch == constants.current_epoch { + // On the first epoch, we have to get the + // sum of all deltas at and before that + // epoch as the `pre` could have been set in + // an older epoch + pre.get(epoch) + } else { + pre.get_delta_at_epoch(epoch).copied() + } + } { + delta -= change; + pre_deltas_sum += change; + } + // Find the delta in `post` + if let Some(change) = post.get_delta_at_epoch(epoch) { + delta += *change; + post_deltas_sum += *change; + let stake_pre: i128 = Into::into(pre_deltas_sum); + let stake_post: i128 = Into::into(post_deltas_sum); + match ( + u64::try_from(stake_pre), + u64::try_from(stake_post), + ) { + (Ok(stake_pre), Ok(stake_post)) => { + let stake_pre = TokenAmount::from(stake_pre); + let stake_post = TokenAmount::from(stake_post); + total_stake_by_epoch + .entry(epoch) + .or_insert_with(HashMap::default) + .insert( + address.clone(), + (stake_pre, stake_post), + ); + } + _ => { + errors.push(Error::InvalidValidatorTotalDeltas( + address.clone(), + stake_post, + )) + } + } + } + deltas += delta; + // A total delta can only be increased at + // `pipeline_offset` from bonds and decreased at + // `unbonding_offset` from unbonding + if delta > TokenChange::default() + && epoch != constants.pipeline_epoch + { + errors.push(Error::EpochedDataWrongEpoch { + got: epoch.into(), + expected: vec![constants.pipeline_epoch.into()], + }) + } + if delta < TokenChange::default() + && epoch != constants.unbonding_epoch + { + errors.push(Error::EpochedDataWrongEpoch { + got: epoch.into(), + expected: vec![constants.unbonding_epoch.into()], + }) + } + } + if post_deltas_sum < TokenChange::default() { + errors.push(Error::NegativeValidatorTotalDeltasSum( + address.clone(), + )) + } + if deltas != TokenChange::default() { + let deltas_entry = total_deltas.entry(address).or_default(); + *deltas_entry += deltas; + } + } + (None, Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + // Changes of all total deltas (up to `unbonding_epoch`) + let mut deltas = TokenChange::default(); + for epoch in Epoch::iter_range( + constants.current_epoch, + constants.unbonding_offset + 1, + ) { + if let Some(change) = post.get_delta_at_epoch(epoch) { + // A new total delta can only be initialized + // at `pipeline_offset` (from bonds) and updated + // at `unbonding_offset` (from unbonding) + if epoch != constants.pipeline_epoch + && epoch != constants.unbonding_epoch + { + errors.push(Error::EpochedDataWrongEpoch { + got: epoch.into(), + expected: vec![constants.pipeline_epoch.into()], + }) + } + deltas += *change; + let stake: i128 = Into::into(deltas); + match u64::try_from(stake) { + Ok(stake) => { + let stake = TokenAmount::from(stake); + total_stake_by_epoch + .entry(epoch) + .or_insert_with(HashMap::default) + .insert(address.clone(), (0.into(), stake)); + } + Err(_) => { + errors.push(Error::InvalidValidatorTotalDeltas( + address.clone(), + stake, + )) + } + } + } + } + if deltas < TokenChange::default() { + errors.push(Error::NegativeValidatorTotalDeltasSum( + address.clone(), + )) + } + if deltas != TokenChange::default() { + let deltas_entry = + total_deltas.entry(address.clone()).or_default(); + *deltas_entry += deltas; + } + let validator = new_validators.entry(address).or_default(); + validator.has_total_deltas = true; + } + (Some(_), None) => { + errors.push(Error::MissingValidatorTotalDeltas(address)) + } + (None, None) => {} + } + } + + #[allow(clippy::too_many_arguments)] + fn validator_voting_power( + params: &PosParams, + constants: &Constants, + errors: &mut Vec>, + voting_power_by_epoch: &mut HashMap< + Epoch, + HashMap, + >, + expected_total_voting_power_delta_by_epoch: &mut HashMap< + Epoch, + VotingPowerDelta, + >, + new_validators: &mut HashMap, + address: Address, + data: Data, + ) { + match (&data.pre, data.post) { + (Some(_), Some(post)) | (None, Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + let mut voting_power = VotingPowerDelta::default(); + // Iter from the current epoch to the last epoch of + // `post` + for epoch in Epoch::iter_range( + constants.current_epoch, + constants.unbonding_offset + 1, + ) { + if let Some(delta_post) = post.get_delta_at_epoch(epoch) { + voting_power += *delta_post; + + // If the delta is not the same as in pre-state, + // accumulate the expected total voting power + // change + let delta_pre = data + .pre + .as_ref() + .and_then(|data| { + if epoch == constants.current_epoch { + // On the first epoch, we have to + // get the sum of all deltas at and + // before that epoch as the `pre` + // could have been set in an older + // epoch + data.get(epoch) + } else { + data.get_delta_at_epoch(epoch).copied() + } + }) + .unwrap_or_default(); + if delta_pre != *delta_post { + let current_delta = + expected_total_voting_power_delta_by_epoch + .entry(epoch) + .or_insert_with(Default::default); + *current_delta += *delta_post - delta_pre; + } + + let vp: i64 = Into::into(voting_power); + match u64::try_from(vp) { + Ok(vp) => { + let vp = VotingPower::from(vp); + voting_power_by_epoch + .entry(epoch) + .or_insert_with(HashMap::default) + .insert(address.clone(), vp); + } + Err(_) => { + errors.push(Error::InvalidValidatorVotingPower( + address.clone(), + vp, + )) + } + } + } + } + if data.pre.is_none() { + let validator = new_validators.entry(address).or_default(); + validator.has_voting_power = true; + validator.voting_power = post + .get_at_offset( + constants.current_epoch, + DynEpochOffset::PipelineLen, + params, + ) + .unwrap_or_default() + .try_into() + .unwrap_or_default() + } + } + (Some(_), None) => { + errors.push(Error::MissingValidatorVotingPower(address)) + } + (None, None) => {} + } + } + + fn balance( + errors: &mut Vec>, + balance_delta: &mut TokenChange, + data: Data, + ) { + match (data.pre, data.post) { + (None, Some(post)) => *balance_delta += TokenChange::from(post), + (Some(pre), Some(post)) => { + *balance_delta += + TokenChange::from(post) - TokenChange::from(pre); + } + (Some(_), None) => errors.push(Error::MissingBalance), + (None, None) => {} + } + } + + fn bond( + constants: &Constants, + errors: &mut Vec>, + bond_delta: &mut HashMap, + id: BondId
, + data: Data>, + slashes: Vec, + ) { + match (data.pre, data.post) { + // Bond may be updated from newly bonded tokens and unbonding + (Some(pre), Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + let pre_offset: u64 = match constants + .current_epoch + .checked_sub(pre.last_update()) + { + Some(offset) => offset.into(), + None => { + // If the last_update > current_epoch, the check + // above must have failed with + // `Error::InvalidLastUpdate` + return; + } + }; + + // Pre-bonds keyed by their `start_epoch` + let mut pre_bonds: HashMap = + HashMap::default(); + // We have to slash only the difference between post and + // pre, not both pre and post to avoid rounding errors + let mut slashed_deltas: HashMap = + HashMap::default(); + let mut neg_deltas: HashMap = + Default::default(); + // Iter from the first epoch of `pre` to the last epoch of + // `post` + for epoch in Epoch::iter_range( + pre.last_update(), + pre_offset + constants.unbonding_offset + 1, + ) { + if let Some(bond) = pre.get_delta_at_epoch(epoch) { + for (start_epoch, delta) in bond.pos_deltas.iter() { + let delta = TokenChange::from(*delta); + slashed_deltas.insert(*start_epoch, -delta); + pre_bonds.insert(*start_epoch, delta); + } + let ins_epoch = if epoch <= constants.current_epoch { + constants.current_epoch + } else { + epoch + }; + let entry = neg_deltas.entry(ins_epoch).or_default(); + *entry -= TokenChange::from(bond.neg_deltas); + } + if let Some(bond) = post.get_delta_at_epoch(epoch) { + for (start_epoch, delta) in bond.pos_deltas.iter() { + // An empty bond must be deleted + if *delta == TokenAmount::default() { + errors.push(Error::EmptyBond(id.clone())) + } + // On the current epoch, all bond's + // `start_epoch`s must be equal or lower than + // `current_epoch`. For all others, the + // `start_epoch` must be equal + // to the `epoch` at which it's set. + if (epoch == constants.current_epoch + && *start_epoch > constants.current_epoch) + || (epoch != constants.current_epoch + && *start_epoch != epoch) + { + errors.push(Error::InvalidBondStartEpoch { + id: id.clone(), + got: (*start_epoch).into(), + expected: epoch.into(), + }) + } + let delta = TokenChange::from(*delta); + match slashed_deltas.get_mut(start_epoch) { + Some(pre_delta) => { + if *pre_delta + delta == 0_i128.into() { + slashed_deltas.remove(start_epoch); + } else { + *pre_delta += delta; + } + } + None => { + slashed_deltas.insert(*start_epoch, delta); + } + } + + // Anywhere other than at `pipeline_offset` + // where new bonds are added, check against the + // data in `pre_bonds` to ensure that no new + // bond has been added and that the deltas are + // equal or lower to `pre_bonds` deltas. + // Note that any bonds from any epoch can be + // unbonded, even if they are not yet active. + if epoch != constants.pipeline_epoch { + match pre_bonds.get(start_epoch) { + Some(pre_delta) => { + if &delta != pre_delta { + errors.push( + Error::InvalidNewBondEpoch { + id: id.clone(), + got: epoch.into(), + expected: constants + .pipeline_epoch + .into(), + }, + ); + } + } + None => { + errors.push( + Error::InvalidNewBondEpoch { + id: id.clone(), + got: epoch.into(), + expected: (constants + .current_epoch + + constants + .pipeline_offset) + .into(), + }, + ); + } + } + } + } + if epoch != constants.unbonding_epoch { + match neg_deltas.get(&epoch) { + Some(deltas) => { + if -*deltas + != TokenChange::from(bond.neg_deltas) + { + errors.push( + Error::InvalidNegDeltaEpoch { + id: id.clone(), + got: epoch.into(), + expected: constants + .unbonding_epoch + .into(), + }, + ) + } + } + None => { + if bond.neg_deltas != 0.into() { + errors.push( + Error::InvalidNegDeltaEpoch { + id: id.clone(), + got: epoch.into(), + expected: constants + .unbonding_epoch + .into(), + }, + ) + } + } + } + } + let entry = neg_deltas.entry(epoch).or_default(); + *entry += TokenChange::from(bond.neg_deltas); + } + } + // Check slashes + for (start_epoch, delta) in slashed_deltas.iter_mut() { + for slash in &slashes { + if slash.epoch >= *start_epoch { + let raw_delta: i128 = (*delta).into(); + let current_slashed = + TokenChange::from(slash.rate * raw_delta); + *delta -= current_slashed; + } + } + } + let total = slashed_deltas + .values() + .fold(TokenChange::default(), |acc, delta| acc + *delta) + - neg_deltas + .values() + .fold(TokenChange::default(), |acc, delta| { + acc + *delta + }); + + if total != TokenChange::default() { + let bond_entry = + bond_delta.entry(id.validator).or_default(); + *bond_entry += total; + } + } + // Bond may be created from newly bonded tokens only + (None, Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + let mut total_delta = TokenChange::default(); + for epoch in Epoch::iter_range( + constants.current_epoch, + constants.unbonding_offset + 1, + ) { + if let Some(bond) = post.get_delta_at_epoch(epoch) { + // A new bond must be initialized at + // `pipeline_offset` + if epoch != constants.pipeline_epoch + && !bond.pos_deltas.is_empty() + { + dbg!(&bond.pos_deltas); + errors.push(Error::EpochedDataWrongEpoch { + got: epoch.into(), + expected: vec![constants.pipeline_epoch.into()], + }) + } + if epoch != constants.unbonding_epoch + && bond.neg_deltas != 0.into() + { + errors.push(Error::InvalidNegDeltaEpoch { + id: id.clone(), + got: epoch.into(), + expected: constants.unbonding_epoch.into(), + }) + } + for (start_epoch, delta) in bond.pos_deltas.iter() { + if *start_epoch != epoch { + errors.push(Error::InvalidBondStartEpoch { + id: id.clone(), + got: (*start_epoch).into(), + expected: epoch.into(), + }) + } + let mut delta = *delta; + // Check slashes + for slash in &slashes { + if slash.epoch >= *start_epoch { + let raw_delta: u64 = delta.into(); + let current_slashed = TokenAmount::from( + slash.rate * raw_delta, + ); + delta -= current_slashed; + } + } + let delta = TokenChange::from(delta); + total_delta += delta + } + total_delta -= TokenChange::from(bond.neg_deltas) + } + } + // An empty bond must be deleted + if total_delta == TokenChange::default() { + errors.push(Error::EmptyBond(id.clone())) + } + let bond_entry = bond_delta.entry(id.validator).or_default(); + *bond_entry += total_delta; + } + // Bond may be deleted when all the tokens are unbonded + (Some(pre), None) => { + let mut total_delta = TokenChange::default(); + for index in 0..constants.pipeline_offset + 1 { + let index = index as usize; + let epoch = pre.last_update() + index; + if let Some(bond) = pre.get_delta_at_epoch(epoch) { + for (start_epoch, delta) in &bond.pos_deltas { + let mut delta = *delta; + // Check slashes + for slash in &slashes { + if slash.epoch >= *start_epoch { + let raw_delta: u64 = delta.into(); + let current_slashed = TokenAmount::from( + slash.rate * raw_delta, + ); + delta -= current_slashed; + } + } + let delta = TokenChange::from(delta); + total_delta -= delta + } + total_delta += TokenChange::from(bond.neg_deltas) + } + } + let bond_entry = bond_delta.entry(id.validator).or_default(); + *bond_entry += total_delta; + } + (None, None) => {} + } + } + + fn unbond( + constants: &Constants, + errors: &mut Vec>, + unbond_delta: &mut HashMap, + id: BondId
, + data: Data>, + slashes: Vec, + ) { + match (data.pre, data.post) { + // Unbond may be updated from newly unbonded tokens + (Some(pre), Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + let pre_offset: u64 = match constants + .current_epoch + .checked_sub(pre.last_update()) + { + Some(offset) => offset.into(), + None => { + // If the last_update > current_epoch, the check + // above must have failed with + // `Error::InvalidLastUpdate` + return; + } + }; + + // We have to slash only the difference between post and + // pre, not both pre and post to avoid rounding errors + let mut slashed_deltas: HashMap<(Epoch, Epoch), TokenChange> = + HashMap::default(); + // Iter from the first epoch of `pre` to the last epoch of + // `post` + for epoch in Epoch::iter_range( + pre.last_update(), + pre_offset + constants.unbonding_offset + 1, + ) { + if let Some(unbond) = pre.get_delta_at_epoch(epoch) { + for ((start_epoch, end_epoch), delta) in + unbond.deltas.iter() + { + let delta = TokenChange::from(*delta); + slashed_deltas + .insert((*start_epoch, *end_epoch), -delta); + } + } + if let Some(unbond) = post.get_delta_at_epoch(epoch) { + for ((start_epoch, end_epoch), delta) in + unbond.deltas.iter() + { + let delta = TokenChange::from(*delta); + let key = (*start_epoch, *end_epoch); + match slashed_deltas.get_mut(&key) { + Some(pre_delta) => { + if *pre_delta + delta == 0_i128.into() { + slashed_deltas.remove(&key); + } else { + *pre_delta += delta; + } + } + None => { + slashed_deltas.insert(key, delta); + } + } + } + } + } + // Check slashes + for ((start_epoch, end_epoch), delta) in + slashed_deltas.iter_mut() + { + for slash in &slashes { + if slash.epoch >= *start_epoch + && slash.epoch <= *end_epoch + { + let raw_delta: i128 = (*delta).into(); + let current_slashed = + TokenChange::from(slash.rate * raw_delta); + *delta -= current_slashed; + } + } + } + let total = slashed_deltas + .values() + .fold(TokenChange::default(), |acc, delta| acc + *delta); + if total != TokenChange::default() { + let unbond_entry = + unbond_delta.entry(id.validator).or_default(); + *unbond_entry += total; + } + } + // Unbond may be created from a bond + (None, Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + let mut total_delta = TokenChange::default(); + for epoch in Epoch::iter_range( + post.last_update(), + constants.unbonding_offset + 1, + ) { + if let Some(unbond) = post.get_delta_at_epoch(epoch) { + for ((start_epoch, end_epoch), delta) in + unbond.deltas.iter() + { + let mut delta = *delta; + // Check and apply slashes, if any + for slash in &slashes { + if slash.epoch >= *start_epoch + && slash.epoch <= *end_epoch + { + let raw_delta: u64 = delta.into(); + let current_slashed = TokenAmount::from( + slash.rate * raw_delta, + ); + delta -= current_slashed; + } + } + let delta = TokenChange::from(delta); + total_delta += delta; + } + } + } + let unbond_entry = + unbond_delta.entry(id.validator).or_default(); + *unbond_entry += total_delta; + } + // Unbond may be deleted when all the tokens are withdrawn + (Some(pre), None) => { + let mut total_delta = TokenChange::default(); + for epoch in Epoch::iter_range( + pre.last_update(), + constants.unbonding_offset + 1, + ) { + if let Some(unbond) = pre.get_delta_at_epoch(epoch) { + for ((start_epoch, end_epoch), delta) in + unbond.deltas.iter() + { + let mut delta = *delta; + // Check and apply slashes, if any + for slash in &slashes { + if slash.epoch >= *start_epoch + && slash.epoch <= *end_epoch + { + let raw_delta: u64 = delta.into(); + let current_slashed = TokenAmount::from( + slash.rate * raw_delta, + ); + delta -= current_slashed; + } + } + let delta = TokenChange::from(delta); + total_delta -= delta; + } + } + } + let unbond_entry = + unbond_delta.entry(id.validator).or_default(); + *unbond_entry += total_delta; + } + (None, None) => {} + } + } + + fn validator_set( + constants: &Constants, + errors: &mut Vec>, + validator_set_pre: &mut Option>, + validator_set_post: &mut Option>, + data: Data>, + ) { + match (data.pre, data.post) { + (Some(pre), Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + *validator_set_pre = Some(pre); + *validator_set_post = Some(post); + } + _ => errors.push(Error::MissingValidatorSet), + } + } + + fn total_voting_power( + constants: &Constants, + errors: &mut Vec>, + total_voting_power_delta_by_epoch: &mut HashMap< + Epoch, + VotingPowerDelta, + >, + data: Data, + ) { + match (data.pre, data.post) { + (Some(pre), Some(post)) => { + if post.last_update() != constants.current_epoch { + errors.push(Error::InvalidLastUpdate) + } + // Iter from the first epoch to the last epoch of `post` + for epoch in Epoch::iter_range( + post.last_update(), + constants.unbonding_offset + 1, + ) { + // Find the delta in `pre` + let delta_pre = (if epoch == post.last_update() { + // On the first epoch, we have to get the + // sum of all deltas at and before that + // epoch as the `pre` could have been set in + // an older epoch + pre.get(epoch) + } else { + pre.get_delta_at_epoch(epoch).copied() + }) + .unwrap_or_default(); + // Find the delta in `post` + let delta_post = post + .get_delta_at_epoch(epoch) + .copied() + .unwrap_or_default(); + if delta_pre != delta_post { + total_voting_power_delta_by_epoch + .insert(epoch, delta_post - delta_pre); + } + } + } + _ => errors.push(Error::MissingTotalVotingPower), + } + } + + fn validator_address_raw_hash( + errors: &mut Vec>, + new_validators: &mut HashMap, + raw_hash: String, + data: Data<(Address, String)>, + ) { + match (data.pre, data.post) { + (None, Some((address, expected_raw_hash))) => { + if raw_hash != expected_raw_hash { + errors.push(Error::InvalidAddressRawHash( + raw_hash, + expected_raw_hash, + )) + } + let validator = new_validators.entry(address).or_default(); + validator.has_address_raw_hash = true; + } + (pre, post) if pre != post => { + errors.push(Error::InvalidRawHashUpdate) + } + _ => {} + } + } +} From f716311b7df9071496425ce4b323e30cf02fc6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 6 Jul 2022 18:03:52 +0200 Subject: [PATCH 239/373] pos: fix bond zero amount error msg --- proof_of_stake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index c00ec478ef..8677baf9c8 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -921,7 +921,7 @@ pub enum BondError { InactiveValidator(Address), #[error("Voting power overflow: {0}")] VotingPowerOverflow(TryFromIntError), - #[error("Given zero amount to unbond")] + #[error("Given zero amount to bond")] ZeroAmount, } From 9a135eb9396889eb33fd2239c7bcc60ff045017f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 09:45:22 +0200 Subject: [PATCH 240/373] ledger: add StorageRead trait with extensible error type --- shared/src/ledger/mod.rs | 1 + shared/src/ledger/storage_api/error.rs | 68 ++++++++++++++++++++++++++ shared/src/ledger/storage_api/mod.rs | 53 ++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 shared/src/ledger/storage_api/error.rs create mode 100644 shared/src/ledger/storage_api/mod.rs diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 7dd7bc2d46..fefb32ac64 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -8,6 +8,7 @@ pub mod native_vp; pub mod parameters; pub mod pos; pub mod storage; +pub mod storage_api; pub mod treasury; pub mod tx_env; pub mod vp_env; diff --git a/shared/src/ledger/storage_api/error.rs b/shared/src/ledger/storage_api/error.rs new file mode 100644 index 0000000000..d01fbfd287 --- /dev/null +++ b/shared/src/ledger/storage_api/error.rs @@ -0,0 +1,68 @@ +//! Storage API error type, extensible with custom user errors and static string +//! messages. + +use thiserror::Error; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Custom(CustomError), + #[error("{0}: {1}")] + CustomWithMessage(&'static str, CustomError), +} + +/// Result of a storage API call. +pub type Result = std::result::Result; + +/// Result extension to easily wrap custom errors into [`Error`]. +// This is separate from `ResultExt`, because the implementation requires +// different bounds for `T`. +pub trait ResultExt { + /// Convert a [`std::result::Result`] into storage_api [`Result`]. + fn into_storage_result(self) -> Result; + + /// Add a static message to a possible error in [`Result`]. + fn wrap_err(self, msg: &'static str) -> Result; +} + +impl ResultExt for std::result::Result +where + E: std::error::Error + Send + Sync + 'static, +{ + fn into_storage_result(self) -> Result { + self.map_err(Error::new) + } + + fn wrap_err(self, msg: &'static str) -> Result { + self.map_err(|err| Error::wrap(msg, err)) + } +} + +impl Error { + /// Create an [`enum@Error`] from another [`std::error::Error`]. + pub fn new(error: E) -> Self + where + E: Into>, + { + Self::Custom(CustomError(error.into())) + } + + /// Wrap another [`std::error::Error`] with a static message. + pub fn wrap(msg: &'static str, error: E) -> Self + where + E: Into>, + { + Self::CustomWithMessage(msg, CustomError(error.into())) + } +} + +/// A custom error +#[derive(Debug)] +pub struct CustomError(pub Box); + +impl std::fmt::Display for CustomError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs new file mode 100644 index 0000000000..0c0e4f4083 --- /dev/null +++ b/shared/src/ledger/storage_api/mod.rs @@ -0,0 +1,53 @@ +//! The common storage read trait is implemented in the storage, client RPC, tx +//! and VPs (both native and WASM). + +mod error; + +use borsh::BorshDeserialize; +pub use error::{CustomError, Error, Result, ResultExt}; + +use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; + +/// Common storage read interface +pub trait StorageRead { + /// Storage read prefix iterator + type PrefixIter; + + /// Storage read Borsh encoded value. It will try to read from the storage + /// and decode it if found. + fn read( + &self, + key: &storage::Key, + ) -> Result>; + + /// Storage read raw bytes. It will try to read from the storage. + fn read_bytes(&self, key: &storage::Key) -> Result>>; + + /// Storage `has_key` in. It will try to read from the storage. + fn has_key(&self, key: &storage::Key) -> Result; + + /// Storage prefix iterator. It will try to get an iterator from the + /// storage. + fn iter_prefix(&self, prefix: &storage::Key) -> Result; + + /// Storage prefix iterator for. It will try to read from the storage. + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>>; + + /// Getting the chain ID. + fn get_chain_id(&self) -> Result; + + /// Getting the block height. The height is that of the block to which the + /// current transaction is being applied. + fn get_block_height(&self) -> Result; + + /// Getting the block hash. The height is that of the block to which the + /// current transaction is being applied. + fn get_block_hash(&self) -> Result; + + /// Getting the block epoch. The epoch is that of the block to which the + /// current transaction is being applied. + fn get_block_epoch(&self) -> Result; +} From 97c69475935bbab22cfefa5a90ce77b8f5241107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 09:46:53 +0200 Subject: [PATCH 241/373] ledger/native_vp: implement StorageRead for pre and post state --- shared/src/ledger/native_vp.rs | 157 +++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 3893e847ed..fa30efb7ea 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -3,6 +3,7 @@ use std::cell::RefCell; use std::collections::BTreeSet; +use super::storage_api::{self, ResultExt, StorageRead}; pub use super::vp_env::VpEnv; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; @@ -72,6 +73,30 @@ where pub cache_access: std::marker::PhantomData, } +/// Read access to the prior storage (state before tx execution) via +/// [`trait@StorageRead`]. +#[derive(Debug)] +pub struct CtxPreStorageRead<'b, 'a: 'b, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + ctx: &'b Ctx<'a, DB, H, CA>, +} + +/// Read access to the posterior storage (state after tx execution) via +/// [`trait@StorageRead`]. +#[derive(Debug)] +pub struct CtxPostStorageRead<'f, 'a: 'f, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + ctx: &'f Ctx<'a, DB, H, CA>, +} + impl<'a, DB, H, CA> Ctx<'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, @@ -111,6 +136,138 @@ where pub fn add_gas(&self, used_gas: u64) -> Result<(), vp_env::RuntimeError> { vp_env::add_gas(&mut *self.gas_meter.borrow_mut(), used_gas) } + + /// Read access to the prior storage (state before tx execution) + /// via [`trait@StorageRead`]. + pub fn pre<'b>(&'b self) -> CtxPreStorageRead<'b, 'a, DB, H, CA> { + CtxPreStorageRead { ctx: self } + } + + /// Read access to the posterior storage (state after tx execution) + /// via [`trait@StorageRead`]. + pub fn post<'b>(&'b self) -> CtxPostStorageRead<'b, 'a, DB, H, CA> { + CtxPostStorageRead { ctx: self } + } +} + +impl<'f, 'a, DB, H, CA> StorageRead for CtxPreStorageRead<'f, 'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type PrefixIter = >::PrefixIter; + + fn read( + &self, + key: &crate::types::storage::Key, + ) -> Result, storage_api::Error> { + self.ctx.read_pre(key).into_storage_result() + } + + fn read_bytes( + &self, + key: &crate::types::storage::Key, + ) -> Result>, storage_api::Error> { + self.ctx.read_bytes_pre(key).into_storage_result() + } + + fn has_key( + &self, + key: &crate::types::storage::Key, + ) -> Result { + self.ctx.has_key_pre(key).into_storage_result() + } + + fn iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> Result { + self.ctx.iter_prefix(prefix).into_storage_result() + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + self.ctx.iter_pre_next(iter).into_storage_result() + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id().into_storage_result() + } + + fn get_block_height(&self) -> Result { + self.ctx.get_block_height().into_storage_result() + } + + fn get_block_hash(&self) -> Result { + self.ctx.get_block_hash().into_storage_result() + } + + fn get_block_epoch(&self) -> Result { + self.ctx.get_block_epoch().into_storage_result() + } +} + +impl<'f, 'a, DB, H, CA> StorageRead for CtxPostStorageRead<'f, 'a, DB, H, CA> +where + DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + type PrefixIter = >::PrefixIter; + + fn read( + &self, + key: &crate::types::storage::Key, + ) -> Result, storage_api::Error> { + self.ctx.read_post(key).into_storage_result() + } + + fn read_bytes( + &self, + key: &crate::types::storage::Key, + ) -> Result>, storage_api::Error> { + self.ctx.read_bytes_post(key).into_storage_result() + } + + fn has_key( + &self, + key: &crate::types::storage::Key, + ) -> Result { + self.ctx.has_key_post(key).into_storage_result() + } + + fn iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> Result { + self.ctx.iter_prefix(prefix).into_storage_result() + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + self.ctx.iter_post_next(iter).into_storage_result() + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id().into_storage_result() + } + + fn get_block_height(&self) -> Result { + self.ctx.get_block_height().into_storage_result() + } + + fn get_block_hash(&self) -> Result { + self.ctx.get_block_hash().into_storage_result() + } + + fn get_block_epoch(&self) -> Result { + self.ctx.get_block_epoch().into_storage_result() + } } impl<'a, DB, H, CA> VpEnv for Ctx<'a, DB, H, CA> From 7552c1ee7d39b75c4e08d8b3aac1022663827c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 09:47:42 +0200 Subject: [PATCH 242/373] ledger/vp: impl StorageRead for WASM VPs pre and post state --- shared/src/ledger/vp_env.rs | 9 ++ vp_prelude/src/error.rs | 7 + vp_prelude/src/lib.rs | 290 ++++++++++++++++++++++++++++-------- 3 files changed, 247 insertions(+), 59 deletions(-) diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index e2ffd72e13..95e7c48782 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -7,6 +7,7 @@ use borsh::BorshDeserialize; use thiserror::Error; use super::gas::MIN_STORAGE_GAS; +use super::storage_api; use crate::ledger::gas; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; @@ -157,6 +158,8 @@ pub enum RuntimeError { ReadTemporaryValueError, #[error("Trying to read a permament value with read_temp")] ReadPermanentValueError, + #[error("Storage error: {0}")] + StorageApi(storage_api::Error), } /// VP environment function result @@ -460,3 +463,9 @@ where } Ok(None) } + +impl From for RuntimeError { + fn from(err: storage_api::Error) -> Self { + Self::StorageApi(err) + } +} diff --git a/vp_prelude/src/error.rs b/vp_prelude/src/error.rs index b34d954166..099ae2540a 100644 --- a/vp_prelude/src/error.rs +++ b/vp_prelude/src/error.rs @@ -5,6 +5,7 @@ //! avoiding `error[E0117]: only traits defined in the current crate can be //! implemented for arbitrary types` +use namada::ledger::storage_api; use thiserror::Error; #[allow(missing_docs)] @@ -101,3 +102,9 @@ impl std::fmt::Display for CustomError { self.0.fmt(f) } } + +impl From for Error { + fn from(err: storage_api::Error) -> Self { + Self::new(err) + } +} diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index c1be4465f1..37d1958aa9 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -22,6 +22,7 @@ use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; pub use error::*; pub use namada::ledger::governance::storage as gov_storage; +pub use namada::ledger::storage_api::{self, StorageRead}; pub use namada::ledger::vp_env::VpEnv; pub use namada::ledger::{parameters, pos as proof_of_stake}; pub use namada::proto::{Signed, SignedTxData}; @@ -80,6 +81,7 @@ macro_rules! debug_log { }} } +#[derive(Debug)] pub struct Ctx(()); impl Ctx { @@ -104,6 +106,32 @@ impl Ctx { pub const unsafe fn new() -> Self { Self(()) } + + /// Read access to the prior storage (state before tx execution) + /// via [`trait@StorageRead`]. + pub fn pre(&self) -> CtxPreStorageRead<'_> { + CtxPreStorageRead { _ctx: self } + } + + /// Read access to the posterior storage (state after tx execution) + /// via [`trait@StorageRead`]. + pub fn post(&self) -> CtxPostStorageRead<'_> { + CtxPostStorageRead { _ctx: self } + } +} + +/// Read access to the prior storage (state before tx execution) via +/// [`trait@StorageRead`]. +#[derive(Debug)] +pub struct CtxPreStorageRead<'a> { + _ctx: &'a Ctx, +} + +/// Read access to the posterior storage (state after tx execution) via +/// [`trait@StorageRead`]. +#[derive(Debug)] +pub struct CtxPostStorageRead<'a> { + _ctx: &'a Ctx, } /// Validity predicate result @@ -130,42 +158,28 @@ impl VpEnv for Ctx { &self, key: &storage::Key, ) -> Result, Self::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_vp_result_buffer) - .and_then(|bytes| T::try_from_slice(&bytes[..]).ok())) + self.pre().read(key).into_env_result() } fn read_bytes_pre( &self, key: &storage::Key, ) -> Result>, Self::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + self.pre().read_bytes(key).into_env_result() } fn read_post( &self, key: &storage::Key, ) -> Result, Self::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_vp_result_buffer) - .and_then(|bytes| T::try_from_slice(&bytes[..]).ok())) + self.post().read(key).into_env_result() } fn read_bytes_post( &self, key: &storage::Key, ) -> Result>, Self::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + self.post().read_bytes(key).into_env_result() } fn read_temp( @@ -190,80 +204,50 @@ impl VpEnv for Ctx { } fn has_key_pre(&self, key: &storage::Key) -> Result { - let key = key.to_string(); - let found = - unsafe { anoma_vp_has_key_pre(key.as_ptr() as _, key.len() as _) }; - Ok(HostEnvResult::is_success(found)) + self.pre().has_key(key).into_env_result() } fn has_key_post(&self, key: &storage::Key) -> Result { - let key = key.to_string(); - let found = - unsafe { anoma_vp_has_key_post(key.as_ptr() as _, key.len() as _) }; - Ok(HostEnvResult::is_success(found)) + self.post().has_key(key).into_env_result() } fn get_chain_id(&self) -> Result { - let result = Vec::with_capacity(CHAIN_ID_LENGTH); - unsafe { - anoma_vp_get_chain_id(result.as_ptr() as _); - } - let slice = - unsafe { slice::from_raw_parts(result.as_ptr(), CHAIN_ID_LENGTH) }; - Ok(String::from_utf8(slice.to_vec()) - .expect("Cannot convert the ID string")) + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + self.pre().get_chain_id().into_env_result() } fn get_block_height(&self) -> Result { - Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) + self.pre().get_block_height().into_env_result() } fn get_block_hash(&self) -> Result { - let result = Vec::with_capacity(BLOCK_HASH_LENGTH); - unsafe { - anoma_vp_get_block_hash(result.as_ptr() as _); - } - let slice = unsafe { - slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) - }; - Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) + self.pre().get_block_hash().into_env_result() } fn get_block_epoch(&self) -> Result { - Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) + self.pre().get_block_epoch().into_env_result() } fn iter_prefix( &self, prefix: &storage::Key, ) -> Result { - let prefix = prefix.to_string(); - let iter_id = unsafe { - anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) - }; - Ok(KeyValIterator(iter_id, PhantomData)) + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + self.pre().iter_prefix(prefix).into_env_result() } fn iter_pre_next( &self, iter: &mut Self::PrefixIter, ) -> Result)>, Self::Error> { - let read_result = unsafe { anoma_vp_iter_pre_next(iter.0) }; - Ok(read_key_val_bytes_from_buffer( - read_result, - anoma_vp_result_buffer, - )) + self.pre().iter_next(iter).into_env_result() } fn iter_post_next( &self, iter: &mut Self::PrefixIter, ) -> Result)>, Self::Error> { - let read_result = unsafe { anoma_vp_iter_post_next(iter.0) }; - Ok(read_key_val_bytes_from_buffer( - read_result, - anoma_vp_result_buffer, - )) + self.post().iter_next(iter).into_env_result() } fn eval( @@ -310,3 +294,191 @@ impl VpEnv for Ctx { Ok(Hash::try_from(slice).expect("Cannot convert the hash")) } } + +impl StorageRead for CtxPreStorageRead<'_> { + type PrefixIter = KeyValIterator<(String, Vec)>; + + fn read( + &self, + key: &storage::Key, + ) -> Result, storage_api::Error> { + let bytes = self.read_bytes(key)?; + match bytes { + Some(bytes) => match T::try_from_slice(&bytes[..]) { + Ok(val) => Ok(Some(val)), + Err(err) => Err(storage_api::Error::new(err)), + }, + None => Ok(None), + } + } + + fn read_bytes( + &self, + key: &storage::Key, + ) -> Result>, storage_api::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + } + + fn has_key(&self, key: &storage::Key) -> Result { + let key = key.to_string(); + let found = + unsafe { anoma_vp_has_key_pre(key.as_ptr() as _, key.len() as _) }; + Ok(HostEnvResult::is_success(found)) + } + + fn iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + // Note that this is the same as `CtxPostStorageRead` + iter_prefix(prefix) + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + let read_result = unsafe { anoma_vp_iter_pre_next(iter.0) }; + Ok(read_key_val_bytes_from_buffer( + read_result, + anoma_vp_result_buffer, + )) + } + + fn get_chain_id(&self) -> Result { + get_chain_id() + } + + fn get_block_height(&self) -> Result { + Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) + } + + fn get_block_hash(&self) -> Result { + let result = Vec::with_capacity(BLOCK_HASH_LENGTH); + unsafe { + anoma_vp_get_block_hash(result.as_ptr() as _); + } + let slice = unsafe { + slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) + }; + Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) + } + + fn get_block_epoch(&self) -> Result { + Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) + } +} + +impl StorageRead for CtxPostStorageRead<'_> { + type PrefixIter = KeyValIterator<(String, Vec)>; + + fn read( + &self, + key: &storage::Key, + ) -> Result, storage_api::Error> { + let bytes = self.read_bytes(key)?; + match bytes { + Some(bytes) => match T::try_from_slice(&bytes[..]) { + Ok(val) => Ok(Some(val)), + Err(err) => Err(storage_api::Error::new(err)), + }, + None => Ok(None), + } + } + + fn read_bytes( + &self, + key: &storage::Key, + ) -> Result>, storage_api::Error> { + let key = key.to_string(); + let read_result = + unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; + Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) + } + + fn has_key(&self, key: &storage::Key) -> Result { + let key = key.to_string(); + let found = + unsafe { anoma_vp_has_key_post(key.as_ptr() as _, key.len() as _) }; + Ok(HostEnvResult::is_success(found)) + } + + fn iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + // Note that this is the same as `CtxPreStorageRead` + iter_prefix(prefix) + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + let read_result = unsafe { anoma_vp_iter_post_next(iter.0) }; + Ok(read_key_val_bytes_from_buffer( + read_result, + anoma_vp_result_buffer, + )) + } + + fn get_chain_id(&self) -> Result { + get_chain_id() + } + + fn get_block_height(&self) -> Result { + get_block_height() + } + + fn get_block_hash(&self) -> Result { + get_block_hash() + } + + fn get_block_epoch(&self) -> Result { + get_block_epoch() + } +} + +fn iter_prefix( + prefix: &storage::Key, +) -> Result)>, storage_api::Error> { + let prefix = prefix.to_string(); + let iter_id = unsafe { + anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) +} + +fn get_chain_id() -> Result { + let result = Vec::with_capacity(CHAIN_ID_LENGTH); + unsafe { + anoma_vp_get_chain_id(result.as_ptr() as _); + } + let slice = + unsafe { slice::from_raw_parts(result.as_ptr(), CHAIN_ID_LENGTH) }; + Ok( + String::from_utf8(slice.to_vec()) + .expect("Cannot convert the ID string"), + ) +} + +fn get_block_height() -> Result { + Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) +} + +fn get_block_hash() -> Result { + let result = Vec::with_capacity(BLOCK_HASH_LENGTH); + unsafe { + anoma_vp_get_block_hash(result.as_ptr() as _); + } + let slice = + unsafe { slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) }; + Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) +} + +fn get_block_epoch() -> Result { + Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) +} From 45f3f947968070d0826b46f12d795aedeb1d0981 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 09:49:20 +0200 Subject: [PATCH 243/373] ledger/storage: impl StorageRead for Storage --- shared/src/ledger/storage/mod.rs | 76 +++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0b3d19a742..1f106bcf69 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -11,8 +11,9 @@ use core::fmt::Debug; use tendermint::merkle::proof::Proof; use thiserror::Error; -use super::parameters; use super::parameters::Parameters; +use super::storage_api::{ResultExt, StorageRead}; +use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; use crate::ledger::parameters::EpochDuration; use crate::ledger::storage::merkle_tree::{ @@ -684,6 +685,79 @@ where } } +// The `'iter` lifetime is needed for the associated type `PrefixIter`. +// Note that the `D: DBIter<'iter>` bound uses another higher-rank lifetime +// (see https://doc.rust-lang.org/nomicon/hrtb.html). +impl<'iter, D, H> StorageRead for &'iter Storage +where + D: DB + for<'iter_> DBIter<'iter_>, + H: StorageHasher, +{ + type PrefixIter = >::PrefixIter; + + fn read( + &self, + key: &crate::types::storage::Key, + ) -> std::result::Result, storage_api::Error> { + self.read_bytes(key) + .map(|maybe_value| { + maybe_value.and_then(|t| T::try_from_slice(&t[..]).ok()) + }) + .into_storage_result() + } + + fn read_bytes( + &self, + key: &crate::types::storage::Key, + ) -> std::result::Result>, storage_api::Error> { + self.db.read_subspace_val(key).into_storage_result() + } + + fn has_key( + &self, + key: &crate::types::storage::Key, + ) -> std::result::Result { + self.block.tree.has_key(key).into_storage_result() + } + + fn iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> std::result::Result { + Ok(self.db.iter_prefix(prefix)) + } + + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> std::result::Result)>, storage_api::Error> + { + Ok(iter.next().map(|(key, val, _gas)| (key, val))) + } + + fn get_chain_id(&self) -> std::result::Result { + Ok(self.chain_id.to_string()) + } + + fn get_block_height( + &self, + ) -> std::result::Result { + Ok(self.block.height) + } + + fn get_block_hash( + &self, + ) -> std::result::Result { + Ok(self.block.hash.clone()) + } + + fn get_block_epoch( + &self, + ) -> std::result::Result { + Ok(self.block.epoch) + } +} + impl From for Error { fn from(error: MerkleTreeError) -> Self { Self::MerkleTreeError(error) From 5989412b90f9d2b45bc28d8b41ec4340435bd462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 09:50:38 +0200 Subject: [PATCH 244/373] ledger/tx: inherit StorageRead in TxEnv --- shared/src/ledger/tx_env.rs | 78 +++++---------------------- tests/src/vm_host_env/mod.rs | 2 +- tx_prelude/src/error.rs | 9 ++++ tx_prelude/src/ibc.rs | 1 + tx_prelude/src/intent.rs | 3 +- tx_prelude/src/lib.rs | 55 ++++++++++--------- wasm_for_tests/wasm_source/src/lib.rs | 6 ++- 7 files changed, 61 insertions(+), 93 deletions(-) diff --git a/shared/src/ledger/tx_env.rs b/shared/src/ledger/tx_env.rs index 578357b733..1fc7fa788c 100644 --- a/shared/src/ledger/tx_env.rs +++ b/shared/src/ledger/tx_env.rs @@ -1,87 +1,35 @@ //! Transaction environment contains functions that can be called from //! inside a tx. -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshSerialize; +use crate::ledger::storage_api::{self, StorageRead}; use crate::types::address::Address; use crate::types::ibc::IbcEvent; -use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; +use crate::types::storage; use crate::types::time::Rfc3339String; /// Transaction host functions -pub trait TxEnv { - /// Storage read prefix iterator - type PrefixIter; - - /// Host functions possible errors, extensible with custom user errors. +pub trait TxEnv: StorageRead { + /// Host env functions possible errors type Error; - /// Storage read Borsh encoded value. It will try to read from the write log - /// first and if no entry found then from the storage and then decode it if - /// found. - fn read( - &self, - key: &storage::Key, - ) -> Result, Self::Error>; - - /// Storage read raw bytes. It will try to read from the write log first and - /// if no entry found then from the storage. - fn read_bytes( - &self, - key: &storage::Key, - ) -> Result>, Self::Error>; - - /// Check if the storage contains the given key. It will try - /// to check the write log first and if no entry found then the storage. - fn has_key(&self, key: &storage::Key) -> Result; - - /// Getting the chain ID. - fn get_chain_id(&self) -> Result; - - /// Getting the block height. The height is that of the block to which the - /// current transaction is being applied. - fn get_block_height(&self) -> Result; - - /// Getting the block hash. The height is that of the block to which the - /// current transaction is being applied. - fn get_block_hash(&self) -> Result; - - /// Getting the block epoch. The epoch is that of the block to which the - /// current transaction is being applied. - fn get_block_epoch(&self) -> Result; - - /// Get time of the current block header as rfc 3339 string - fn get_block_time(&self) -> Result; - - /// Storage prefix iterator. It will try to get an iterator from the - /// storage. - fn iter_prefix( - &self, - prefix: &storage::Key, - ) -> Result; - - /// Storage prefix iterator next. It will try to read from the write log - /// first and if no entry found then from the storage. - fn iter_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error>; - - // --- MUTABLE ---- - /// Write a value to be encoded with Borsh at the given key to storage. fn write( &mut self, key: &storage::Key, val: T, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; /// Write a value as bytes at the given key to storage. fn write_bytes( &mut self, key: &storage::Key, val: impl AsRef<[u8]>, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; + + /// Delete a value at the given key from storage. + fn delete(&mut self, key: &storage::Key) -> Result<(), storage_api::Error>; /// Write a temporary value to be encoded with Borsh at the given key to /// storage. @@ -98,9 +46,6 @@ pub trait TxEnv { val: impl AsRef<[u8]>, ) -> Result<(), Self::Error>; - /// Delete a value at the given key from storage. - fn delete(&mut self, key: &storage::Key) -> Result<(), Self::Error>; - /// Insert a verifier address. This address must exist on chain, otherwise /// the transaction will be rejected. /// @@ -126,4 +71,7 @@ pub trait TxEnv { /// Emit an IBC event. There can be only one event per transaction. On /// multiple calls, only the last emitted event will be used. fn emit_ibc_event(&mut self, event: &IbcEvent) -> Result<(), Self::Error>; + + /// Get time of the current block header as rfc 3339 string + fn get_block_time(&self) -> Result; } diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index d539289533..cf9dccfb56 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -34,7 +34,7 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; - use namada_tx_prelude::{BorshDeserialize, BorshSerialize}; + use namada_tx_prelude::{BorshDeserialize, BorshSerialize, StorageRead}; use namada_vp_prelude::VpEnv; use prost::Message; use test_log::test; diff --git a/tx_prelude/src/error.rs b/tx_prelude/src/error.rs index b34d954166..ce7b9fa5e9 100644 --- a/tx_prelude/src/error.rs +++ b/tx_prelude/src/error.rs @@ -5,6 +5,7 @@ //! avoiding `error[E0117]: only traits defined in the current crate can be //! implemented for arbitrary types` +use namada::ledger::storage_api; use thiserror::Error; #[allow(missing_docs)] @@ -101,3 +102,11 @@ impl std::fmt::Display for CustomError { self.0.fmt(f) } } + +impl From for Error { + fn from(err: storage_api::Error) -> Self { + // storage_api::Error::Custom(CustomError {Box}) + // Error:Custom(storage_api::Error::Custom(CustomError {Box})) + Self::new(err) + } +} diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 21be213ea3..7be4e4c064 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -1,6 +1,7 @@ //! IBC lower-level functions for transactions. pub use namada::ledger::ibc::handler::{Error, IbcActions, Result}; +use namada::ledger::storage_api::StorageRead; use namada::ledger::tx_env::TxEnv; use namada::types::address::Address; pub use namada::types::ibc::IbcEvent; diff --git a/tx_prelude/src/intent.rs b/tx_prelude/src/intent.rs index 7b6826342e..05f7cede91 100644 --- a/tx_prelude/src/intent.rs +++ b/tx_prelude/src/intent.rs @@ -14,5 +14,6 @@ pub fn invalidate_exchange( let mut invalid_intent: HashSet = ctx.read(&key)?.unwrap_or_default(); invalid_intent.insert(intent.sig.clone()); - ctx.write(&key, &invalid_intent) + ctx.write(&key, &invalid_intent)?; + Ok(()) } diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 5d0009b01b..42b63a892a 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -22,6 +22,8 @@ pub use error::*; pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; +use namada::ledger::storage_api; +pub use namada::ledger::storage_api::StorageRead; pub use namada::ledger::treasury::storage as treasury_storage; pub use namada::ledger::tx_env::TxEnv; pub use namada::proto::{Signed, SignedTxData}; @@ -93,14 +95,13 @@ pub type TxResult = EnvResult<()>; #[derive(Debug)] pub struct KeyValIterator(pub u64, pub PhantomData); -impl TxEnv for Ctx { - type Error = Error; +impl StorageRead for Ctx { type PrefixIter = KeyValIterator<(String, Vec)>; fn read( &self, key: &namada::types::storage::Key, - ) -> Result, Error> { + ) -> Result, storage_api::Error> { let key = key.to_string(); let read_result = unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; @@ -111,7 +112,7 @@ impl TxEnv for Ctx { fn read_bytes( &self, key: &namada::types::storage::Key, - ) -> Result>, Error> { + ) -> Result>, storage_api::Error> { let key = key.to_string(); let read_result = unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; @@ -121,14 +122,14 @@ impl TxEnv for Ctx { fn has_key( &self, key: &namada::types::storage::Key, - ) -> Result { + ) -> Result { let key = key.to_string(); let found = unsafe { anoma_tx_has_key(key.as_ptr() as _, key.len() as _) }; Ok(HostEnvResult::is_success(found)) } - fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { let result = Vec::with_capacity(CHAIN_ID_LENGTH); unsafe { anoma_tx_get_chain_id(result.as_ptr() as _); @@ -141,13 +142,13 @@ impl TxEnv for Ctx { fn get_block_height( &self, - ) -> Result { + ) -> Result { Ok(BlockHeight(unsafe { anoma_tx_get_block_height() })) } fn get_block_hash( &self, - ) -> Result { + ) -> Result { let result = Vec::with_capacity(BLOCK_HASH_LENGTH); unsafe { anoma_tx_get_block_hash(result.as_ptr() as _); @@ -158,24 +159,16 @@ impl TxEnv for Ctx { Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) } - fn get_block_epoch(&self) -> Result { + fn get_block_epoch( + &self, + ) -> Result { Ok(Epoch(unsafe { anoma_tx_get_block_epoch() })) } - fn get_block_time(&self) -> Result { - let read_result = unsafe { anoma_tx_get_block_time() }; - let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) - .expect("The block time should exist"); - Ok(Rfc3339String( - String::try_from_slice(&time_value[..]) - .expect("The conversion shouldn't fail"), - )) - } - fn iter_prefix( &self, prefix: &namada::types::storage::Key, - ) -> Result { + ) -> Result { let prefix = prefix.to_string(); let iter_id = unsafe { anoma_tx_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) @@ -186,19 +179,33 @@ impl TxEnv for Ctx { fn iter_next( &self, iter: &mut Self::PrefixIter, - ) -> Result)>, Error> { + ) -> Result)>, storage_api::Error> { let read_result = unsafe { anoma_tx_iter_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, anoma_tx_result_buffer, )) } +} + +impl TxEnv for Ctx { + type Error = Error; + + fn get_block_time(&self) -> Result { + let read_result = unsafe { anoma_tx_get_block_time() }; + let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) + .expect("The block time should exist"); + Ok(Rfc3339String( + String::try_from_slice(&time_value[..]) + .expect("The conversion shouldn't fail"), + )) + } fn write( &mut self, key: &namada::types::storage::Key, val: T, - ) -> Result<(), Error> { + ) -> storage_api::Result<()> { let buf = val.try_to_vec().unwrap(); self.write_bytes(key, buf) } @@ -207,7 +214,7 @@ impl TxEnv for Ctx { &mut self, key: &namada::types::storage::Key, val: impl AsRef<[u8]>, - ) -> Result<(), Error> { + ) -> storage_api::Result<()> { let key = key.to_string(); unsafe { anoma_tx_write( @@ -249,7 +256,7 @@ impl TxEnv for Ctx { fn delete( &mut self, key: &namada::types::storage::Key, - ) -> Result<(), Error> { + ) -> storage_api::Result<()> { let key = key.to_string(); unsafe { anoma_tx_delete(key.as_ptr() as _, key.len() as _) }; Ok(()) diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 0e47437704..2b6ef24242 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -119,7 +119,8 @@ pub mod main { "attempting to write new value {} to key {}", ARBITRARY_VALUE, key )); - ctx.write(&key, ARBITRARY_VALUE) + ctx.write(&key, ARBITRARY_VALUE)?; + Ok(()) } } @@ -147,7 +148,8 @@ pub mod main { let mut target_bal: token::Amount = ctx.read(&target_key)?.unwrap_or_default(); target_bal.receive(&amount); - ctx.write(&target_key, target_bal) + ctx.write(&target_key, target_bal)?; + Ok(()) } } From 10f94c2dcf950bcf6045da626c9e10fbc547dbc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 09:48:55 +0200 Subject: [PATCH 245/373] ledger/pos: implement PosReadOnly using StorageRead trait --- shared/src/ledger/pos/mod.rs | 136 +++++++++++++++++ shared/src/ledger/pos/vp.rs | 246 +++++++++++-------------------- tests/src/native_vp/pos.rs | 2 +- tx_prelude/src/proof_of_stake.rs | 106 ++----------- 4 files changed, 236 insertions(+), 254 deletions(-) diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index c980da81da..a9d72e84bb 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -87,3 +87,139 @@ impl From for Epoch { Epoch(epoch) } } + +#[macro_use] +mod macros { + /// Implement `PosReadOnly` for a type that implements + /// [`trait@crate::ledger::storage_api::StorageRead`]. + /// + /// Excuse the horrible syntax - we haven't found a better way to use this + /// for native_vp `CtxPreStorageRead`/`CtxPostStorageRead`, which have + /// generics and explicit lifetimes. + /// + /// # Examples + /// + /// ```ignore + /// impl_pos_read_only! { impl PosReadOnly for X } + /// ``` + #[macro_export] + macro_rules! impl_pos_read_only { + ( + // Type error type has to be declared before the impl. + // This error type must `impl From for $error`. + type $error:tt = $err_ty:ty ; + // Matches anything, so that we can use lifetimes and generic types. + // This expects `impl(<.*>)? PoSReadOnly for $ty(<.*>)?`. + $( $any:tt )* ) + => { + $( $any )* + { + type Address = $crate::types::address::Address; + // type Error = $crate::ledger::native_vp::Error; + type $error = $err_ty; + type PublicKey = $crate::types::key::common::PublicKey; + type TokenAmount = $crate::types::token::Amount; + type TokenChange = $crate::types::token::Change; + + const POS_ADDRESS: Self::Address = $crate::ledger::pos::ADDRESS; + + fn staking_token_address() -> Self::Address { + $crate::ledger::pos::staking_token_address() + } + + fn read_pos_params(&self) -> std::result::Result { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, ¶ms_key())?.unwrap(); + Ok($crate::ledger::storage::types::decode(value).unwrap()) + } + + fn read_validator_staking_reward_address( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes( + self, + &validator_staking_reward_address_key(key), + )?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_consensus_key( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_consensus_key_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_state( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_state_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_total_deltas( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_total_deltas_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_voting_power( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_voting_power_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_slashes( + &self, + key: &Self::Address, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_slashes_key(key))?; + Ok(value + .map(|value| $crate::ledger::storage::types::decode(value).unwrap()) + .unwrap_or_default()) + } + + fn read_bond( + &self, + key: &BondId, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &bond_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_unbond( + &self, + key: &BondId, + ) -> std::result::Result, Self::Error> { + let value = $crate::ledger::storage_api::StorageRead::read_bytes(self, &unbond_key(key))?; + Ok(value.map(|value| $crate::ledger::storage::types::decode(value).unwrap())) + } + + fn read_validator_set( + &self, + ) -> std::result::Result { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &validator_set_key())?.unwrap(); + Ok($crate::ledger::storage::types::decode(value).unwrap()) + } + + fn read_total_voting_power( + &self, + ) -> std::result::Result { + let value = + $crate::ledger::storage_api::StorageRead::read_bytes(self, &total_voting_power_key())?.unwrap(); + Ok($crate::ledger::storage::types::decode(value).unwrap()) + } + } + } +} +} diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 80572c1f57..2f19b6d1a4 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -26,17 +26,20 @@ use super::{ validator_total_deltas_key, validator_voting_power_key, BondId, Bonds, Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorTotalDeltas, }; +use crate::impl_pos_read_only; use crate::ledger::governance::vp::is_proposal_accepted; -use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; +use crate::ledger::native_vp::{ + self, Ctx, CtxPostStorageRead, CtxPreStorageRead, NativeVp, +}; use crate::ledger::pos::{ is_validator_address_raw_hash_key, is_validator_consensus_key_key, is_validator_state_key, }; -use crate::ledger::storage::types::decode; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage_api::{self, StorageRead}; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; -use crate::types::{key, token}; +use crate::types::token; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -44,6 +47,8 @@ use crate::vm::WasmCacheAccess; pub enum Error { #[error("Native VP error: {0}")] NativeVpError(native_vp::Error), + #[error("Storage error: {0}")] + StorageApi(storage_api::Error), } /// PoS functions result @@ -118,7 +123,8 @@ where let addr = Address::Internal(Self::ADDR); let mut changes: Vec> = vec![]; - let current_epoch = self.ctx.get_block_epoch()?; + let current_epoch = self.ctx.pre().get_block_epoch()?; + for key in keys_changed { if is_params_key(key) { let proposal_id = u64::try_from_slice(tx_data).ok(); @@ -127,8 +133,8 @@ where _ => return Ok(false), } } else if let Some(owner) = key.is_validity_predicate() { - let has_pre = self.ctx.has_key_pre(key)?; - let has_post = self.ctx.has_key_post(key)?; + let has_pre = self.ctx.pre().has_key(key)?; + let has_post = self.ctx.post().has_key(key)?; if has_pre && has_post { // VP updates must be verified by the owner return Ok(!verifiers.contains(owner)); @@ -137,18 +143,18 @@ where return Ok(false); } } else if is_validator_set_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorSets::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorSets::try_from_slice(&bytes[..]).ok() }); changes.push(ValidatorSet(Data { pre, post })); } else if let Some(validator) = is_validator_state_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorStates::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorStates::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -158,24 +164,24 @@ where } else if let Some(validator) = is_validator_staking_reward_address_key(key) { - let pre = self - .ctx - .read_bytes_pre(key)? - .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); - let post = self - .ctx - .read_bytes_post(key)? - .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Address::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Address::try_from_slice(&bytes[..]).ok() + }); changes.push(Validator { address: validator.clone(), update: StakingRewardAddress(Data { pre, post }), }); } else if let Some(validator) = is_validator_consensus_key_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorConsensusKeys::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorConsensusKeys::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -183,10 +189,10 @@ where update: ConsensusKey(Data { pre, post }), }); } else if let Some(validator) = is_validator_total_deltas_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorTotalDeltas::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -194,10 +200,10 @@ where update: TotalDeltas(Data { pre, post }), }); } else if let Some(validator) = is_validator_voting_power_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { ValidatorVotingPowers::try_from_slice(&bytes[..]).ok() }); changes.push(Validator { @@ -207,14 +213,14 @@ where } else if let Some(raw_hash) = is_validator_address_raw_hash_key(key) { - let pre = self - .ctx - .read_bytes_pre(key)? - .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); - let post = self - .ctx - .read_bytes_post(key)? - .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Address::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Address::try_from_slice(&bytes[..]).ok() + }); // Find the raw hashes of the addresses let pre = pre.map(|pre| { let raw_hash = @@ -236,26 +242,27 @@ where if owner != &addr { continue; } - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { token::Amount::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { token::Amount::try_from_slice(&bytes[..]).ok() }); changes.push(Balance(Data { pre, post })); } else if let Some(bond_id) = is_bond_key(key) { - let pre = self - .ctx - .read_bytes_pre(key)? - .and_then(|bytes| Bonds::try_from_slice(&bytes[..]).ok()); - let post = self - .ctx - .read_bytes_post(key)? - .and_then(|bytes| Bonds::try_from_slice(&bytes[..]).ok()); + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Bonds::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Bonds::try_from_slice(&bytes[..]).ok() + }); // For bonds, we need to look-up slashes let slashes = self .ctx - .read_bytes_pre(&validator_slashes_key(&bond_id.validator))? + .pre() + .read_bytes(&validator_slashes_key(&bond_id.validator))? .and_then(|bytes| Slashes::try_from_slice(&bytes[..]).ok()) .unwrap_or_default(); changes.push(Bond { @@ -264,20 +271,19 @@ where slashes, }); } else if let Some(unbond_id) = is_unbond_key(key) { - let pre = self - .ctx - .read_bytes_pre(key)? - .and_then(|bytes| Unbonds::try_from_slice(&bytes[..]).ok()); - let post = self - .ctx - .read_bytes_post(key)? - .and_then(|bytes| Unbonds::try_from_slice(&bytes[..]).ok()); + let pre = + self.ctx.pre().read_bytes(key)?.and_then(|bytes| { + Unbonds::try_from_slice(&bytes[..]).ok() + }); + let post = + self.ctx.post().read_bytes(key)?.and_then(|bytes| { + Unbonds::try_from_slice(&bytes[..]).ok() + }); // For unbonds, we need to look-up slashes let slashes = self .ctx - .read_bytes_pre(&validator_slashes_key( - &unbond_id.validator, - ))? + .pre() + .read_bytes(&validator_slashes_key(&unbond_id.validator))? .and_then(|bytes| Slashes::try_from_slice(&bytes[..]).ok()) .unwrap_or_default(); changes.push(Unbond { @@ -286,10 +292,10 @@ where slashes, }); } else if is_total_voting_power_key(key) { - let pre = self.ctx.read_bytes_pre(key)?.and_then(|bytes| { + let pre = self.ctx.pre().read_bytes(key)?.and_then(|bytes| { TotalVotingPowers::try_from_slice(&bytes[..]).ok() }); - let post = self.ctx.read_bytes_post(key)?.and_then(|bytes| { + let post = self.ctx.post().read_bytes(key)?.and_then(|bytes| { TotalVotingPowers::try_from_slice(&bytes[..]).ok() }); changes.push(TotalVotingPower(Data { pre, post })); @@ -303,7 +309,7 @@ where } } - let params = self.read_pos_params()?; + let params = self.ctx.pre().read_pos_params()?; let errors = validate(¶ms, changes, current_epoch); Ok(if errors.is_empty() { true @@ -317,114 +323,22 @@ where } } -impl PosReadOnly for PosVP<'_, D, H, CA> -where - D: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - type Address = Address; +impl_pos_read_only! { type Error = native_vp::Error; - type PublicKey = key::common::PublicKey; - type TokenAmount = token::Amount; - type TokenChange = token::Change; - - const POS_ADDRESS: Self::Address = super::ADDRESS; - - fn staking_token_address() -> Self::Address { - super::staking_token_address() - } - - fn read_pos_params(&self) -> std::result::Result { - let value = self.ctx.read_bytes_pre(¶ms_key())?.unwrap(); - Ok(decode(value).unwrap()) - } - - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = self - .ctx - .read_bytes_pre(&validator_staking_reward_address_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_consensus_key( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = - self.ctx.read_bytes_pre(&validator_consensus_key_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_state( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = self.ctx.read_bytes_pre(&validator_state_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_total_deltas( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = - self.ctx.read_bytes_pre(&validator_total_deltas_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = - self.ctx.read_bytes_pre(&validator_voting_power_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_slashes( - &self, - key: &Self::Address, - ) -> std::result::Result, Self::Error> { - let value = self.ctx.read_bytes_pre(&validator_slashes_key(key))?; - Ok(value - .map(|value| decode(value).unwrap()) - .unwrap_or_default()) - } - - fn read_bond( - &self, - key: &BondId, - ) -> std::result::Result, Self::Error> { - let value = self.ctx.read_bytes_pre(&bond_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_unbond( - &self, - key: &BondId, - ) -> std::result::Result, Self::Error> { - let value = self.ctx.read_bytes_pre(&unbond_key(key))?; - Ok(value.map(|value| decode(value).unwrap())) - } - - fn read_validator_set( - &self, - ) -> std::result::Result { - let value = self.ctx.read_bytes_pre(&validator_set_key())?.unwrap(); - Ok(decode(value).unwrap()) - } + impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPreStorageRead<'f, 'a, DB, H, CA> + where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, + H: StorageHasher +'static, + CA: WasmCacheAccess +'static +} - fn read_total_voting_power( - &self, - ) -> std::result::Result { - let value = - self.ctx.read_bytes_pre(&total_voting_power_key())?.unwrap(); - Ok(decode(value).unwrap()) - } +impl_pos_read_only! { + type Error = native_vp::Error; + impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPostStorageRead<'f, 'a, DB, H, CA> + where + DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, + H: StorageHasher +'static, + CA: WasmCacheAccess +'static } impl From for Error { @@ -432,3 +346,9 @@ impl From for Error { Self::NativeVpError(err) } } + +impl From for Error { + fn from(err: storage_api::Error) -> Self { + Self::StorageApi(err) + } +} diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 1700be2264..af63d1f175 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -552,7 +552,7 @@ pub mod testing { use namada_tx_prelude::proof_of_stake::{ staking_token_address, BondId, Bonds, PosParams, Unbonds, }; - use namada_tx_prelude::Address; + use namada_tx_prelude::{Address, StorageRead}; use proptest::prelude::*; use crate::tx::{self, tx_host_env}; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index ce856cd876..65a6c3f6cd 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -1,6 +1,5 @@ //! Proof of Stake system integration with functions for transactions -use namada::ledger::pos::types::Slash; pub use namada::ledger::pos::*; use namada::ledger::pos::{ bond_key, namada_proof_of_stake, params_key, total_voting_power_key, @@ -9,7 +8,7 @@ use namada::ledger::pos::{ validator_staking_reward_address_key, validator_state_key, validator_total_deltas_key, validator_voting_power_key, }; -use namada::types::address::{self, Address, InternalAddress}; +use namada::types::address::Address; use namada::types::transaction::InitValidator; use namada::types::{key, token}; pub use namada_proof_of_stake::{ @@ -114,89 +113,9 @@ impl Ctx { } } -impl namada_proof_of_stake::PosReadOnly for Ctx { - type Address = Address; +namada::impl_pos_read_only! { type Error = crate::Error; - type PublicKey = key::common::PublicKey; - type TokenAmount = token::Amount; - type TokenChange = token::Change; - - const POS_ADDRESS: Self::Address = Address::Internal(InternalAddress::PoS); - - fn staking_token_address() -> Self::Address { - address::xan() - } - - fn read_pos_params(&self) -> Result { - let params = self.read(¶ms_key())?; - Ok(params.expect("PoS params should always be set")) - } - - fn read_validator_staking_reward_address( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_staking_reward_address_key(key)) - } - - fn read_validator_consensus_key( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_consensus_key_key(key)) - } - - fn read_validator_state( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_state_key(key)) - } - - fn read_validator_total_deltas( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_total_deltas_key(key)) - } - - fn read_validator_voting_power( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - self.read(&validator_voting_power_key(key)) - } - - fn read_validator_slashes( - &self, - key: &Self::Address, - ) -> Result, Self::Error> { - let val = self.read(&validator_slashes_key(key))?; - Ok(val.unwrap_or_default()) - } - - fn read_bond(&self, key: &BondId) -> Result, Self::Error> { - self.read(&bond_key(key)) - } - - fn read_unbond( - &self, - key: &BondId, - ) -> Result, Self::Error> { - self.read(&unbond_key(key)) - } - - fn read_validator_set(&self) -> Result { - let val = self.read(&validator_set_key())?; - Ok(val.expect("Validator sets must always have a value")) - } - - fn read_total_voting_power( - &self, - ) -> Result { - let val = self.read(&total_voting_power_key())?; - Ok(val.expect("Total voting power must always have a value")) - } + impl namada_proof_of_stake::PosReadOnly for Ctx } impl From> for Error { @@ -237,7 +156,7 @@ impl namada_proof_of_stake::PosActions for Ctx { &mut self, params: &PosParams, ) -> Result<(), Self::Error> { - self.write(¶ms_key(), params) + self.write(¶ms_key(), params).into_env_result() } fn write_validator_address_raw_hash( @@ -246,6 +165,7 @@ impl namada_proof_of_stake::PosActions for Ctx { ) -> Result<(), Self::Error> { let raw_hash = address.raw_hash().unwrap().to_owned(); self.write(&validator_address_raw_hash_key(raw_hash), address) + .into_env_result() } fn write_validator_staking_reward_address( @@ -254,6 +174,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: Self::Address, ) -> Result<(), Self::Error> { self.write(&validator_staking_reward_address_key(key), &value) + .into_env_result() } fn write_validator_consensus_key( @@ -262,6 +183,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorConsensusKeys, ) -> Result<(), Self::Error> { self.write(&validator_consensus_key_key(key), &value) + .into_env_result() } fn write_validator_state( @@ -270,6 +192,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorStates, ) -> Result<(), Self::Error> { self.write(&validator_state_key(key), &value) + .into_env_result() } fn write_validator_total_deltas( @@ -278,6 +201,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorTotalDeltas, ) -> Result<(), Self::Error> { self.write(&validator_total_deltas_key(key), &value) + .into_env_result() } fn write_validator_voting_power( @@ -286,6 +210,7 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorVotingPowers, ) -> Result<(), Self::Error> { self.write(&validator_voting_power_key(key), &value) + .into_env_result() } fn write_bond( @@ -293,7 +218,7 @@ impl namada_proof_of_stake::PosActions for Ctx { key: &BondId, value: Bonds, ) -> Result<(), Self::Error> { - self.write(&bond_key(key), &value) + self.write(&bond_key(key), &value).into_env_result() } fn write_unbond( @@ -301,14 +226,14 @@ impl namada_proof_of_stake::PosActions for Ctx { key: &BondId, value: Unbonds, ) -> Result<(), Self::Error> { - self.write(&unbond_key(key), &value) + self.write(&unbond_key(key), &value).into_env_result() } fn write_validator_set( &mut self, value: ValidatorSets, ) -> Result<(), Self::Error> { - self.write(&validator_set_key(), &value) + self.write(&validator_set_key(), &value).into_env_result() } fn write_total_voting_power( @@ -316,14 +241,15 @@ impl namada_proof_of_stake::PosActions for Ctx { value: TotalVotingPowers, ) -> Result<(), Self::Error> { self.write(&total_voting_power_key(), &value) + .into_env_result() } fn delete_bond(&mut self, key: &BondId) -> Result<(), Self::Error> { - self.delete(&bond_key(key)) + self.delete(&bond_key(key)).into_env_result() } fn delete_unbond(&mut self, key: &BondId) -> Result<(), Self::Error> { - self.delete(&unbond_key(key)) + self.delete(&unbond_key(key)).into_env_result() } fn transfer( From eeb1e8cfd8bc151c7c19bf8c35b22514196fbef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 10:10:53 +0200 Subject: [PATCH 246/373] update wasm checksums --- wasm/checksums.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index b8e98c6cf2..4654a502c5 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.091bf7885488fe9d01643d448236a3c5a8496fc72b1992d98633fa9954a79505.wasm", - "tx_from_intent.wasm": "tx_from_intent.65511fd08da70dc102f8c9f2c4c96fa611d7d2c9b680bcd323da242b1ce6d563.wasm", - "tx_ibc.wasm": "tx_ibc.bfc87f17efbd799cfa73487b7e080f845a6004cdc5f9b26c74cee93587f9d3c4.wasm", - "tx_init_account.wasm": "tx_init_account.ca95fc31bafe84f9bccc4e01336d2d993f4652fef9ea3d0bcfc10edbd9ef30d8.wasm", - "tx_init_nft.wasm": "tx_init_nft.683188dc9eceaf55263d4021bb0fe6c733a74e79998370fc54994eff0d4fd061.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.c4612a75320aa5ceb23cd61da9b43cf89b15feadad9fc28886eadfdeac38bbca.wasm", - "tx_init_validator.wasm": "tx_init_validator.05b94e3de1ff997c830ec0393df6d51fdcbe0410b28e3b9b4cc585c7132141b7.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.e29e4510c1bb9df5b98671ab2b5b5c728d21812924398e810f8d4a00885d9b98.wasm", - "tx_transfer.wasm": "tx_transfer.38739d844a43275e1e4bc18f79a225d1abece7bf302a49763073c1a5188f54d9.wasm", - "tx_unbond.wasm": "tx_unbond.c943d4a1759b2912dc1762e9bce55f16226db12b081afce912e3fa1a28459ba2.wasm", + "tx_bond.wasm": "tx_bond.683e3d5d9ad06b24df792e1a6a5f113fa238bc5635949a8da8f6826a0e338367.wasm", + "tx_from_intent.wasm": "tx_from_intent.51ab8df28b2e09232469043903ed490a42fa04afaa7827b42d5d895906a45ed8.wasm", + "tx_ibc.wasm": "tx_ibc.d79a0434a22727d0c3b926e2fb02969fe02efa58bff02b94b6f64ea6b6c6f0b2.wasm", + "tx_init_account.wasm": "tx_init_account.9a35cbe6781c2df33beb894ac2b1d323d575f859f119a43f34857eb34d7c22d2.wasm", + "tx_init_nft.wasm": "tx_init_nft.8079d16641f9f2af458f3c9818ffa2250994fdd20ec5fcdba87feab23c3576c6.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.3f5057d1a874eaa92f8ba8a8f8afc83a20139edb4bf2056300ce0c8e463950ad.wasm", + "tx_init_validator.wasm": "tx_init_validator.32936b7d7d0cdd961f11a1759f96dc09a0329528f3b2cc03ecf60751fa31f798.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.a41830bed7d64926f8c4e2fe8ea56a7b485a26402fa3b0556d6ba14bc105e808.wasm", + "tx_transfer.wasm": "tx_transfer.11b66a337f337c836e4a7dd3356cc16ecede1c7c14a3dd56ed08c96557c03025.wasm", + "tx_unbond.wasm": "tx_unbond.db1f677a158a5ac1bda63d653a8f22f59ed72e0e425d55918ef70e87a4aa95e0.wasm", "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.7c0b48866b9b31e913f338d0afaf41605f51c0cddf2e3a3f0d231e57e68c72f2.wasm", - "tx_withdraw.wasm": "tx_withdraw.1ec566946f3178c7a7e639e56eb4d1e26ac24e4d5cb856efd1a81bbe59390a3e.wasm", - "vp_nft.wasm": "vp_nft.ea4754591552e715ffdb125b649b3d1bb4b45ca93c3c808a51fa5ccaa9787d5c.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.70588d919e1c3d7a9c2ec37c67c5587c835ab0b9b485b73cec25b9413c9ccfd8.wasm", - "vp_token.wasm": "vp_token.5e619a0471b160712711bf44de9db1950f12900d1405dbcca0a2cea41d16a8a1.wasm", - "vp_user.wasm": "vp_user.3712b862e606effd77fc8c82058d960012ea5aef09580b8521de827484af27d1.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.5e51a66746df8c7e786cc6837a74f87d239442ace67d1c4ef4e6751aa725514f.wasm", + "tx_withdraw.wasm": "tx_withdraw.b4705cbc2eb566faf1d14fff10190970f62aa90c30420e8b3ebbec0a85e7d04b.wasm", + "vp_nft.wasm": "vp_nft.1a32d37b32c616119d156128560a0eeb43206e91069e2c3875e74211ed3ad01f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.4c91dfdf6ac6cdccc931ee391167033a9cdbd1b8551a428e90295bd0d6f68d85.wasm", + "vp_token.wasm": "vp_token.9f27c2e823691487bb6631fbfecb39837c1f404ef9b263e810a5a2a76f4c5957.wasm", + "vp_user.wasm": "vp_user.fb06cb724aa92d615b8da971b0ca1c89bf0caf8c1b9ea3536c252fd1c3b98881.wasm" } \ No newline at end of file From abd10c1079476d4f2643b641a1a5621af4a4007b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 17:18:18 +0200 Subject: [PATCH 247/373] ledger: factor out TxEnv write methods into a new StorageWrite trait --- shared/src/ledger/storage_api/mod.rs | 22 ++++++++++- shared/src/ledger/tx_env.rs | 21 +--------- tests/src/native_vp/pos.rs | 3 +- tests/src/vm_host_env/ibc.rs | 1 + tests/src/vm_host_env/mod.rs | 4 +- tx_prelude/src/ibc.rs | 2 +- tx_prelude/src/lib.rs | 48 ++++++++++++----------- wasm/wasm_source/src/vp_nft.rs | 2 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 2 +- wasm/wasm_source/src/vp_user.rs | 2 +- 10 files changed, 57 insertions(+), 50 deletions(-) diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 0c0e4f4083..36e89a8171 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -3,7 +3,7 @@ mod error; -use borsh::BorshDeserialize; +use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, Result, ResultExt}; use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; @@ -51,3 +51,23 @@ pub trait StorageRead { /// current transaction is being applied. fn get_block_epoch(&self) -> Result; } + +/// Common storage write interface +pub trait StorageWrite { + /// Write a value to be encoded with Borsh at the given key to storage. + fn write( + &mut self, + key: &storage::Key, + val: T, + ) -> Result<()>; + + /// Write a value as bytes at the given key to storage. + fn write_bytes( + &mut self, + key: &storage::Key, + val: impl AsRef<[u8]>, + ) -> Result<()>; + + /// Delete a value at the given key from storage. + fn delete(&mut self, key: &storage::Key) -> Result<()>; +} diff --git a/shared/src/ledger/tx_env.rs b/shared/src/ledger/tx_env.rs index 1fc7fa788c..421cc84687 100644 --- a/shared/src/ledger/tx_env.rs +++ b/shared/src/ledger/tx_env.rs @@ -3,34 +3,17 @@ use borsh::BorshSerialize; -use crate::ledger::storage_api::{self, StorageRead}; +use crate::ledger::storage_api::{StorageRead, StorageWrite}; use crate::types::address::Address; use crate::types::ibc::IbcEvent; use crate::types::storage; use crate::types::time::Rfc3339String; /// Transaction host functions -pub trait TxEnv: StorageRead { +pub trait TxEnv: StorageRead + StorageWrite { /// Host env functions possible errors type Error; - /// Write a value to be encoded with Borsh at the given key to storage. - fn write( - &mut self, - key: &storage::Key, - val: T, - ) -> Result<(), storage_api::Error>; - - /// Write a value as bytes at the given key to storage. - fn write_bytes( - &mut self, - key: &storage::Key, - val: impl AsRef<[u8]>, - ) -> Result<(), storage_api::Error>; - - /// Delete a value at the given key from storage. - fn delete(&mut self, key: &storage::Key) -> Result<(), storage_api::Error>; - /// Write a temporary value to be encoded with Borsh at the given key to /// storage. fn write_temp( diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index af63d1f175..1c68ac0269 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -537,7 +537,6 @@ pub mod testing { use derivative::Derivative; use itertools::Either; use namada::ledger::pos::namada_proof_of_stake::btree_set::BTreeSetShims; - use namada::ledger::tx_env::TxEnv; use namada::types::key::common::PublicKey; use namada::types::key::RefTo; use namada::types::storage::Epoch; @@ -552,7 +551,7 @@ pub mod testing { use namada_tx_prelude::proof_of_stake::{ staking_token_address, BondId, Bonds, PosParams, Unbonds, }; - use namada_tx_prelude::{Address, StorageRead}; + use namada_tx_prelude::{Address, StorageRead, StorageWrite}; use proptest::prelude::*; use crate::tx::{self, tx_host_env}; diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index a838f37819..72b33f9e5e 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -68,6 +68,7 @@ use namada::types::ibc::data::FungibleTokenPacketData; use namada::types::storage::{self, BlockHash, BlockHeight}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; +use namada_tx_prelude::StorageWrite; use crate::tx::{self, *}; diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index cf9dccfb56..37d1afb790 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -34,7 +34,9 @@ mod tests { use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; use namada::types::{address, key}; - use namada_tx_prelude::{BorshDeserialize, BorshSerialize, StorageRead}; + use namada_tx_prelude::{ + BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, + }; use namada_vp_prelude::VpEnv; use prost::Message; use test_log::test; diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 7be4e4c064..4a8a3ef3a9 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -1,7 +1,7 @@ //! IBC lower-level functions for transactions. pub use namada::ledger::ibc::handler::{Error, IbcActions, Result}; -use namada::ledger::storage_api::StorageRead; +use namada::ledger::storage_api::{StorageRead, StorageWrite}; use namada::ledger::tx_env::TxEnv; use namada::types::address::Address; pub use namada::types::ibc::IbcEvent; diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 42b63a892a..dd9b973921 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -23,7 +23,7 @@ pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; use namada::ledger::storage_api; -pub use namada::ledger::storage_api::StorageRead; +pub use namada::ledger::storage_api::{StorageRead, StorageWrite}; pub use namada::ledger::treasury::storage as treasury_storage; pub use namada::ledger::tx_env::TxEnv; pub use namada::proto::{Signed, SignedTxData}; @@ -188,19 +188,7 @@ impl StorageRead for Ctx { } } -impl TxEnv for Ctx { - type Error = Error; - - fn get_block_time(&self) -> Result { - let read_result = unsafe { anoma_tx_get_block_time() }; - let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) - .expect("The block time should exist"); - Ok(Rfc3339String( - String::try_from_slice(&time_value[..]) - .expect("The conversion shouldn't fail"), - )) - } - +impl StorageWrite for Ctx { fn write( &mut self, key: &namada::types::storage::Key, @@ -227,6 +215,29 @@ impl TxEnv for Ctx { Ok(()) } + fn delete( + &mut self, + key: &namada::types::storage::Key, + ) -> storage_api::Result<()> { + let key = key.to_string(); + unsafe { anoma_tx_delete(key.as_ptr() as _, key.len() as _) }; + Ok(()) + } +} + +impl TxEnv for Ctx { + type Error = Error; + + fn get_block_time(&self) -> Result { + let read_result = unsafe { anoma_tx_get_block_time() }; + let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) + .expect("The block time should exist"); + Ok(Rfc3339String( + String::try_from_slice(&time_value[..]) + .expect("The conversion shouldn't fail"), + )) + } + fn write_temp( &mut self, key: &namada::types::storage::Key, @@ -253,15 +264,6 @@ impl TxEnv for Ctx { Ok(()) } - fn delete( - &mut self, - key: &namada::types::storage::Key, - ) -> storage_api::Result<()> { - let key = key.to_string(); - unsafe { anoma_tx_delete(key.as_ptr() as _, key.len() as _) }; - Ok(()) - } - fn insert_verifier(&mut self, addr: &Address) -> Result<(), Error> { let addr = addr.encode(); unsafe { anoma_tx_insert_verifier(addr.as_ptr() as _, addr.len() as _) } diff --git a/wasm/wasm_source/src/vp_nft.rs b/wasm/wasm_source/src/vp_nft.rs index 4ab7f232bd..956a0a5d42 100644 --- a/wasm/wasm_source/src/vp_nft.rs +++ b/wasm/wasm_source/src/vp_nft.rs @@ -43,7 +43,7 @@ mod tests { use namada_tests::log::test; use namada_tests::tx::{self, tx_host_env, TestTxEnv}; use namada_tests::vp::*; - use namada_tx_prelude::TxEnv; + use namada_tx_prelude::{StorageWrite, TxEnv}; use super::*; diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index a3a5630ab7..b599f82251 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -97,7 +97,7 @@ mod tests { use namada_tests::tx::{self, tx_host_env, TestTxEnv}; use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; - use namada_tx_prelude::TxEnv; + use namada_tx_prelude::{StorageWrite, TxEnv}; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 3b6ddcf65f..d20472d9ec 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -380,7 +380,7 @@ mod tests { use namada_tests::tx::{self, tx_host_env, TestTxEnv}; use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; - use namada_tx_prelude::TxEnv; + use namada_tx_prelude::{StorageWrite, TxEnv}; use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; From 08b0a8f1d8aa22813dbda2782aa00be651c8e682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 12 Aug 2022 17:32:34 +0200 Subject: [PATCH 248/373] ledger: impl StorageWrite for Storage --- shared/src/ledger/storage/mod.rs | 40 +++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1f106bcf69..28b160ddb4 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -12,7 +12,7 @@ use tendermint::merkle::proof::Proof; use thiserror::Error; use super::parameters::Parameters; -use super::storage_api::{ResultExt, StorageRead}; +use super::storage_api::{ResultExt, StorageRead, StorageWrite}; use super::{parameters, storage_api}; use crate::ledger::gas::MIN_STORAGE_GAS; use crate::ledger::parameters::EpochDuration; @@ -758,6 +758,44 @@ where } } +impl StorageWrite for Storage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + fn write( + &mut self, + key: &crate::types::storage::Key, + val: T, + ) -> storage_api::Result<()> { + let val = val.try_to_vec().unwrap(); + self.write_bytes(key, val) + } + + fn write_bytes( + &mut self, + key: &crate::types::storage::Key, + val: impl AsRef<[u8]>, + ) -> storage_api::Result<()> { + let _ = self + .db + .write_subspace_val(self.block.height, key, val) + .into_storage_result()?; + Ok(()) + } + + fn delete( + &mut self, + key: &crate::types::storage::Key, + ) -> storage_api::Result<()> { + let _ = self + .db + .delete_subspace_val(self.block.height, key) + .into_storage_result()?; + Ok(()) + } +} + impl From for Error { fn from(error: MerkleTreeError) -> Self { Self::MerkleTreeError(error) From 773033dafcfdec5b159ad7581278d952d8f08b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 16:41:27 +0200 Subject: [PATCH 249/373] add <'iter> lifetime to StorageRead trait to get around lack of GATs --- shared/src/ledger/native_vp.rs | 5 +++-- shared/src/ledger/storage/mod.rs | 7 ++----- shared/src/ledger/storage_api/mod.rs | 16 ++++++++++++++-- shared/src/ledger/tx_env.rs | 2 +- tx_prelude/src/lib.rs | 4 ++-- vp_prelude/src/lib.rs | 4 ++-- 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index fa30efb7ea..5cbab7d639 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -150,7 +150,7 @@ where } } -impl<'f, 'a, DB, H, CA> StorageRead for CtxPreStorageRead<'f, 'a, DB, H, CA> +impl<'f, 'a, DB, H, CA> StorageRead<'f> for CtxPreStorageRead<'f, 'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: 'static + StorageHasher, @@ -210,7 +210,8 @@ where } } -impl<'f, 'a, DB, H, CA> StorageRead for CtxPostStorageRead<'f, 'a, DB, H, CA> +impl<'f, 'a, DB, H, CA> StorageRead<'f> + for CtxPostStorageRead<'f, 'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: 'static + StorageHasher, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 28b160ddb4..1fb84c9a94 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -685,10 +685,7 @@ where } } -// The `'iter` lifetime is needed for the associated type `PrefixIter`. -// Note that the `D: DBIter<'iter>` bound uses another higher-rank lifetime -// (see https://doc.rust-lang.org/nomicon/hrtb.html). -impl<'iter, D, H> StorageRead for &'iter Storage +impl<'iter, D, H> StorageRead<'iter> for Storage where D: DB + for<'iter_> DBIter<'iter_>, H: StorageHasher, @@ -721,7 +718,7 @@ where } fn iter_prefix( - &self, + &'iter self, prefix: &crate::types::storage::Key, ) -> std::result::Result { Ok(self.db.iter_prefix(prefix)) diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 36e89a8171..235108c52f 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -8,8 +8,14 @@ pub use error::{CustomError, Error, Result, ResultExt}; use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; +// TODO: once GATs are stabilized, we should be able to remove the `'iter` +// lifetime param that is currently the only way to make the prefix iterator +// typecheck in the `>::PrefixIter` associated type used in +// `impl StorageRead for Storage` (shared/src/ledger/storage/mod.rs). +// +// See /// Common storage read interface -pub trait StorageRead { +pub trait StorageRead<'iter> { /// Storage read prefix iterator type PrefixIter; @@ -28,7 +34,13 @@ pub trait StorageRead { /// Storage prefix iterator. It will try to get an iterator from the /// storage. - fn iter_prefix(&self, prefix: &storage::Key) -> Result; + /// + /// For a more user-friendly iterator API, use [`fn@iter_prefix`] or + /// [`fn@iter_prefix_bytes`] instead. + fn iter_prefix( + &'iter self, + prefix: &storage::Key, + ) -> Result; /// Storage prefix iterator for. It will try to read from the storage. fn iter_next( diff --git a/shared/src/ledger/tx_env.rs b/shared/src/ledger/tx_env.rs index 421cc84687..1db8fad09b 100644 --- a/shared/src/ledger/tx_env.rs +++ b/shared/src/ledger/tx_env.rs @@ -10,7 +10,7 @@ use crate::types::storage; use crate::types::time::Rfc3339String; /// Transaction host functions -pub trait TxEnv: StorageRead + StorageWrite { +pub trait TxEnv<'iter>: StorageRead<'iter> + StorageWrite { /// Host env functions possible errors type Error; diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index dd9b973921..02341e70e5 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -95,7 +95,7 @@ pub type TxResult = EnvResult<()>; #[derive(Debug)] pub struct KeyValIterator(pub u64, pub PhantomData); -impl StorageRead for Ctx { +impl StorageRead<'_> for Ctx { type PrefixIter = KeyValIterator<(String, Vec)>; fn read( @@ -225,7 +225,7 @@ impl StorageWrite for Ctx { } } -impl TxEnv for Ctx { +impl TxEnv<'_> for Ctx { type Error = Error; fn get_block_time(&self) -> Result { diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 37d1958aa9..24d702b6b5 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -295,7 +295,7 @@ impl VpEnv for Ctx { } } -impl StorageRead for CtxPreStorageRead<'_> { +impl StorageRead<'_> for CtxPreStorageRead<'_> { type PrefixIter = KeyValIterator<(String, Vec)>; fn read( @@ -372,7 +372,7 @@ impl StorageRead for CtxPreStorageRead<'_> { } } -impl StorageRead for CtxPostStorageRead<'_> { +impl StorageRead<'_> for CtxPostStorageRead<'_> { type PrefixIter = KeyValIterator<(String, Vec)>; fn read( From 28b4307e92a4ac01fdab65aa422dc5ba2f6e90f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 17:01:14 +0200 Subject: [PATCH 250/373] add more comments for StorageRead lifetime with an example usage --- shared/src/ledger/storage_api/mod.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 235108c52f..0e7e299970 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -8,13 +8,26 @@ pub use error::{CustomError, Error, Result, ResultExt}; use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; -// TODO: once GATs are stabilized, we should be able to remove the `'iter` -// lifetime param that is currently the only way to make the prefix iterator -// typecheck in the `>::PrefixIter` associated type used in -// `impl StorageRead for Storage` (shared/src/ledger/storage/mod.rs). -// -// See /// Common storage read interface +/// +/// If you're using this trait and having compiler complaining about needing an +/// explicit lifetime parameter, simply use trait bounds with the following +/// syntax: +/// +/// ```rust,ignore +/// where +/// S: for<'iter> StorageRead<'iter> +/// ``` +/// +/// If you want to know why this is needed, see the to-do task below. The +/// syntax for this relies on higher-rank lifetimes, see e.g. +/// . +/// +/// TODO: once GATs are stabilized, we should be able to remove the `'iter` +/// lifetime param that is currently the only way to make the prefix iterator +/// typecheck in the `>::PrefixIter` associated type used in +/// `impl StorageRead for Storage` (shared/src/ledger/storage/mod.rs). +/// See pub trait StorageRead<'iter> { /// Storage read prefix iterator type PrefixIter; From 8bc5816483f6eec4980e757815d3a656d73215e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 17:48:14 +0200 Subject: [PATCH 251/373] storage: remove unnecessary clone --- shared/src/ledger/storage/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1fb84c9a94..bb97ab8fae 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -424,12 +424,13 @@ where pub fn write( &mut self, key: &Key, - value: impl AsRef<[u8]> + Clone, + value: impl AsRef<[u8]>, ) -> Result<(u64, i64)> { tracing::debug!("storage write key {}", key,); - self.block.tree.update(key, value.clone())?; + let value = value.as_ref(); + self.block.tree.update(key, &value)?; - let len = value.as_ref().len(); + let len = value.len(); let gas = key.len() + len; let size_diff = self.db.write_subspace_val(self.last_height, key, value)?; From 7decfab26b9c3d2b165f574e80cb5e2f27146307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 17:48:26 +0200 Subject: [PATCH 252/373] fix missing StorageWrite for Storage merkle tree update --- shared/src/ledger/storage/mod.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index bb97ab8fae..d8c8e28091 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -426,6 +426,8 @@ where key: &Key, value: impl AsRef<[u8]>, ) -> Result<(u64, i64)> { + // Note that this method is the same as `StorageWrite::write_bytes`, + // but with gas and storage bytes len diff accounting tracing::debug!("storage write key {}", key,); let value = value.as_ref(); self.block.tree.update(key, &value)?; @@ -440,6 +442,8 @@ where /// Delete the specified subspace and returns the gas cost and the size /// difference pub fn delete(&mut self, key: &Key) -> Result<(u64, i64)> { + // Note that this method is the same as `StorageWrite::delete`, + // but with gas and storage bytes len diff accounting let mut deleted_bytes_len = 0; if self.has_key(key)?.0 { self.block.tree.delete(key)?; @@ -766,7 +770,7 @@ where key: &crate::types::storage::Key, val: T, ) -> storage_api::Result<()> { - let val = val.try_to_vec().unwrap(); + let val = val.try_to_vec().into_storage_result()?; self.write_bytes(key, val) } @@ -775,6 +779,11 @@ where key: &crate::types::storage::Key, val: impl AsRef<[u8]>, ) -> storage_api::Result<()> { + // Note that this method is the same as `Storage::write`, but without + // gas and storage bytes len diff accounting, because it can only be + // used by the protocol that has a direct mutable access to storage + let val = val.as_ref(); + self.block.tree.update(key, &val).into_storage_result()?; let _ = self .db .write_subspace_val(self.block.height, key, val) @@ -786,6 +795,10 @@ where &mut self, key: &crate::types::storage::Key, ) -> storage_api::Result<()> { + // Note that this method is the same as `Storage::delete`, but without + // gas and storage bytes len diff accounting, because it can only be + // used by the protocol that has a direct mutable access to storage + self.block.tree.delete(key).into_storage_result()?; let _ = self .db .delete_subspace_val(self.block.height, key) From b92edd43b720b404584e306d081621c7314a0886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sat, 20 Aug 2022 19:11:40 +0200 Subject: [PATCH 253/373] update wasm checksums --- wasm/checksums.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 4654a502c5..5c658485f0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,17 +1,17 @@ { - "tx_bond.wasm": "tx_bond.683e3d5d9ad06b24df792e1a6a5f113fa238bc5635949a8da8f6826a0e338367.wasm", - "tx_from_intent.wasm": "tx_from_intent.51ab8df28b2e09232469043903ed490a42fa04afaa7827b42d5d895906a45ed8.wasm", - "tx_ibc.wasm": "tx_ibc.d79a0434a22727d0c3b926e2fb02969fe02efa58bff02b94b6f64ea6b6c6f0b2.wasm", - "tx_init_account.wasm": "tx_init_account.9a35cbe6781c2df33beb894ac2b1d323d575f859f119a43f34857eb34d7c22d2.wasm", - "tx_init_nft.wasm": "tx_init_nft.8079d16641f9f2af458f3c9818ffa2250994fdd20ec5fcdba87feab23c3576c6.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.3f5057d1a874eaa92f8ba8a8f8afc83a20139edb4bf2056300ce0c8e463950ad.wasm", - "tx_init_validator.wasm": "tx_init_validator.32936b7d7d0cdd961f11a1759f96dc09a0329528f3b2cc03ecf60751fa31f798.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.a41830bed7d64926f8c4e2fe8ea56a7b485a26402fa3b0556d6ba14bc105e808.wasm", - "tx_transfer.wasm": "tx_transfer.11b66a337f337c836e4a7dd3356cc16ecede1c7c14a3dd56ed08c96557c03025.wasm", - "tx_unbond.wasm": "tx_unbond.db1f677a158a5ac1bda63d653a8f22f59ed72e0e425d55918ef70e87a4aa95e0.wasm", + "tx_bond.wasm": "tx_bond.98a51e759f7b41873faee295fa69ee919d64ee95e4aa5cd923daff59325fda94.wasm", + "tx_from_intent.wasm": "tx_from_intent.c598df3ee8cc5980f83df56b1acd8132c85421d360a4f54c1235c8576f0d3390.wasm", + "tx_ibc.wasm": "tx_ibc.c838d61c5ad5b5f24855f19a85fda1bad159701b90a4672e98db6d8fee966880.wasm", + "tx_init_account.wasm": "tx_init_account.02a6a717930a35962e5d2fddd76bb78194796379d7c584645ce8c30b1e672ae6.wasm", + "tx_init_nft.wasm": "tx_init_nft.09974068959257551e82281839f4506fcc377713d174984c25af37455e08ac84.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.a9a45a5a4ec4fffe2e28d8bec8c013dc571e62d01b730eea2d09fc12a3c700dd.wasm", + "tx_init_validator.wasm": "tx_init_validator.03d30dad39d35d4285ff012af80ad3e94f8d10d802410aac4a7fd1c86e8d3f6c.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.32747cf99afd40b14114812bea65198d454eb9d7c86d6300a8ed9306df9aa9be.wasm", + "tx_transfer.wasm": "tx_transfer.05c24ab9ad8bdd09bc910d9bbfc8686fc5c436594b635cc2105f0ddde5c8ab63.wasm", + "tx_unbond.wasm": "tx_unbond.a3ffc49d7b481e1d43c7f67cbccf9ce31476f98a9822b6b57ddf51c33f427605.wasm", "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.5e51a66746df8c7e786cc6837a74f87d239442ace67d1c4ef4e6751aa725514f.wasm", - "tx_withdraw.wasm": "tx_withdraw.b4705cbc2eb566faf1d14fff10190970f62aa90c30420e8b3ebbec0a85e7d04b.wasm", + "tx_withdraw.wasm": "tx_withdraw.efab1590d68edee54058ddf03313697be4aaa8adf8b012f98338be514e9f6e87.wasm", "vp_nft.wasm": "vp_nft.1a32d37b32c616119d156128560a0eeb43206e91069e2c3875e74211ed3ad01f.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.4c91dfdf6ac6cdccc931ee391167033a9cdbd1b8551a428e90295bd0d6f68d85.wasm", "vp_token.wasm": "vp_token.9f27c2e823691487bb6631fbfecb39837c1f404ef9b263e810a5a2a76f4c5957.wasm", From 2a537465d799c43b5d6cea903037b445c5a01291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sun, 21 Aug 2022 12:45:34 +0200 Subject: [PATCH 254/373] changelog: add #318 --- .changelog/unreleased/improvements/318-refactor-pos-vp.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/improvements/318-refactor-pos-vp.md diff --git a/.changelog/unreleased/improvements/318-refactor-pos-vp.md b/.changelog/unreleased/improvements/318-refactor-pos-vp.md new file mode 100644 index 0000000000..5ed78c3cc6 --- /dev/null +++ b/.changelog/unreleased/improvements/318-refactor-pos-vp.md @@ -0,0 +1 @@ +- Refactored PoS VP logic ([#318](https://github.com/anoma/namada/pull/318)) \ No newline at end of file From ff268e1527298e24580f23b41cb1d0d1608c5aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sun, 21 Aug 2022 14:20:14 +0200 Subject: [PATCH 255/373] changelog: add #324 --- .../unreleased/improvements/324-common-read-storage-trait.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/324-common-read-storage-trait.md diff --git a/.changelog/unreleased/improvements/324-common-read-storage-trait.md b/.changelog/unreleased/improvements/324-common-read-storage-trait.md new file mode 100644 index 0000000000..b8e9defe95 --- /dev/null +++ b/.changelog/unreleased/improvements/324-common-read-storage-trait.md @@ -0,0 +1,3 @@ +- Added a StorageRead trait for a common interface for VPs prior and posterior + state, transactions and direct storage access for protocol and RPC handlers + ([#324](https://github.com/anoma/namada/pull/324)) \ No newline at end of file From ace6716fba7be13c4424e919ca0aae631f0b5fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sun, 21 Aug 2022 14:26:44 +0200 Subject: [PATCH 256/373] changelog: add #331 --- .../unreleased/improvements/331-common-write-storage-trait.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/331-common-write-storage-trait.md diff --git a/.changelog/unreleased/improvements/331-common-write-storage-trait.md b/.changelog/unreleased/improvements/331-common-write-storage-trait.md new file mode 100644 index 0000000000..2e3e605197 --- /dev/null +++ b/.changelog/unreleased/improvements/331-common-write-storage-trait.md @@ -0,0 +1,2 @@ +- Added a StorageWrite trait for a common interface for transactions and direct + storage access for protocol ([#331](https://github.com/anoma/namada/pull/331)) \ No newline at end of file From ead98a38a9b55100fe08dd217b2dd25904eae72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Sun, 21 Aug 2022 14:24:33 +0200 Subject: [PATCH 257/373] changelog: add #326 --- .changelog/unreleased/bug-fixes/326-fix-validator-raw-hash.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/326-fix-validator-raw-hash.md diff --git a/.changelog/unreleased/bug-fixes/326-fix-validator-raw-hash.md b/.changelog/unreleased/bug-fixes/326-fix-validator-raw-hash.md new file mode 100644 index 0000000000..bf8ef22579 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/326-fix-validator-raw-hash.md @@ -0,0 +1,2 @@ +- Fixed validator raw hash corresponding to validator address in Tendermint + ([#326](https://github.com/anoma/namada/pull/326)) \ No newline at end of file From fb97e3c915323d97b6de4461bc9115a3725418d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 22 Aug 2022 08:55:26 +0200 Subject: [PATCH 258/373] pos: update validator raw hash validation --- Cargo.lock | 1 + proof_of_stake/Cargo.toml | 1 + proof_of_stake/src/types.rs | 6 +++ proof_of_stake/src/validation.rs | 76 ++++++++++++++++----------- shared/src/ledger/pos/vp.rs | 11 ---- shared/src/types/key/common.rs | 12 ++++- shared/src/types/key/mod.rs | 6 +++ tests/src/native_vp/pos.rs | 71 ++++++++++++++++++------- wasm/tx_template/Cargo.lock | 1 + wasm/vp_template/Cargo.lock | 1 + wasm/wasm_source/Cargo.lock | 1 + wasm_for_tests/wasm_source/Cargo.lock | 1 + 12 files changed, 126 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f9c1414272..f93cae918e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3994,6 +3994,7 @@ name = "namada_proof_of_stake" version = "0.7.0" dependencies = [ "borsh", + "derivative", "proptest", "thiserror", ] diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 449df509d0..ca7ecc5ca2 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -18,5 +18,6 @@ borsh = "0.9.1" thiserror = "1.0.30" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm", optional = true} +derivative = "2.2.0" [dev-dependencies] diff --git a/proof_of_stake/src/types.rs b/proof_of_stake/src/types.rs index 45342ef277..f7945b4525 100644 --- a/proof_of_stake/src/types.rs +++ b/proof_of_stake/src/types.rs @@ -354,6 +354,12 @@ pub enum SlashType { )] pub struct BasisPoints(u64); +/// Derive Tendermint raw hash from the public key +pub trait PublicKeyTmRawHash { + /// Derive Tendermint raw hash from the public key + fn tm_raw_hash(&self) -> String; +} + impl VotingPower { /// Convert token amount into a voting power. pub fn from_tokens(tokens: impl Into, params: &PosParams) -> Self { diff --git a/proof_of_stake/src/validation.rs b/proof_of_stake/src/validation.rs index 41485bcbb0..604dd9a89d 100644 --- a/proof_of_stake/src/validation.rs +++ b/proof_of_stake/src/validation.rs @@ -8,21 +8,22 @@ use std::hash::Hash; use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use derivative::Derivative; use thiserror::Error; use crate::btree_set::BTreeSetShims; use crate::epoched::DynEpochOffset; use crate::parameters::PosParams; use crate::types::{ - BondId, Bonds, Epoch, Slashes, TotalVotingPowers, Unbonds, - ValidatorConsensusKeys, ValidatorSets, ValidatorState, ValidatorStates, - ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, VotingPowerDelta, - WeightedValidator, + BondId, Bonds, Epoch, PublicKeyTmRawHash, Slashes, TotalVotingPowers, + Unbonds, ValidatorConsensusKeys, ValidatorSets, ValidatorState, + ValidatorStates, ValidatorTotalDeltas, ValidatorVotingPowers, VotingPower, + VotingPowerDelta, WeightedValidator, }; #[allow(missing_docs)] #[derive(Error, Debug)] -pub enum Error +pub enum Error where Address: Display + Debug @@ -36,6 +37,7 @@ where + BorshSchema + BorshDeserialize, TokenChange: Debug + Display, + PublicKey: Debug, { #[error("Unexpectedly missing state value for validator {0}")] ValidatorStateIsRequired(Address), @@ -158,7 +160,7 @@ where #[error("Invalid address raw hash update")] InvalidRawHashUpdate, #[error("Invalid new validator {0}, some fields are missing: {1:?}.")] - InvalidNewValidator(Address, NewValidator), + InvalidNewValidator(Address, NewValidator), #[error("New validator {0} has not been added to the validator set.")] NewValidatorMissingInValidatorSet(Address), #[error("Validator set has not been updated for new validators.")] @@ -241,8 +243,8 @@ where ValidatorAddressRawHash { /// Raw hash value raw_hash: String, - /// The address and raw hash derived from it - data: Data<(Address, String)>, + /// The validator's address + data: Data
, }, } @@ -291,14 +293,16 @@ where /// A new validator account initialized in a transaction, which is used to check /// that all the validator's required fields have been written. -#[derive(Clone, Debug, Default)] -pub struct NewValidator { +#[derive(Clone, Debug, Derivative)] +// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound +#[derivative(Default(bound = ""))] +pub struct NewValidator { has_state: bool, - has_consensus_key: bool, + has_consensus_key: Option, has_total_deltas: bool, has_voting_power: bool, has_staking_reward_address: bool, - has_address_raw_hash: bool, + has_address_raw_hash: Option, voting_power: VotingPower, } @@ -309,7 +313,7 @@ pub fn validate( params: &PosParams, changes: Vec>, current_epoch: impl Into, -) -> Vec> +) -> Vec> where Address: Display + Debug @@ -362,7 +366,8 @@ where + BorshDeserialize + BorshSerialize + BorshSchema - + PartialEq, + + PartialEq + + PublicKeyTmRawHash, { let current_epoch = current_epoch.into(); use DataUpdate::*; @@ -408,7 +413,8 @@ where VotingPowerDelta, > = HashMap::default(); - let mut new_validators: HashMap = HashMap::default(); + let mut new_validators: HashMap> = + HashMap::default(); for change in changes { match change { @@ -493,16 +499,19 @@ where } // The value must be known at pipeline epoch match post.get(pipeline_epoch) { - Some(_) => {} + Some(consensus_key) => { + let validator = new_validators + .entry(address.clone()) + .or_default(); + validator.has_consensus_key = + Some(consensus_key.clone()); + } _ => errors.push( Error::MissingNewValidatorConsensusKey( pipeline_epoch.into(), ), ), } - let validator = - new_validators.entry(address.clone()).or_default(); - validator.has_consensus_key = true; } (Some(pre), Some(post)) => { if post.last_update() != current_epoch { @@ -1231,16 +1240,10 @@ where }, ValidatorAddressRawHash { raw_hash, data } => { match (data.pre, data.post) { - (None, Some((address, expected_raw_hash))) => { - if raw_hash != expected_raw_hash { - errors.push(Error::InvalidAddressRawHash( - raw_hash, - expected_raw_hash, - )) - } + (None, Some(address)) => { let validator = new_validators.entry(address.clone()).or_default(); - validator.has_address_raw_hash = true; + validator.has_address_raw_hash = Some(raw_hash); } (pre, post) if pre != post => { errors.push(Error::InvalidRawHashUpdate) @@ -1641,17 +1644,30 @@ where } = &new_validator; // The new validator must have set all the required fields if !(*has_state - && *has_consensus_key && *has_total_deltas && *has_voting_power - && *has_staking_reward_address - && *has_address_raw_hash) + && *has_staking_reward_address) { errors.push(Error::InvalidNewValidator( address.clone(), new_validator.clone(), )) } + match (has_address_raw_hash, has_consensus_key) { + (Some(raw_hash), Some(consensus_key)) => { + let expected_raw_hash = consensus_key.tm_raw_hash(); + if raw_hash != &expected_raw_hash { + errors.push(Error::InvalidAddressRawHash( + raw_hash.clone(), + expected_raw_hash, + )) + } + } + _ => errors.push(Error::InvalidNewValidator( + address.clone(), + new_validator.clone(), + )), + } let weighted_validator = WeightedValidator { voting_power: *voting_power, address: address.clone(), diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 26be440536..f09e421e43 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -215,17 +215,6 @@ where .ctx .read_post(key)? .and_then(|bytes| Address::try_from_slice(&bytes[..]).ok()); - // Find the raw hashes of the addresses - let pre = pre.map(|pre| { - let raw_hash = - pre.raw_hash().map(String::from).unwrap_or_default(); - (pre, raw_hash) - }); - let post = post.map(|post| { - let raw_hash = - post.raw_hash().map(String::from).unwrap_or_default(); - (post, raw_hash) - }); changes.push(ValidatorAddressRawHash { raw_hash: raw_hash.to_string(), data: Data { pre, post }, diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 27e7c29b89..d3401258d1 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -4,13 +4,15 @@ use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use namada_proof_of_stake::types::PublicKeyTmRawHash; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use super::{ - ed25519, ParsePublicKeyError, ParseSecretKeyError, ParseSignatureError, - RefTo, SchemeType, SigScheme as SigSchemeTrait, VerifySigError, + ed25519, tm_consensus_key_raw_hash, ParsePublicKeyError, + ParseSecretKeyError, ParseSignatureError, RefTo, SchemeType, + SigScheme as SigSchemeTrait, VerifySigError, }; /// Public key @@ -275,3 +277,9 @@ impl super::SigScheme for SigScheme { } } } + +impl PublicKeyTmRawHash for PublicKey { + fn tm_raw_hash(&self) -> String { + tm_consensus_key_raw_hash(self) + } +} diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index 59dd6b2cd5..9b560a4c84 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -395,6 +395,12 @@ pub mod testing { }) } + /// Generate an arbitrary [`common::SecretKey`]. + pub fn arb_common_keypair() -> impl Strategy { + arb_keypair::() + .prop_map(|keypair| keypair.try_to_sk().unwrap()) + } + /// Generate a new random [`super::SecretKey`]. pub fn gen_keypair() -> S::SecretKey { let mut rng: ThreadRng = thread_rng(); diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 83878f6965..1779be7e21 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -104,6 +104,7 @@ mod tests { use namada::ledger::pos::namada_proof_of_stake::PosBase; use namada::ledger::pos::PosParams; + use namada::types::key::common::PublicKey; use namada::types::storage::Epoch; use namada::types::token; use namada_vm_env::proof_of_stake::parameters::testing::arb_pos_params; @@ -163,6 +164,7 @@ mod tests { } /// State machine transitions + #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug)] enum Transition { /// Commit all the tx changes already applied in the tx env @@ -379,8 +381,12 @@ mod tests { Transition::CommitTx => true, Transition::NextEpoch => true, Transition::Valid(action) => match action { - ValidPosAction::InitValidator(address) => { + ValidPosAction::InitValidator { + address, + consensus_key, + } => { !state.is_validator(address) + && !state.is_used_key(consensus_key) } ValidPosAction::Bond { amount: _, @@ -457,7 +463,19 @@ mod tests { /// Find if the given address is a validator fn is_validator(&self, addr: &Address) -> bool { self.all_valid_actions().iter().any(|action| match action { - ValidPosAction::InitValidator(validator) => validator == addr, + ValidPosAction::InitValidator { address, .. } => { + address == addr + } + _ => false, + }) + } + + /// Find if the given consensus key is already used by any validators + fn is_used_key(&self, given_consensus_key: &PublicKey) -> bool { + self.all_valid_actions().iter().any(|action| match action { + ValidPosAction::InitValidator { consensus_key, .. } => { + consensus_key == given_consensus_key + } _ => false, }) } @@ -568,7 +586,10 @@ pub mod testing { #[derive(Clone, Debug)] pub enum ValidPosAction { - InitValidator(Address), + InitValidator { + address: Address, + consensus_key: PublicKey, + }, Bond { amount: token::Amount, owner: Address, @@ -666,13 +687,21 @@ pub mod testing { let validators: Vec
= valid_actions .iter() .filter_map(|action| match action { - ValidPosAction::InitValidator(addr) => Some(addr.clone()), + ValidPosAction::InitValidator { address, .. } => { + Some(address.clone()) + } _ => None, }) .collect(); - let init_validator = address::testing::arb_established_address() - .prop_map(|addr| { - ValidPosAction::InitValidator(Address::Established(addr)) + let init_validator = ( + address::testing::arb_established_address(), + key::testing::arb_common_keypair(), + ) + .prop_map(|(addr, consensus_key)| { + ValidPosAction::InitValidator { + address: Address::Established(addr), + consensus_key: consensus_key.ref_to(), + } }); if validators.is_empty() { @@ -816,45 +845,47 @@ pub mod testing { use namada_vm_env::tx_prelude::PosRead; match self { - ValidPosAction::InitValidator(addr) => { + ValidPosAction::InitValidator { + address, + consensus_key, + } => { let offset = DynEpochOffset::PipelineLen; - let consensus_key = key::testing::keypair_1().ref_to(); vec![ PosStorageChange::SpawnAccount { - address: addr.clone(), + address: address.clone(), }, PosStorageChange::ValidatorAddressRawHash { - address: addr.clone(), + address: address.clone(), consensus_key: consensus_key.clone(), }, PosStorageChange::ValidatorSet { - validator: addr.clone(), + validator: address.clone(), token_delta: 0, offset, }, PosStorageChange::ValidatorConsensusKey { - validator: addr.clone(), + validator: address.clone(), pk: consensus_key, }, PosStorageChange::ValidatorStakingRewardsAddress { - validator: addr.clone(), + validator: address.clone(), address: address::testing::established_address_1(), }, PosStorageChange::ValidatorState { - validator: addr.clone(), + validator: address.clone(), state: ValidatorState::Pending, }, PosStorageChange::ValidatorState { - validator: addr.clone(), + validator: address.clone(), state: ValidatorState::Candidate, }, PosStorageChange::ValidatorTotalDeltas { - validator: addr.clone(), + validator: address.clone(), delta: 0, offset, }, PosStorageChange::ValidatorVotingPower { - validator: addr, + validator: address, vp_delta: 0, offset: Either::Left(offset), }, @@ -1577,7 +1608,9 @@ pub mod testing { let validators: Vec
= valid_actions .iter() .filter_map(|action| match action { - ValidPosAction::InitValidator(addr) => Some(addr.clone()), + ValidPosAction::InitValidator { address, .. } => { + Some(address.clone()) + } _ => None, }) .collect(); diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index bf9d222920..dbab33357a 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1409,6 +1409,7 @@ name = "namada_proof_of_stake" version = "0.7.0" dependencies = [ "borsh", + "derivative", "proptest", "thiserror", ] diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 98f6c7f71c..d363d8940b 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1409,6 +1409,7 @@ name = "namada_proof_of_stake" version = "0.7.0" dependencies = [ "borsh", + "derivative", "proptest", "thiserror", ] diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index b7185cc110..b57c7ea681 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1409,6 +1409,7 @@ name = "namada_proof_of_stake" version = "0.7.0" dependencies = [ "borsh", + "derivative", "proptest", "thiserror", ] diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 39f70c1787..ef4d313e75 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1420,6 +1420,7 @@ name = "namada_proof_of_stake" version = "0.7.0" dependencies = [ "borsh", + "derivative", "proptest", "thiserror", ] From 4b8d423802c15290dd6ae85939d7ceb06721c1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 22 Aug 2022 11:28:02 +0200 Subject: [PATCH 259/373] update wasm checksums --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5d670022b7..c9f4b9e0ba 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.71acaf1ff3a4ac1ce6550c4ce594cf70d684213769f45641be5cdb4b7321184e.wasm", - "tx_from_intent.wasm": "tx_from_intent.e18907b1fb0e1c4e4946228c0ec57838a783a5d88c6b3129d44935386693cee8.wasm", - "tx_ibc.wasm": "tx_ibc.38ee97db63b765f1639ee24dd3e7f264139f146a56f7c06edafde63eaecedade.wasm", - "tx_init_account.wasm": "tx_init_account.6be05f8ca240d9e978dfe6a1566b3860e07fa3aee334003fc5d85a5a1ae99cf7.wasm", - "tx_init_nft.wasm": "tx_init_nft.5f710b641c38cbe7822c2359bf7a764e864e952f4660ccff9b58b97fbaa4b3a2.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.25666fc8fbf251bd4ad478cb968f1d50a8100a0a7aae0e3ee5e50e28304e8e3e.wasm", - "tx_init_validator.wasm": "tx_init_validator.2b1a4c947242ddddf94a491b8c571185f649913606bfa7d37f668ddc86fe1049.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9af8eea9219db09161b98405c18ea3c82db03b982f69a66ce1d117e64a715c3c.wasm", - "tx_transfer.wasm": "tx_transfer.87e4fba9d2476388e0f2b1d4bc00c396ac7b8b7b38d5079735fe53bc5984b583.wasm", - "tx_unbond.wasm": "tx_unbond.61f2ac46e7680ec35aba367ec908ac63aa43d6765cd07da932aa9ea45d77be46.wasm", - "tx_update_vp.wasm": "tx_update_vp.00d828bdf510d92d971ca59015945c8c00f285a802a655451cc5ee87c5ee99bc.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.92ed628aca7856e2b0fe461c0a0a65c6f7c1f585ac542c10feceef0e4b9ce611.wasm", - "tx_withdraw.wasm": "tx_withdraw.fb54d108a6fe7c6db16e573110c4a9f52158ec993e04e5a06e1425b8dd676c54.wasm", - "vp_nft.wasm": "vp_nft.3421840146f8d2e6c3ce3a0d94bbb0dad6ef296c50e9d00744179d290f32745b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1b37ab6939280fbb54512918053362fae34e3db74f029ef31c9f08e767539afb.wasm", - "vp_token.wasm": "vp_token.08dcec2d3ecb0942d0a244bbd176f9a42424ec09544120db1b30c0d97ed7dbb1.wasm", - "vp_user.wasm": "vp_user.404f089f0c73fcf906f7d6059d74c6bab3e2e3d3a108394503bbc22588ff95b9.wasm" + "tx_bond.wasm": "tx_bond.1cd47906cc24865f295f67930e1584c4caaea7a38b8fefd5f5fa38167cba0608.wasm", + "tx_from_intent.wasm": "tx_from_intent.5ebf8c0763c8631fbc048646dceef5fd925319fd6e80676d67c90e70ed597167.wasm", + "tx_ibc.wasm": "tx_ibc.d4aa65a59d3a38f650b6b920c56bafebb9989a94d5d32104e72b3676402983cf.wasm", + "tx_init_account.wasm": "tx_init_account.9b76e18e80fb9894ae591702a2c226d382fe52cc9ff5428ae3c10c82266098ed.wasm", + "tx_init_nft.wasm": "tx_init_nft.237a81e0ab6bf2a19003b08044432bda05cad2ec5cbb3c3b3463433f01918e54.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.613c467b1bdc576a83687e83363124f460080f8c9ddddb90b75eb00b62e45eb6.wasm", + "tx_init_validator.wasm": "tx_init_validator.dc8dc9979714b1aee26888e721e5d35aacbb8acbea077ecac5e8350e8e7c6cee.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.0017721b896e677fc1e69f76e77e5f7896d5a561df6df867f1364179e461a788.wasm", + "tx_transfer.wasm": "tx_transfer.d7e60a93823dfac3c1588e2a6818253cd24c602d34a63eb7ebfcb2de3925a321.wasm", + "tx_unbond.wasm": "tx_unbond.86bcc51a3cc51c487a9aebd49692886c0e15ecd64ee62455bd1c34f61a5a06b3.wasm", + "tx_update_vp.wasm": "tx_update_vp.a3321c096bd9ff43affd4bdfa50f57e85ec11366f163d5a8b2bc560c5647b5ad.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.f91efec45813cf6242ea433b9a41c85b6a0541a4c96d43976a87aee688a5d57f.wasm", + "tx_withdraw.wasm": "tx_withdraw.4c042548ade425e169fbb74588340a1589b4953d069db0add357d1b80659e75d.wasm", + "vp_nft.wasm": "vp_nft.916ffad1a706c72739116fd21773e1f9eb2c1f1a57cb37046e6f6ce9985e154f.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.2cfa8bbfbd02e19dbe69508b5d233c902e2c52d6b0703ccf3669cc8c87cc58a7.wasm", + "vp_token.wasm": "vp_token.99413e885b6ef6cd068cf44e83e89f85286c8da1a8dd1efff2f0b03ddef65b94.wasm", + "vp_user.wasm": "vp_user.f85e0befe71b24302f627e9a19eddd3e8e264280ac6bbc7f538c62f5c8cd7c04.wasm" } \ No newline at end of file From 999df37b8675742ee703556871e5850e64da19e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 15 Aug 2022 15:54:35 +0200 Subject: [PATCH 260/373] storage_api: add default borsh encoded read/write impl and handle errors --- shared/src/ledger/native_vp.rs | 14 -------------- shared/src/ledger/storage/mod.rs | 20 -------------------- shared/src/ledger/storage_api/mod.rs | 16 ++++++++++++++-- tx_prelude/src/lib.rs | 20 -------------------- vp_prelude/src/lib.rs | 28 ---------------------------- 5 files changed, 14 insertions(+), 84 deletions(-) diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 5cbab7d639..41fbc1cda3 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -158,13 +158,6 @@ where { type PrefixIter = >::PrefixIter; - fn read( - &self, - key: &crate::types::storage::Key, - ) -> Result, storage_api::Error> { - self.ctx.read_pre(key).into_storage_result() - } - fn read_bytes( &self, key: &crate::types::storage::Key, @@ -219,13 +212,6 @@ where { type PrefixIter = >::PrefixIter; - fn read( - &self, - key: &crate::types::storage::Key, - ) -> Result, storage_api::Error> { - self.ctx.read_post(key).into_storage_result() - } - fn read_bytes( &self, key: &crate::types::storage::Key, diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index d8c8e28091..235e2164e3 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -697,17 +697,6 @@ where { type PrefixIter = >::PrefixIter; - fn read( - &self, - key: &crate::types::storage::Key, - ) -> std::result::Result, storage_api::Error> { - self.read_bytes(key) - .map(|maybe_value| { - maybe_value.and_then(|t| T::try_from_slice(&t[..]).ok()) - }) - .into_storage_result() - } - fn read_bytes( &self, key: &crate::types::storage::Key, @@ -765,15 +754,6 @@ where D: DB + for<'iter> DBIter<'iter>, H: StorageHasher, { - fn write( - &mut self, - key: &crate::types::storage::Key, - val: T, - ) -> storage_api::Result<()> { - let val = val.try_to_vec().into_storage_result()?; - self.write_bytes(key, val) - } - fn write_bytes( &mut self, key: &crate::types::storage::Key, diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 0e7e299970..873c14ef9b 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -37,7 +37,16 @@ pub trait StorageRead<'iter> { fn read( &self, key: &storage::Key, - ) -> Result>; + ) -> Result> { + let bytes = self.read_bytes(key)?; + match bytes { + Some(bytes) => { + let val = T::try_from_slice(&bytes).into_storage_result()?; + Ok(Some(val)) + } + None => Ok(None), + } + } /// Storage read raw bytes. It will try to read from the storage. fn read_bytes(&self, key: &storage::Key) -> Result>>; @@ -84,7 +93,10 @@ pub trait StorageWrite { &mut self, key: &storage::Key, val: T, - ) -> Result<()>; + ) -> Result<()> { + let bytes = val.try_to_vec().into_storage_result()?; + self.write_bytes(key, bytes) + } /// Write a value as bytes at the given key to storage. fn write_bytes( diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 02341e70e5..ade290bf65 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -98,17 +98,6 @@ pub struct KeyValIterator(pub u64, pub PhantomData); impl StorageRead<'_> for Ctx { type PrefixIter = KeyValIterator<(String, Vec)>; - fn read( - &self, - key: &namada::types::storage::Key, - ) -> Result, storage_api::Error> { - let key = key.to_string(); - let read_result = - unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; - Ok(read_from_buffer(read_result, anoma_tx_result_buffer) - .and_then(|t| T::try_from_slice(&t[..]).ok())) - } - fn read_bytes( &self, key: &namada::types::storage::Key, @@ -189,15 +178,6 @@ impl StorageRead<'_> for Ctx { } impl StorageWrite for Ctx { - fn write( - &mut self, - key: &namada::types::storage::Key, - val: T, - ) -> storage_api::Result<()> { - let buf = val.try_to_vec().unwrap(); - self.write_bytes(key, buf) - } - fn write_bytes( &mut self, key: &namada::types::storage::Key, diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 24d702b6b5..056d9cb1d5 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -298,20 +298,6 @@ impl VpEnv for Ctx { impl StorageRead<'_> for CtxPreStorageRead<'_> { type PrefixIter = KeyValIterator<(String, Vec)>; - fn read( - &self, - key: &storage::Key, - ) -> Result, storage_api::Error> { - let bytes = self.read_bytes(key)?; - match bytes { - Some(bytes) => match T::try_from_slice(&bytes[..]) { - Ok(val) => Ok(Some(val)), - Err(err) => Err(storage_api::Error::new(err)), - }, - None => Ok(None), - } - } - fn read_bytes( &self, key: &storage::Key, @@ -375,20 +361,6 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { impl StorageRead<'_> for CtxPostStorageRead<'_> { type PrefixIter = KeyValIterator<(String, Vec)>; - fn read( - &self, - key: &storage::Key, - ) -> Result, storage_api::Error> { - let bytes = self.read_bytes(key)?; - match bytes { - Some(bytes) => match T::try_from_slice(&bytes[..]) { - Ok(val) => Ok(Some(val)), - Err(err) => Err(storage_api::Error::new(err)), - }, - None => Ok(None), - } - } - fn read_bytes( &self, key: &storage::Key, From a5473788002c9847734b08c42fdc0548fbde4fcb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 15 Aug 2022 16:00:41 +0000 Subject: [PATCH 261/373] wasm checksums update --- wasm/checksums.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5c658485f0..2478056cb0 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.98a51e759f7b41873faee295fa69ee919d64ee95e4aa5cd923daff59325fda94.wasm", - "tx_from_intent.wasm": "tx_from_intent.c598df3ee8cc5980f83df56b1acd8132c85421d360a4f54c1235c8576f0d3390.wasm", - "tx_ibc.wasm": "tx_ibc.c838d61c5ad5b5f24855f19a85fda1bad159701b90a4672e98db6d8fee966880.wasm", - "tx_init_account.wasm": "tx_init_account.02a6a717930a35962e5d2fddd76bb78194796379d7c584645ce8c30b1e672ae6.wasm", - "tx_init_nft.wasm": "tx_init_nft.09974068959257551e82281839f4506fcc377713d174984c25af37455e08ac84.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.a9a45a5a4ec4fffe2e28d8bec8c013dc571e62d01b730eea2d09fc12a3c700dd.wasm", - "tx_init_validator.wasm": "tx_init_validator.03d30dad39d35d4285ff012af80ad3e94f8d10d802410aac4a7fd1c86e8d3f6c.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.32747cf99afd40b14114812bea65198d454eb9d7c86d6300a8ed9306df9aa9be.wasm", - "tx_transfer.wasm": "tx_transfer.05c24ab9ad8bdd09bc910d9bbfc8686fc5c436594b635cc2105f0ddde5c8ab63.wasm", - "tx_unbond.wasm": "tx_unbond.a3ffc49d7b481e1d43c7f67cbccf9ce31476f98a9822b6b57ddf51c33f427605.wasm", + "tx_bond.wasm": "tx_bond.078d5be45d839fc1b6c034c4fdfe2055add709daa05868826682d3b6abf27ac4.wasm", + "tx_from_intent.wasm": "tx_from_intent.dcdfc49a02c76f277afac84e3511ad47c70b3bf54d1273a15c6dc75648316937.wasm", + "tx_ibc.wasm": "tx_ibc.3957053e0ea9caaa49128994711339aea6ede77a99f150688df3de870c8b17d4.wasm", + "tx_init_account.wasm": "tx_init_account.92e59887817a6d789dc8a85bb1eff3c86bd0a9bd1ddc8a9e0e5de5c9e9c2ddc6.wasm", + "tx_init_nft.wasm": "tx_init_nft.4125bdf149533cd201895cfaf6cdfbb9616182c187137029fe3c9214030f1877.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8da848905d5a8ad1b15046d5e59e04f307bde73dcc8d2ab0b6d8d235a31a8b52.wasm", + "tx_init_validator.wasm": "tx_init_validator.364786e78253bd9ce72d089cc1539a882eb8ef6fd8c818616d003241b47ac010.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.b2c34b21a2f0b533e3dcd4b2ee64e45ca00ccad8b3f22c410474c7a67c3302e8.wasm", + "tx_transfer.wasm": "tx_transfer.6fac9a68bcd1a50d9cec64605f8637dfa897ce3be242edc344858cf4075fc100.wasm", + "tx_unbond.wasm": "tx_unbond.eeaf8ff32984275288b0a9a36c9579dace6f3ecfaf59255af769acf57a00df4a.wasm", "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.5e51a66746df8c7e786cc6837a74f87d239442ace67d1c4ef4e6751aa725514f.wasm", - "tx_withdraw.wasm": "tx_withdraw.efab1590d68edee54058ddf03313697be4aaa8adf8b012f98338be514e9f6e87.wasm", - "vp_nft.wasm": "vp_nft.1a32d37b32c616119d156128560a0eeb43206e91069e2c3875e74211ed3ad01f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4c91dfdf6ac6cdccc931ee391167033a9cdbd1b8551a428e90295bd0d6f68d85.wasm", - "vp_token.wasm": "vp_token.9f27c2e823691487bb6631fbfecb39837c1f404ef9b263e810a5a2a76f4c5957.wasm", - "vp_user.wasm": "vp_user.fb06cb724aa92d615b8da971b0ca1c89bf0caf8c1b9ea3536c252fd1c3b98881.wasm" + "tx_vote_proposal.wasm": "tx_vote_proposal.8f306e1d1bcc9ca7f47a39108554fc847d4ac6df7a8d87a6effdffeae6adc4c4.wasm", + "tx_withdraw.wasm": "tx_withdraw.c288861e842f0b1ac6fbb95b14b33c8819ac587bbd5b483f2cdd0ea184206c65.wasm", + "vp_nft.wasm": "vp_nft.557883861270a0a5e42802ce4bfbbc28f9f0dfa3b76520ffdd319b69a4f2f34c.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.1fb8c53e7c164d141661743f3cdfaf30d30e6e16abd2f8814c0d8829a2e36e8a.wasm", + "vp_token.wasm": "vp_token.b130ca28e8029f5ad7a33bf3d4fbb9a3b08a29484feba34b988e902c4ce9c966.wasm", + "vp_user.wasm": "vp_user.2c45ba3e0b442210d4dd953679c774cc15f8773c5c25bda7f2ad60eaaff2d0a9.wasm" } \ No newline at end of file From 6f0be8fa9ac507e5868765070b5b0a0f16a1a4d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 22 Aug 2022 13:10:15 +0200 Subject: [PATCH 262/373] changelog: add #334 --- .../unreleased/improvements/334-refactor-storage-read-write.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/334-refactor-storage-read-write.md diff --git a/.changelog/unreleased/improvements/334-refactor-storage-read-write.md b/.changelog/unreleased/improvements/334-refactor-storage-read-write.md new file mode 100644 index 0000000000..0642596268 --- /dev/null +++ b/.changelog/unreleased/improvements/334-refactor-storage-read-write.md @@ -0,0 +1,2 @@ +- Re-use encoding/decoding storage write/read and handle any errors + ([#334](https://github.com/anoma/namada/pull/334)) \ No newline at end of file From d737acd9ecdeff80fc62712fa19c571ace76097e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 15 Aug 2022 17:51:04 +0200 Subject: [PATCH 263/373] storage_api: build a nicer `iter_prefix` function on top of StorageRead --- shared/src/ledger/storage_api/mod.rs | 66 ++++++++++++++++++++++++++++ tx_prelude/src/lib.rs | 4 +- vp_prelude/src/lib.rs | 10 +++-- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 0e7e299970..ab2f73784f 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -96,3 +96,69 @@ pub trait StorageWrite { /// Delete a value at the given key from storage. fn delete(&mut self, key: &storage::Key) -> Result<()>; } + +/// Iterate items matching the given prefix. +pub fn iter_prefix_bytes<'a>( + storage: &'a impl StorageRead<'a>, + prefix: &crate::types::storage::Key, +) -> Result)>> + 'a> { + let iter = storage.iter_prefix(prefix)?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((key, val))) => { + let key = match storage::Key::parse(key).into_storage_result() { + Ok(key) => key, + Err(err) => { + // Propagate key encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + Some(Ok((key, val))) + } + Ok(None) => None, + Err(err) => { + // Propagate `iter_next` errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) +} + +/// Iterate Borsh encoded items matching the given prefix. +pub fn iter_prefix<'a, T>( + storage: &'a impl StorageRead<'a>, + prefix: &crate::types::storage::Key, +) -> Result> + 'a> +where + T: BorshDeserialize, +{ + let iter = storage.iter_prefix(prefix)?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((key, val))) => { + let key = match storage::Key::parse(key).into_storage_result() { + Ok(key) => key, + Err(err) => { + // Propagate key encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + let val = match T::try_from_slice(&val).into_storage_result() { + Ok(val) => val, + Err(err) => { + // Propagate val encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + Some(Ok((key, val))) + } + Ok(None) => None, + Err(err) => { + // Propagate `iter_next` errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) +} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index 02341e70e5..bd1833ec58 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -23,7 +23,9 @@ pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; use namada::ledger::storage_api; -pub use namada::ledger::storage_api::{StorageRead, StorageWrite}; +pub use namada::ledger::storage_api::{ + iter_prefix, iter_prefix_bytes, StorageRead, StorageWrite, +}; pub use namada::ledger::treasury::storage as treasury_storage; pub use namada::ledger::tx_env::TxEnv; pub use namada::proto::{Signed, SignedTxData}; diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 24d702b6b5..3ee761f78f 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -22,7 +22,9 @@ use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; pub use error::*; pub use namada::ledger::governance::storage as gov_storage; -pub use namada::ledger::storage_api::{self, StorageRead}; +pub use namada::ledger::storage_api::{ + self, iter_prefix, iter_prefix_bytes, StorageRead, +}; pub use namada::ledger::vp_env::VpEnv; pub use namada::ledger::{parameters, pos as proof_of_stake}; pub use namada::proto::{Signed, SignedTxData}; @@ -334,7 +336,7 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { prefix: &storage::Key, ) -> Result { // Note that this is the same as `CtxPostStorageRead` - iter_prefix(prefix) + iter_prefix_impl(prefix) } fn iter_next( @@ -411,7 +413,7 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { prefix: &storage::Key, ) -> Result { // Note that this is the same as `CtxPreStorageRead` - iter_prefix(prefix) + iter_prefix_impl(prefix) } fn iter_next( @@ -442,7 +444,7 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { } } -fn iter_prefix( +fn iter_prefix_impl( prefix: &storage::Key, ) -> Result)>, storage_api::Error> { let prefix = prefix.to_string(); From 4558dfa42248a9282bb6d4b5a492ac22bb3aa040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 15 Aug 2022 17:59:08 +0200 Subject: [PATCH 264/373] test/vm_host_env: refactor prefix iter tests with new `iter_prefix` fn --- tests/src/vm_host_env/mod.rs | 51 ++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 37d1afb790..98f95e7fae 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -148,9 +148,11 @@ mod tests { tx_host_env::init(); let empty_key = storage::Key::parse("empty").unwrap(); - let mut iter = tx::ctx().iter_prefix(&empty_key).unwrap(); + let mut iter = + namada_tx_prelude::iter_prefix_bytes(tx::ctx(), &empty_key) + .unwrap(); assert!( - tx::ctx().iter_next(&mut iter).unwrap().is_none(), + iter.next().is_none(), "Trying to iter a prefix that doesn't have any matching keys \ should yield an empty iterator." ); @@ -167,15 +169,12 @@ mod tests { }); // Then try to iterate over their prefix - let iter = tx::ctx().iter_prefix(&prefix).unwrap(); - let iter = itertools::unfold(iter, |iter| { - if let Ok(Some((key, value))) = tx::ctx().iter_next(iter) { - let decoded_value = i32::try_from_slice(&value[..]).unwrap(); - return Some((key, decoded_value)); - } - None + let iter = namada_tx_prelude::iter_prefix(tx::ctx(), &prefix) + .unwrap() + .map(|item| item.unwrap()); + let expected = (0..10).map(|i| { + (storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), i) }); - let expected = (0..10).map(|i| (format!("{}/{}", prefix, i), i)); itertools::assert_equal(iter.sorted(), expected.sorted()); } @@ -380,29 +379,25 @@ mod tests { tx::ctx().write(&new_key, 11_i32).unwrap(); }); - let iter_pre = vp::CTX.iter_prefix(&prefix).unwrap(); - let iter_pre = itertools::unfold(iter_pre, |iter| { - if let Ok(Some((key, value))) = vp::CTX.iter_pre_next(iter) { - if let Ok(decoded_value) = i32::try_from_slice(&value[..]) { - return Some((key, decoded_value)); - } - } - None + let ctx_pre = vp::CTX.pre(); + let iter_pre = namada_vp_prelude::iter_prefix(&ctx_pre, &prefix) + .unwrap() + .map(|item| item.unwrap()); + let expected_pre = (0..10).map(|i| { + (storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), i) }); - let expected_pre = (0..10).map(|i| (format!("{}/{}", prefix, i), i)); itertools::assert_equal(iter_pre.sorted(), expected_pre.sorted()); - let iter_post = vp::CTX.iter_prefix(&prefix).unwrap(); - let iter_post = itertools::unfold(iter_post, |iter| { - if let Ok(Some((key, value))) = vp::CTX.iter_post_next(iter) { - let decoded_value = i32::try_from_slice(&value[..]).unwrap(); - return Some((key, decoded_value)); - } - None - }); + let ctx_post = vp::CTX.post(); + let iter_post = namada_vp_prelude::iter_prefix(&ctx_post, &prefix) + .unwrap() + .map(|item| item.unwrap()); let expected_post = (0..10).map(|i| { let val = if i == 5 { 100 } else { i }; - (format!("{}/{}", prefix, i), val) + ( + storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), + val, + ) }); itertools::assert_equal(iter_post.sorted(), expected_post.sorted()); } From d32d7af363124d52d41cd7f4a0758fa0fb88d97e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 15 Aug 2022 16:34:57 +0000 Subject: [PATCH 265/373] wasm checksums update --- wasm/checksums.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 5c658485f0..0c7b7cf504 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.98a51e759f7b41873faee295fa69ee919d64ee95e4aa5cd923daff59325fda94.wasm", - "tx_from_intent.wasm": "tx_from_intent.c598df3ee8cc5980f83df56b1acd8132c85421d360a4f54c1235c8576f0d3390.wasm", - "tx_ibc.wasm": "tx_ibc.c838d61c5ad5b5f24855f19a85fda1bad159701b90a4672e98db6d8fee966880.wasm", - "tx_init_account.wasm": "tx_init_account.02a6a717930a35962e5d2fddd76bb78194796379d7c584645ce8c30b1e672ae6.wasm", - "tx_init_nft.wasm": "tx_init_nft.09974068959257551e82281839f4506fcc377713d174984c25af37455e08ac84.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.a9a45a5a4ec4fffe2e28d8bec8c013dc571e62d01b730eea2d09fc12a3c700dd.wasm", - "tx_init_validator.wasm": "tx_init_validator.03d30dad39d35d4285ff012af80ad3e94f8d10d802410aac4a7fd1c86e8d3f6c.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.32747cf99afd40b14114812bea65198d454eb9d7c86d6300a8ed9306df9aa9be.wasm", - "tx_transfer.wasm": "tx_transfer.05c24ab9ad8bdd09bc910d9bbfc8686fc5c436594b635cc2105f0ddde5c8ab63.wasm", - "tx_unbond.wasm": "tx_unbond.a3ffc49d7b481e1d43c7f67cbccf9ce31476f98a9822b6b57ddf51c33f427605.wasm", + "tx_bond.wasm": "tx_bond.d80c5ac518c223eea92a51eeb033462e9ea4498530d12de5b6e6ca6b3fa59ea8.wasm", + "tx_from_intent.wasm": "tx_from_intent.49565974c6e2c3d64a05729bd57217b244d804fc9f4e5369d1f3515aeaa8397f.wasm", + "tx_ibc.wasm": "tx_ibc.0d9d639037a8dc54c53ecbedc8396ea103ee7c8f485790ddf7223f4e7e6a9779.wasm", + "tx_init_account.wasm": "tx_init_account.97bfee0b78c87abc217c58351f1d6242990a06c7379b27f948950f04f36b49a2.wasm", + "tx_init_nft.wasm": "tx_init_nft.6207eabda37cd356b6093d069f388bd84463b5e1b8860811a36a9b63da84951f.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.1073d69f69172276c61c104f7b4678019723df19ddb30aedf6293a00de973846.wasm", + "tx_init_validator.wasm": "tx_init_validator.40f0152c1bd59f46ec26123d98d0b49a0a458335b6012818cf8504b7708bf625.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.f1c8039a6fb5e01e7441cfa2e3fb2f84a1cb60ed660745ddc172d0d01f1b0ab1.wasm", + "tx_transfer.wasm": "tx_transfer.191d28c340900e6dc297e66b054cd83b690ae0357a8e13b37962b265b2e17da8.wasm", + "tx_unbond.wasm": "tx_unbond.ea73369f68abef405c4f7a3a09c3da6aa68493108d48b1e4e243d26766f00283.wasm", "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.5e51a66746df8c7e786cc6837a74f87d239442ace67d1c4ef4e6751aa725514f.wasm", - "tx_withdraw.wasm": "tx_withdraw.efab1590d68edee54058ddf03313697be4aaa8adf8b012f98338be514e9f6e87.wasm", + "tx_withdraw.wasm": "tx_withdraw.f776265133f972e6705797561b6bb37f9e21de07f3611b23cfdd6e39cb252c0f.wasm", "vp_nft.wasm": "vp_nft.1a32d37b32c616119d156128560a0eeb43206e91069e2c3875e74211ed3ad01f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.4c91dfdf6ac6cdccc931ee391167033a9cdbd1b8551a428e90295bd0d6f68d85.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.c30baa6d2dab2c336a6b2485e3ccf41cd548b135ca5245b80fab15858c70365c.wasm", "vp_token.wasm": "vp_token.9f27c2e823691487bb6631fbfecb39837c1f404ef9b263e810a5a2a76f4c5957.wasm", - "vp_user.wasm": "vp_user.fb06cb724aa92d615b8da971b0ca1c89bf0caf8c1b9ea3536c252fd1c3b98881.wasm" + "vp_user.wasm": "vp_user.59b7a9262c951ff451d3aec0604ae3dd0fc4729f8d994c114be6320ce5d38712.wasm" } \ No newline at end of file From fec72205d4417afa358121674d2110ffb81c2a06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 22 Aug 2022 13:27:11 +0200 Subject: [PATCH 266/373] changelog: add #335 --- .../improvements/335-refactor-storage-prefix-iter.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/335-refactor-storage-prefix-iter.md diff --git a/.changelog/unreleased/improvements/335-refactor-storage-prefix-iter.md b/.changelog/unreleased/improvements/335-refactor-storage-prefix-iter.md new file mode 100644 index 0000000000..d51f6c72f0 --- /dev/null +++ b/.changelog/unreleased/improvements/335-refactor-storage-prefix-iter.md @@ -0,0 +1,3 @@ +- Added a simpler prefix iterator API that returns `std::iter::Iterator` with + the storage keys parsed and a variant that also decodes stored values with + Borsh ([#335](https://github.com/anoma/namada/pull/335)) \ No newline at end of file From ca5f0040e5b9942151ac24733dc9c5afab6ee13d Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 16 Aug 2022 13:40:19 +0100 Subject: [PATCH 267/373] Specify --target-dir when building wasms Handles the case where a custom $CARGO_TARGET_DIR is set --- wasm/wasm_source/Makefile | 2 +- wasm_for_tests/wasm_source/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/wasm_source/Makefile b/wasm/wasm_source/Makefile index 39562a4a1e..7ef59778dd 100644 --- a/wasm/wasm_source/Makefile +++ b/wasm/wasm_source/Makefile @@ -50,7 +50,7 @@ fmt-check: # Build a selected wasm # Linker flag "-s" for stripping (https://github.com/rust-lang/cargo/issues/3483#issuecomment-431209957) $(wasms): %: - RUSTFLAGS='-C link-arg=-s' $(cargo) build --release --target wasm32-unknown-unknown --features $@ && \ + RUSTFLAGS='-C link-arg=-s' $(cargo) build --release --target wasm32-unknown-unknown --target-dir 'target' --features $@ && \ cp "./target/wasm32-unknown-unknown/release/namada_wasm.wasm" ../$@.wasm # `cargo check` one of the wasms, e.g. `make check_tx_transfer` diff --git a/wasm_for_tests/wasm_source/Makefile b/wasm_for_tests/wasm_source/Makefile index 142199dbc9..38ed4a890f 100644 --- a/wasm_for_tests/wasm_source/Makefile +++ b/wasm_for_tests/wasm_source/Makefile @@ -46,7 +46,7 @@ fmt-check: # Build a selected wasm $(wasms): %: - $(cargo) build --release --target wasm32-unknown-unknown --features $@ && \ + $(cargo) build --release --target wasm32-unknown-unknown --target-dir 'target' --features $@ && \ cp "./target/wasm32-unknown-unknown/release/namada_wasm_for_tests.wasm" ../$@.wasm # `cargo check` one of the wasms, e.g. `make check_tx_no_op` From 476be4828578b39aab3b1990400d349edaf12d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 22 Aug 2022 14:45:41 +0200 Subject: [PATCH 268/373] changelog: add #337 --- .changelog/unreleased/improvements/337-wasm-cargo-target-dir.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/337-wasm-cargo-target-dir.md diff --git a/.changelog/unreleased/improvements/337-wasm-cargo-target-dir.md b/.changelog/unreleased/improvements/337-wasm-cargo-target-dir.md new file mode 100644 index 0000000000..4cc10d7292 --- /dev/null +++ b/.changelog/unreleased/improvements/337-wasm-cargo-target-dir.md @@ -0,0 +1,2 @@ +- Handles the case where a custom `$CARGO_TARGET_DIR` is set during WASM build + ([#337](https://github.com/anoma/anoma/pull/337)) \ No newline at end of file From e4ff05f71753717ce8893dab72b0a908d36d2d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 26 Jul 2022 11:52:30 +0200 Subject: [PATCH 269/373] ledger: fix last_epoch to only change when committing block --- shared/src/ledger/storage/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 0b3d19a742..d9e1fb548c 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -358,6 +358,7 @@ where }; self.db.write_block(state)?; self.last_height = self.block.height; + self.last_epoch = self.block.epoch; self.header = None; Ok(()) } @@ -597,8 +598,6 @@ where if new_epoch { // Begin a new epoch self.block.epoch = self.block.epoch.next(); - self.last_epoch = self.last_epoch.next(); - debug_assert_eq!(self.block.epoch, self.last_epoch); let EpochDuration { min_num_of_blocks, min_duration, From 91d038e1b998aa781054c657ce8db3cebae11834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 26 Jul 2022 12:23:42 +0200 Subject: [PATCH 270/373] test/ledger: update last_epoch assertion --- shared/src/ledger/storage/mod.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index d9e1fb548c..124ee61d57 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -824,7 +824,6 @@ mod tests { ) { assert_eq!(storage.block.epoch, epoch_before.next()); - assert_eq!(storage.last_epoch, epoch_before.next()); assert_eq!(storage.next_epoch_min_start_height, block_height + epoch_duration.min_num_of_blocks); assert_eq!(storage.next_epoch_min_start_time, @@ -832,9 +831,10 @@ mod tests { assert_eq!(storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before.next())); } else { assert_eq!(storage.block.epoch, epoch_before); - assert_eq!(storage.last_epoch, epoch_before); assert_eq!(storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before)); } + // Last epoch should only change when the block is committed + assert_eq!(storage.last_epoch, epoch_before); // Update the epoch duration parameters parameters.epoch_duration.min_num_of_blocks = @@ -848,7 +848,7 @@ mod tests { parameters::update_epoch_parameter(&mut storage, ¶meters.epoch_duration).unwrap(); // Test for 2. - let epoch_before = storage.last_epoch; + let epoch_before = storage.block.epoch; let height_of_update = storage.next_epoch_min_start_height.0 ; let time_of_update = storage.next_epoch_min_start_time; let height_before_update = BlockHeight(height_of_update - 1); @@ -859,18 +859,14 @@ mod tests { // satisfied storage.update_epoch(height_before_update, time_before_update).unwrap(); assert_eq!(storage.block.epoch, epoch_before); - assert_eq!(storage.last_epoch, epoch_before); storage.update_epoch(height_of_update, time_before_update).unwrap(); assert_eq!(storage.block.epoch, epoch_before); - assert_eq!(storage.last_epoch, epoch_before); storage.update_epoch(height_before_update, time_of_update).unwrap(); assert_eq!(storage.block.epoch, epoch_before); - assert_eq!(storage.last_epoch, epoch_before); // Update should happen at this or after this height and time storage.update_epoch(height_of_update, time_of_update).unwrap(); assert_eq!(storage.block.epoch, epoch_before.next()); - assert_eq!(storage.last_epoch, epoch_before.next()); // The next epoch's minimum duration should change assert_eq!(storage.next_epoch_min_start_height, height_of_update + parameters.epoch_duration.min_num_of_blocks); From 096b45a4d9f9fdee7b08c3b0ed76df602bdff0ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 26 Jul 2022 12:31:04 +0200 Subject: [PATCH 271/373] changelog: add #1249 --- .changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md diff --git a/.changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md b/.changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md new file mode 100644 index 0000000000..d4419e52a5 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/1249-fix-shell-last-epoch.md @@ -0,0 +1,2 @@ +- Fix the `last_epoch` field in the shell to only be updated when the block is + committed. ([#1249](https://github.com/anoma/anoma/pull/1249)) \ No newline at end of file From 2d177f8bd3548739818524255892589b9d424a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 25 Aug 2022 10:30:32 +0200 Subject: [PATCH 272/373] shared: Add pre/post to VpEnv and use them to provide default impls --- shared/src/ledger/native_vp.rs | 191 +++++++++++---------------- shared/src/ledger/storage_api/mod.rs | 12 +- shared/src/ledger/vp_env.rs | 142 ++++++++++++-------- vp_prelude/src/lib.rs | 122 ++++++----------- 4 files changed, 210 insertions(+), 257 deletions(-) diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 41fbc1cda3..f17b339086 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -76,25 +76,25 @@ where /// Read access to the prior storage (state before tx execution) via /// [`trait@StorageRead`]. #[derive(Debug)] -pub struct CtxPreStorageRead<'b, 'a: 'b, DB, H, CA> +pub struct CtxPreStorageRead<'view, 'a: 'view, DB, H, CA> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, CA: WasmCacheAccess, { - ctx: &'b Ctx<'a, DB, H, CA>, + ctx: &'view Ctx<'a, DB, H, CA>, } /// Read access to the posterior storage (state after tx execution) via /// [`trait@StorageRead`]. #[derive(Debug)] -pub struct CtxPostStorageRead<'f, 'a: 'f, DB, H, CA> +pub struct CtxPostStorageRead<'view, 'a: 'view, DB, H, CA> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, CA: WasmCacheAccess, { - ctx: &'f Ctx<'a, DB, H, CA>, + ctx: &'view Ctx<'a, DB, H, CA>, } impl<'a, DB, H, CA> Ctx<'a, DB, H, CA> @@ -139,18 +139,21 @@ where /// Read access to the prior storage (state before tx execution) /// via [`trait@StorageRead`]. - pub fn pre<'b>(&'b self) -> CtxPreStorageRead<'b, 'a, DB, H, CA> { + pub fn pre<'view>(&'view self) -> CtxPreStorageRead<'view, 'a, DB, H, CA> { CtxPreStorageRead { ctx: self } } /// Read access to the posterior storage (state after tx execution) /// via [`trait@StorageRead`]. - pub fn post<'b>(&'b self) -> CtxPostStorageRead<'b, 'a, DB, H, CA> { + pub fn post<'view>( + &'view self, + ) -> CtxPostStorageRead<'view, 'a, DB, H, CA> { CtxPostStorageRead { ctx: self } } } -impl<'f, 'a, DB, H, CA> StorageRead<'f> for CtxPreStorageRead<'f, 'a, DB, H, CA> +impl<'view, 'a, DB, H, CA> StorageRead<'view> + for CtxPreStorageRead<'view, 'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: 'static + StorageHasher, @@ -162,16 +165,38 @@ where &self, key: &crate::types::storage::Key, ) -> Result>, storage_api::Error> { - self.ctx.read_bytes_pre(key).into_storage_result() + vp_env::read_pre( + &mut *self.ctx.gas_meter.borrow_mut(), + self.ctx.storage, + self.ctx.write_log, + key, + ) + .into_storage_result() } fn has_key( &self, key: &crate::types::storage::Key, ) -> Result { - self.ctx.has_key_pre(key).into_storage_result() + vp_env::has_key_pre( + &mut *self.ctx.gas_meter.borrow_mut(), + self.ctx.storage, + key, + ) + .into_storage_result() } + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + vp_env::iter_pre_next::(&mut *self.ctx.gas_meter.borrow_mut(), iter) + .into_storage_result() + } + + // ---- Methods below are implemented in `self.ctx`, because they are + // the same in `pre/post` ---- + fn iter_prefix( &self, prefix: &crate::types::storage::Key, @@ -179,13 +204,6 @@ where self.ctx.iter_prefix(prefix).into_storage_result() } - fn iter_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { - self.ctx.iter_pre_next(iter).into_storage_result() - } - fn get_chain_id(&self) -> Result { self.ctx.get_chain_id().into_storage_result() } @@ -203,8 +221,8 @@ where } } -impl<'f, 'a, DB, H, CA> StorageRead<'f> - for CtxPostStorageRead<'f, 'a, DB, H, CA> +impl<'view, 'a, DB, H, CA> StorageRead<'view> + for CtxPostStorageRead<'view, 'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: 'static + StorageHasher, @@ -216,16 +234,43 @@ where &self, key: &crate::types::storage::Key, ) -> Result>, storage_api::Error> { - self.ctx.read_bytes_post(key).into_storage_result() + vp_env::read_post( + &mut *self.ctx.gas_meter.borrow_mut(), + self.ctx.storage, + self.ctx.write_log, + key, + ) + .into_storage_result() } fn has_key( &self, key: &crate::types::storage::Key, ) -> Result { - self.ctx.has_key_post(key).into_storage_result() + vp_env::has_key_post( + &mut *self.ctx.gas_meter.borrow_mut(), + self.ctx.storage, + self.ctx.write_log, + key, + ) + .into_storage_result() } + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>, storage_api::Error> { + vp_env::iter_post_next::( + &mut *self.ctx.gas_meter.borrow_mut(), + self.ctx.write_log, + iter, + ) + .into_storage_result() + } + + // ---- Methods below are implemented in `self.ctx`, because they are + // the same in `pre/post` ---- + fn iter_prefix( &self, prefix: &crate::types::storage::Key, @@ -233,13 +278,6 @@ where self.ctx.iter_prefix(prefix).into_storage_result() } - fn iter_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { - self.ctx.iter_post_next(iter).into_storage_result() - } - fn get_chain_id(&self) -> Result { self.ctx.get_chain_id().into_storage_result() } @@ -257,63 +295,23 @@ where } } -impl<'a, DB, H, CA> VpEnv for Ctx<'a, DB, H, CA> +impl<'view, 'a: 'view, DB, H, CA> VpEnv<'view> for Ctx<'a, DB, H, CA> where DB: 'static + storage::DB + for<'iter> storage::DBIter<'iter>, H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { type Error = Error; + type Post = CtxPostStorageRead<'view, 'a, DB, H, CA>; + type Pre = CtxPreStorageRead<'view, 'a, DB, H, CA>; type PrefixIter = >::PrefixIter; - fn read_pre( - &self, - key: &Key, - ) -> Result, Self::Error> { - vp_env::read_pre( - &mut *self.gas_meter.borrow_mut(), - self.storage, - self.write_log, - key, - ) - .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) - } - - fn read_bytes_pre( - &self, - key: &Key, - ) -> Result>, Self::Error> { - vp_env::read_pre( - &mut *self.gas_meter.borrow_mut(), - self.storage, - self.write_log, - key, - ) - } - - fn read_post( - &self, - key: &Key, - ) -> Result, Self::Error> { - vp_env::read_post( - &mut *self.gas_meter.borrow_mut(), - self.storage, - self.write_log, - key, - ) - .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) + fn pre(&'view self) -> Self::Pre { + CtxPreStorageRead { ctx: self } } - fn read_bytes_post( - &self, - key: &Key, - ) -> Result>, Self::Error> { - vp_env::read_post( - &mut *self.gas_meter.borrow_mut(), - self.storage, - self.write_log, - key, - ) + fn post(&'view self) -> Self::Post { + CtxPostStorageRead { ctx: self } } fn read_temp( @@ -339,44 +337,27 @@ where ) } - fn has_key_pre(&self, key: &Key) -> Result { - vp_env::has_key_pre( - &mut *self.gas_meter.borrow_mut(), - self.storage, - key, - ) - } - - fn has_key_post(&self, key: &Key) -> Result { - vp_env::has_key_post( - &mut *self.gas_meter.borrow_mut(), - self.storage, - self.write_log, - key, - ) - } - - fn get_chain_id(&self) -> Result { + fn get_chain_id(&'view self) -> Result { vp_env::get_chain_id(&mut *self.gas_meter.borrow_mut(), self.storage) } - fn get_block_height(&self) -> Result { + fn get_block_height(&'view self) -> Result { vp_env::get_block_height( &mut *self.gas_meter.borrow_mut(), self.storage, ) } - fn get_block_hash(&self) -> Result { + fn get_block_hash(&'view self) -> Result { vp_env::get_block_hash(&mut *self.gas_meter.borrow_mut(), self.storage) } - fn get_block_epoch(&self) -> Result { + fn get_block_epoch(&'view self) -> Result { vp_env::get_block_epoch(&mut *self.gas_meter.borrow_mut(), self.storage) } fn iter_prefix( - &self, + &'view self, prefix: &Key, ) -> Result { vp_env::iter_prefix( @@ -386,24 +367,6 @@ where ) } - fn iter_pre_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error> { - vp_env::iter_pre_next::(&mut *self.gas_meter.borrow_mut(), iter) - } - - fn iter_post_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error> { - vp_env::iter_post_next::( - &mut *self.gas_meter.borrow_mut(), - self.write_log, - iter, - ) - } - fn eval( &self, vp_code: Vec, diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 873c14ef9b..94be4d8568 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -54,6 +54,12 @@ pub trait StorageRead<'iter> { /// Storage `has_key` in. It will try to read from the storage. fn has_key(&self, key: &storage::Key) -> Result; + /// Storage prefix iterator for. It will try to read from the storage. + fn iter_next( + &self, + iter: &mut Self::PrefixIter, + ) -> Result)>>; + /// Storage prefix iterator. It will try to get an iterator from the /// storage. /// @@ -64,12 +70,6 @@ pub trait StorageRead<'iter> { prefix: &storage::Key, ) -> Result; - /// Storage prefix iterator for. It will try to read from the storage. - fn iter_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>>; - /// Getting the chain ID. fn get_chain_id(&self) -> Result; diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 95e7c48782..e1f72d8505 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -7,7 +7,7 @@ use borsh::BorshDeserialize; use thiserror::Error; use super::gas::MIN_STORAGE_GAS; -use super::storage_api; +use super::storage_api::{self, StorageRead}; use crate::ledger::gas; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; @@ -18,40 +18,24 @@ use crate::types::key::common; use crate::types::storage::{BlockHash, BlockHeight, Epoch, Key}; /// Validity predicate's environment is available for native VPs and WASM VPs -pub trait VpEnv { +pub trait VpEnv<'view> { /// Storage read prefix iterator type PrefixIter; /// Host functions possible errors, extensible with custom user errors. - type Error; + type Error: From; - /// Storage read prior state Borsh encoded value (before tx execution). It - /// will try to read from the storage and decode it if found. - fn read_pre( - &self, - key: &Key, - ) -> Result, Self::Error>; + /// Type to read storage state before the transaction execution + type Pre: StorageRead<'view, PrefixIter = Self::PrefixIter>; - /// Storage read prior state raw bytes (before tx execution). It - /// will try to read from the storage. - fn read_bytes_pre(&self, key: &Key) - -> Result>, Self::Error>; + /// Type to read storage state after the transaction execution + type Post: StorageRead<'view, PrefixIter = Self::PrefixIter>; - /// Storage read posterior state Borsh encoded value (after tx execution). - /// It will try to read from the write log first and if no entry found - /// then from the storage and then decode it if found. - fn read_post( - &self, - key: &Key, - ) -> Result, Self::Error>; + /// Read storage state before the transaction execution + fn pre(&'view self) -> Self::Pre; - /// Storage read posterior state raw bytes (after tx execution). It will try - /// to read from the write log first and if no entry found then from the - /// storage. - fn read_bytes_post( - &self, - key: &Key, - ) -> Result>, Self::Error>; + /// Read storage state after the transaction execution + fn post(&'view self) -> Self::Post; /// Storage read temporary state Borsh encoded value (after tx execution). /// It will try to read from only the write log and then decode it if @@ -68,51 +52,28 @@ pub trait VpEnv { key: &Key, ) -> Result>, Self::Error>; - /// Storage `has_key` in prior state (before tx execution). It will try to - /// read from the storage. - fn has_key_pre(&self, key: &Key) -> Result; - - /// Storage `has_key` in posterior state (after tx execution). It will try - /// to check the write log first and if no entry found then the storage. - fn has_key_post(&self, key: &Key) -> Result; - /// Getting the chain ID. - fn get_chain_id(&self) -> Result; + fn get_chain_id(&'view self) -> Result; /// Getting the block height. The height is that of the block to which the /// current transaction is being applied. - fn get_block_height(&self) -> Result; + fn get_block_height(&'view self) -> Result; /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. - fn get_block_hash(&self) -> Result; + fn get_block_hash(&'view self) -> Result; /// Getting the block epoch. The epoch is that of the block to which the /// current transaction is being applied. - fn get_block_epoch(&self) -> Result; + fn get_block_epoch(&'view self) -> Result; /// Storage prefix iterator. It will try to get an iterator from the /// storage. fn iter_prefix( - &self, + &'view self, prefix: &Key, ) -> Result; - /// Storage prefix iterator for prior state (before tx execution). It will - /// try to read from the storage. - fn iter_pre_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error>; - - /// Storage prefix iterator next for posterior state (after tx execution). - /// It will try to read from the write log first and if no entry found - /// then from the storage. - fn iter_post_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error>; - /// Evaluate a validity predicate with given data. The address, changed /// storage keys and verifiers will have the same values as the input to /// caller's validity predicate. @@ -136,6 +97,77 @@ pub trait VpEnv { /// Get a tx hash fn get_tx_code_hash(&self) -> Result; + + // ---- Methods below have default implementation via `pre/post` ---- + + /// Storage read prior state Borsh encoded value (before tx execution). It + /// will try to read from the storage and decode it if found. + fn read_pre( + &'view self, + key: &Key, + ) -> Result, Self::Error> { + self.pre().read(key).map_err(Into::into) + } + + /// Storage read prior state raw bytes (before tx execution). It + /// will try to read from the storage. + fn read_bytes_pre( + &'view self, + key: &Key, + ) -> Result>, Self::Error> { + self.pre().read_bytes(key).map_err(Into::into) + } + + /// Storage read posterior state Borsh encoded value (after tx execution). + /// It will try to read from the write log first and if no entry found + /// then from the storage and then decode it if found. + fn read_post( + &'view self, + key: &Key, + ) -> Result, Self::Error> { + self.post().read(key).map_err(Into::into) + } + + /// Storage read posterior state raw bytes (after tx execution). It will try + /// to read from the write log first and if no entry found then from the + /// storage. + fn read_bytes_post( + &'view self, + key: &Key, + ) -> Result>, Self::Error> { + self.post().read_bytes(key).map_err(Into::into) + } + + /// Storage `has_key` in prior state (before tx execution). It will try to + /// read from the storage. + fn has_key_pre(&'view self, key: &Key) -> Result { + self.pre().has_key(key).map_err(Into::into) + } + + /// Storage `has_key` in posterior state (after tx execution). It will try + /// to check the write log first and if no entry found then the storage. + fn has_key_post(&'view self, key: &Key) -> Result { + self.post().has_key(key).map_err(Into::into) + } + + /// Storage prefix iterator for prior state (before tx execution). It will + /// try to read from the storage. + fn iter_pre_next( + &'view self, + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error> { + self.pre().iter_next(iter).map_err(Into::into) + } + + /// Storage prefix iterator next for posterior state (after tx execution). + /// It will try to read from the write log first and if no entry found + /// then from the storage. + fn iter_post_next( + &'view self, + iter: &mut Self::PrefixIter, + ) -> Result)>, Self::Error> { + self.post().iter_next(iter).map_err(Into::into) + } } /// These runtime errors will abort VP execution immediately diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 056d9cb1d5..d1d3845a48 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -150,36 +150,18 @@ pub fn reject() -> VpResult { #[derive(Debug)] pub struct KeyValIterator(pub u64, pub PhantomData); -impl VpEnv for Ctx { +impl<'view> VpEnv<'view> for Ctx { type Error = Error; + type Post = CtxPostStorageRead<'view>; + type Pre = CtxPreStorageRead<'view>; type PrefixIter = KeyValIterator<(String, Vec)>; - fn read_pre( - &self, - key: &storage::Key, - ) -> Result, Self::Error> { - self.pre().read(key).into_env_result() - } - - fn read_bytes_pre( - &self, - key: &storage::Key, - ) -> Result>, Self::Error> { - self.pre().read_bytes(key).into_env_result() - } - - fn read_post( - &self, - key: &storage::Key, - ) -> Result, Self::Error> { - self.post().read(key).into_env_result() + fn pre(&'view self) -> Self::Pre { + CtxPreStorageRead { _ctx: self } } - fn read_bytes_post( - &self, - key: &storage::Key, - ) -> Result>, Self::Error> { - self.post().read_bytes(key).into_env_result() + fn post(&'view self) -> Self::Post { + CtxPostStorageRead { _ctx: self } } fn read_temp( @@ -203,29 +185,24 @@ impl VpEnv for Ctx { Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) } - fn has_key_pre(&self, key: &storage::Key) -> Result { - self.pre().has_key(key).into_env_result() - } - - fn has_key_post(&self, key: &storage::Key) -> Result { - self.post().has_key(key).into_env_result() - } - - fn get_chain_id(&self) -> Result { + fn get_chain_id(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - self.pre().get_chain_id().into_env_result() + get_chain_id().into_env_result() } - fn get_block_height(&self) -> Result { - self.pre().get_block_height().into_env_result() + fn get_block_height(&'view self) -> Result { + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + get_block_height().into_env_result() } - fn get_block_hash(&self) -> Result { - self.pre().get_block_hash().into_env_result() + fn get_block_hash(&'view self) -> Result { + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + get_block_hash().into_env_result() } - fn get_block_epoch(&self) -> Result { - self.pre().get_block_epoch().into_env_result() + fn get_block_epoch(&'view self) -> Result { + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + get_block_epoch().into_env_result() } fn iter_prefix( @@ -233,21 +210,7 @@ impl VpEnv for Ctx { prefix: &storage::Key, ) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - self.pre().iter_prefix(prefix).into_env_result() - } - - fn iter_pre_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error> { - self.pre().iter_next(iter).into_env_result() - } - - fn iter_post_next( - &self, - iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error> { - self.post().iter_next(iter).into_env_result() + iter_prefix(prefix).into_env_result() } fn eval( @@ -315,14 +278,6 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { Ok(HostEnvResult::is_success(found)) } - fn iter_prefix( - &self, - prefix: &storage::Key, - ) -> Result { - // Note that this is the same as `CtxPostStorageRead` - iter_prefix(prefix) - } - fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -334,27 +289,29 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { )) } + // ---- Methods below share the same implementation in `pre/post` ---- + + fn iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + iter_prefix(prefix) + } + fn get_chain_id(&self) -> Result { get_chain_id() } fn get_block_height(&self) -> Result { - Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) + get_block_height() } fn get_block_hash(&self) -> Result { - let result = Vec::with_capacity(BLOCK_HASH_LENGTH); - unsafe { - anoma_vp_get_block_hash(result.as_ptr() as _); - } - let slice = unsafe { - slice::from_raw_parts(result.as_ptr(), BLOCK_HASH_LENGTH) - }; - Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) + get_block_hash() } fn get_block_epoch(&self) -> Result { - Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) + get_block_epoch() } } @@ -378,14 +335,6 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { Ok(HostEnvResult::is_success(found)) } - fn iter_prefix( - &self, - prefix: &storage::Key, - ) -> Result { - // Note that this is the same as `CtxPreStorageRead` - iter_prefix(prefix) - } - fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -397,6 +346,15 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { )) } + // ---- Methods below share the same implementation in `pre/post` ---- + + fn iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + iter_prefix(prefix) + } + fn get_chain_id(&self) -> Result { get_chain_id() } From c9e16e726b5aa5f97f03db5fc5c30a6febcd8860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 25 Aug 2022 12:30:47 +0200 Subject: [PATCH 273/373] shared/storage: fix the height recorded for a new epoch --- shared/src/ledger/storage/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 124ee61d57..f3e561616c 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -609,7 +609,7 @@ where let evidence_max_age_num_blocks: u64 = 100000; self.block .pred_epochs - .new_epoch(height, evidence_max_age_num_blocks); + .new_epoch(height + 1, evidence_max_age_num_blocks); tracing::info!("Began a new epoch {}", self.block.epoch); } self.update_epoch_in_merkle_tree()?; @@ -828,10 +828,12 @@ mod tests { block_height + epoch_duration.min_num_of_blocks); assert_eq!(storage.next_epoch_min_start_time, block_time + epoch_duration.min_duration); - assert_eq!(storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before.next())); + assert_eq!(storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before)); + assert_eq!(storage.block.pred_epochs.get_epoch(block_height + 1), Some(epoch_before.next())); } else { assert_eq!(storage.block.epoch, epoch_before); assert_eq!(storage.block.pred_epochs.get_epoch(block_height), Some(epoch_before)); + assert_eq!(storage.block.pred_epochs.get_epoch(block_height + 1), Some(epoch_before)); } // Last epoch should only change when the block is committed assert_eq!(storage.last_epoch, epoch_before); From 388a69d6ef4410a11e2e5ca354c704b179649f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 25 Aug 2022 12:54:30 +0200 Subject: [PATCH 274/373] changelog: add #384 --- .../unreleased/bug-fixes/384-fix-new-epoch-start-height.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md diff --git a/.changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md b/.changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md new file mode 100644 index 0000000000..cf2bb8f399 --- /dev/null +++ b/.changelog/unreleased/bug-fixes/384-fix-new-epoch-start-height.md @@ -0,0 +1,2 @@ +- Fix the value recorded for epoch start block height. + ([#384](https://github.com/anoma/namada/issues/384)) \ No newline at end of file From c86185c640fdb91ff16f558d861fe7fed313e8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 25 Aug 2022 13:03:44 +0200 Subject: [PATCH 275/373] changelog: add #380 --- .../improvements/380-vp-env-pre-post-via-storage-api.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/380-vp-env-pre-post-via-storage-api.md diff --git a/.changelog/unreleased/improvements/380-vp-env-pre-post-via-storage-api.md b/.changelog/unreleased/improvements/380-vp-env-pre-post-via-storage-api.md new file mode 100644 index 0000000000..655cdf256a --- /dev/null +++ b/.changelog/unreleased/improvements/380-vp-env-pre-post-via-storage-api.md @@ -0,0 +1,3 @@ +- Added `pre/post` methods into `trait VpEnv` that return objects implementing + `trait StorageRead` for re-use of library code written on top of `StorageRead` + inside validity predicates. ([#380](https://github.com/anoma/namada/pull/380)) \ No newline at end of file From b811466de29d4c819b89d1a16f882a9082f5a100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 25 Aug 2022 14:38:03 +0200 Subject: [PATCH 276/373] update wasm checksums --- wasm/checksums.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 2478056cb0..1c5fec452f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -12,8 +12,8 @@ "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", "tx_vote_proposal.wasm": "tx_vote_proposal.8f306e1d1bcc9ca7f47a39108554fc847d4ac6df7a8d87a6effdffeae6adc4c4.wasm", "tx_withdraw.wasm": "tx_withdraw.c288861e842f0b1ac6fbb95b14b33c8819ac587bbd5b483f2cdd0ea184206c65.wasm", - "vp_nft.wasm": "vp_nft.557883861270a0a5e42802ce4bfbbc28f9f0dfa3b76520ffdd319b69a4f2f34c.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.1fb8c53e7c164d141661743f3cdfaf30d30e6e16abd2f8814c0d8829a2e36e8a.wasm", - "vp_token.wasm": "vp_token.b130ca28e8029f5ad7a33bf3d4fbb9a3b08a29484feba34b988e902c4ce9c966.wasm", - "vp_user.wasm": "vp_user.2c45ba3e0b442210d4dd953679c774cc15f8773c5c25bda7f2ad60eaaff2d0a9.wasm" + "vp_nft.wasm": "vp_nft.379f0a9fdbc9611ba9afc8b03ea17eb1e7c63992be3c2ecd5dd506a0ec3809f3.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.9d28df0e4eea55c98ac05638cfc6211aaf8e1a4b9489f7057634c66d39835c36.wasm", + "vp_token.wasm": "vp_token.6506aea021bb6de9186e96fa0d9ea2ad35bcb66777d6ecf890a66cfe36a74f23.wasm", + "vp_user.wasm": "vp_user.77a0d0d406e300b2d5b9bc1c13bc50f233b6923d369db939ac82c8e10b64543c.wasm" } \ No newline at end of file From b8eccf3d04fefa61203a5fa6b1a534156e256313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 17:11:57 +0200 Subject: [PATCH 277/373] deps: replace hex with data-encoding --- Cargo.lock | 6 +++--- apps/Cargo.toml | 2 +- apps/src/lib/client/rpc.rs | 3 ++- apps/src/lib/config/genesis.rs | 12 ++++++------ apps/src/lib/node/gossip/p2p/identity.rs | 6 ++++-- apps/src/lib/wallet/keys.rs | 7 ++++--- apps/src/lib/wasm_loader/mod.rs | 4 ++-- shared/Cargo.toml | 2 +- shared/src/proto/mod.rs | 5 +++-- shared/src/proto/types.rs | 3 ++- shared/src/types/key/common.rs | 13 +++++++++---- shared/src/types/key/dkg_session_keys.rs | 7 +++++-- shared/src/types/key/ed25519.rs | 13 +++++++++---- shared/src/types/key/mod.rs | 6 +++--- tests/Cargo.toml | 2 +- tests/src/e2e/ledger_tests.rs | 3 ++- wasm/wasm_source/Cargo.lock | 8 +++++++- wasm_for_tests/wasm_source/Cargo.lock | 8 +++++++- 18 files changed, 71 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7fce8b64d..3aef73a09a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3843,12 +3843,12 @@ dependencies = [ "byte-unit", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ferveo", "ferveo-common", "group-threshold-cryptography", - "hex", "ibc", "ibc-proto", "ics23", @@ -3905,6 +3905,7 @@ dependencies = [ "color-eyre", "config", "curl", + "data-encoding", "derivative", "directories", "ed25519-consensus", @@ -3915,7 +3916,6 @@ dependencies = [ "flate2", "futures 0.3.21", "git2", - "hex", "itertools 0.10.3", "jsonpath_lib", "libc", @@ -4007,13 +4007,13 @@ dependencies = [ "chrono", "color-eyre", "concat-idents", + "data-encoding", "derivative", "escargot", "expectrl", "eyre", "file-serve", "fs_extra", - "hex", "itertools 0.10.3", "libp2p", "namada", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 7a4dffac76..3fcd3af977 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -62,6 +62,7 @@ clap = {git = "https://github.com/clap-rs/clap/", tag = "v3.0.0-beta.2", default color-eyre = "0.5.10" config = "0.11.0" curl = "0.4.43" +data-encoding = "2.3.2" derivative = "2.2.0" directories = "4.0.1" ed25519-consensus = "1.2.0" @@ -71,7 +72,6 @@ eyre = "0.6.5" flate2 = "1.0.22" file-lock = "2.0.2" futures = "0.3" -hex = "0.4.3" itertools = "0.10.1" jsonpath_lib = "0.3.0" libc = "0.2.97" diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..67e81c1588 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -11,6 +11,7 @@ use async_std::fs::{self}; use async_std::path::PathBuf; use async_std::prelude::*; use borsh::BorshDeserialize; +use data_encoding::HEXLOWER; use itertools::Itertools; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; @@ -81,7 +82,7 @@ pub async fn query_raw_bytes(_ctx: Context, args: args::QueryRawBytes) { .unwrap(); match response.code { Code::Ok => { - println!("{}", hex::encode(&response.value)); + println!("{}", HEXLOWER.encode(&response.value)); } Code::Err(err) => { eprintln!( diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..9c1fff3dcc 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -26,7 +26,7 @@ pub mod genesis_config { use std::path::Path; use std::str::FromStr; - use hex; + use data_encoding::HEXLOWER; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::pos::types::BasisPoints; @@ -50,12 +50,12 @@ pub mod genesis_config { impl HexString { pub fn to_bytes(&self) -> Result, HexKeyError> { - let bytes = hex::decode(&self.0)?; + let bytes = HEXLOWER.decode(self.0.as_ref())?; Ok(bytes) } pub fn to_sha256_bytes(&self) -> Result<[u8; 32], HexKeyError> { - let bytes = hex::decode(&self.0)?; + let bytes = HEXLOWER.decode(self.0.as_ref())?; let slice = bytes.as_slice(); let array: [u8; 32] = slice.try_into()?; Ok(array) @@ -76,15 +76,15 @@ pub mod genesis_config { #[derive(Error, Debug)] pub enum HexKeyError { #[error("Invalid hex string: {0:?}")] - InvalidHexString(hex::FromHexError), + InvalidHexString(data_encoding::DecodeError), #[error("Invalid sha256 checksum: {0}")] InvalidSha256(TryFromSliceError), #[error("Invalid public key: {0}")] InvalidPublicKey(ParsePublicKeyError), } - impl From for HexKeyError { - fn from(err: hex::FromHexError) -> Self { + impl From for HexKeyError { + fn from(err: data_encoding::DecodeError) -> Self { Self::InvalidHexString(err) } } diff --git a/apps/src/lib/node/gossip/p2p/identity.rs b/apps/src/lib/node/gossip/p2p/identity.rs index 42442054c8..3227060014 100644 --- a/apps/src/lib/node/gossip/p2p/identity.rs +++ b/apps/src/lib/node/gossip/p2p/identity.rs @@ -21,6 +21,7 @@ pub struct Identity { // TODO this is needed because libp2p does not export ed255519 serde // feature maybe a MR for libp2p to export theses functions ? mod keypair_serde { + use data_encoding::HEXLOWER; use libp2p::identity::ed25519::Keypair; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -33,7 +34,7 @@ mod keypair_serde { S: Serializer, { let bytes = value.encode(); - let string = hex::encode(&bytes[..]); + let string = HEXLOWER.encode(&bytes[..]); string.serialize(serializer) } pub fn deserialize<'d, D>(deserializer: D) -> Result @@ -41,7 +42,8 @@ mod keypair_serde { D: Deserializer<'d>, { let string = String::deserialize(deserializer)?; - let mut bytes = hex::decode(&string).map_err(Error::custom)?; + let mut bytes = + HEXLOWER.decode(string.as_ref()).map_err(Error::custom)?; Keypair::decode(bytes.as_mut()).map_err(Error::custom) } } diff --git a/apps/src/lib/wallet/keys.rs b/apps/src/lib/wallet/keys.rs index 1c521e7515..e922c7df5a 100644 --- a/apps/src/lib/wallet/keys.rs +++ b/apps/src/lib/wallet/keys.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use data_encoding::HEXLOWER; use namada::types::key::*; use orion::{aead, kdf}; use serde::{Deserialize, Serialize}; @@ -108,15 +109,15 @@ pub struct EncryptedKeypair(Vec); impl Display for EncryptedKeypair { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0)) + write!(f, "{}", HEXLOWER.encode(self.0.as_ref())) } } impl FromStr for EncryptedKeypair { - type Err = hex::FromHexError; + type Err = data_encoding::DecodeError; fn from_str(s: &str) -> Result { - hex::decode(s).map(Self) + HEXLOWER.decode(s.as_ref()).map(Self) } } diff --git a/apps/src/lib/wasm_loader/mod.rs b/apps/src/lib/wasm_loader/mod.rs index b6cb424457..e41efdbf77 100644 --- a/apps/src/lib/wasm_loader/mod.rs +++ b/apps/src/lib/wasm_loader/mod.rs @@ -4,8 +4,8 @@ use std::collections::HashMap; use std::fs; use std::path::Path; +use data_encoding::HEXLOWER; use futures::future::join_all; -use hex; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use thiserror::Error; @@ -144,7 +144,7 @@ pub async fn pre_fetch_wasm(wasm_directory: impl AsRef) { Ok(bytes) => { let mut hasher = Sha256::new(); hasher.update(bytes); - let result = hex::encode(hasher.finalize()); + let result = HEXLOWER.encode(&hasher.finalize()); let derived_name = format!( "{}.{}.wasm", &name.split('.').collect::>()[0], diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 241fd034da..7bff772557 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -52,11 +52,11 @@ borsh = "0.9.0" chrono = "0.4.19" # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} +data-encoding = "2.3.2" derivative = "2.2.0" ed25519-consensus = "1.2.0" ferveo = {optional = true, git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} -hex = "0.4.3" tpke = {package = "group-threshold-cryptography", optional = true, git = "https://github.com/anoma/ferveo"} # TODO using the same version of tendermint-rs as we do here. ibc = {git = "https://github.com/heliaxdev/ibc-rs", rev = "30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc", default-features = false} diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index aa971f0b96..1ee5da63c8 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -9,6 +9,7 @@ pub use types::{ #[cfg(test)] mod tests { + use data_encoding::HEXLOWER; use generated::types::Tx; use prost::Message; @@ -23,8 +24,8 @@ mod tests { }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); - let tx_hex = hex::encode(tx_bytes); - let tx_from_hex = hex::decode(tx_hex).unwrap(); + let tx_hex = HEXLOWER.encode(&tx_bytes); + let tx_from_hex = HEXLOWER.decode(tx_hex.as_ref()).unwrap(); let tx_from_bytes = Tx::decode(&tx_from_hex[..]).unwrap(); assert_eq!(tx, tx_from_bytes); } diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 7a936dd58f..6c6aa23234 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use std::hash::{Hash, Hasher}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use prost::Message; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -374,7 +375,7 @@ impl>> From for IntentId { impl Display for IntentId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0)) + write!(f, "{}", HEXLOWER.encode(&self.0)) } } diff --git a/shared/src/types/key/common.rs b/shared/src/types/key/common.rs index 27e7c29b89..bc3257f06a 100644 --- a/shared/src/types/key/common.rs +++ b/shared/src/types/key/common.rs @@ -4,6 +4,7 @@ use std::fmt::Display; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -57,7 +58,7 @@ impl super::PublicKey for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -65,7 +66,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -152,7 +155,7 @@ impl RefTo for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.try_to_vec().unwrap())) + write!(f, "{}", HEXLOWER.encode(&self.try_to_vec().unwrap())) } } @@ -160,7 +163,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(str: &str) -> Result { - let vec = hex::decode(str).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(str.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; Self::try_from_slice(vec.as_slice()) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/dkg_session_keys.rs b/shared/src/types/key/dkg_session_keys.rs index 26f6fffa00..f2cafb639c 100644 --- a/shared/src/types/key/dkg_session_keys.rs +++ b/shared/src/types/key/dkg_session_keys.rs @@ -7,6 +7,7 @@ use std::str::FromStr; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; use serde::{Deserialize, Serialize}; use crate::types::address::Address; @@ -142,7 +143,7 @@ impl Display for DkgPublicKey { let vec = self .try_to_vec() .expect("Encoding public key shouldn't fail"); - write!(f, "{}", hex::encode(&vec)) + write!(f, "{}", HEXLOWER.encode(&vec)) } } @@ -150,7 +151,9 @@ impl FromStr for DkgPublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index 12e5093bd2..90ab540f19 100644 --- a/shared/src/types/key/ed25519.rs +++ b/shared/src/types/key/ed25519.rs @@ -6,6 +6,7 @@ use std::io::Write; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; @@ -107,7 +108,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -115,7 +116,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -204,7 +207,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.to_bytes())) + write!(f, "{}", HEXLOWER.encode(&self.0.to_bytes())) } } @@ -212,7 +215,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_ref()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a1343cd7e5..28a0fe1bf6 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -79,7 +79,7 @@ pub enum VerifySigError { #[derive(Error, Debug)] pub enum ParsePublicKeyError { #[error("Invalid public key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid public key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed public key does not belong to desired scheme")] @@ -90,7 +90,7 @@ pub enum ParsePublicKeyError { #[derive(Error, Debug)] pub enum ParseSignatureError { #[error("Invalid signature hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid signature encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed signature does not belong to desired scheme")] @@ -101,7 +101,7 @@ pub enum ParseSignatureError { #[derive(Error, Debug)] pub enum ParseSecretKeyError { #[error("Invalid secret key hex: {0}")] - InvalidHex(hex::FromHexError), + InvalidHex(data_encoding::DecodeError), #[error("Invalid secret key encoding: {0}")] InvalidEncoding(std::io::Error), #[error("Parsed secret key does not belong to desired scheme")] diff --git a/tests/Cargo.toml b/tests/Cargo.toml index cc437b3686..6872e912b7 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -31,13 +31,13 @@ namada_apps = {path = "../apps", default-features = false, features = ["testing" assert_cmd = "1.0.7" borsh = "0.9.1" color-eyre = "0.5.11" +data-encoding = "2.3.2" # NOTE: enable "print" feature to see output from builds ran by e2e tests escargot = {version = "0.5.7"} # , features = ["print"]} expectrl = {version = "=0.5.2"} eyre = "0.6.5" file-serve = "0.2.0" fs_extra = "1.2.0" -hex = "0.4.3" itertools = "0.10.0" libp2p = "0.38.0" pretty_assertions = "0.7.2" diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 552e675e67..307112ac2a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -17,6 +17,7 @@ use std::time::{Duration, Instant}; use borsh::BorshSerialize; use color_eyre::eyre::Result; +use data_encoding::HEXLOWER; use namada::types::token; use namada_apps::config::genesis::genesis_config::{ GenesisConfig, ParametersConfig, PosParamsConfig, @@ -386,7 +387,7 @@ fn ledger_txs_and_queries() -> Result<()> { &validator_one_rpc, ], // expect hex encoded of borsh encoded bytes - hex::encode(christel_balance.try_to_vec().unwrap()), + HEXLOWER.encode(&christel_balance.try_to_vec().unwrap()), ), ]; for (query_args, expected) in &query_args_and_expected_response { diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index 19a50588dc..0a186f3fd4 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -602,6 +602,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1359,10 +1365,10 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ferveo-common", - "hex", "ibc", "ibc-proto", "ics23", diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 3e7bbbe365..e82a61634b 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -603,6 +603,12 @@ dependencies = [ "syn", ] +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + [[package]] name = "derivative" version = "2.2.0" @@ -1370,10 +1376,10 @@ dependencies = [ "borsh", "chrono", "clru", + "data-encoding", "derivative", "ed25519-consensus", "ferveo-common", - "hex", "ibc", "ibc-proto", "ics23", From b47d1c1d16699a538fb81886615cf930046687b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 17:12:52 +0200 Subject: [PATCH 278/373] shared/storage/key: add support for int/uint keys that maintain order --- shared/src/types/storage.rs | 71 ++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index fc87bc8d51..1892334031 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -6,6 +6,7 @@ use std::ops::{Add, Div, Mul, Rem, Sub}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::BASE32HEX_NOPAD; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -27,6 +28,8 @@ pub enum Error { ParseAddressFromKey, #[error("Reserved prefix or string is specified: {0}")] InvalidKeySeg(String), + #[error("Error parsing key segment {0}")] + ParseKeySeg(String), } /// Result for functions that may fail @@ -193,6 +196,7 @@ impl Header { BorshDeserialize, BorshSchema, Debug, + Default, Eq, PartialEq, Ord, @@ -446,14 +450,12 @@ impl KeySeg for String { impl KeySeg for BlockHeight { fn parse(string: String) -> Result { - let h = string.parse::().map_err(|e| Error::Temporary { - error: format!("Unexpected height value {}, {}", string, e), - })?; + let h: u64 = KeySeg::parse(string)?; Ok(BlockHeight(h)) } fn raw(&self) -> String { - format!("{}", self.0) + self.0.raw() } fn to_db_key(&self) -> DbKeySeg { @@ -481,6 +483,67 @@ impl KeySeg for Address { } } +/// Implement [`KeySeg`] for a type via base32hex of its BE bytes (using +/// `to_le_bytes()` and `from_le_bytes` methods) that maintains sort order of +/// the original data. +// TODO this could be a bit more efficient without the string conversion (atm +// with base32hex), if we can use bytes for storage key directly (which we can +// with rockDB, but atm, we're calling `to_string()` using the custom `Display` +// impl from here) +macro_rules! impl_int_key_seg { + ($unsigned:ty, $signed:ty, $len:literal) => { + impl KeySeg for $unsigned { + fn parse(string: String) -> Result { + let bytes = + BASE32HEX_NOPAD.decode(string.as_ref()).map_err(|err| { + Error::ParseKeySeg(format!( + "Failed parsing {} with {}", + string, err + )) + })?; + let mut fixed_bytes = [0; $len]; + fixed_bytes.copy_from_slice(&bytes); + Ok(<$unsigned>::from_be_bytes(fixed_bytes)) + } + + fn raw(&self) -> String { + BASE32HEX_NOPAD.encode(&self.to_be_bytes()) + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } + } + + impl KeySeg for $signed { + fn parse(string: String) -> Result { + // get signed int from a unsigned int complemented with a min + // value + let complemented = <$unsigned>::parse(string)?; + let signed = (complemented as $signed) ^ <$signed>::MIN; + Ok(signed) + } + + fn raw(&self) -> String { + // signed int is converted to unsigned int that preserves the + // order by complementing it with a min value + let complemented = (*self ^ <$signed>::MIN) as $unsigned; + complemented.raw() + } + + fn to_db_key(&self) -> DbKeySeg { + DbKeySeg::StringSeg(self.raw()) + } + } + }; +} + +impl_int_key_seg!(u8, i8, 1); +impl_int_key_seg!(u16, i16, 2); +impl_int_key_seg!(u32, i32, 4); +impl_int_key_seg!(u64, i64, 8); +impl_int_key_seg!(u128, i128, 16); + /// Epoch identifier. Epochs are identified by consecutive numbers. #[derive( Clone, From e64808559e6f8673c482dd8a3081bb3bfda8a845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 17:27:14 +0200 Subject: [PATCH 279/373] test/vm_host_env: check prefix iter order in tx and VP --- tests/src/vm_host_env/mod.rs | 67 +++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 98f95e7fae..f846325c40 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -157,11 +157,14 @@ mod tests { should yield an empty iterator." ); - // Write some values directly into the storage first - let prefix = Key::parse("prefix").unwrap(); + let prefix = storage::Key::parse("prefix").unwrap(); + // We'll write sub-key in some random order to check prefix iter's order + let sub_keys = [2_i32, 1, i32::MAX, -1, 260, -2, i32::MIN, 5, 0]; + + // Write the values directly into the storage first tx_host_env::with(|env| { - for i in 0..10_i32 { - let key = prefix.join(&Key::parse(i.to_string()).unwrap()); + for i in sub_keys.iter() { + let key = prefix.push(i).unwrap(); let value = i.try_to_vec().unwrap(); env.storage.write(&key, value).unwrap(); } @@ -171,11 +174,14 @@ mod tests { // Then try to iterate over their prefix let iter = namada_tx_prelude::iter_prefix(tx::ctx(), &prefix) .unwrap() - .map(|item| item.unwrap()); - let expected = (0..10).map(|i| { - (storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), i) - }); - itertools::assert_equal(iter.sorted(), expected.sorted()); + .map(Result::unwrap); + + // The order has to be sorted by sub-key value + let expected = sub_keys + .iter() + .sorted() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter, expected); } #[test] @@ -251,7 +257,7 @@ mod tests { // We can add some data to the environment let key_raw = "key"; - let key = Key::parse(key_raw).unwrap(); + let key = storage::Key::parse(key_raw).unwrap(); let value = "test".to_string(); let value_raw = value.try_to_vec().unwrap(); vp_host_env::with(|env| { @@ -269,7 +275,7 @@ mod tests { let mut tx_env = TestTxEnv::default(); let addr = address::testing::established_address_1(); - let addr_key = Key::from(addr.to_db_key()); + let addr_key = storage::Key::from(addr.to_db_key()); // Write some value to storage let existing_key = @@ -354,12 +360,15 @@ mod tests { let mut tx_env = TestTxEnv::default(); let addr = address::testing::established_address_1(); - let addr_key = Key::from(addr.to_db_key()); + let addr_key = storage::Key::from(addr.to_db_key()); - // Write some value to storage let prefix = addr_key.join(&Key::parse("prefix").unwrap()); - for i in 0..10_i32 { - let key = prefix.join(&Key::parse(i.to_string()).unwrap()); + // We'll write sub-key in some random order to check prefix iter's order + let sub_keys = [2_i32, 1, i32::MAX, -1, 260, -2, i32::MIN, 5, 0]; + + // Write some values to storage + for i in sub_keys.iter() { + let key = prefix.push(i).unwrap(); let value = i.try_to_vec().unwrap(); tx_env.storage.write(&key, value).unwrap(); } @@ -367,8 +376,8 @@ mod tests { // In a transaction, write override the existing key's value and add // another key-value - let existing_key = prefix.join(&Key::parse(5.to_string()).unwrap()); - let new_key = prefix.join(&Key::parse(11.to_string()).unwrap()); + let existing_key = prefix.push(&5).unwrap(); + let new_key = prefix.push(&11).unwrap(); // Initialize the VP environment via a transaction vp_host_env::init_from_tx(addr, tx_env, |_addr| { @@ -383,23 +392,25 @@ mod tests { let iter_pre = namada_vp_prelude::iter_prefix(&ctx_pre, &prefix) .unwrap() .map(|item| item.unwrap()); - let expected_pre = (0..10).map(|i| { - (storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), i) - }); - itertools::assert_equal(iter_pre.sorted(), expected_pre.sorted()); + + // The order in pre has to be sorted by sub-key value + let expected_pre = sub_keys + .iter() + .sorted() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter_pre, expected_pre); let ctx_post = vp::CTX.post(); let iter_post = namada_vp_prelude::iter_prefix(&ctx_post, &prefix) .unwrap() .map(|item| item.unwrap()); - let expected_post = (0..10).map(|i| { - let val = if i == 5 { 100 } else { i }; - ( - storage::Key::parse(format!("{}/{}", prefix, i)).unwrap(), - val, - ) + + // The order in post also has to be sorted + let expected_post = sub_keys.iter().sorted().map(|i| { + let val = if *i == 5 { 100 } else { *i }; + (prefix.push(i).unwrap(), val) }); - itertools::assert_equal(iter_post.sorted(), expected_post.sorted()); + itertools::assert_equal(iter_post, expected_post); } #[test] From 1429b929923c5973be8e3ea1e8970e1a3d63e090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 18:04:55 +0200 Subject: [PATCH 280/373] add support for rev_iter_prefix in storage and VP and tx envs --- apps/src/lib/node/ledger/storage/rocksdb.rs | 42 ++++++---- shared/src/ledger/native_vp.rs | 25 ++++++ shared/src/ledger/storage/mockdb.rs | 48 +++++++++-- shared/src/ledger/storage/mod.rs | 25 +++++- shared/src/ledger/storage_api/mod.rs | 89 +++++++++++++++++++-- shared/src/ledger/vp_env.rs | 30 ++++++- shared/src/vm/host_env.rs | 68 +++++++++++++++- shared/src/vm/wasm/host_env.rs | 2 + tests/src/vm_host_env/tx.rs | 1 + tests/src/vm_host_env/vp.rs | 1 + tx_prelude/src/lib.rs | 14 +++- vm_env/src/lib.rs | 20 ++++- vp_prelude/src/lib.rs | 37 ++++++++- 13 files changed, 364 insertions(+), 38 deletions(-) diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 71ed305ea5..44f55fa8e3 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -806,24 +806,36 @@ impl<'iter> DBIter<'iter> for RocksDB { &'iter self, prefix: &Key, ) -> PersistentPrefixIterator<'iter> { - let db_prefix = "subspace/".to_owned(); - let prefix = format!("{}{}", db_prefix, prefix); + iter_prefix(self, prefix, Direction::Forward) + } - let mut read_opts = ReadOptions::default(); - // don't use the prefix bloom filter - read_opts.set_total_order_seek(true); - let mut upper_prefix = prefix.clone().into_bytes(); - if let Some(last) = upper_prefix.pop() { - upper_prefix.push(last + 1); - } - read_opts.set_iterate_upper_bound(upper_prefix); + fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter { + iter_prefix(self, prefix, Direction::Reverse) + } +} - let iter = self.0.iterator_opt( - IteratorMode::From(prefix.as_bytes(), Direction::Forward), - read_opts, - ); - PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) +fn iter_prefix<'iter>( + db: &'iter RocksDB, + prefix: &Key, + direction: Direction, +) -> PersistentPrefixIterator<'iter> { + let db_prefix = "subspace/".to_owned(); + let prefix = format!("{}{}", db_prefix, prefix); + + let mut read_opts = ReadOptions::default(); + // don't use the prefix bloom filter + read_opts.set_total_order_seek(true); + let mut upper_prefix = prefix.clone().into_bytes(); + if let Some(last) = upper_prefix.pop() { + upper_prefix.push(last + 1); } + read_opts.set_iterate_upper_bound(upper_prefix); + + let iter = db.0.iterator_opt( + IteratorMode::From(prefix.as_bytes(), direction), + read_opts, + ); + PersistentPrefixIterator(PrefixIterator::new(iter, db_prefix)) } #[derive(Debug)] diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index 5cbab7d639..845a44c0ed 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -186,6 +186,13 @@ where self.ctx.iter_prefix(prefix).into_storage_result() } + fn rev_iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> storage_api::Result { + self.ctx.rev_iter_prefix(prefix).into_storage_result() + } + fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -247,6 +254,13 @@ where self.ctx.iter_prefix(prefix).into_storage_result() } + fn rev_iter_prefix( + &self, + prefix: &crate::types::storage::Key, + ) -> storage_api::Result { + self.ctx.rev_iter_prefix(prefix).into_storage_result() + } + fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -400,6 +414,17 @@ where ) } + fn rev_iter_prefix( + &self, + prefix: &Key, + ) -> Result { + vp_env::rev_iter_prefix( + &mut *self.gas_meter.borrow_mut(), + self.storage, + prefix, + ) + } + fn iter_pre_next( &self, iter: &mut Self::PrefixIter, diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 3cc7e8863c..a9e3e8057b 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -431,7 +431,28 @@ impl<'iter> DBIter<'iter> for MockDB { let db_prefix = "subspace/".to_owned(); let prefix = format!("{}{}", db_prefix, prefix); let iter = self.0.borrow().clone().into_iter(); - MockPrefixIterator::new(MockIterator { prefix, iter }, db_prefix) + MockPrefixIterator::new( + MockIterator { + prefix, + iter, + reverse_order: false, + }, + db_prefix, + ) + } + + fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter { + let db_prefix = "subspace/".to_owned(); + let prefix = format!("{}{}", db_prefix, prefix); + let iter = self.0.borrow().clone().into_iter(); + MockPrefixIterator::new( + MockIterator { + prefix, + iter, + reverse_order: true, + }, + db_prefix, + ) } } @@ -441,6 +462,8 @@ pub struct MockIterator { prefix: String, /// The concrete iterator pub iter: btree_map::IntoIter>, + /// Is the iterator in reverse order? + reverse_order: bool, } /// A prefix iterator for the [`MockDB`]. @@ -450,12 +473,23 @@ impl Iterator for MockIterator { type Item = KVBytes; fn next(&mut self) -> Option { - for (key, val) in &mut self.iter { - if key.starts_with(&self.prefix) { - return Some(( - Box::from(key.as_bytes()), - Box::from(val.as_slice()), - )); + if self.reverse_order { + for (key, val) in (&mut self.iter).rev() { + if key.starts_with(&self.prefix) { + return Some(( + Box::from(key.as_bytes()), + Box::from(val.as_slice()), + )); + } + } + } else { + for (key, val) in &mut self.iter { + if key.starts_with(&self.prefix) { + return Some(( + Box::from(key.as_bytes()), + Box::from(val.as_slice()), + )); + } } } None diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index d8c8e28091..347d0596cf 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -243,8 +243,13 @@ pub trait DBIter<'iter> { /// The concrete type of the iterator type PrefixIter: Debug + Iterator, u64)>; - /// Read account subspace key value pairs with the given prefix from the DB + /// Read account subspace key value pairs with the given prefix from the DB, + /// ordered by the storage keys. fn iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; + + /// Read account subspace key value pairs with the given prefix from the DB, + /// reverse ordered by the storage keys. + fn rev_iter_prefix(&'iter self, prefix: &Key) -> Self::PrefixIter; } /// Atomic batch write. @@ -411,7 +416,7 @@ where } } - /// Returns a prefix iterator and the gas cost + /// Returns a prefix iterator, ordered by storage keys, and the gas cost pub fn iter_prefix( &self, prefix: &Key, @@ -419,6 +424,15 @@ where (self.db.iter_prefix(prefix), prefix.len() as _) } + /// Returns a prefix iterator, reverse ordered by storage keys, and the gas + /// cost + pub fn rev_iter_prefix( + &self, + prefix: &Key, + ) -> (>::PrefixIter, u64) { + (self.db.rev_iter_prefix(prefix), prefix.len() as _) + } + /// Write a value to the specified subspace and returns the gas cost and the /// size difference pub fn write( @@ -729,6 +743,13 @@ where Ok(self.db.iter_prefix(prefix)) } + fn rev_iter_prefix( + &'iter self, + prefix: &crate::types::storage::Key, + ) -> std::result::Result { + Ok(self.db.rev_iter_prefix(prefix)) + } + fn iter_next( &self, iter: &mut Self::PrefixIter, diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index ab2f73784f..39b44d24d9 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -45,8 +45,8 @@ pub trait StorageRead<'iter> { /// Storage `has_key` in. It will try to read from the storage. fn has_key(&self, key: &storage::Key) -> Result; - /// Storage prefix iterator. It will try to get an iterator from the - /// storage. + /// Storage prefix iterator ordered by the storage keys. It will try to get + /// an iterator from the storage. /// /// For a more user-friendly iterator API, use [`fn@iter_prefix`] or /// [`fn@iter_prefix_bytes`] instead. @@ -55,7 +55,17 @@ pub trait StorageRead<'iter> { prefix: &storage::Key, ) -> Result; - /// Storage prefix iterator for. It will try to read from the storage. + /// Storage prefix iterator in reverse order of the storage keys. It will + /// try to get an iterator from the storage. + /// + /// For a more user-friendly iterator API, use [`fn@rev_iter_prefix`] or + /// [`fn@rev_iter_prefix_bytes`] instead. + fn rev_iter_prefix( + &'iter self, + prefix: &storage::Key, + ) -> Result; + + /// Storage prefix iterator. It will try to read from the storage. fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -97,7 +107,7 @@ pub trait StorageWrite { fn delete(&mut self, key: &storage::Key) -> Result<()>; } -/// Iterate items matching the given prefix. +/// Iterate items matching the given prefix, ordered by the storage keys. pub fn iter_prefix_bytes<'a>( storage: &'a impl StorageRead<'a>, prefix: &crate::types::storage::Key, @@ -125,7 +135,8 @@ pub fn iter_prefix_bytes<'a>( Ok(iter) } -/// Iterate Borsh encoded items matching the given prefix. +/// Iterate Borsh encoded items matching the given prefix, ordered by the +/// storage keys. pub fn iter_prefix<'a, T>( storage: &'a impl StorageRead<'a>, prefix: &crate::types::storage::Key, @@ -162,3 +173,71 @@ where }); Ok(iter) } + +/// Iterate items matching the given prefix, reverse ordered by the storage +/// keys. +pub fn rev_iter_prefix_bytes<'a>( + storage: &'a impl StorageRead<'a>, + prefix: &crate::types::storage::Key, +) -> Result)>> + 'a> { + let iter = storage.rev_iter_prefix(prefix)?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((key, val))) => { + let key = match storage::Key::parse(key).into_storage_result() { + Ok(key) => key, + Err(err) => { + // Propagate key encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + Some(Ok((key, val))) + } + Ok(None) => None, + Err(err) => { + // Propagate `iter_next` errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) +} + +/// Iterate Borsh encoded items matching the given prefix, reverse ordered by +/// the storage keys. +pub fn rev_iter_prefix<'a, T>( + storage: &'a impl StorageRead<'a>, + prefix: &crate::types::storage::Key, +) -> Result> + 'a> +where + T: BorshDeserialize, +{ + let iter = storage.rev_iter_prefix(prefix)?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((key, val))) => { + let key = match storage::Key::parse(key).into_storage_result() { + Ok(key) => key, + Err(err) => { + // Propagate key encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + let val = match T::try_from_slice(&val).into_storage_result() { + Ok(val) => val, + Err(err) => { + // Propagate val encoding errors into Iterator's Item + return Some(Err(err)); + } + }; + Some(Ok((key, val))) + } + Ok(None) => None, + Err(err) => { + // Propagate `iter_next` errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) +} diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index 95e7c48782..9af4a992d3 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -91,13 +91,20 @@ pub trait VpEnv { /// current transaction is being applied. fn get_block_epoch(&self) -> Result; - /// Storage prefix iterator. It will try to get an iterator from the - /// storage. + /// Storage prefix iterator, ordered by storage keys. It will try to get an + /// iterator from the storage. fn iter_prefix( &self, prefix: &Key, ) -> Result; + /// Storage prefix iterator, reverse ordered by storage keys. It will try to + /// get an iterator from the storage. + fn rev_iter_prefix( + &self, + prefix: &Key, + ) -> Result; + /// Storage prefix iterator for prior state (before tx execution). It will /// try to read from the storage. fn iter_pre_next( @@ -396,7 +403,8 @@ where Ok(epoch) } -/// Storage prefix iterator. It will try to get an iterator from the storage. +/// Storage prefix iterator, ordered by storage keys. It will try to get an +/// iterator from the storage. pub fn iter_prefix<'a, DB, H>( gas_meter: &mut VpGasMeter, storage: &'a Storage, @@ -411,6 +419,22 @@ where Ok(iter) } +/// Storage prefix iterator, reverse ordered by storage keys. It will try to get +/// an iterator from the storage. +pub fn rev_iter_prefix<'a, DB, H>( + gas_meter: &mut VpGasMeter, + storage: &'a Storage, + prefix: &Key, +) -> EnvResult<>::PrefixIter> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, +{ + let (iter, gas) = storage.rev_iter_prefix(prefix); + add_gas(gas_meter, gas)?; + Ok(iter) +} + /// Storage prefix iterator for prior state (before tx execution). It will try /// to read from the storage. pub fn iter_pre_next( diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index fc7fd33e0c..533a05d94e 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -665,7 +665,7 @@ where /// Storage prefix iterator function exposed to the wasm VM Tx environment. /// It will try to get an iterator from the storage and return the corresponding -/// ID of the iterator. +/// ID of the iterator, ordered by storage keys. pub fn tx_iter_prefix( env: &TxVmEnv, prefix_ptr: u64, @@ -695,6 +695,38 @@ where Ok(iterators.insert(iter).id()) } +/// Storage prefix iterator function exposed to the wasm VM Tx environment. +/// It will try to get an iterator from the storage and return the corresponding +/// ID of the iterator, reverse ordered by storage keys. +pub fn tx_rev_iter_prefix( + env: &TxVmEnv, + prefix_ptr: u64, + prefix_len: u64, +) -> TxResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + let (prefix, gas) = env + .memory + .read_string(prefix_ptr, prefix_len as _) + .map_err(|e| TxRuntimeError::MemoryError(Box::new(e)))?; + tx_add_gas(env, gas)?; + + tracing::debug!("tx_rev_iter_prefix {}, prefix {}", prefix, prefix_ptr); + + let prefix = + Key::parse(prefix).map_err(TxRuntimeError::StorageDataError)?; + + let storage = unsafe { env.ctx.storage.get() }; + let iterators = unsafe { env.ctx.iterators.get() }; + let (iter, gas) = storage.rev_iter_prefix(&prefix); + tx_add_gas(env, gas)?; + Ok(iterators.insert(iter).id()) +} + /// Storage prefix iterator next function exposed to the wasm VM Tx environment. /// It will try to read from the write log first and if no entry found then from /// the storage. @@ -1195,7 +1227,7 @@ where /// Storage prefix iterator function exposed to the wasm VM VP environment. /// It will try to get an iterator from the storage and return the corresponding -/// ID of the iterator. +/// ID of the iterator, ordered by storage keys. pub fn vp_iter_prefix( env: &VpVmEnv, prefix_ptr: u64, @@ -1225,6 +1257,38 @@ where Ok(iterators.insert(iter).id()) } +/// Storage prefix iterator function exposed to the wasm VM VP environment. +/// It will try to get an iterator from the storage and return the corresponding +/// ID of the iterator, reverse ordered by storage keys. +pub fn vp_rev_iter_prefix( + env: &VpVmEnv, + prefix_ptr: u64, + prefix_len: u64, +) -> vp_env::EnvResult +where + MEM: VmMemory, + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + EVAL: VpEvaluator, + CA: WasmCacheAccess, +{ + let (prefix, gas) = env + .memory + .read_string(prefix_ptr, prefix_len as _) + .map_err(|e| vp_env::RuntimeError::MemoryError(Box::new(e)))?; + let gas_meter = unsafe { env.ctx.gas_meter.get() }; + vp_env::add_gas(gas_meter, gas)?; + + let prefix = + Key::parse(prefix).map_err(vp_env::RuntimeError::StorageDataError)?; + tracing::debug!("vp_rev_iter_prefix {}", prefix); + + let storage = unsafe { env.ctx.storage.get() }; + let iter = vp_env::rev_iter_prefix(gas_meter, storage, &prefix)?; + let iterators = unsafe { env.ctx.iterators.get() }; + Ok(iterators.insert(iter).id()) +} + /// Storage prefix iterator for prior state (before tx execution) function /// exposed to the wasm VM VP environment. It will try to read from the storage. /// diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index 1a50c5533d..06d45fa4f9 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -67,6 +67,7 @@ where "anoma_tx_write_temp" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_write_temp), "anoma_tx_delete" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_delete), "anoma_tx_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_iter_prefix), + "anoma_tx_rev_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_rev_iter_prefix), "anoma_tx_iter_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_iter_next), "anoma_tx_insert_verifier" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_insert_verifier), "anoma_tx_update_validity_predicate" => Function::new_native_with_env(wasm_store, env.clone(), host_env::tx_update_validity_predicate), @@ -107,6 +108,7 @@ where "anoma_vp_has_key_pre" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_has_key_pre), "anoma_vp_has_key_post" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_has_key_post), "anoma_vp_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_prefix), + "anoma_vp_rev_iter_prefix" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_rev_iter_prefix), "anoma_vp_iter_pre_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_pre_next), "anoma_vp_iter_post_next" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_iter_post_next), "anoma_vp_get_chain_id" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_chain_id), diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 346cb6bdd4..728c488bca 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -335,6 +335,7 @@ mod native_tx_host_env { )); native_host_fn!(tx_delete(key_ptr: u64, key_len: u64)); native_host_fn!(tx_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); + native_host_fn!(tx_rev_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); native_host_fn!(tx_iter_next(iter_id: u64) -> i64); native_host_fn!(tx_insert_verifier(addr_ptr: u64, addr_len: u64)); native_host_fn!(tx_update_validity_predicate( diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index d849a11487..88aa63d530 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -326,6 +326,7 @@ mod native_vp_host_env { native_host_fn!(vp_has_key_pre(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_has_key_post(key_ptr: u64, key_len: u64) -> i64); native_host_fn!(vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); + native_host_fn!(vp_rev_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64); native_host_fn!(vp_iter_pre_next(iter_id: u64) -> i64); native_host_fn!(vp_iter_post_next(iter_id: u64) -> i64); native_host_fn!(vp_get_chain_id(result_ptr: u64)); diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index bd1833ec58..c609f944dd 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -24,7 +24,8 @@ pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; use namada::ledger::storage_api; pub use namada::ledger::storage_api::{ - iter_prefix, iter_prefix_bytes, StorageRead, StorageWrite, + iter_prefix, iter_prefix_bytes, rev_iter_prefix, rev_iter_prefix_bytes, + StorageRead, StorageWrite, }; pub use namada::ledger::treasury::storage as treasury_storage; pub use namada::ledger::tx_env::TxEnv; @@ -178,6 +179,17 @@ impl StorageRead<'_> for Ctx { Ok(KeyValIterator(iter_id, PhantomData)) } + fn rev_iter_prefix( + &self, + prefix: &storage::Key, + ) -> storage_api::Result { + let prefix = prefix.to_string(); + let iter_id = unsafe { + anoma_tx_rev_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) + } + fn iter_next( &self, iter: &mut Self::PrefixIter, diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 53c594dbab..1421dbde48 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -48,9 +48,17 @@ pub mod tx { // Delete the given key and its value pub fn anoma_tx_delete(key_ptr: u64, key_len: u64); - // Get an ID of a data iterator with key prefix + // Get an ID of a data iterator with key prefix, ordered by storage + // keys. pub fn anoma_tx_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; + // Get an ID of a data iterator with key prefix, reverse ordered by + // storage keys. + pub fn anoma_tx_rev_iter_prefix( + prefix_ptr: u64, + prefix_len: u64, + ) -> u64; + // Returns the size of the value (can be 0), or -1 if there's no next // value. If a value is found, it will be placed in the read // cache, because we cannot allocate a buffer for it before we know @@ -133,9 +141,17 @@ pub mod vp { // Returns 1 if the key is present in posterior state, -1 otherwise. pub fn anoma_vp_has_key_post(key_ptr: u64, key_len: u64) -> i64; - // Get an ID of a data iterator with key prefix + // Get an ID of a data iterator with key prefix, ordered by storage + // keys. pub fn anoma_vp_iter_prefix(prefix_ptr: u64, prefix_len: u64) -> u64; + // Get an ID of a data iterator with key prefix, reverse ordered by + // storage keys. + pub fn anoma_vp_rev_iter_prefix( + prefix_ptr: u64, + prefix_len: u64, + ) -> u64; + // Read variable-length prior state when we don't know the size // up-front, returns the size of the value (can be 0), or -1 if // the key is not present. If a value is found, it will be placed in the diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 3ee761f78f..862071084a 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -23,7 +23,8 @@ pub use borsh::{BorshDeserialize, BorshSerialize}; pub use error::*; pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::storage_api::{ - self, iter_prefix, iter_prefix_bytes, StorageRead, + self, iter_prefix, iter_prefix_bytes, rev_iter_prefix, + rev_iter_prefix_bytes, StorageRead, }; pub use namada::ledger::vp_env::VpEnv; pub use namada::ledger::{parameters, pos as proof_of_stake}; @@ -238,6 +239,14 @@ impl VpEnv for Ctx { self.pre().iter_prefix(prefix).into_env_result() } + fn rev_iter_prefix( + &self, + prefix: &storage::Key, + ) -> Result { + // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl + self.pre().rev_iter_prefix(prefix).into_env_result() + } + fn iter_pre_next( &self, iter: &mut Self::PrefixIter, @@ -339,6 +348,14 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { iter_prefix_impl(prefix) } + fn rev_iter_prefix( + &self, + prefix: &storage::Key, + ) -> storage_api::Result { + // Note that this is the same as `CtxPostStorageRead` + rev_iter_prefix_impl(prefix) + } + fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -416,6 +433,14 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { iter_prefix_impl(prefix) } + fn rev_iter_prefix( + &self, + prefix: &storage::Key, + ) -> storage_api::Result { + // Note that this is the same as `CtxPreStorageRead` + rev_iter_prefix_impl(prefix) + } + fn iter_next( &self, iter: &mut Self::PrefixIter, @@ -454,6 +479,16 @@ fn iter_prefix_impl( Ok(KeyValIterator(iter_id, PhantomData)) } +fn rev_iter_prefix_impl( + prefix: &storage::Key, +) -> Result)>, storage_api::Error> { + let prefix = prefix.to_string(); + let iter_id = unsafe { + anoma_vp_rev_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) + }; + Ok(KeyValIterator(iter_id, PhantomData)) +} + fn get_chain_id() -> Result { let result = Vec::with_capacity(CHAIN_ID_LENGTH); unsafe { From 881c93cbd9406bf8f48062f6fafbbb3750f91e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 18:13:13 +0200 Subject: [PATCH 281/373] tests: extend prefix iter tests for reverse order --- tests/src/vm_host_env/mod.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index f846325c40..e585ab7924 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -182,6 +182,19 @@ mod tests { .sorted() .map(|i| (prefix.push(i).unwrap(), *i)); itertools::assert_equal(iter, expected); + + // Try to iterate over their prefix in reverse + let iter = namada_tx_prelude::rev_iter_prefix(tx::ctx(), &prefix) + .unwrap() + .map(Result::unwrap); + + // The order has to be reverse sorted by sub-key value + let expected = sub_keys + .iter() + .sorted() + .rev() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter, expected); } #[test] @@ -411,6 +424,19 @@ mod tests { (prefix.push(i).unwrap(), val) }); itertools::assert_equal(iter_post, expected_post); + + // Try to iterate over their prefix in reverse + let iter_pre = namada_vp_prelude::rev_iter_prefix(&ctx_pre, &prefix) + .unwrap() + .map(|item| item.unwrap()); + + // The order in has to be reverse sorted by sub-key value + let expected_pre = sub_keys + .iter() + .sorted() + .rev() + .map(|i| (prefix.push(i).unwrap(), *i)); + itertools::assert_equal(iter_pre, expected_pre); } #[test] From d786a4aa979195ad65d381ac0ab61119db2f9712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 8 Sep 2022 18:16:51 +0200 Subject: [PATCH 282/373] changelog: add #458 --- .changelog/unreleased/improvements/409-sorted-prefix-iter.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/unreleased/improvements/409-sorted-prefix-iter.md diff --git a/.changelog/unreleased/improvements/409-sorted-prefix-iter.md b/.changelog/unreleased/improvements/409-sorted-prefix-iter.md new file mode 100644 index 0000000000..2f95505960 --- /dev/null +++ b/.changelog/unreleased/improvements/409-sorted-prefix-iter.md @@ -0,0 +1,3 @@ +- Fix order of prefix iterator to be sorted by storage + keys and add support for a reverse order prefix iterator. + ([#409](https://github.com/anoma/namada/issues/409)) \ No newline at end of file From 1d2f1dd15364890a6001139d592f4cda1a396f80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 9 Sep 2022 05:57:59 +0000 Subject: [PATCH 283/373] [ci skip] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 0c7b7cf504..f5295c8f1a 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.d80c5ac518c223eea92a51eeb033462e9ea4498530d12de5b6e6ca6b3fa59ea8.wasm", - "tx_from_intent.wasm": "tx_from_intent.49565974c6e2c3d64a05729bd57217b244d804fc9f4e5369d1f3515aeaa8397f.wasm", - "tx_ibc.wasm": "tx_ibc.0d9d639037a8dc54c53ecbedc8396ea103ee7c8f485790ddf7223f4e7e6a9779.wasm", - "tx_init_account.wasm": "tx_init_account.97bfee0b78c87abc217c58351f1d6242990a06c7379b27f948950f04f36b49a2.wasm", - "tx_init_nft.wasm": "tx_init_nft.6207eabda37cd356b6093d069f388bd84463b5e1b8860811a36a9b63da84951f.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.1073d69f69172276c61c104f7b4678019723df19ddb30aedf6293a00de973846.wasm", - "tx_init_validator.wasm": "tx_init_validator.40f0152c1bd59f46ec26123d98d0b49a0a458335b6012818cf8504b7708bf625.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.f1c8039a6fb5e01e7441cfa2e3fb2f84a1cb60ed660745ddc172d0d01f1b0ab1.wasm", - "tx_transfer.wasm": "tx_transfer.191d28c340900e6dc297e66b054cd83b690ae0357a8e13b37962b265b2e17da8.wasm", - "tx_unbond.wasm": "tx_unbond.ea73369f68abef405c4f7a3a09c3da6aa68493108d48b1e4e243d26766f00283.wasm", - "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.5e51a66746df8c7e786cc6837a74f87d239442ace67d1c4ef4e6751aa725514f.wasm", - "tx_withdraw.wasm": "tx_withdraw.f776265133f972e6705797561b6bb37f9e21de07f3611b23cfdd6e39cb252c0f.wasm", - "vp_nft.wasm": "vp_nft.1a32d37b32c616119d156128560a0eeb43206e91069e2c3875e74211ed3ad01f.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.c30baa6d2dab2c336a6b2485e3ccf41cd548b135ca5245b80fab15858c70365c.wasm", - "vp_token.wasm": "vp_token.9f27c2e823691487bb6631fbfecb39837c1f404ef9b263e810a5a2a76f4c5957.wasm", - "vp_user.wasm": "vp_user.59b7a9262c951ff451d3aec0604ae3dd0fc4729f8d994c114be6320ce5d38712.wasm" + "tx_bond.wasm": "tx_bond.bef50a074940c1b8cd788b93376162553bc11ec3521ffbbc0e8913ae27edfac1.wasm", + "tx_from_intent.wasm": "tx_from_intent.41b56839c7b797407b71a9b44b1b58b0b9f98069ccfbbdba1d5ce9501a78ddea.wasm", + "tx_ibc.wasm": "tx_ibc.b0ab86c7b67a612ed559b57e2277e307139223f3fed245b62e6d0524e5785cad.wasm", + "tx_init_account.wasm": "tx_init_account.913c7218e5f24ee4d86b89d9020624e54340bd597278b0596baba721a2944e70.wasm", + "tx_init_nft.wasm": "tx_init_nft.2f2c198643996a99ef0621f7a1c5ad824e176258f8f5755209378cf5b7770f64.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d4332ff996775b3e243afacdf311be7e0b3f32dc3c15edfa89d0ce9ee6b6bb6b.wasm", + "tx_init_validator.wasm": "tx_init_validator.5f6b566d549ab182874688a635f1c2900605d7a7a8d6850fa4c084ce5277d212.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.19a9c2b62ff8301979cb038e2ad26fcf8f67c089ec22cb915e1c17e4f95adf1e.wasm", + "tx_transfer.wasm": "tx_transfer.c90e697f22ce49d30e9f8120f5e37452bcd8bf5b7d0f65426da7283a9c141ab9.wasm", + "tx_unbond.wasm": "tx_unbond.f01fea78e3225ae0029b99a735f72e9309a9e68c80bf8cd3e620456898edd49c.wasm", + "tx_update_vp.wasm": "tx_update_vp.f8eef21b09d932da44babf0faf2a4137fa075d952ea92bb0b652ce141475ad42.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.fb2f1b95798704a8df8860c18777e59ad8bf0b69e31f928f1bb5ed0c8c0eed55.wasm", + "tx_withdraw.wasm": "tx_withdraw.843be20010c36500170b231f10fdf80c4486b7afef81217c4f0ac2600229db67.wasm", + "vp_nft.wasm": "vp_nft.10393706d5d717a17285fc16128fee8703f2a0b07bf952e275c8836b7df4b1fd.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.f86a33e5e023a07f420f4d44a01f0c686e84989790a9c47c718cb83b6fce65fb.wasm", + "vp_token.wasm": "vp_token.b2d86a59d1024992ade25ae2a513abdddd41a3f13f33fff0eea35bec0e5faca0.wasm", + "vp_user.wasm": "vp_user.f66dae6078241f8a9a564bf58928bf9f005a5ee1318e245850c8cbfb45474a5c.wasm" } \ No newline at end of file From a700e4902a177408f8945f1014e557a3d62b84c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 15:31:10 +0200 Subject: [PATCH 284/373] ledger: use storage_api::Error in VpEnv and TxEnv instead of generic This inverts the wrapping of errors, storage_api::Error now wraps NativeVp:Error and some other IBC and PoS custom errors --- shared/src/ledger/ibc/handler.rs | 9 ++ shared/src/ledger/native_vp.rs | 55 ++++++----- shared/src/ledger/pos/mod.rs | 36 ++++++- shared/src/ledger/pos/vp.rs | 12 +-- shared/src/ledger/storage_api/error.rs | 23 +++++ shared/src/ledger/storage_api/mod.rs | 2 +- shared/src/ledger/tx_env.rs | 25 ++--- shared/src/ledger/vp_env.rs | 67 ++++++------- tx_prelude/src/error.rs | 112 ---------------------- tx_prelude/src/ibc.rs | 7 -- tx_prelude/src/lib.rs | 30 +++--- tx_prelude/src/proof_of_stake.rs | 47 ++------- vp_prelude/src/error.rs | 110 --------------------- vp_prelude/src/lib.rs | 88 ++++++++--------- wasm/wasm_source/src/tx_bond.rs | 4 +- wasm/wasm_source/src/tx_from_intent.rs | 4 +- wasm/wasm_source/src/tx_ibc.rs | 2 +- wasm/wasm_source/src/tx_init_account.rs | 4 +- wasm/wasm_source/src/tx_init_nft.rs | 4 +- wasm/wasm_source/src/tx_init_proposal.rs | 4 +- wasm/wasm_source/src/tx_init_validator.rs | 4 +- wasm/wasm_source/src/tx_mint_nft.rs | 4 +- wasm/wasm_source/src/tx_transfer.rs | 4 +- wasm/wasm_source/src/tx_unbond.rs | 4 +- wasm/wasm_source/src/tx_update_vp.rs | 4 +- wasm/wasm_source/src/tx_vote_proposal.rs | 4 +- wasm/wasm_source/src/tx_withdraw.rs | 4 +- wasm_for_tests/wasm_source/src/lib.rs | 4 +- 28 files changed, 237 insertions(+), 440 deletions(-) delete mode 100644 tx_prelude/src/error.rs delete mode 100644 vp_prelude/src/error.rs diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index 5cbee20756..4a3fe528a9 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -70,6 +70,7 @@ use crate::ibc::events::IbcEvent; use crate::ibc::mock::client_state::{MockClientState, MockConsensusState}; use crate::ibc::timestamp::Timestamp; use crate::ledger::ibc::storage; +use crate::ledger::storage_api; use crate::tendermint::Time; use crate::tendermint_proto::{Error as ProtoError, Protobuf}; use crate::types::address::{Address, InternalAddress}; @@ -117,6 +118,14 @@ pub enum Error { ReceivingToken(String), } +// This is needed to use `ibc::Handler::Error` with `IbcActions` in +// `tx_prelude/src/ibc.rs` +impl From for storage_api::Error { + fn from(err: Error) -> Self { + storage_api::Error::new(err) + } +} + /// for handling IBC modules pub type Result = std::result::Result; diff --git a/shared/src/ledger/native_vp.rs b/shared/src/ledger/native_vp.rs index f17b339086..b98794d12c 100644 --- a/shared/src/ledger/native_vp.rs +++ b/shared/src/ledger/native_vp.rs @@ -17,7 +17,9 @@ use crate::vm::prefix_iter::PrefixIterators; use crate::vm::WasmCacheAccess; /// Possible error in a native VP host function call -pub type Error = vp_env::RuntimeError; +/// The `storage_api::Error` may wrap the `vp_env::RuntimeError` and can +/// be extended with other custom errors when using `trait VpEnv`. +pub type Error = storage_api::Error; /// A native VP module should implement its validation logic using this trait. pub trait NativeVp { @@ -201,23 +203,23 @@ where &self, prefix: &crate::types::storage::Key, ) -> Result { - self.ctx.iter_prefix(prefix).into_storage_result() + self.ctx.iter_prefix(prefix) } fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id().into_storage_result() + self.ctx.get_chain_id() } fn get_block_height(&self) -> Result { - self.ctx.get_block_height().into_storage_result() + self.ctx.get_block_height() } fn get_block_hash(&self) -> Result { - self.ctx.get_block_hash().into_storage_result() + self.ctx.get_block_hash() } fn get_block_epoch(&self) -> Result { - self.ctx.get_block_epoch().into_storage_result() + self.ctx.get_block_epoch() } } @@ -275,23 +277,23 @@ where &self, prefix: &crate::types::storage::Key, ) -> Result { - self.ctx.iter_prefix(prefix).into_storage_result() + self.ctx.iter_prefix(prefix) } fn get_chain_id(&self) -> Result { - self.ctx.get_chain_id().into_storage_result() + self.ctx.get_chain_id() } fn get_block_height(&self) -> Result { - self.ctx.get_block_height().into_storage_result() + self.ctx.get_block_height() } fn get_block_hash(&self) -> Result { - self.ctx.get_block_hash().into_storage_result() + self.ctx.get_block_hash() } fn get_block_epoch(&self) -> Result { - self.ctx.get_block_epoch().into_storage_result() + self.ctx.get_block_epoch() } } @@ -301,7 +303,6 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type Error = Error; type Post = CtxPostStorageRead<'view, 'a, DB, H, CA>; type Pre = CtxPreStorageRead<'view, 'a, DB, H, CA>; type PrefixIter = >::PrefixIter; @@ -317,61 +318,70 @@ where fn read_temp( &self, key: &Key, - ) -> Result, Self::Error> { + ) -> Result, storage_api::Error> { vp_env::read_temp( &mut *self.gas_meter.borrow_mut(), self.write_log, key, ) .map(|data| data.and_then(|t| T::try_from_slice(&t[..]).ok())) + .into_storage_result() } fn read_bytes_temp( &self, key: &Key, - ) -> Result>, Self::Error> { + ) -> Result>, storage_api::Error> { vp_env::read_temp( &mut *self.gas_meter.borrow_mut(), self.write_log, key, ) + .into_storage_result() } - fn get_chain_id(&'view self) -> Result { + fn get_chain_id(&'view self) -> Result { vp_env::get_chain_id(&mut *self.gas_meter.borrow_mut(), self.storage) + .into_storage_result() } - fn get_block_height(&'view self) -> Result { + fn get_block_height( + &'view self, + ) -> Result { vp_env::get_block_height( &mut *self.gas_meter.borrow_mut(), self.storage, ) + .into_storage_result() } - fn get_block_hash(&'view self) -> Result { + fn get_block_hash(&'view self) -> Result { vp_env::get_block_hash(&mut *self.gas_meter.borrow_mut(), self.storage) + .into_storage_result() } - fn get_block_epoch(&'view self) -> Result { + fn get_block_epoch(&'view self) -> Result { vp_env::get_block_epoch(&mut *self.gas_meter.borrow_mut(), self.storage) + .into_storage_result() } fn iter_prefix( &'view self, prefix: &Key, - ) -> Result { + ) -> Result { vp_env::iter_prefix( &mut *self.gas_meter.borrow_mut(), self.storage, prefix, ) + .into_storage_result() } fn eval( &self, vp_code: Vec, input_data: Vec, - ) -> Result { + ) -> Result { #[cfg(feature = "wasm-runtime")] { use std::marker::PhantomData; @@ -429,11 +439,12 @@ where &self, pk: &crate::types::key::common::PublicKey, sig: &crate::types::key::common::Signature, - ) -> Result { + ) -> Result { Ok(self.tx.verify_sig(pk, sig).is_ok()) } - fn get_tx_code_hash(&self) -> Result { + fn get_tx_code_hash(&self) -> Result { vp_env::get_tx_code_hash(&mut *self.gas_meter.borrow_mut(), self.tx) + .into_storage_result() } } diff --git a/shared/src/ledger/pos/mod.rs b/shared/src/ledger/pos/mod.rs index a9d72e84bb..3b498727df 100644 --- a/shared/src/ledger/pos/mod.rs +++ b/shared/src/ledger/pos/mod.rs @@ -13,6 +13,7 @@ use namada_proof_of_stake::PosBase; pub use storage::*; pub use vp::PosVP; +use super::storage_api; use crate::ledger::storage::{self as ledger_storage, Storage, StorageHasher}; use crate::types::address::{self, Address, InternalAddress}; use crate::types::storage::Epoch; @@ -88,6 +89,40 @@ impl From for Epoch { } } +// The error conversions are needed to implement `PosActions` in +// `tx_prelude/src/proof_of_stake.rs` +impl From> + for storage_api::Error +{ + fn from(err: namada_proof_of_stake::BecomeValidatorError
) -> Self { + Self::new(err) + } +} + +impl From> for storage_api::Error { + fn from(err: namada_proof_of_stake::BondError
) -> Self { + Self::new(err) + } +} + +impl From> + for storage_api::Error +{ + fn from( + err: namada_proof_of_stake::UnbondError, + ) -> Self { + Self::new(err) + } +} + +impl From> + for storage_api::Error +{ + fn from(err: namada_proof_of_stake::WithdrawError
) -> Self { + Self::new(err) + } +} + #[macro_use] mod macros { /// Implement `PosReadOnly` for a type that implements @@ -115,7 +150,6 @@ mod macros { $( $any )* { type Address = $crate::types::address::Address; - // type Error = $crate::ledger::native_vp::Error; type $error = $err_ty; type PublicKey = $crate::types::key::common::PublicKey; type TokenAmount = $crate::types::token::Amount; diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 2f19b6d1a4..0551c5de7f 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -47,8 +47,6 @@ use crate::vm::WasmCacheAccess; pub enum Error { #[error("Native VP error: {0}")] NativeVpError(native_vp::Error), - #[error("Storage error: {0}")] - StorageApi(storage_api::Error), } /// PoS functions result @@ -324,7 +322,7 @@ where } impl_pos_read_only! { - type Error = native_vp::Error; + type Error = storage_api::Error; impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPreStorageRead<'f, 'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, @@ -333,7 +331,7 @@ impl_pos_read_only! { } impl_pos_read_only! { - type Error = native_vp::Error; + type Error = storage_api::Error; impl<'f, 'a, DB, H, CA> PosReadOnly for CtxPostStorageRead<'f, 'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter> +'static, @@ -346,9 +344,3 @@ impl From for Error { Self::NativeVpError(err) } } - -impl From for Error { - fn from(err: storage_api::Error) -> Self { - Self::StorageApi(err) - } -} diff --git a/shared/src/ledger/storage_api/error.rs b/shared/src/ledger/storage_api/error.rs index d01fbfd287..8af95be723 100644 --- a/shared/src/ledger/storage_api/error.rs +++ b/shared/src/ledger/storage_api/error.rs @@ -6,6 +6,8 @@ use thiserror::Error; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { + #[error("{0}")] + SimpleMessage(&'static str), #[error("{0}")] Custom(CustomError), #[error("{0}: {1}")] @@ -48,6 +50,12 @@ impl Error { Self::Custom(CustomError(error.into())) } + /// Create an [`enum@Error`] from a static message. + #[inline] + pub const fn new_const(msg: &'static str) -> Self { + Self::SimpleMessage(msg) + } + /// Wrap another [`std::error::Error`] with a static message. pub fn wrap(msg: &'static str, error: E) -> Self where @@ -66,3 +74,18 @@ impl std::fmt::Display for CustomError { self.0.fmt(f) } } + +/// An extension to [`Option`] to allow turning `None` case to an Error from a +/// static string (handy for WASM). +pub trait OptionExt { + /// Transforms the [`Option`] into a [`Result`], mapping + /// [`Some(v)`] to [`Ok(v)`] and [`None`] to the given static error + /// message. + fn ok_or_err_msg(self, msg: &'static str) -> Result; +} + +impl OptionExt for Option { + fn ok_or_err_msg(self, msg: &'static str) -> Result { + self.ok_or_else(|| Error::new_const(msg)) + } +} diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 94be4d8568..5dfec7e76e 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -4,7 +4,7 @@ mod error; use borsh::{BorshDeserialize, BorshSerialize}; -pub use error::{CustomError, Error, Result, ResultExt}; +pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; use crate::types::storage::{self, BlockHash, BlockHeight, Epoch}; diff --git a/shared/src/ledger/tx_env.rs b/shared/src/ledger/tx_env.rs index 1db8fad09b..7672ac6505 100644 --- a/shared/src/ledger/tx_env.rs +++ b/shared/src/ledger/tx_env.rs @@ -3,7 +3,7 @@ use borsh::BorshSerialize; -use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::address::Address; use crate::types::ibc::IbcEvent; use crate::types::storage; @@ -11,23 +11,20 @@ use crate::types::time::Rfc3339String; /// Transaction host functions pub trait TxEnv<'iter>: StorageRead<'iter> + StorageWrite { - /// Host env functions possible errors - type Error; - /// Write a temporary value to be encoded with Borsh at the given key to /// storage. fn write_temp( &mut self, key: &storage::Key, val: T, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; /// Write a temporary value as bytes at the given key to storage. fn write_bytes_temp( &mut self, key: &storage::Key, val: impl AsRef<[u8]>, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; /// Insert a verifier address. This address must exist on chain, otherwise /// the transaction will be rejected. @@ -35,26 +32,32 @@ pub trait TxEnv<'iter>: StorageRead<'iter> + StorageWrite { /// Validity predicates of each verifier addresses inserted in the /// transaction will validate the transaction and will receive all the /// changed storage keys and initialized accounts in their inputs. - fn insert_verifier(&mut self, addr: &Address) -> Result<(), Self::Error>; + fn insert_verifier( + &mut self, + addr: &Address, + ) -> Result<(), storage_api::Error>; /// Initialize a new account generates a new established address and /// writes the given code as its validity predicate into the storage. fn init_account( &mut self, code: impl AsRef<[u8]>, - ) -> Result; + ) -> Result; /// Update a validity predicate fn update_validity_predicate( &mut self, addr: &Address, code: impl AsRef<[u8]>, - ) -> Result<(), Self::Error>; + ) -> Result<(), storage_api::Error>; /// Emit an IBC event. There can be only one event per transaction. On /// multiple calls, only the last emitted event will be used. - fn emit_ibc_event(&mut self, event: &IbcEvent) -> Result<(), Self::Error>; + fn emit_ibc_event( + &mut self, + event: &IbcEvent, + ) -> Result<(), storage_api::Error>; /// Get time of the current block header as rfc 3339 string - fn get_block_time(&self) -> Result; + fn get_block_time(&self) -> Result; } diff --git a/shared/src/ledger/vp_env.rs b/shared/src/ledger/vp_env.rs index e1f72d8505..8398f37cda 100644 --- a/shared/src/ledger/vp_env.rs +++ b/shared/src/ledger/vp_env.rs @@ -22,9 +22,6 @@ pub trait VpEnv<'view> { /// Storage read prefix iterator type PrefixIter; - /// Host functions possible errors, extensible with custom user errors. - type Error: From; - /// Type to read storage state before the transaction execution type Pre: StorageRead<'view, PrefixIter = Self::PrefixIter>; @@ -43,36 +40,37 @@ pub trait VpEnv<'view> { fn read_temp( &self, key: &Key, - ) -> Result, Self::Error>; + ) -> Result, storage_api::Error>; /// Storage read temporary state raw bytes (after tx execution). It will try /// to read from only the write log. fn read_bytes_temp( &self, key: &Key, - ) -> Result>, Self::Error>; + ) -> Result>, storage_api::Error>; /// Getting the chain ID. - fn get_chain_id(&'view self) -> Result; + fn get_chain_id(&'view self) -> Result; /// Getting the block height. The height is that of the block to which the /// current transaction is being applied. - fn get_block_height(&'view self) -> Result; + fn get_block_height(&'view self) + -> Result; /// Getting the block hash. The height is that of the block to which the /// current transaction is being applied. - fn get_block_hash(&'view self) -> Result; + fn get_block_hash(&'view self) -> Result; /// Getting the block epoch. The epoch is that of the block to which the /// current transaction is being applied. - fn get_block_epoch(&'view self) -> Result; + fn get_block_epoch(&'view self) -> Result; /// Storage prefix iterator. It will try to get an iterator from the /// storage. fn iter_prefix( &'view self, prefix: &Key, - ) -> Result; + ) -> Result; /// Evaluate a validity predicate with given data. The address, changed /// storage keys and verifiers will have the same values as the input to @@ -84,7 +82,7 @@ pub trait VpEnv<'view> { &self, vp_code: Vec, input_data: Vec, - ) -> Result; + ) -> Result; /// Verify a transaction signature. The signature is expected to have been /// produced on the encoded transaction [`crate::proto::Tx`] @@ -93,10 +91,10 @@ pub trait VpEnv<'view> { &self, pk: &common::PublicKey, sig: &common::Signature, - ) -> Result; + ) -> Result; /// Get a tx hash - fn get_tx_code_hash(&self) -> Result; + fn get_tx_code_hash(&self) -> Result; // ---- Methods below have default implementation via `pre/post` ---- @@ -105,8 +103,8 @@ pub trait VpEnv<'view> { fn read_pre( &'view self, key: &Key, - ) -> Result, Self::Error> { - self.pre().read(key).map_err(Into::into) + ) -> Result, storage_api::Error> { + self.pre().read(key) } /// Storage read prior state raw bytes (before tx execution). It @@ -114,8 +112,8 @@ pub trait VpEnv<'view> { fn read_bytes_pre( &'view self, key: &Key, - ) -> Result>, Self::Error> { - self.pre().read_bytes(key).map_err(Into::into) + ) -> Result>, storage_api::Error> { + self.pre().read_bytes(key) } /// Storage read posterior state Borsh encoded value (after tx execution). @@ -124,8 +122,8 @@ pub trait VpEnv<'view> { fn read_post( &'view self, key: &Key, - ) -> Result, Self::Error> { - self.post().read(key).map_err(Into::into) + ) -> Result, storage_api::Error> { + self.post().read(key) } /// Storage read posterior state raw bytes (after tx execution). It will try @@ -134,20 +132,23 @@ pub trait VpEnv<'view> { fn read_bytes_post( &'view self, key: &Key, - ) -> Result>, Self::Error> { - self.post().read_bytes(key).map_err(Into::into) + ) -> Result>, storage_api::Error> { + self.post().read_bytes(key) } /// Storage `has_key` in prior state (before tx execution). It will try to /// read from the storage. - fn has_key_pre(&'view self, key: &Key) -> Result { - self.pre().has_key(key).map_err(Into::into) + fn has_key_pre(&'view self, key: &Key) -> Result { + self.pre().has_key(key) } /// Storage `has_key` in posterior state (after tx execution). It will try /// to check the write log first and if no entry found then the storage. - fn has_key_post(&'view self, key: &Key) -> Result { - self.post().has_key(key).map_err(Into::into) + fn has_key_post( + &'view self, + key: &Key, + ) -> Result { + self.post().has_key(key) } /// Storage prefix iterator for prior state (before tx execution). It will @@ -155,8 +156,8 @@ pub trait VpEnv<'view> { fn iter_pre_next( &'view self, iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error> { - self.pre().iter_next(iter).map_err(Into::into) + ) -> Result)>, storage_api::Error> { + self.pre().iter_next(iter) } /// Storage prefix iterator next for posterior state (after tx execution). @@ -165,8 +166,8 @@ pub trait VpEnv<'view> { fn iter_post_next( &'view self, iter: &mut Self::PrefixIter, - ) -> Result)>, Self::Error> { - self.post().iter_next(iter).map_err(Into::into) + ) -> Result)>, storage_api::Error> { + self.post().iter_next(iter) } } @@ -190,8 +191,6 @@ pub enum RuntimeError { ReadTemporaryValueError, #[error("Trying to read a permament value with read_temp")] ReadPermanentValueError, - #[error("Storage error: {0}")] - StorageApi(storage_api::Error), } /// VP environment function result @@ -495,9 +494,3 @@ where } Ok(None) } - -impl From for RuntimeError { - fn from(err: storage_api::Error) -> Self { - Self::StorageApi(err) - } -} diff --git a/tx_prelude/src/error.rs b/tx_prelude/src/error.rs deleted file mode 100644 index ce7b9fa5e9..0000000000 --- a/tx_prelude/src/error.rs +++ /dev/null @@ -1,112 +0,0 @@ -//! Helpers for error handling in WASM -//! -//! This module is currently duplicated in tx_prelude and vp_prelude crates to -//! be able to implement `From` conversion on error types from other crates, -//! avoiding `error[E0117]: only traits defined in the current crate can be -//! implemented for arbitrary types` - -use namada::ledger::storage_api; -use thiserror::Error; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("{0}")] - SimpleMessage(&'static str), - #[error("{0}")] - Custom(CustomError), - #[error("{0}: {1}")] - CustomWithMessage(&'static str, CustomError), -} - -/// Result of transaction or VP. -pub type EnvResult = Result; - -pub trait ResultExt { - /// Replace a possible error with a static message in [`EnvResult`]. - fn err_msg(self, msg: &'static str) -> EnvResult; -} - -// This is separate from `ResultExt`, because the implementation requires -// different bounds for `T`. -pub trait ResultExt2 { - /// Convert a [`Result`] into [`EnvResult`]. - fn into_env_result(self) -> EnvResult; - - /// Add a static message to a possible error in [`EnvResult`]. - fn wrap_err(self, msg: &'static str) -> EnvResult; -} - -pub trait OptionExt { - /// Transforms the [`Option`] into a [`EnvResult`], mapping - /// [`Some(v)`] to [`Ok(v)`] and [`None`] to the given static error - /// message. - fn ok_or_err_msg(self, msg: &'static str) -> EnvResult; -} - -impl ResultExt for Result { - fn err_msg(self, msg: &'static str) -> EnvResult { - self.map_err(|_err| Error::new_const(msg)) - } -} - -impl ResultExt2 for Result -where - E: std::error::Error + Send + Sync + 'static, -{ - fn into_env_result(self) -> EnvResult { - self.map_err(Error::new) - } - - fn wrap_err(self, msg: &'static str) -> EnvResult { - self.map_err(|err| Error::wrap(msg, err)) - } -} - -impl OptionExt for Option { - fn ok_or_err_msg(self, msg: &'static str) -> EnvResult { - self.ok_or_else(|| Error::new_const(msg)) - } -} - -impl Error { - /// Create an [`enum@Error`] from a static message. - #[inline] - pub const fn new_const(msg: &'static str) -> Self { - Self::SimpleMessage(msg) - } - - /// Create an [`enum@Error`] from another [`std::error::Error`]. - pub fn new(error: E) -> Self - where - E: Into>, - { - Self::Custom(CustomError(error.into())) - } - - /// Wrap another [`std::error::Error`] with a static message. - pub fn wrap(msg: &'static str, error: E) -> Self - where - E: Into>, - { - Self::CustomWithMessage(msg, CustomError(error.into())) - } -} - -/// A custom error -#[derive(Debug)] -pub struct CustomError(Box); - -impl std::fmt::Display for CustomError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl From for Error { - fn from(err: storage_api::Error) -> Self { - // storage_api::Error::Custom(CustomError {Box}) - // Error:Custom(storage_api::Error::Custom(CustomError {Box})) - Self::new(err) - } -} diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 4a8a3ef3a9..494d5e7cd3 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -12,13 +12,6 @@ use namada::types::token::Amount; use crate::token::transfer; use crate::Ctx; -// This is needed to use `ibc::Handler::Error` with `IbcActions` below -impl From for crate::Error { - fn from(err: Error) -> Self { - crate::Error::new(err) - } -} - impl IbcActions for Ctx { type Error = crate::Error; diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index ade290bf65..a18d59c9a8 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -6,7 +6,6 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -mod error; pub mod governance; pub mod ibc; pub mod intent; @@ -18,12 +17,13 @@ use core::slice; use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; -pub use error::*; pub use namada::ledger::governance::storage as gov_storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; use namada::ledger::storage_api; -pub use namada::ledger::storage_api::{StorageRead, StorageWrite}; +pub use namada::ledger::storage_api::{ + Error, OptionExt, ResultExt, StorageRead, StorageWrite, +}; pub use namada::ledger::treasury::storage as treasury_storage; pub use namada::ledger::tx_env::TxEnv; pub use namada::proto::{Signed, SignedTxData}; @@ -89,6 +89,10 @@ impl Ctx { } } +/// Result of `TxEnv`, `storage_api::StorageRead` or `storage_api::StorageWrite` +/// method call +pub type EnvResult = Result; + /// Transaction result pub type TxResult = EnvResult<()>; @@ -101,7 +105,7 @@ impl StorageRead<'_> for Ctx { fn read_bytes( &self, key: &namada::types::storage::Key, - ) -> Result>, storage_api::Error> { + ) -> Result>, Error> { let key = key.to_string(); let read_result = unsafe { anoma_tx_read(key.as_ptr() as _, key.len() as _) }; @@ -111,14 +115,14 @@ impl StorageRead<'_> for Ctx { fn has_key( &self, key: &namada::types::storage::Key, - ) -> Result { + ) -> Result { let key = key.to_string(); let found = unsafe { anoma_tx_has_key(key.as_ptr() as _, key.len() as _) }; Ok(HostEnvResult::is_success(found)) } - fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { let result = Vec::with_capacity(CHAIN_ID_LENGTH); unsafe { anoma_tx_get_chain_id(result.as_ptr() as _); @@ -131,13 +135,13 @@ impl StorageRead<'_> for Ctx { fn get_block_height( &self, - ) -> Result { + ) -> Result { Ok(BlockHeight(unsafe { anoma_tx_get_block_height() })) } fn get_block_hash( &self, - ) -> Result { + ) -> Result { let result = Vec::with_capacity(BLOCK_HASH_LENGTH); unsafe { anoma_tx_get_block_hash(result.as_ptr() as _); @@ -148,16 +152,14 @@ impl StorageRead<'_> for Ctx { Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) } - fn get_block_epoch( - &self, - ) -> Result { + fn get_block_epoch(&self) -> Result { Ok(Epoch(unsafe { anoma_tx_get_block_epoch() })) } fn iter_prefix( &self, prefix: &namada::types::storage::Key, - ) -> Result { + ) -> Result { let prefix = prefix.to_string(); let iter_id = unsafe { anoma_tx_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) @@ -168,7 +170,7 @@ impl StorageRead<'_> for Ctx { fn iter_next( &self, iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { + ) -> Result)>, Error> { let read_result = unsafe { anoma_tx_iter_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, @@ -206,8 +208,6 @@ impl StorageWrite for Ctx { } impl TxEnv<'_> for Ctx { - type Error = Error; - fn get_block_time(&self) -> Result { let read_result = unsafe { anoma_tx_get_block_time() }; let time_value = read_from_buffer(read_result, anoma_tx_result_buffer) diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 65a6c3f6cd..97a258365c 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -118,34 +118,6 @@ namada::impl_pos_read_only! { impl namada_proof_of_stake::PosReadOnly for Ctx } -impl From> for Error { - fn from(err: namada_proof_of_stake::BecomeValidatorError
) -> Self { - Self::new(err) - } -} - -impl From> for Error { - fn from(err: namada_proof_of_stake::BondError
) -> Self { - Self::new(err) - } -} - -impl From> - for Error -{ - fn from( - err: namada_proof_of_stake::UnbondError, - ) -> Self { - Self::new(err) - } -} - -impl From> for Error { - fn from(err: namada_proof_of_stake::WithdrawError
) -> Self { - Self::new(err) - } -} - impl namada_proof_of_stake::PosActions for Ctx { type BecomeValidatorError = crate::Error; type BondError = crate::Error; @@ -156,7 +128,7 @@ impl namada_proof_of_stake::PosActions for Ctx { &mut self, params: &PosParams, ) -> Result<(), Self::Error> { - self.write(¶ms_key(), params).into_env_result() + self.write(¶ms_key(), params) } fn write_validator_address_raw_hash( @@ -165,7 +137,6 @@ impl namada_proof_of_stake::PosActions for Ctx { ) -> Result<(), Self::Error> { let raw_hash = address.raw_hash().unwrap().to_owned(); self.write(&validator_address_raw_hash_key(raw_hash), address) - .into_env_result() } fn write_validator_staking_reward_address( @@ -174,7 +145,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: Self::Address, ) -> Result<(), Self::Error> { self.write(&validator_staking_reward_address_key(key), &value) - .into_env_result() } fn write_validator_consensus_key( @@ -183,7 +153,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorConsensusKeys, ) -> Result<(), Self::Error> { self.write(&validator_consensus_key_key(key), &value) - .into_env_result() } fn write_validator_state( @@ -192,7 +161,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorStates, ) -> Result<(), Self::Error> { self.write(&validator_state_key(key), &value) - .into_env_result() } fn write_validator_total_deltas( @@ -201,7 +169,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorTotalDeltas, ) -> Result<(), Self::Error> { self.write(&validator_total_deltas_key(key), &value) - .into_env_result() } fn write_validator_voting_power( @@ -210,7 +177,6 @@ impl namada_proof_of_stake::PosActions for Ctx { value: ValidatorVotingPowers, ) -> Result<(), Self::Error> { self.write(&validator_voting_power_key(key), &value) - .into_env_result() } fn write_bond( @@ -218,7 +184,7 @@ impl namada_proof_of_stake::PosActions for Ctx { key: &BondId, value: Bonds, ) -> Result<(), Self::Error> { - self.write(&bond_key(key), &value).into_env_result() + self.write(&bond_key(key), &value) } fn write_unbond( @@ -226,14 +192,14 @@ impl namada_proof_of_stake::PosActions for Ctx { key: &BondId, value: Unbonds, ) -> Result<(), Self::Error> { - self.write(&unbond_key(key), &value).into_env_result() + self.write(&unbond_key(key), &value) } fn write_validator_set( &mut self, value: ValidatorSets, ) -> Result<(), Self::Error> { - self.write(&validator_set_key(), &value).into_env_result() + self.write(&validator_set_key(), &value) } fn write_total_voting_power( @@ -241,15 +207,14 @@ impl namada_proof_of_stake::PosActions for Ctx { value: TotalVotingPowers, ) -> Result<(), Self::Error> { self.write(&total_voting_power_key(), &value) - .into_env_result() } fn delete_bond(&mut self, key: &BondId) -> Result<(), Self::Error> { - self.delete(&bond_key(key)).into_env_result() + self.delete(&bond_key(key)) } fn delete_unbond(&mut self, key: &BondId) -> Result<(), Self::Error> { - self.delete(&unbond_key(key)).into_env_result() + self.delete(&unbond_key(key)) } fn transfer( diff --git a/vp_prelude/src/error.rs b/vp_prelude/src/error.rs deleted file mode 100644 index 099ae2540a..0000000000 --- a/vp_prelude/src/error.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! Helpers for error handling in WASM -//! -//! This module is currently duplicated in tx_prelude and vp_prelude crates to -//! be able to implement `From` conversion on error types from other crates, -//! avoiding `error[E0117]: only traits defined in the current crate can be -//! implemented for arbitrary types` - -use namada::ledger::storage_api; -use thiserror::Error; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("{0}")] - SimpleMessage(&'static str), - #[error("{0}")] - Custom(CustomError), - #[error("{0}: {1}")] - CustomWithMessage(&'static str, CustomError), -} - -/// Result of transaction or VP. -pub type EnvResult = Result; - -pub trait ResultExt { - /// Replace a possible error with a static message in [`EnvResult`]. - fn err_msg(self, msg: &'static str) -> EnvResult; -} - -// This is separate from `ResultExt`, because the implementation requires -// different bounds for `T`. -pub trait ResultExt2 { - /// Convert a [`Result`] into [`EnvResult`]. - fn into_env_result(self) -> EnvResult; - - /// Add a static message to a possible error in [`EnvResult`]. - fn wrap_err(self, msg: &'static str) -> EnvResult; -} - -pub trait OptionExt { - /// Transforms the [`Option`] into a [`EnvResult`], mapping - /// [`Some(v)`] to [`Ok(v)`] and [`None`] to the given static error - /// message. - fn ok_or_err_msg(self, msg: &'static str) -> EnvResult; -} - -impl ResultExt for Result { - fn err_msg(self, msg: &'static str) -> EnvResult { - self.map_err(|_err| Error::new_const(msg)) - } -} - -impl ResultExt2 for Result -where - E: std::error::Error + Send + Sync + 'static, -{ - fn into_env_result(self) -> EnvResult { - self.map_err(Error::new) - } - - fn wrap_err(self, msg: &'static str) -> EnvResult { - self.map_err(|err| Error::wrap(msg, err)) - } -} - -impl OptionExt for Option { - fn ok_or_err_msg(self, msg: &'static str) -> EnvResult { - self.ok_or_else(|| Error::new_const(msg)) - } -} - -impl Error { - /// Create an [`enum@Error`] from a static message. - #[inline] - pub const fn new_const(msg: &'static str) -> Self { - Self::SimpleMessage(msg) - } - - /// Create an [`enum@Error`] from another [`std::error::Error`]. - pub fn new(error: E) -> Self - where - E: Into>, - { - Self::Custom(CustomError(error.into())) - } - - /// Wrap another [`std::error::Error`] with a static message. - pub fn wrap(msg: &'static str, error: E) -> Self - where - E: Into>, - { - Self::CustomWithMessage(msg, CustomError(error.into())) - } -} - -/// A custom error -#[derive(Debug)] -pub struct CustomError(Box); - -impl std::fmt::Display for CustomError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl From for Error { - fn from(err: storage_api::Error) -> Self { - Self::new(err) - } -} diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index d1d3845a48..46432a3b52 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -6,7 +6,6 @@ #![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::private_intra_doc_links)] -mod error; pub mod intent; pub mod key; pub mod nft; @@ -20,9 +19,10 @@ use std::convert::TryFrom; use std::marker::PhantomData; pub use borsh::{BorshDeserialize, BorshSerialize}; -pub use error::*; pub use namada::ledger::governance::storage as gov_storage; -pub use namada::ledger::storage_api::{self, StorageRead}; +pub use namada::ledger::storage_api::{ + self, Error, OptionExt, ResultExt, StorageRead, +}; pub use namada::ledger::vp_env::VpEnv; pub use namada::ledger::{parameters, pos as proof_of_stake}; pub use namada::proto::{Signed, SignedTxData}; @@ -134,6 +134,9 @@ pub struct CtxPostStorageRead<'a> { _ctx: &'a Ctx, } +/// Result of `VpEnv` or `storage_api::StorageRead` method call +pub type EnvResult = Result; + /// Validity predicate result pub type VpResult = EnvResult; @@ -151,7 +154,6 @@ pub fn reject() -> VpResult { pub struct KeyValIterator(pub u64, pub PhantomData); impl<'view> VpEnv<'view> for Ctx { - type Error = Error; type Post = CtxPostStorageRead<'view>; type Pre = CtxPreStorageRead<'view>; type PrefixIter = KeyValIterator<(String, Vec)>; @@ -167,7 +169,7 @@ impl<'view> VpEnv<'view> for Ctx { fn read_temp( &self, key: &storage::Key, - ) -> Result, Self::Error> { + ) -> Result, Error> { let key = key.to_string(); let read_result = unsafe { anoma_vp_read_temp(key.as_ptr() as _, key.len() as _) }; @@ -178,46 +180,46 @@ impl<'view> VpEnv<'view> for Ctx { fn read_bytes_temp( &self, key: &storage::Key, - ) -> Result>, Self::Error> { + ) -> Result>, Error> { let key = key.to_string(); let read_result = unsafe { anoma_vp_read_temp(key.as_ptr() as _, key.len() as _) }; Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) } - fn get_chain_id(&'view self) -> Result { + fn get_chain_id(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - get_chain_id().into_env_result() + get_chain_id() } - fn get_block_height(&'view self) -> Result { + fn get_block_height(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - get_block_height().into_env_result() + get_block_height() } - fn get_block_hash(&'view self) -> Result { + fn get_block_hash(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - get_block_hash().into_env_result() + get_block_hash() } - fn get_block_epoch(&'view self) -> Result { + fn get_block_epoch(&'view self) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - get_block_epoch().into_env_result() + get_block_epoch() } fn iter_prefix( &self, prefix: &storage::Key, - ) -> Result { + ) -> Result { // Both `CtxPreStorageRead` and `CtxPostStorageRead` have the same impl - iter_prefix(prefix).into_env_result() + iter_prefix(prefix) } fn eval( &self, vp_code: Vec, input_data: Vec, - ) -> Result { + ) -> Result { let result = unsafe { anoma_vp_eval( vp_code.as_ptr() as _, @@ -233,7 +235,7 @@ impl<'view> VpEnv<'view> for Ctx { &self, pk: &common::PublicKey, sig: &common::Signature, - ) -> Result { + ) -> Result { let pk = BorshSerialize::try_to_vec(pk).unwrap(); let sig = BorshSerialize::try_to_vec(sig).unwrap(); let valid = unsafe { @@ -247,7 +249,7 @@ impl<'view> VpEnv<'view> for Ctx { Ok(HostEnvResult::is_success(valid)) } - fn get_tx_code_hash(&self) -> Result { + fn get_tx_code_hash(&self) -> Result { let result = Vec::with_capacity(HASH_LENGTH); unsafe { anoma_vp_get_tx_code_hash(result.as_ptr() as _); @@ -261,17 +263,14 @@ impl<'view> VpEnv<'view> for Ctx { impl StorageRead<'_> for CtxPreStorageRead<'_> { type PrefixIter = KeyValIterator<(String, Vec)>; - fn read_bytes( - &self, - key: &storage::Key, - ) -> Result>, storage_api::Error> { + fn read_bytes(&self, key: &storage::Key) -> Result>, Error> { let key = key.to_string(); let read_result = unsafe { anoma_vp_read_pre(key.as_ptr() as _, key.len() as _) }; Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) } - fn has_key(&self, key: &storage::Key) -> Result { + fn has_key(&self, key: &storage::Key) -> Result { let key = key.to_string(); let found = unsafe { anoma_vp_has_key_pre(key.as_ptr() as _, key.len() as _) }; @@ -281,7 +280,7 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { fn iter_next( &self, iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { + ) -> Result)>, Error> { let read_result = unsafe { anoma_vp_iter_pre_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, @@ -294,23 +293,23 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { fn iter_prefix( &self, prefix: &storage::Key, - ) -> Result { + ) -> Result { iter_prefix(prefix) } - fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { get_chain_id() } - fn get_block_height(&self) -> Result { + fn get_block_height(&self) -> Result { get_block_height() } - fn get_block_hash(&self) -> Result { + fn get_block_hash(&self) -> Result { get_block_hash() } - fn get_block_epoch(&self) -> Result { + fn get_block_epoch(&self) -> Result { get_block_epoch() } } @@ -318,17 +317,14 @@ impl StorageRead<'_> for CtxPreStorageRead<'_> { impl StorageRead<'_> for CtxPostStorageRead<'_> { type PrefixIter = KeyValIterator<(String, Vec)>; - fn read_bytes( - &self, - key: &storage::Key, - ) -> Result>, storage_api::Error> { + fn read_bytes(&self, key: &storage::Key) -> Result>, Error> { let key = key.to_string(); let read_result = unsafe { anoma_vp_read_post(key.as_ptr() as _, key.len() as _) }; Ok(read_from_buffer(read_result, anoma_vp_result_buffer)) } - fn has_key(&self, key: &storage::Key) -> Result { + fn has_key(&self, key: &storage::Key) -> Result { let key = key.to_string(); let found = unsafe { anoma_vp_has_key_post(key.as_ptr() as _, key.len() as _) }; @@ -338,7 +334,7 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { fn iter_next( &self, iter: &mut Self::PrefixIter, - ) -> Result)>, storage_api::Error> { + ) -> Result)>, Error> { let read_result = unsafe { anoma_vp_iter_post_next(iter.0) }; Ok(read_key_val_bytes_from_buffer( read_result, @@ -351,30 +347,30 @@ impl StorageRead<'_> for CtxPostStorageRead<'_> { fn iter_prefix( &self, prefix: &storage::Key, - ) -> Result { + ) -> Result { iter_prefix(prefix) } - fn get_chain_id(&self) -> Result { + fn get_chain_id(&self) -> Result { get_chain_id() } - fn get_block_height(&self) -> Result { + fn get_block_height(&self) -> Result { get_block_height() } - fn get_block_hash(&self) -> Result { + fn get_block_hash(&self) -> Result { get_block_hash() } - fn get_block_epoch(&self) -> Result { + fn get_block_epoch(&self) -> Result { get_block_epoch() } } fn iter_prefix( prefix: &storage::Key, -) -> Result)>, storage_api::Error> { +) -> Result)>, Error> { let prefix = prefix.to_string(); let iter_id = unsafe { anoma_vp_iter_prefix(prefix.as_ptr() as _, prefix.len() as _) @@ -382,7 +378,7 @@ fn iter_prefix( Ok(KeyValIterator(iter_id, PhantomData)) } -fn get_chain_id() -> Result { +fn get_chain_id() -> Result { let result = Vec::with_capacity(CHAIN_ID_LENGTH); unsafe { anoma_vp_get_chain_id(result.as_ptr() as _); @@ -395,11 +391,11 @@ fn get_chain_id() -> Result { ) } -fn get_block_height() -> Result { +fn get_block_height() -> Result { Ok(BlockHeight(unsafe { anoma_vp_get_block_height() })) } -fn get_block_hash() -> Result { +fn get_block_hash() -> Result { let result = Vec::with_capacity(BLOCK_HASH_LENGTH); unsafe { anoma_vp_get_block_hash(result.as_ptr() as _); @@ -409,6 +405,6 @@ fn get_block_hash() -> Result { Ok(BlockHash::try_from(slice).expect("Cannot convert the hash")) } -fn get_block_epoch() -> Result { +fn get_block_epoch() -> Result { Ok(Epoch(unsafe { anoma_vp_get_block_epoch() })) } diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 9b04e2a939..e880380802 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -5,10 +5,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let bond = transaction::pos::Bond::try_from_slice(&data[..]) - .err_msg("failed to decode Bond")?; + .wrap_err("failed to decode Bond")?; ctx.bond_tokens(bond.source.as_ref(), &bond.validator, bond.amount) } diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs index a299070393..deeb5f3eb0 100644 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ b/wasm/wasm_source/src/tx_from_intent.rs @@ -7,10 +7,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = intent::IntentTransfers::try_from_slice(&data[..]) - .err_msg("failed to decode IntentTransfers")?; + .wrap_err("failed to decode IntentTransfers")?; // make sure that the matchmaker has to validate this tx ctx.insert_verifier(&tx_data.source)?; diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index 08b3c60d60..79cbc6cf96 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -8,7 +8,7 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; ctx.dispatch_ibc_action(&data) } diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index 05789751b2..e0fe700d63 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -6,10 +6,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::InitAccount::try_from_slice(&data[..]) - .err_msg("failed to decode InitAccount")?; + .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); let address = ctx.init_account(&tx_data.vp_code)?; diff --git a/wasm/wasm_source/src/tx_init_nft.rs b/wasm/wasm_source/src/tx_init_nft.rs index ace54fc161..de67dfbb53 100644 --- a/wasm/wasm_source/src/tx_init_nft.rs +++ b/wasm/wasm_source/src/tx_init_nft.rs @@ -5,10 +5,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::nft::CreateNft::try_from_slice(&data[..]) - .err_msg("failed to decode CreateNft")?; + .wrap_err("failed to decode CreateNft")?; log_string("apply_tx called to create a new NFT"); let _address = nft::init_nft(ctx, tx_data)?; diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index 728d7613ae..cb7fe9ffbb 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -5,11 +5,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::governance::InitProposalData::try_from_slice(&data[..]) - .err_msg("failed to decode InitProposalData")?; + .wrap_err("failed to decode InitProposalData")?; log_string("apply_tx called to create a new governance proposal"); governance::init_proposal(ctx, tx_data) diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 2bfed44fac..2d5f1a6256 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -7,10 +7,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let init_validator = InitValidator::try_from_slice(&data[..]) - .err_msg("failed to decode InitValidator")?; + .wrap_err("failed to decode InitValidator")?; debug_log!("apply_tx called to init a new validator account"); // Register the validator in PoS diff --git a/wasm/wasm_source/src/tx_mint_nft.rs b/wasm/wasm_source/src/tx_mint_nft.rs index f132b74158..d3ab17e7ad 100644 --- a/wasm/wasm_source/src/tx_mint_nft.rs +++ b/wasm/wasm_source/src/tx_mint_nft.rs @@ -5,10 +5,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::nft::MintNft::try_from_slice(&data[..]) - .err_msg("failed to decode MintNft")?; + .wrap_err("failed to decode MintNft")?; log_string("apply_tx called to mint a new NFT tokens"); nft::mint_tokens(ctx, tx_data) diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index 5731612888..eccddee2f0 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -7,10 +7,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let transfer = token::Transfer::try_from_slice(&data[..]) - .err_msg("failed to decode token::Transfer")?; + .wrap_err("failed to decode token::Transfer")?; debug_log!("apply_tx called with transfer: {:#?}", transfer); let token::Transfer { source, diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index c5ffc1ab6e..5d1765bb38 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -6,10 +6,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let unbond = transaction::pos::Unbond::try_from_slice(&data[..]) - .err_msg("failed to decode Unbond")?; + .wrap_err("failed to decode Unbond")?; ctx.unbond_tokens(unbond.source.as_ref(), &unbond.validator, unbond.amount) } diff --git a/wasm/wasm_source/src/tx_update_vp.rs b/wasm/wasm_source/src/tx_update_vp.rs index d0c41d3bd9..0bb819f026 100644 --- a/wasm/wasm_source/src/tx_update_vp.rs +++ b/wasm/wasm_source/src/tx_update_vp.rs @@ -7,10 +7,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let update_vp = transaction::UpdateVp::try_from_slice(&data[..]) - .err_msg("failed to decode UpdateVp")?; + .wrap_err("failed to decode UpdateVp")?; debug_log!("update VP for: {:#?}", update_vp.addr); diff --git a/wasm/wasm_source/src/tx_vote_proposal.rs b/wasm/wasm_source/src/tx_vote_proposal.rs index 614e4a9fa1..92c7af4c7f 100644 --- a/wasm/wasm_source/src/tx_vote_proposal.rs +++ b/wasm/wasm_source/src/tx_vote_proposal.rs @@ -5,11 +5,11 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let tx_data = transaction::governance::VoteProposalData::try_from_slice(&data[..]) - .err_msg("failed to decode VoteProposalData")?; + .wrap_err("failed to decode VoteProposalData")?; debug_log!("apply_tx called to vote a governance proposal"); diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index bcb64b4af0..3c841d88b0 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -6,10 +6,10 @@ use namada_tx_prelude::*; #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = signed.data.ok_or_err_msg("Missing data")?; let withdraw = transaction::pos::Withdraw::try_from_slice(&data[..]) - .err_msg("failed to decode Withdraw")?; + .wrap_err("failed to decode Withdraw")?; let slashed = ctx.withdraw_tokens(withdraw.source.as_ref(), &withdraw.validator)?; diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 2b6ef24242..4731ef60be 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -88,7 +88,7 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let data = match signed.data { Some(data) => { log(&format!("got data ({} bytes)", data.len())); @@ -134,7 +134,7 @@ pub mod main { #[transaction] fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { let signed = SignedTxData::try_from_slice(&tx_data[..]) - .err_msg("failed to decode SignedTxData")?; + .wrap_err("failed to decode SignedTxData")?; let transfer = token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); log_string(format!("apply_tx called to mint tokens: {:#?}", transfer)); From 45dddbeaed67c8bbc2720c8b7109a4e7dc0fd971 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 13 Sep 2022 15:51:26 +0000 Subject: [PATCH 285/373] [ci skip] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 1c5fec452f..06f33fcda7 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.078d5be45d839fc1b6c034c4fdfe2055add709daa05868826682d3b6abf27ac4.wasm", - "tx_from_intent.wasm": "tx_from_intent.dcdfc49a02c76f277afac84e3511ad47c70b3bf54d1273a15c6dc75648316937.wasm", - "tx_ibc.wasm": "tx_ibc.3957053e0ea9caaa49128994711339aea6ede77a99f150688df3de870c8b17d4.wasm", - "tx_init_account.wasm": "tx_init_account.92e59887817a6d789dc8a85bb1eff3c86bd0a9bd1ddc8a9e0e5de5c9e9c2ddc6.wasm", - "tx_init_nft.wasm": "tx_init_nft.4125bdf149533cd201895cfaf6cdfbb9616182c187137029fe3c9214030f1877.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.8da848905d5a8ad1b15046d5e59e04f307bde73dcc8d2ab0b6d8d235a31a8b52.wasm", - "tx_init_validator.wasm": "tx_init_validator.364786e78253bd9ce72d089cc1539a882eb8ef6fd8c818616d003241b47ac010.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.b2c34b21a2f0b533e3dcd4b2ee64e45ca00ccad8b3f22c410474c7a67c3302e8.wasm", - "tx_transfer.wasm": "tx_transfer.6fac9a68bcd1a50d9cec64605f8637dfa897ce3be242edc344858cf4075fc100.wasm", - "tx_unbond.wasm": "tx_unbond.eeaf8ff32984275288b0a9a36c9579dace6f3ecfaf59255af769acf57a00df4a.wasm", - "tx_update_vp.wasm": "tx_update_vp.a304b3c70361cde5fda458ba5637d6825eb002198e73990a1c74351113b83d43.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.8f306e1d1bcc9ca7f47a39108554fc847d4ac6df7a8d87a6effdffeae6adc4c4.wasm", - "tx_withdraw.wasm": "tx_withdraw.c288861e842f0b1ac6fbb95b14b33c8819ac587bbd5b483f2cdd0ea184206c65.wasm", - "vp_nft.wasm": "vp_nft.379f0a9fdbc9611ba9afc8b03ea17eb1e7c63992be3c2ecd5dd506a0ec3809f3.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9d28df0e4eea55c98ac05638cfc6211aaf8e1a4b9489f7057634c66d39835c36.wasm", - "vp_token.wasm": "vp_token.6506aea021bb6de9186e96fa0d9ea2ad35bcb66777d6ecf890a66cfe36a74f23.wasm", - "vp_user.wasm": "vp_user.77a0d0d406e300b2d5b9bc1c13bc50f233b6923d369db939ac82c8e10b64543c.wasm" + "tx_bond.wasm": "tx_bond.b3529e7dcdaf314353fa43b04eba22c44deb6143f8243933f66d20554565fa51.wasm", + "tx_from_intent.wasm": "tx_from_intent.6985cd22aa16334b008262beda3051d16f0522e019ba178c20b2cf0c92ef3cf5.wasm", + "tx_ibc.wasm": "tx_ibc.3ffa1662cb15d178be631adaeb060e96deaa44dfca8ee2978fc7d9ea09776977.wasm", + "tx_init_account.wasm": "tx_init_account.81c82e4f85575244dd833f8de566844de926f8869f1cce1dd86a1d69fe527a2f.wasm", + "tx_init_nft.wasm": "tx_init_nft.cde4c8c381369a61c6f0363c6748118d064202541c41c69c4e534f3ebbba2567.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.bfc77114ad453d9791d115fd97d681f4bbba956c77fcdb97c619637bb807a7c8.wasm", + "tx_init_validator.wasm": "tx_init_validator.7ecc353a6668788e01aac3a5799e9baa7f83979cdb8c82011225a53f1952672d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.3bc71754956503b44f764dc27cd51796e0613435864dc3f10d089e3b99bda21e.wasm", + "tx_transfer.wasm": "tx_transfer.38b4c4ec9d949b72e51547ab55737c42db383f5e04385cffafccb36fa5a5dccb.wasm", + "tx_unbond.wasm": "tx_unbond.174499645c2aa9242f29050406a0c835d78e6c030b1f7978b5d5d1a602e88e99.wasm", + "tx_update_vp.wasm": "tx_update_vp.4ee18fa789b30215220f6edfd317314abb575baab34e9b0147afa65cbfec4543.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.17c3d54babf760367f63609f4e7aa6cf4500b80a5ff01c2917b54b4cfdf78df4.wasm", + "tx_withdraw.wasm": "tx_withdraw.010c8141fe8dfe470887d80f77db482ddc7081e318496180208490226060787e.wasm", + "vp_nft.wasm": "vp_nft.bfc7e3a5a33226ee19111cc0cc627da4da7db3d863ce1955d195873b36ba6374.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.b8d0069f0a287c57513b64daab378084d49c7e2cd52c44b0f5aa1a3ded262c88.wasm", + "vp_token.wasm": "vp_token.9f27451dba52d022ff02bbdf2421895e285e79ce2867dd1193a27fbfd2bff4fc.wasm", + "vp_user.wasm": "vp_user.eac794136d3c148b9997cc4e6fb458dcfc5e7c1f2edea49dad7c8b6d35b9c306.wasm" } \ No newline at end of file From b9406a224ddc314ee2e4e00e49b8500a30ef7eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 18:01:44 +0200 Subject: [PATCH 286/373] fixup! Merge branch 'namada/tomas/sorted-prefix-iter' (#458) --- shared/src/types/key/secp256k1.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 99bcbb3f67..889b4de258 100644 --- a/shared/src/types/key/secp256k1.rs +++ b/shared/src/types/key/secp256k1.rs @@ -7,6 +7,7 @@ use std::io::{ErrorKind, Write}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use data_encoding::HEXLOWER; #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; use serde::de::{Error, SeqAccess, Visitor}; @@ -113,7 +114,7 @@ impl Ord for PublicKey { impl Display for PublicKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize_compressed())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize_compressed())) } } @@ -121,7 +122,9 @@ impl FromStr for PublicKey { type Err = ParsePublicKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParsePublicKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParsePublicKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParsePublicKeyError::InvalidEncoding) } @@ -226,7 +229,7 @@ impl BorshSchema for SecretKey { impl Display for SecretKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0.serialize())) + write!(f, "{}", HEXLOWER.encode(&self.0.serialize())) } } @@ -234,7 +237,9 @@ impl FromStr for SecretKey { type Err = ParseSecretKeyError; fn from_str(s: &str) -> Result { - let vec = hex::decode(s).map_err(ParseSecretKeyError::InvalidHex)?; + let vec = HEXLOWER + .decode(s.as_bytes()) + .map_err(ParseSecretKeyError::InvalidHex)?; BorshDeserialize::try_from_slice(&vec) .map_err(ParseSecretKeyError::InvalidEncoding) } From 16d9aa6c8ad6b3f1b8fc4be1ecba6f9b31f17f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 18:42:46 +0200 Subject: [PATCH 287/373] changelog: add #465 --- .../unreleased/improvements/465-vp-tx-env-conrete-error.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md diff --git a/.changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md b/.changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md new file mode 100644 index 0000000000..e40ff76a17 --- /dev/null +++ b/.changelog/unreleased/improvements/465-vp-tx-env-conrete-error.md @@ -0,0 +1,2 @@ +- Re-use `storage_api::Error` type that supports wrapping custom error in `VpEnv` and `TxEnv` traits. + ([#465](https://github.com/anoma/namada/pull/465)) From a0f193ad8fc21470646c45c049d563f40463bb2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 30 Aug 2022 15:16:48 +0200 Subject: [PATCH 288/373] rustdoc: resolve ambiguous link --- shared/src/ledger/storage_api/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/storage_api/error.rs b/shared/src/ledger/storage_api/error.rs index 8af95be723..f99539bc87 100644 --- a/shared/src/ledger/storage_api/error.rs +++ b/shared/src/ledger/storage_api/error.rs @@ -17,7 +17,7 @@ pub enum Error { /// Result of a storage API call. pub type Result = std::result::Result; -/// Result extension to easily wrap custom errors into [`Error`]. +/// Result extension to easily wrap custom errors into [`enum@Error`]. // This is separate from `ResultExt`, because the implementation requires // different bounds for `T`. pub trait ResultExt { From d7c30ac20f2d2c391eeb029f0a2092d0b2d4ed48 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 12 Aug 2022 16:08:07 -0400 Subject: [PATCH 289/373] create lazy data structures for storage access --- shared/src/ledger/storage_api/collections/mod.rs | 12 ++++++++++++ shared/src/ledger/storage_api/mod.rs | 1 + 2 files changed, 13 insertions(+) create mode 100644 shared/src/ledger/storage_api/collections/mod.rs diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs new file mode 100644 index 0000000000..982ebbd99a --- /dev/null +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -0,0 +1,12 @@ +//! Lazy data structures for storage access where elements are not all loaded +//! into memory. This serves to minimize gas costs, avoid unbounded iteration +//! in some cases, and ease the validation of storage changes in the VP. +//! +//! Rather than finding the diff of the state before and after, the VP will +//! just receive the storage sub-keys that have experienced changes. +//! +//! CONTINUE TO UPDATE THE ABOVE + +pub mod lazy_map; +pub mod lazy_set; +pub mod lazy_vec; \ No newline at end of file diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 06ec8361f5..5e8c570cd3 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -2,6 +2,7 @@ //! and VPs (both native and WASM). mod error; +pub mod collections; use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; From b07b84495cb01df90f2e1849bb38fa7a8b2523c6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 12 Aug 2022 16:08:35 -0400 Subject: [PATCH 290/373] add lazy vector --- .../storage_api/collections/lazy_vec.rs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 shared/src/ledger/storage_api/collections/lazy_vec.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs new file mode 100644 index 0000000000..fe97683c49 --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -0,0 +1,86 @@ +//! Lazy vec + +use std::marker::PhantomData; + +use borsh::{BorshSerialize, BorshDeserialize}; +use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; +use super::super::Result; + +/// Subkey pointing to the length of the LazyVec +pub const LEN_SUBKEY: &str = "len"; +/// Subkey corresponding to the data elements of the LazyVec +pub const DATA_SUBKEY: &str = "data"; + +/// LazyVec ! fill in ! +pub struct LazyVec { + key: storage::Key, + phantom: PhantomData, +} + + +impl LazyVec where T: BorshSerialize + BorshDeserialize { + + /// new + pub fn new(key: storage::Key) -> Self { + Self { key, phantom: PhantomData} + } + + /// push + pub fn push(&self, val: T, storage_read: &impl StorageRead, storage_write: &mut impl StorageWrite) -> Result<()> { + let len = self.read_len(storage_read)?; + + let sub_index = len.unwrap_or(0); + let len = sub_index + 1; + + let data_key = self.get_data_key(sub_index); + + storage_write.write(&data_key, val)?; + storage_write.write(&self.get_len_key(), len)?; + + Ok(()) + } + + /// pop + pub fn pop(&self, storage_read: &impl StorageRead, storage_write: &mut impl StorageWrite) -> Result> { + let len = self.read_len(storage_read)?; + match len { + Some(0) | None => Ok(None), + Some(len) => { + let sub_index = len - 1; + let data_key = self.get_data_key(sub_index); + if len == 1 { + storage_write.delete(&self.get_len_key())?; + + } else { + storage_write.write(&self.get_len_key(), sub_index)?; + + } + let popped_val = storage_read.read(&data_key)?; + storage_write.delete(&data_key)?; + Ok(popped_val) + }, + } + + } + + /// get the length subkey + fn get_len_key(&self) -> storage::Key { + self.key.push(&LEN_SUBKEY.to_owned()).unwrap() + } + + /// read the length of the LazyVec + pub fn read_len(&self, storage_read: &impl StorageRead) -> Result> { + storage_read.read(&self.get_len_key()) + } + + /// get the data subkey + fn get_data_key(&self, sub_index: u64) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&sub_index.to_string()).unwrap() + } + + /// get the data held at a specific index within the data subkey + fn get(&self, sub_index: u64, storage_read: &impl StorageRead) -> Result> { + storage_read.read(&self.get_data_key(sub_index)) + } + +} \ No newline at end of file From 13173b3c3c102d945e2467d338c8ee989a19e6c7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 12 Aug 2022 17:09:59 -0400 Subject: [PATCH 291/373] add lazy set (WIP), make LazyVec.get public --- .../storage_api/collections/lazy_set.rs | 69 +++++++++++++++++++ .../storage_api/collections/lazy_vec.rs | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 shared/src/ledger/storage_api/collections/lazy_set.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs new file mode 100644 index 0000000000..143f234e3e --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -0,0 +1,69 @@ +//! Lazy hash set + +use std::{marker::PhantomData, hash::Hash, hash::Hasher}; +use borsh::{BorshSerialize, BorshDeserialize}; +use std::collections::hash_map::DefaultHasher; + +use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; +use super::super::Result; + +/// Subkey corresponding to the data elements of the LazyVec +pub const DATA_SUBKEY: &str = "data"; + +/// lazy hash set +pub struct LazySet { + key: storage::Key, + phantom: PhantomData +} + +impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { + + /// new + pub fn new(key: storage::Key) -> Self { + Self { key, phantom: PhantomData} + } + + /// insert + pub fn insert(&self, val: &T, storage_write: &mut impl StorageWrite) -> Result<()> { + + // Do we need to read to see if this val is already in the set? + + let data_key = self.get_data_key(val); + storage_write.write(&data_key, &val)?; + Ok(()) + } + + /// remove + pub fn remove(&self, val: &T, storage_write: &mut impl StorageWrite) -> Result<()> { + let data_key = self.get_data_key(val); + storage_write.delete(&data_key)?; + Ok(()) + } + + /// check if the hash set contains a value + pub fn contains(&self, val: &T, storage_read: &impl StorageRead) -> Result { + let digest: Option = storage_read.read(&self.get_data_key(val))?; + match digest { + Some(_) => Ok(true), + None => Ok(false), + } + } + + /// check if hash set is empty + pub fn is_empty(&self) { + todo!(); + } + + fn hash_val(&self, val: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + val.hash(&mut hasher); + hasher.finish() + } + + /// get the data subkey + fn get_data_key(&self, val: &T) -> storage::Key { + let hash_str = self.hash_val(val).to_string(); + self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&hash_str).unwrap() + } + +} \ No newline at end of file diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index fe97683c49..31bc7e98db 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -79,7 +79,7 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize { } /// get the data held at a specific index within the data subkey - fn get(&self, sub_index: u64, storage_read: &impl StorageRead) -> Result> { + pub fn get(&self, sub_index: u64, storage_read: &impl StorageRead) -> Result> { storage_read.read(&self.get_data_key(sub_index)) } From 72e48e69fad7aca0c9a3f0e1292a3ae7fe3d1059 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 14 Aug 2022 03:15:02 -0400 Subject: [PATCH 292/373] lazy hash map first commit --- .../storage_api/collections/lazy_map.rs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 shared/src/ledger/storage_api/collections/lazy_map.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs new file mode 100644 index 0000000000..321ef6362d --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -0,0 +1,68 @@ +//! Lazy map + +use borsh::{BorshSerialize, BorshDeserialize}; +use std::{marker::PhantomData, hash::Hash, hash::Hasher}; +use std::collections::hash_map::DefaultHasher; + +use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; +use super::super::Result; + + +/// Subkey corresponding to the data elements of the LazyMap +pub const DATA_SUBKEY: &str = "data"; + +/// LazyMap ! fill in ! +pub struct LazyMap { + key: storage::Key, + phantom_h: PhantomData, + phantom_t: PhantomData +} + +impl LazyMap where H: BorshDeserialize + BorshSerialize + Hash, +T: BorshDeserialize + BorshSerialize { + + /// insert + pub fn insert(&self, elem_key: &H, elem_val: T, storage_write: &mut impl StorageWrite) -> Result<()> { + + // TODO: Check to see if map element exists already ?? + + let data_key = self.get_data_key(elem_key); + storage_write.write(&data_key, (elem_key, elem_val))?; + + + Ok(()) + } + + /// remove + pub fn remove(&self, elem_key: &H, storage_write: &mut impl StorageWrite) -> Result<()> { + + let data_key = self.get_data_key(elem_key); + storage_write.delete(&data_key)?; + + Ok(()) + } + + /// get value + pub fn get_val(&self, elem_key: &H, storage_read: &mut impl StorageRead) -> Result> { + + // check if elem_key exists in the first place? + + let data_key = self.get_data_key(elem_key); + storage_read.read(&data_key) + + } + + /// hash + fn hash(&self, elem_key: &H) -> u64 { + let mut hasher = DefaultHasher::new(); + elem_key.hash(&mut hasher); + hasher.finish() + } + + /// get the data subkey + fn get_data_key(&self, elem_key: &H) -> storage::Key { + let hash_str = self.hash(elem_key).to_string(); + self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&hash_str).unwrap() + } + +} \ No newline at end of file From d964880226ab41de852c6986a39265dd05b78de7 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 14 Aug 2022 04:38:32 -0400 Subject: [PATCH 293/373] add fn get_elem_key_by_hash to LazyMap --- .../storage_api/collections/lazy_map.rs | 76 ++++++++++++++----- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 321ef6362d..731cafd3b1 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,12 +1,14 @@ //! Lazy map -use borsh::{BorshSerialize, BorshDeserialize}; -use std::{marker::PhantomData, hash::Hash, hash::Hasher}; use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; -use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; -use super::super::Result; +use borsh::{BorshDeserialize, BorshSerialize}; +use super::super::Result; +use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::types::storage; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; @@ -15,27 +17,35 @@ pub const DATA_SUBKEY: &str = "data"; pub struct LazyMap { key: storage::Key, phantom_h: PhantomData, - phantom_t: PhantomData + phantom_t: PhantomData, } -impl LazyMap where H: BorshDeserialize + BorshSerialize + Hash, -T: BorshDeserialize + BorshSerialize { - +impl LazyMap +where + H: BorshDeserialize + BorshSerialize + Hash, + T: BorshDeserialize + BorshSerialize, +{ /// insert - pub fn insert(&self, elem_key: &H, elem_val: T, storage_write: &mut impl StorageWrite) -> Result<()> { - + pub fn insert( + &self, + elem_key: &H, + elem_val: T, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { // TODO: Check to see if map element exists already ?? let data_key = self.get_data_key(elem_key); storage_write.write(&data_key, (elem_key, elem_val))?; - Ok(()) } /// remove - pub fn remove(&self, elem_key: &H, storage_write: &mut impl StorageWrite) -> Result<()> { - + pub fn remove( + &self, + elem_key: &H, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { let data_key = self.get_data_key(elem_key); storage_write.delete(&data_key)?; @@ -43,13 +53,38 @@ T: BorshDeserialize + BorshSerialize { } /// get value - pub fn get_val(&self, elem_key: &H, storage_read: &mut impl StorageRead) -> Result> { - + pub fn get( + &self, + elem_key: &H, + storage_read: &mut impl StorageRead, + ) -> Result> { // check if elem_key exists in the first place? let data_key = self.get_data_key(elem_key); - storage_read.read(&data_key) + let res: Option<(H, T)> = storage_read.read(&data_key)?; + match res { + Some(pair) => Ok(Some(pair.1)), + None => Ok(None), + } + } + /// get the element key by its hash + pub fn get_elem_key_by_hash( + &self, + elem_key_hash: &str, + storage_read: &mut impl StorageRead, + ) -> Result> { + let data_key = self + .key + .push(&DATA_SUBKEY.to_owned()) + .unwrap() + .push(&elem_key_hash.to_string()) + .unwrap(); + let res: Option<(H, T)> = storage_read.read(&data_key)?; + match res { + Some(pair) => Ok(Some(pair.0)), + None => Ok(None), + } } /// hash @@ -62,7 +97,10 @@ T: BorshDeserialize + BorshSerialize { /// get the data subkey fn get_data_key(&self, elem_key: &H) -> storage::Key { let hash_str = self.hash(elem_key).to_string(); - self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&hash_str).unwrap() + self.key + .push(&DATA_SUBKEY.to_owned()) + .unwrap() + .push(&hash_str) + .unwrap() } - -} \ No newline at end of file +} From 6752132b06f2128a5e26360e04173ef53a0fc4a0 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sun, 14 Aug 2022 04:42:40 -0400 Subject: [PATCH 294/373] fmt && clippy --- .../storage_api/collections/lazy_set.rs | 59 +++++++++++++------ .../storage_api/collections/lazy_vec.rs | 58 ++++++++++++------ .../src/ledger/storage_api/collections/mod.rs | 6 +- shared/src/ledger/storage_api/mod.rs | 2 +- 4 files changed, 85 insertions(+), 40 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 143f234e3e..187f5b5123 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -1,31 +1,42 @@ //! Lazy hash set -use std::{marker::PhantomData, hash::Hash, hash::Hasher}; -use borsh::{BorshSerialize, BorshDeserialize}; use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; + +use borsh::{BorshDeserialize, BorshSerialize}; -use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; use super::super::Result; +use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::types::storage; -/// Subkey corresponding to the data elements of the LazyVec +/// Subkey corresponding to the data elements of the LazySet pub const DATA_SUBKEY: &str = "data"; /// lazy hash set pub struct LazySet { key: storage::Key, - phantom: PhantomData + phantom: PhantomData, } -impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { - +impl LazySet +where + T: BorshSerialize + BorshDeserialize + Hash, +{ /// new pub fn new(key: storage::Key) -> Self { - Self { key, phantom: PhantomData} - } + Self { + key, + phantom: PhantomData, + } + } /// insert - pub fn insert(&self, val: &T, storage_write: &mut impl StorageWrite) -> Result<()> { - + pub fn insert( + &self, + val: &T, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { // Do we need to read to see if this val is already in the set? let data_key = self.get_data_key(val); @@ -34,14 +45,22 @@ impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { } /// remove - pub fn remove(&self, val: &T, storage_write: &mut impl StorageWrite) -> Result<()> { + pub fn remove( + &self, + val: &T, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { let data_key = self.get_data_key(val); storage_write.delete(&data_key)?; Ok(()) } /// check if the hash set contains a value - pub fn contains(&self, val: &T, storage_read: &impl StorageRead) -> Result { + pub fn contains( + &self, + val: &T, + storage_read: &impl StorageRead, + ) -> Result { let digest: Option = storage_read.read(&self.get_data_key(val))?; match digest { Some(_) => Ok(true), @@ -49,7 +68,8 @@ impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { } } - /// check if hash set is empty + /// check if hash set is empty (if we want to do this we prob need a length + /// field) pub fn is_empty(&self) { todo!(); } @@ -60,10 +80,13 @@ impl LazySet where T: BorshSerialize + BorshDeserialize + Hash { hasher.finish() } - /// get the data subkey + /// get the data subkey fn get_data_key(&self, val: &T) -> storage::Key { let hash_str = self.hash_val(val).to_string(); - self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&hash_str).unwrap() + self.key + .push(&DATA_SUBKEY.to_owned()) + .unwrap() + .push(&hash_str) + .unwrap() } - -} \ No newline at end of file +} diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 31bc7e98db..4c08f906d9 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -2,9 +2,11 @@ use std::marker::PhantomData; -use borsh::{BorshSerialize, BorshDeserialize}; -use crate::{types::{storage}, ledger::storage_api::{StorageWrite, StorageRead}}; +use borsh::{BorshDeserialize, BorshSerialize}; + use super::super::Result; +use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::types::storage; /// Subkey pointing to the length of the LazyVec pub const LEN_SUBKEY: &str = "len"; @@ -17,16 +19,25 @@ pub struct LazyVec { phantom: PhantomData, } - -impl LazyVec where T: BorshSerialize + BorshDeserialize { - +impl LazyVec +where + T: BorshSerialize + BorshDeserialize, +{ /// new pub fn new(key: storage::Key) -> Self { - Self { key, phantom: PhantomData} + Self { + key, + phantom: PhantomData, + } } /// push - pub fn push(&self, val: T, storage_read: &impl StorageRead, storage_write: &mut impl StorageWrite) -> Result<()> { + pub fn push( + &self, + val: T, + storage_read: &impl StorageRead, + storage_write: &mut impl StorageWrite, + ) -> Result<()> { let len = self.read_len(storage_read)?; let sub_index = len.unwrap_or(0); @@ -36,12 +47,16 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize { storage_write.write(&data_key, val)?; storage_write.write(&self.get_len_key(), len)?; - + Ok(()) } /// pop - pub fn pop(&self, storage_read: &impl StorageRead, storage_write: &mut impl StorageWrite) -> Result> { + pub fn pop( + &self, + storage_read: &impl StorageRead, + storage_write: &mut impl StorageWrite, + ) -> Result> { let len = self.read_len(storage_read)?; match len { Some(0) | None => Ok(None), @@ -50,17 +65,14 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize { let data_key = self.get_data_key(sub_index); if len == 1 { storage_write.delete(&self.get_len_key())?; - } else { storage_write.write(&self.get_len_key(), sub_index)?; - } let popped_val = storage_read.read(&data_key)?; storage_write.delete(&data_key)?; Ok(popped_val) - }, + } } - } /// get the length subkey @@ -69,18 +81,28 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize { } /// read the length of the LazyVec - pub fn read_len(&self, storage_read: &impl StorageRead) -> Result> { + pub fn read_len( + &self, + storage_read: &impl StorageRead, + ) -> Result> { storage_read.read(&self.get_len_key()) } /// get the data subkey fn get_data_key(&self, sub_index: u64) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap().push(&sub_index.to_string()).unwrap() + self.key + .push(&DATA_SUBKEY.to_owned()) + .unwrap() + .push(&sub_index.to_string()) + .unwrap() } /// get the data held at a specific index within the data subkey - pub fn get(&self, sub_index: u64, storage_read: &impl StorageRead) -> Result> { + pub fn get( + &self, + sub_index: u64, + storage_read: &impl StorageRead, + ) -> Result> { storage_read.read(&self.get_data_key(sub_index)) } - -} \ No newline at end of file +} diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index 982ebbd99a..c3784c2525 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -1,12 +1,12 @@ //! Lazy data structures for storage access where elements are not all loaded //! into memory. This serves to minimize gas costs, avoid unbounded iteration //! in some cases, and ease the validation of storage changes in the VP. -//! +//! //! Rather than finding the diff of the state before and after, the VP will //! just receive the storage sub-keys that have experienced changes. -//! +//! //! CONTINUE TO UPDATE THE ABOVE pub mod lazy_map; pub mod lazy_set; -pub mod lazy_vec; \ No newline at end of file +pub mod lazy_vec; diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 5e8c570cd3..8cb04434e2 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -1,8 +1,8 @@ //! The common storage read trait is implemented in the storage, client RPC, tx //! and VPs (both native and WASM). -mod error; pub mod collections; +mod error; use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; From 68cd35a5475ea80e0720d711906632ac208bf07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 15 Aug 2022 14:09:19 +0200 Subject: [PATCH 295/373] refactored lazy collections, replaced hasher, added iter --- .../ledger/storage_api/collections/hasher.rs | 9 + .../storage_api/collections/lazy_map.rs | 202 ++++++++++++------ .../storage_api/collections/lazy_set.rs | 116 ++++++---- .../storage_api/collections/lazy_vec.rs | 147 +++++++------ .../src/ledger/storage_api/collections/mod.rs | 1 + 5 files changed, 300 insertions(+), 175 deletions(-) create mode 100644 shared/src/ledger/storage_api/collections/hasher.rs diff --git a/shared/src/ledger/storage_api/collections/hasher.rs b/shared/src/ledger/storage_api/collections/hasher.rs new file mode 100644 index 0000000000..0f864259f5 --- /dev/null +++ b/shared/src/ledger/storage_api/collections/hasher.rs @@ -0,0 +1,9 @@ +use borsh::BorshSerialize; + +/// Hash borsh encoded data into a storage sub-key. +/// This is a sha256 as an uppercase hexadecimal string. +pub fn hash_for_storage_key(data: impl BorshSerialize) -> String { + let bytes = data.try_to_vec().unwrap(); + let hash = crate::types::hash::Hash::sha256(bytes); + hash.to_string() +} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 731cafd3b1..25b4cd4d5d 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,106 +1,170 @@ //! Lazy map -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use super::hasher::hash_for_storage_key; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; /// LazyMap ! fill in ! -pub struct LazyMap { +pub struct LazyMap { key: storage::Key, - phantom_h: PhantomData, - phantom_t: PhantomData, + phantom_h: PhantomData, + phantom_t: PhantomData, } -impl LazyMap +#[derive(Debug, BorshSerialize, BorshDeserialize)] +struct KeyVal { + key: K, + val: V, +} + +impl LazyMap where - H: BorshDeserialize + BorshSerialize + Hash, - T: BorshDeserialize + BorshSerialize, + K: BorshDeserialize + BorshSerialize, + V: BorshDeserialize + BorshSerialize, { - /// insert - pub fn insert( + /// Create or use an existing map with the given storage `key`. + pub fn new(key: storage::Key) -> Self { + Self { + key, + phantom_h: PhantomData, + phantom_t: PhantomData, + } + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not have this key present, `None` is returned. + /// If the map did have this key present, the value is updated, and the old + /// value is returned. Unlike in `std::collection::HashMap`, the key is also + /// updated; this matters for types that can be `==` without being + /// identical. + pub fn insert( &self, - elem_key: &H, - elem_val: T, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { - // TODO: Check to see if map element exists already ?? + storage: &mut S, + key: K, + val: V, + ) -> Result> + where + S: StorageWrite + StorageRead, + { + let previous = self.get(storage, &key)?; - let data_key = self.get_data_key(elem_key); - storage_write.write(&data_key, (elem_key, elem_val))?; + let data_key = self.get_data_key(&key); + Self::write_key_val(storage, &data_key, key, val)?; - Ok(()) + Ok(previous) } - /// remove - pub fn remove( - &self, - elem_key: &H, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { - let data_key = self.get_data_key(elem_key); - storage_write.delete(&data_key)?; + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + pub fn remove(&self, storage: &mut S, key: &K) -> Result> + where + S: StorageWrite + StorageRead, + { + let value = self.get(storage, key)?; + + let data_key = self.get_data_key(key); + storage.delete(&data_key)?; - Ok(()) + Ok(value) } - /// get value + /// Returns the value corresponding to the key, if any. pub fn get( &self, - elem_key: &H, - storage_read: &mut impl StorageRead, - ) -> Result> { - // check if elem_key exists in the first place? - - let data_key = self.get_data_key(elem_key); - let res: Option<(H, T)> = storage_read.read(&data_key)?; - match res { - Some(pair) => Ok(Some(pair.1)), - None => Ok(None), - } + storage: &impl StorageRead, + key: &K, + ) -> Result> { + let res = self.get_key_val(storage, key)?; + Ok(res.map(|elem| elem.1)) } - /// get the element key by its hash - pub fn get_elem_key_by_hash( + /// Returns the key-value corresponding to the key, if any. + pub fn get_key_val( &self, - elem_key_hash: &str, - storage_read: &mut impl StorageRead, - ) -> Result> { - let data_key = self - .key - .push(&DATA_SUBKEY.to_owned()) - .unwrap() - .push(&elem_key_hash.to_string()) - .unwrap(); - let res: Option<(H, T)> = storage_read.read(&data_key)?; - match res { - Some(pair) => Ok(Some(pair.0)), - None => Ok(None), - } + storage: &impl StorageRead, + key: &K, + ) -> Result> { + let data_key = self.get_data_key(key); + Self::read_key_val(storage, &data_key) + } + + /// Returns the key-value corresponding to the given hash of a key, if any. + pub fn get_key_val_by_hash( + &self, + storage: &impl StorageRead, + key_hash: &str, + ) -> Result> { + let data_key = + self.get_data_prefix().push(&key_hash.to_string()).unwrap(); + Self::read_key_val(storage, &data_key) + } + + /// An iterator visiting all key-value elements. The iterator element type + /// is `Result<(K, V)>`, because iterator's call to `next` may fail with + /// e.g. out of gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// map. + pub fn iter<'a>( + &self, + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage.iter_prefix(&self.get_data_prefix())?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((_key, value))) => { + match KeyVal::::try_from_slice(&value[..]) { + Ok(KeyVal { key, val }) => Some(Ok((key, val))), + Err(err) => Some(Err(storage_api::Error::new(err))), + } + } + Ok(None) => None, + Err(err) => { + // Propagate errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) + } + + /// Reads a key-value from storage + fn read_key_val( + storage: &impl StorageRead, + storage_key: &storage::Key, + ) -> Result> { + let res = storage.read(storage_key)?; + Ok(res.map(|KeyVal { key, val }| (key, val))) + } + + /// Write a key-value into storage + fn write_key_val( + storage: &mut impl StorageWrite, + storage_key: &storage::Key, + key: K, + val: V, + ) -> Result<()> { + storage.write(storage_key, KeyVal { key, val }) } - /// hash - fn hash(&self, elem_key: &H) -> u64 { - let mut hasher = DefaultHasher::new(); - elem_key.hash(&mut hasher); - hasher.finish() + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() } - /// get the data subkey - fn get_data_key(&self, elem_key: &H) -> storage::Key { - let hash_str = self.hash(elem_key).to_string(); - self.key - .push(&DATA_SUBKEY.to_owned()) - .unwrap() - .push(&hash_str) - .unwrap() + /// Get the sub-key of a given element + fn get_data_key(&self, key: &K) -> storage::Key { + let hash_str = hash_for_storage_key(key); + self.get_data_prefix().push(&hash_str).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 187f5b5123..862485b687 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -1,13 +1,12 @@ //! Lazy hash set -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use super::hasher::hash_for_storage_key; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey corresponding to the data elements of the LazySet @@ -21,9 +20,9 @@ pub struct LazySet { impl LazySet where - T: BorshSerialize + BorshDeserialize + Hash, + T: BorshSerialize + BorshDeserialize, { - /// new + /// Create or use an existing set with the given storage `key`. pub fn new(key: storage::Key) -> Self { Self { key, @@ -31,62 +30,87 @@ where } } - /// insert - pub fn insert( - &self, - val: &T, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { - // Do we need to read to see if this val is already in the set? - - let data_key = self.get_data_key(val); - storage_write.write(&data_key, &val)?; - Ok(()) + /// Adds a value to the set. If the set did not have this value present, + /// `Ok(true)` is returned, `Ok(false)` otherwise. + pub fn insert(&self, storage: &mut S, val: &T) -> Result + where + S: StorageWrite + StorageRead, + { + if self.contains(storage, val)? { + Ok(false) + } else { + let data_key = self.get_data_key(val); + storage.write(&data_key, &val)?; + Ok(true) + } } - /// remove - pub fn remove( - &self, - val: &T, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { + /// Removes a value from the set. Returns whether the value was present in + /// the set. + pub fn remove(&self, storage: &mut S, val: &T) -> Result + where + S: StorageWrite + StorageRead, + { let data_key = self.get_data_key(val); - storage_write.delete(&data_key)?; - Ok(()) + let value: Option = storage.read(&data_key)?; + storage.delete(&data_key)?; + Ok(value.is_some()) } - /// check if the hash set contains a value + /// Returns whether the set contains a value. pub fn contains( &self, + storage: &impl StorageRead, val: &T, - storage_read: &impl StorageRead, ) -> Result { - let digest: Option = storage_read.read(&self.get_data_key(val))?; - match digest { - Some(_) => Ok(true), - None => Ok(false), - } + let value: Option = storage.read(&self.get_data_key(val))?; + Ok(value.is_some()) } - /// check if hash set is empty (if we want to do this we prob need a length - /// field) - pub fn is_empty(&self) { - todo!(); + /// Returns whether the set contains no elements. + pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + let mut iter = storage.iter_prefix(&self.get_data_prefix())?; + Ok(storage.iter_next(&mut iter)?.is_none()) + } + + /// An iterator visiting all elements. The iterator element type is + /// `Result`, because iterator's call to `next` may fail with e.g. out of + /// gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'a>( + &self, + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage.iter_prefix(&self.get_data_prefix())?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((_key, value))) => { + match T::try_from_slice(&value[..]) { + Ok(decoded_value) => Some(Ok(decoded_value)), + Err(err) => Some(Err(storage_api::Error::new(err))), + } + } + Ok(None) => None, + Err(err) => { + // Propagate errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) } - fn hash_val(&self, val: &T) -> u64 { - let mut hasher = DefaultHasher::new(); - val.hash(&mut hasher); - hasher.finish() + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() } - /// get the data subkey + /// Get the sub-key of a given element fn get_data_key(&self, val: &T) -> storage::Key { - let hash_str = self.hash_val(val).to_string(); - self.key - .push(&DATA_SUBKEY.to_owned()) - .unwrap() - .push(&hash_str) - .unwrap() + let hash_str = hash_for_storage_key(val); + self.get_data_prefix().push(&hash_str).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 4c08f906d9..c55c39e516 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use crate::ledger::storage_api::{StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey pointing to the length of the LazyVec @@ -23,7 +23,7 @@ impl LazyVec where T: BorshSerialize + BorshDeserialize, { - /// new + /// Create or use an existing vector with the given storage `key`. pub fn new(key: storage::Key) -> Self { Self { key, @@ -31,78 +31,105 @@ where } } - /// push - pub fn push( - &self, - val: T, - storage_read: &impl StorageRead, - storage_write: &mut impl StorageWrite, - ) -> Result<()> { - let len = self.read_len(storage_read)?; + /// Appends an element to the back of a collection. + pub fn push(&self, storage: &mut S, val: T) -> Result<()> + where + S: StorageWrite + StorageRead, + { + let len = self.len(storage)?; + let data_key = self.get_data_key(len); + storage.write(&data_key, val)?; + storage.write(&self.get_len_key(), len + 1) + } - let sub_index = len.unwrap_or(0); - let len = sub_index + 1; + /// Removes the last element from a vector and returns it, or `Ok(None)` if + /// it is empty. + + /// Note that an empty vector is completely removed from storage. + pub fn pop(&self, storage: &mut S) -> Result> + where + S: StorageWrite + StorageRead, + { + let len = self.len(storage)?; + if len == 0 { + Ok(None) + } else { + let index = len - 1; + let data_key = self.get_data_key(index); + if len == 1 { + storage.delete(&self.get_len_key())?; + } else { + storage.write(&self.get_len_key(), index)?; + } + let popped_val = storage.read(&data_key)?; + storage.delete(&data_key)?; + Ok(popped_val) + } + } - let data_key = self.get_data_key(sub_index); + /// Read an element at the index or `Ok(None)` if out of bounds. + pub fn get( + &self, + storage: &impl StorageRead, + index: u64, + ) -> Result> { + storage.read(&self.get_data_key(index)) + } - storage_write.write(&data_key, val)?; - storage_write.write(&self.get_len_key(), len)?; + /// Reads the number of elements in the vector. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &impl StorageRead) -> Result { + let len = storage.read(&self.get_len_key())?; + Ok(len.unwrap_or_default()) + } - Ok(()) + /// Returns `true` if the vector contains no elements. + pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + Ok(self.len(storage)? == 0) } - /// pop - pub fn pop( + /// An iterator visiting all elements. The iterator element type is + /// `Result`, because iterator's call to `next` may fail with e.g. out of + /// gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'a>( &self, - storage_read: &impl StorageRead, - storage_write: &mut impl StorageWrite, - ) -> Result> { - let len = self.read_len(storage_read)?; - match len { - Some(0) | None => Ok(None), - Some(len) => { - let sub_index = len - 1; - let data_key = self.get_data_key(sub_index); - if len == 1 { - storage_write.delete(&self.get_len_key())?; - } else { - storage_write.write(&self.get_len_key(), sub_index)?; + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage.iter_prefix(&self.get_data_prefix())?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((_key, value))) => { + match T::try_from_slice(&value[..]) { + Ok(decoded_value) => Some(Ok(decoded_value)), + Err(err) => Some(Err(storage_api::Error::new(err))), + } + } + Ok(None) => None, + Err(err) => { + // Propagate errors into Iterator's Item + Some(Err(err)) } - let popped_val = storage_read.read(&data_key)?; - storage_write.delete(&data_key)?; - Ok(popped_val) } - } - } - - /// get the length subkey - fn get_len_key(&self) -> storage::Key { - self.key.push(&LEN_SUBKEY.to_owned()).unwrap() + }); + Ok(iter) } - /// read the length of the LazyVec - pub fn read_len( - &self, - storage_read: &impl StorageRead, - ) -> Result> { - storage_read.read(&self.get_len_key()) + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() } - /// get the data subkey - fn get_data_key(&self, sub_index: u64) -> storage::Key { - self.key - .push(&DATA_SUBKEY.to_owned()) - .unwrap() - .push(&sub_index.to_string()) - .unwrap() + /// Get the sub-key of vector's elements storage + fn get_data_key(&self, index: u64) -> storage::Key { + self.get_data_prefix().push(&index.to_string()).unwrap() } - /// get the data held at a specific index within the data subkey - pub fn get( - &self, - sub_index: u64, - storage_read: &impl StorageRead, - ) -> Result> { - storage_read.read(&self.get_data_key(sub_index)) + /// Get the sub-key of vector's length storage + fn get_len_key(&self) -> storage::Key { + self.key.push(&LEN_SUBKEY.to_owned()).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index c3784c2525..b0dc43779b 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -7,6 +7,7 @@ //! //! CONTINUE TO UPDATE THE ABOVE +mod hasher; pub mod lazy_map; pub mod lazy_set; pub mod lazy_vec; From bc5f615c437e2f4f894988552857fbdb61eb5bde Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 15 Aug 2022 16:38:10 -0400 Subject: [PATCH 296/373] add lazy map without hashing --- .../storage_api/collections/lazy_hashmap.rs | 171 ++++++++++++++++++ .../storage_api/collections/lazy_map.rs | 66 ++----- 2 files changed, 191 insertions(+), 46 deletions(-) create mode 100644 shared/src/ledger/storage_api/collections/lazy_hashmap.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs new file mode 100644 index 0000000000..405262b33d --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -0,0 +1,171 @@ +//! Lazy hash map + +use std::marker::PhantomData; + +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::super::Result; +use super::hasher::hash_for_storage_key; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::storage; + +/// Subkey corresponding to the data elements of the LazyMap +pub const DATA_SUBKEY: &str = "data"; + +/// LazyHashmap ! fill in ! +pub struct LazyHashMap { + key: storage::Key, + phantom_k: PhantomData, + phantom_v: PhantomData, +} + +/// Struct to hold a key-value pair +#[derive(Debug, BorshSerialize, BorshDeserialize)] +struct KeyVal { + key: K, + val: V, +} + +impl LazyMap +where + K: BorshDeserialize + BorshSerialize, + V: BorshDeserialize + BorshSerialize, +{ + /// Create or use an existing map with the given storage `key`. + pub fn new(key: storage::Key) -> Self { + Self { + key, + phantom_k: PhantomData, + phantom_v: PhantomData, + } + } + + /// Inserts a key-value pair into the map. + /// + /// If the map did not have this key present, `None` is returned. + /// If the map did have this key present, the value is updated, and the old + /// value is returned. Unlike in `std::collection::HashMap`, the key is also + /// updated; this matters for types that can be `==` without being + /// identical. + pub fn insert( + &self, + storage: &mut S, + key: K, + val: V, + ) -> Result> + where + S: StorageWrite + StorageRead, + { + let previous = self.get(storage, &key)?; + + let data_key = self.get_data_key(&key); + Self::write_key_val(storage, &data_key, key, val)?; + + Ok(previous) + } + + /// Removes a key from the map, returning the value at the key if the key + /// was previously in the map. + pub fn remove(&self, storage: &mut S, key: &K) -> Result> + where + S: StorageWrite + StorageRead, + { + let value = self.get(storage, key)?; + + let data_key = self.get_data_key(key); + storage.delete(&data_key)?; + + Ok(value) + } + + /// Returns the value corresponding to the key, if any. + pub fn get( + &self, + storage: &impl StorageRead, + key: &K, + ) -> Result> { + let res = self.get_key_val(storage, key)?; + Ok(res.map(|elem| elem.1)) + } + + /// Returns the key-value corresponding to the key, if any. + pub fn get_key_val( + &self, + storage: &impl StorageRead, + key: &K, + ) -> Result> { + let data_key = self.get_data_key(key); + Self::read_key_val(storage, &data_key) + } + + /// Returns the key-value corresponding to the given hash of a key, if any. + pub fn get_key_val_by_hash( + &self, + storage: &impl StorageRead, + key_hash: &str, + ) -> Result> { + let data_key = + self.get_data_prefix().push(&key_hash.to_string()).unwrap(); + Self::read_key_val(storage, &data_key) + } + + /// An iterator visiting all key-value elements. The iterator element type + /// is `Result<(K, V)>`, because iterator's call to `next` may fail with + /// e.g. out of gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// map. + pub fn iter<'a>( + &self, + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage.iter_prefix(&self.get_data_prefix())?; + let iter = itertools::unfold(iter, |iter| { + match storage.iter_next(iter) { + Ok(Some((_key, value))) => { + match KeyVal::::try_from_slice(&value[..]) { + Ok(KeyVal { key, val }) => Some(Ok((key, val))), + Err(err) => Some(Err(storage_api::Error::new(err))), + } + } + Ok(None) => None, + Err(err) => { + // Propagate errors into Iterator's Item + Some(Err(err)) + } + } + }); + Ok(iter) + } + + /// Reads a key-value from storage + fn read_key_val( + storage: &impl StorageRead, + storage_key: &storage::Key, + ) -> Result> { + let res = storage.read(storage_key)?; + Ok(res.map(|KeyVal { key, val }| (key, val))) + } + + /// Write a key-value into storage + fn write_key_val( + storage: &mut impl StorageWrite, + storage_key: &storage::Key, + key: K, + val: V, + ) -> Result<()> { + storage.write(storage_key, KeyVal { key, val }) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of a given element + fn get_data_key(&self, key: &K) -> storage::Key { + let hash_str = hash_for_storage_key(key); + self.get_data_prefix().push(&hash_str).unwrap() + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 25b4cd4d5d..01af3c5a42 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,11 +1,11 @@ -//! Lazy map +//! Lazy hash map +use std::fmt::Display; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use super::hasher::hash_for_storage_key; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::types::storage; @@ -15,32 +15,29 @@ pub const DATA_SUBKEY: &str = "data"; /// LazyMap ! fill in ! pub struct LazyMap { key: storage::Key, - phantom_h: PhantomData, - phantom_t: PhantomData, -} - -#[derive(Debug, BorshSerialize, BorshDeserialize)] -struct KeyVal { - key: K, - val: V, + phantom_k: PhantomData, + phantom_v: PhantomData, } impl LazyMap where - K: BorshDeserialize + BorshSerialize, + K: BorshDeserialize + BorshSerialize + Display, V: BorshDeserialize + BorshSerialize, { /// Create or use an existing map with the given storage `key`. pub fn new(key: storage::Key) -> Self { Self { key, - phantom_h: PhantomData, - phantom_t: PhantomData, + phantom_k: PhantomData, + phantom_v: PhantomData, } } /// Inserts a key-value pair into the map. /// + /// The full storage key identifies the key in the pair, while the value is + /// held within the storage key. + /// /// If the map did not have this key present, `None` is returned. /// If the map did have this key present, the value is updated, and the old /// value is returned. Unlike in `std::collection::HashMap`, the key is also @@ -58,7 +55,7 @@ where let previous = self.get(storage, &key)?; let data_key = self.get_data_key(&key); - Self::write_key_val(storage, &data_key, key, val)?; + Self::write_key_val(storage, &data_key, val)?; Ok(previous) } @@ -83,31 +80,10 @@ where storage: &impl StorageRead, key: &K, ) -> Result> { - let res = self.get_key_val(storage, key)?; - Ok(res.map(|elem| elem.1)) - } - - /// Returns the key-value corresponding to the key, if any. - pub fn get_key_val( - &self, - storage: &impl StorageRead, - key: &K, - ) -> Result> { let data_key = self.get_data_key(key); Self::read_key_val(storage, &data_key) } - /// Returns the key-value corresponding to the given hash of a key, if any. - pub fn get_key_val_by_hash( - &self, - storage: &impl StorageRead, - key_hash: &str, - ) -> Result> { - let data_key = - self.get_data_prefix().push(&key_hash.to_string()).unwrap(); - Self::read_key_val(storage, &data_key) - } - /// An iterator visiting all key-value elements. The iterator element type /// is `Result<(K, V)>`, because iterator's call to `next` may fail with /// e.g. out of gas or data decoding error. @@ -118,13 +94,13 @@ where pub fn iter<'a>( &self, storage: &'a impl StorageRead, - ) -> Result> + 'a> { + ) -> Result> + 'a> { let iter = storage.iter_prefix(&self.get_data_prefix())?; let iter = itertools::unfold(iter, |iter| { match storage.iter_next(iter) { Ok(Some((_key, value))) => { - match KeyVal::::try_from_slice(&value[..]) { - Ok(KeyVal { key, val }) => Some(Ok((key, val))), + match V::try_from_slice(&value[..]) { + Ok(decoded_value) => Some(Ok(decoded_value)), Err(err) => Some(Err(storage_api::Error::new(err))), } } @@ -138,23 +114,22 @@ where Ok(iter) } - /// Reads a key-value from storage + /// Reads a value from storage fn read_key_val( storage: &impl StorageRead, storage_key: &storage::Key, - ) -> Result> { + ) -> Result> { let res = storage.read(storage_key)?; - Ok(res.map(|KeyVal { key, val }| (key, val))) + Ok(res) } - /// Write a key-value into storage + /// Write a value into storage fn write_key_val( storage: &mut impl StorageWrite, storage_key: &storage::Key, - key: K, val: V, ) -> Result<()> { - storage.write(storage_key, KeyVal { key, val }) + storage.write(storage_key, val) } /// Get the prefix of set's elements storage @@ -164,7 +139,6 @@ where /// Get the sub-key of a given element fn get_data_key(&self, key: &K) -> storage::Key { - let hash_str = hash_for_storage_key(key); - self.get_data_prefix().push(&hash_str).unwrap() + self.get_data_prefix().push(&key.to_string()).unwrap() } } From 50e2f258838e4561d64b8421051950368eba394d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 12:51:19 +0200 Subject: [PATCH 297/373] Switch to use storage::KeySeg and add LazySet Also refactored the iterators implementation to take advantage of changes from #335. Note that this requires `'static` lifetime bound on the types of the collections' elements, which means we cannot use non-static references, but we wouldn't do that anyway. --- .../storage_api/collections/lazy_hashmap.rs | 50 ++++---- .../storage_api/collections/lazy_hashset.rs | 119 ++++++++++++++++++ .../storage_api/collections/lazy_map.rs | 58 +++++---- .../storage_api/collections/lazy_set.rs | 70 ++++++----- .../storage_api/collections/lazy_vec.rs | 35 +++--- .../src/ledger/storage_api/collections/mod.rs | 27 +++- 6 files changed, 252 insertions(+), 107 deletions(-) create mode 100644 shared/src/ledger/storage_api/collections/lazy_hashset.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index 405262b33d..fbb76beb8b 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -1,4 +1,4 @@ -//! Lazy hash map +//! Lazy hash map. use std::marker::PhantomData; @@ -12,7 +12,22 @@ use crate::types::storage; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; -/// LazyHashmap ! fill in ! +/// Lazy hash map. +/// +/// This can be used as an alternative to `std::collections::HashMap` and +/// `BTreeMap`. In the lazy map, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` given to +/// construct the map. +/// +/// In the [`LazyHashMap`], the type of key `K` can be anything that +/// [`BorshSerialize`] and [`BorshDeserialize`] and a hex string of sha256 hash +/// over the borsh encoded keys are used as storage key segments. +/// +/// This is different from [`super::LazyMap`], which uses [`storage::KeySeg`] +/// trait. +/// +/// Additionally, [`LazyHashMap`] also writes the unhashed values into the +/// storage together with the values (using an internal `KeyVal` type). pub struct LazyHashMap { key: storage::Key, phantom_k: PhantomData, @@ -26,10 +41,10 @@ struct KeyVal { val: V, } -impl LazyMap +impl LazyHashMap where - K: BorshDeserialize + BorshSerialize, - V: BorshDeserialize + BorshSerialize, + K: BorshDeserialize + BorshSerialize + 'static, + V: BorshDeserialize + BorshSerialize + 'static, { /// Create or use an existing map with the given storage `key`. pub fn new(key: storage::Key) -> Self { @@ -85,7 +100,7 @@ where key: &K, ) -> Result> { let res = self.get_key_val(storage, key)?; - Ok(res.map(|elem| elem.1)) + Ok(res.map(|(_key, val)| val)) } /// Returns the key-value corresponding to the key, if any. @@ -120,23 +135,12 @@ where &self, storage: &'a impl StorageRead, ) -> Result> + 'a> { - let iter = storage.iter_prefix(&self.get_data_prefix())?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((_key, value))) => { - match KeyVal::::try_from_slice(&value[..]) { - Ok(KeyVal { key, val }) => Some(Ok((key, val))), - Err(err) => Some(Err(storage_api::Error::new(err))), - } - } - Ok(None) => None, - Err(err) => { - // Propagate errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (_key, val) = key_val_res?; + let KeyVal { key, val } = val; + Ok((key, val)) + })) } /// Reads a key-value from storage diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs new file mode 100644 index 0000000000..ae03ff1f0e --- /dev/null +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -0,0 +1,119 @@ +//! Lazy hash set. + +use std::marker::PhantomData; + +use borsh::{BorshDeserialize, BorshSerialize}; + +use super::super::Result; +use super::hasher::hash_for_storage_key; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::storage; + +/// Subkey corresponding to the data elements of the LazySet +pub const DATA_SUBKEY: &str = "data"; + +/// Lazy hash set. +/// +/// This can be used as an alternative to `std::collections::HashSet` and +/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` given to +/// construct the set. +/// +/// In the [`LazyHashSet`], the type of value `T` can be anything that +/// [`BorshSerialize`] and [`BorshDeserialize`] and a hex string of sha256 hash +/// over the borsh encoded values are used as storage key segments. +/// +/// This is different from [`super::LazySet`], which uses [`storage::KeySeg`] +/// trait. +/// +/// Additionally, [`LazyHashSet`] also writes the unhashed values into the +/// storage. +pub struct LazyHashSet { + key: storage::Key, + phantom: PhantomData, +} + +impl LazyHashSet +where + T: BorshSerialize + BorshDeserialize + 'static, +{ + /// Create or use an existing set with the given storage `key`. + pub fn new(key: storage::Key) -> Self { + Self { + key, + phantom: PhantomData, + } + } + + /// Adds a value to the set. If the set did not have this value present, + /// `Ok(true)` is returned, `Ok(false)` otherwise. + pub fn insert(&self, storage: &mut S, val: &T) -> Result + where + S: StorageWrite + StorageRead, + { + if self.contains(storage, val)? { + Ok(false) + } else { + let data_key = self.get_data_key(val); + storage.write(&data_key, &val)?; + Ok(true) + } + } + + /// Removes a value from the set. Returns whether the value was present in + /// the set. + pub fn remove(&self, storage: &mut S, val: &T) -> Result + where + S: StorageWrite + StorageRead, + { + let data_key = self.get_data_key(val); + let value: Option = storage.read(&data_key)?; + storage.delete(&data_key)?; + Ok(value.is_some()) + } + + /// Returns whether the set contains a value. + pub fn contains( + &self, + storage: &impl StorageRead, + val: &T, + ) -> Result { + let value: Option = storage.read(&self.get_data_key(val))?; + Ok(value.is_some()) + } + + /// Returns whether the set contains no elements. + pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + let mut iter = storage.iter_prefix(&self.get_data_prefix())?; + Ok(storage.iter_next(&mut iter)?.is_none()) + } + + /// An iterator visiting all elements. The iterator element type is + /// `Result`, because iterator's call to `next` may fail with e.g. out of + /// gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'a>( + &self, + storage: &'a impl StorageRead, + ) -> Result> + 'a> { + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (_key, val) = key_val_res?; + Ok(val) + })) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of a given element + fn get_data_key(&self, val: &T) -> storage::Key { + let hash_str = hash_for_storage_key(val); + self.get_data_prefix().push(&hash_str).unwrap() + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 01af3c5a42..bf7b45324b 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,18 +1,30 @@ -//! Lazy hash map +//! Lazy map. -use std::fmt::Display; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; -use crate::types::storage; +use super::ReadError; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; +use crate::types::storage::{self, KeySeg}; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; -/// LazyMap ! fill in ! +/// Lazy map. +/// +/// This can be used as an alternative to `std::collections::HashMap` and +/// `BTreeMap`. In the lazy map, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` used to +/// construct the map. +/// +/// In the [`LazyMap`], the type of key `K` can be anything that implements +/// [`storage::KeySeg`] and this trait is used to turn the keys into key +/// segments. +/// +/// This is different from [`super::LazyHashMap`], which hashes borsh encoded +/// key. pub struct LazyMap { key: storage::Key, phantom_k: PhantomData, @@ -21,8 +33,8 @@ pub struct LazyMap { impl LazyMap where - K: BorshDeserialize + BorshSerialize + Display, - V: BorshDeserialize + BorshSerialize, + K: storage::KeySeg, + V: BorshDeserialize + BorshSerialize + 'static, { /// Create or use an existing map with the given storage `key`. pub fn new(key: storage::Key) -> Self { @@ -94,24 +106,17 @@ where pub fn iter<'a>( &self, storage: &'a impl StorageRead, - ) -> Result> + 'a> { - let iter = storage.iter_prefix(&self.get_data_prefix())?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((_key, value))) => { - match V::try_from_slice(&value[..]) { - Ok(decoded_value) => Some(Ok(decoded_value)), - Err(err) => Some(Err(storage_api::Error::new(err))), - } - } - Ok(None) => None, - Err(err) => { - // Propagate errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) + ) -> Result> + 'a> { + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (key, val) = key_val_res?; + let last_key_seg = key + .last() + .ok_or(ReadError::UnexpectedlyEmptyStorageKey) + .into_storage_result()?; + let key = K::parse(last_key_seg.raw()).into_storage_result()?; + Ok((key, val)) + })) } /// Reads a value from storage @@ -139,6 +144,7 @@ where /// Get the sub-key of a given element fn get_data_key(&self, key: &K) -> storage::Key { - self.get_data_prefix().push(&key.to_string()).unwrap() + let key_str = key.to_db_key(); + self.get_data_prefix().push(&key_str).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 862485b687..8c1bbd871f 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -1,18 +1,28 @@ -//! Lazy hash set +//! Lazy set. use std::marker::PhantomData; -use borsh::{BorshDeserialize, BorshSerialize}; - use super::super::Result; -use super::hasher::hash_for_storage_key; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; -use crate::types::storage; +use super::ReadError; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; +use crate::types::storage::{self, KeySeg}; /// Subkey corresponding to the data elements of the LazySet pub const DATA_SUBKEY: &str = "data"; -/// lazy hash set +/// Lazy set. +/// +/// This can be used as an alternative to `std::collections::HashSet` and +/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are +/// instead read and written to storage sub-keys of the storage `key` used to +/// construct the set. +/// +/// In the [`LazySet`], the type of value `T` can be anything that implements +/// [`storage::KeySeg`] and this trait is used to turn the values into key +/// segments. +/// +/// This is different from [`super::LazyHashSet`], which hashes borsh encoded +/// values. pub struct LazySet { key: storage::Key, phantom: PhantomData, @@ -20,7 +30,7 @@ pub struct LazySet { impl LazySet where - T: BorshSerialize + BorshDeserialize, + T: storage::KeySeg, { /// Create or use an existing set with the given storage `key`. pub fn new(key: storage::Key) -> Self { @@ -40,7 +50,9 @@ where Ok(false) } else { let data_key = self.get_data_key(val); - storage.write(&data_key, &val)?; + // The actual value is written into the key, so the value written to + // the storage is empty (unit) + storage.write(&data_key, ())?; Ok(true) } } @@ -52,7 +64,7 @@ where S: StorageWrite + StorageRead, { let data_key = self.get_data_key(val); - let value: Option = storage.read(&data_key)?; + let value: Option<()> = storage.read(&data_key)?; storage.delete(&data_key)?; Ok(value.is_some()) } @@ -63,14 +75,15 @@ where storage: &impl StorageRead, val: &T, ) -> Result { - let value: Option = storage.read(&self.get_data_key(val))?; + let value: Option<()> = storage.read(&self.get_data_key(val))?; Ok(value.is_some()) } /// Returns whether the set contains no elements. pub fn is_empty(&self, storage: &impl StorageRead) -> Result { - let mut iter = storage.iter_prefix(&self.get_data_prefix())?; - Ok(storage.iter_next(&mut iter)?.is_none()) + let mut iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + Ok(iter.next().is_none()) } /// An iterator visiting all elements. The iterator element type is @@ -84,23 +97,16 @@ where &self, storage: &'a impl StorageRead, ) -> Result> + 'a> { - let iter = storage.iter_prefix(&self.get_data_prefix())?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((_key, value))) => { - match T::try_from_slice(&value[..]) { - Ok(decoded_value) => Some(Ok(decoded_value)), - Err(err) => Some(Err(storage_api::Error::new(err))), - } - } - Ok(None) => None, - Err(err) => { - // Propagate errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (key, _val) = key_val_res?; + let last_key_seg = key + .last() + .ok_or(ReadError::UnexpectedlyEmptyStorageKey) + .into_storage_result()?; + T::parse(last_key_seg.raw()).into_storage_result() + })) } /// Get the prefix of set's elements storage @@ -110,7 +116,7 @@ where /// Get the sub-key of a given element fn get_data_key(&self, val: &T) -> storage::Key { - let hash_str = hash_for_storage_key(val); - self.get_data_prefix().push(&hash_str).unwrap() + let key_str = val.to_db_key(); + self.get_data_prefix().push(&key_str).unwrap() } } diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index c55c39e516..f57797f35c 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -1,4 +1,4 @@ -//! Lazy vec +//! Lazy dynamically-sized vector. use std::marker::PhantomData; @@ -13,7 +13,12 @@ pub const LEN_SUBKEY: &str = "len"; /// Subkey corresponding to the data elements of the LazyVec pub const DATA_SUBKEY: &str = "data"; -/// LazyVec ! fill in ! +/// Lazy dynamically-sized vector. +/// +/// This can be used as an alternative to `std::collections::Vec`. In the lazy +/// vector, the elements do not reside in memory but are instead read and +/// written to storage sub-keys of the storage `key` used to construct the +/// vector. pub struct LazyVec { key: storage::Key, phantom: PhantomData, @@ -21,7 +26,7 @@ pub struct LazyVec { impl LazyVec where - T: BorshSerialize + BorshDeserialize, + T: BorshSerialize + BorshDeserialize + 'static, { /// Create or use an existing vector with the given storage `key`. pub fn new(key: storage::Key) -> Self { @@ -44,7 +49,7 @@ where /// Removes the last element from a vector and returns it, or `Ok(None)` if /// it is empty. - + /// /// Note that an empty vector is completely removed from storage. pub fn pop(&self, storage: &mut S) -> Result> where @@ -99,23 +104,11 @@ where &self, storage: &'a impl StorageRead, ) -> Result> + 'a> { - let iter = storage.iter_prefix(&self.get_data_prefix())?; - let iter = itertools::unfold(iter, |iter| { - match storage.iter_next(iter) { - Ok(Some((_key, value))) => { - match T::try_from_slice(&value[..]) { - Ok(decoded_value) => Some(Ok(decoded_value)), - Err(err) => Some(Err(storage_api::Error::new(err))), - } - } - Ok(None) => None, - Err(err) => { - // Propagate errors into Iterator's Item - Some(Err(err)) - } - } - }); - Ok(iter) + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (_key, val) = key_val_res?; + Ok(val) + })) } /// Get the prefix of set's elements storage diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index b0dc43779b..156615b9de 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -1,13 +1,30 @@ //! Lazy data structures for storage access where elements are not all loaded //! into memory. This serves to minimize gas costs, avoid unbounded iteration -//! in some cases, and ease the validation of storage changes in the VP. +//! in some cases, and ease the validation of storage changes in VPs. //! -//! Rather than finding the diff of the state before and after, the VP will -//! just receive the storage sub-keys that have experienced changes. -//! -//! CONTINUE TO UPDATE THE ABOVE +//! Rather than finding the diff of the state before and after (which requires +//! iteration over both of the states that also have to be decoded), VPs will +//! just receive the storage sub-keys that have experienced changes without +//! having to check any of the unchanged elements. + +use thiserror::Error; mod hasher; +pub mod lazy_hashmap; +pub mod lazy_hashset; pub mod lazy_map; pub mod lazy_set; pub mod lazy_vec; + +pub use lazy_hashmap::LazyHashMap; +pub use lazy_hashset::LazyHashSet; +pub use lazy_map::LazyMap; +pub use lazy_set::LazySet; +pub use lazy_vec::LazyVec; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ReadError { + #[error("A storage key was unexpectedly empty")] + UnexpectedlyEmptyStorageKey, +} From 12ce117920cc0364b2aa602ba0682ad93ccfb303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 12:54:54 +0200 Subject: [PATCH 298/373] storage: add `Key::last` method, impl KeySeg for all ints --- shared/src/types/storage.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 1892334031..289f115c8f 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -278,6 +278,11 @@ impl Key { self.len() == 0 } + /// Returns the last segment of the key, or `None` if it is empty. + pub fn last(&self) -> Option<&DbKeySeg> { + self.segments.last() + } + /// Returns a key of the validity predicate of the given address /// Only this function can push "?" segment for validity predicate pub fn validity_predicate(addr: &Address) -> Self { @@ -321,8 +326,11 @@ impl Key { .split_off(2) .join(&KEY_SEGMENT_SEPARATOR.to_string()), ) - .map_err(|e| Error::Temporary { - error: format!("Cannot parse key segments {}: {}", db_key, e), + .map_err(|e| { + Error::ParseKeySeg(format!( + "Cannot parse key segments {}: {}", + db_key, e + )) })?, }; Ok(key) @@ -450,7 +458,12 @@ impl KeySeg for String { impl KeySeg for BlockHeight { fn parse(string: String) -> Result { - let h: u64 = KeySeg::parse(string)?; + let h = string.parse::().map_err(|e| { + Error::ParseKeySeg(format!( + "Unexpected height value {}, {}", + string, e + )) + })?; Ok(BlockHeight(h)) } From bb18f2b79562c2ec3e0fddf655c196e6a4e633a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 16:41:27 +0200 Subject: [PATCH 299/373] update lazy for explicit lifetime in StorageRead trait --- .../storage_api/collections/lazy_hashmap.rs | 46 ++++++++++--------- .../storage_api/collections/lazy_hashset.rs | 24 +++++----- .../storage_api/collections/lazy_map.rs | 28 +++++------ .../storage_api/collections/lazy_set.rs | 24 +++++----- .../storage_api/collections/lazy_vec.rs | 29 +++++++----- 5 files changed, 83 insertions(+), 68 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index fbb76beb8b..d626b75451 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -69,7 +69,7 @@ where val: V, ) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let previous = self.get(storage, &key)?; @@ -83,7 +83,7 @@ where /// was previously in the map. pub fn remove(&self, storage: &mut S, key: &K) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let value = self.get(storage, key)?; @@ -94,31 +94,32 @@ where } /// Returns the value corresponding to the key, if any. - pub fn get( - &self, - storage: &impl StorageRead, - key: &K, - ) -> Result> { + pub fn get(&self, storage: &S, key: &K) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let res = self.get_key_val(storage, key)?; Ok(res.map(|(_key, val)| val)) } /// Returns the key-value corresponding to the key, if any. - pub fn get_key_val( - &self, - storage: &impl StorageRead, - key: &K, - ) -> Result> { + pub fn get_key_val(&self, storage: &S, key: &K) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let data_key = self.get_data_key(key); Self::read_key_val(storage, &data_key) } /// Returns the key-value corresponding to the given hash of a key, if any. - pub fn get_key_val_by_hash( + pub fn get_key_val_by_hash( &self, - storage: &impl StorageRead, + storage: &S, key_hash: &str, - ) -> Result> { + ) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let data_key = self.get_data_prefix().push(&key_hash.to_string()).unwrap(); Self::read_key_val(storage, &data_key) @@ -131,10 +132,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// map. - pub fn iter<'a>( + pub fn iter<'iter, S>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { let (_key, val) = key_val_res?; @@ -144,10 +145,13 @@ where } /// Reads a key-value from storage - fn read_key_val( - storage: &impl StorageRead, + fn read_key_val( + storage: &S, storage_key: &storage::Key, - ) -> Result> { + ) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let res = storage.read(storage_key)?; Ok(res.map(|KeyVal { key, val }| (key, val))) } diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index ae03ff1f0e..96a31ecef7 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -49,7 +49,7 @@ where /// `Ok(true)` is returned, `Ok(false)` otherwise. pub fn insert(&self, storage: &mut S, val: &T) -> Result where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { if self.contains(storage, val)? { Ok(false) @@ -64,7 +64,7 @@ where /// the set. pub fn remove(&self, storage: &mut S, val: &T) -> Result where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let data_key = self.get_data_key(val); let value: Option = storage.read(&data_key)?; @@ -73,17 +73,19 @@ where } /// Returns whether the set contains a value. - pub fn contains( - &self, - storage: &impl StorageRead, - val: &T, - ) -> Result { + pub fn contains(&self, storage: &S, val: &T) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let value: Option = storage.read(&self.get_data_key(val))?; Ok(value.is_some()) } /// Returns whether the set contains no elements. - pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let mut iter = storage.iter_prefix(&self.get_data_prefix())?; Ok(storage.iter_next(&mut iter)?.is_none()) } @@ -95,10 +97,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// set. - pub fn iter<'a>( + pub fn iter<'iter>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { let (_key, val) = key_val_res?; diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index bf7b45324b..6da5c97031 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -62,7 +62,7 @@ where val: V, ) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let previous = self.get(storage, &key)?; @@ -76,7 +76,7 @@ where /// was previously in the map. pub fn remove(&self, storage: &mut S, key: &K) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let value = self.get(storage, key)?; @@ -87,11 +87,10 @@ where } /// Returns the value corresponding to the key, if any. - pub fn get( - &self, - storage: &impl StorageRead, - key: &K, - ) -> Result> { + pub fn get(&self, storage: &S, key: &K) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let data_key = self.get_data_key(key); Self::read_key_val(storage, &data_key) } @@ -103,10 +102,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// map. - pub fn iter<'a>( + pub fn iter<'iter>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { let (key, val) = key_val_res?; @@ -120,10 +119,13 @@ where } /// Reads a value from storage - fn read_key_val( - storage: &impl StorageRead, + fn read_key_val( + storage: &S, storage_key: &storage::Key, - ) -> Result> { + ) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { let res = storage.read(storage_key)?; Ok(res) } diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 8c1bbd871f..1e1f259c55 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -44,7 +44,7 @@ where /// `Ok(true)` is returned, `Ok(false)` otherwise. pub fn insert(&self, storage: &mut S, val: &T) -> Result where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { if self.contains(storage, val)? { Ok(false) @@ -61,7 +61,7 @@ where /// the set. pub fn remove(&self, storage: &mut S, val: &T) -> Result where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let data_key = self.get_data_key(val); let value: Option<()> = storage.read(&data_key)?; @@ -70,17 +70,19 @@ where } /// Returns whether the set contains a value. - pub fn contains( - &self, - storage: &impl StorageRead, - val: &T, - ) -> Result { + pub fn contains(&self, storage: &S, val: &T) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let value: Option<()> = storage.read(&self.get_data_key(val))?; Ok(value.is_some()) } /// Returns whether the set contains no elements. - pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; Ok(iter.next().is_none()) @@ -93,10 +95,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// set. - pub fn iter<'a>( + pub fn iter<'iter>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index f57797f35c..e6a186c408 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -39,7 +39,7 @@ where /// Appends an element to the back of a collection. pub fn push(&self, storage: &mut S, val: T) -> Result<()> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let len = self.len(storage)?; let data_key = self.get_data_key(len); @@ -53,7 +53,7 @@ where /// Note that an empty vector is completely removed from storage. pub fn pop(&self, storage: &mut S) -> Result> where - S: StorageWrite + StorageRead, + S: StorageWrite + for<'iter> StorageRead<'iter>, { let len = self.len(storage)?; if len == 0 { @@ -73,23 +73,28 @@ where } /// Read an element at the index or `Ok(None)` if out of bounds. - pub fn get( - &self, - storage: &impl StorageRead, - index: u64, - ) -> Result> { + pub fn get(&self, storage: &S, index: u64) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { storage.read(&self.get_data_key(index)) } /// Reads the number of elements in the vector. #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &impl StorageRead) -> Result { + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { let len = storage.read(&self.get_len_key())?; Ok(len.unwrap_or_default()) } /// Returns `true` if the vector contains no elements. - pub fn is_empty(&self, storage: &impl StorageRead) -> Result { + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { Ok(self.len(storage)? == 0) } @@ -100,10 +105,10 @@ where /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// set. - pub fn iter<'a>( + pub fn iter<'iter>( &self, - storage: &'a impl StorageRead, - ) -> Result> + 'a> { + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; Ok(iter.map(|key_val_res| { let (_key, val) = key_val_res?; From 22c2ea54591761e92a79b3f2b2c4de41d66d5eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 15:01:16 +0200 Subject: [PATCH 300/373] cargo test test_lazy_vec_basics --- shared/src/ledger/storage/mod.rs | 40 +++++++++++++++++- .../storage_api/collections/lazy_vec.rs | 42 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 1c4e4efd94..3a1bfa697e 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -459,7 +459,7 @@ where // Note that this method is the same as `StorageWrite::delete`, // but with gas and storage bytes len diff accounting let mut deleted_bytes_len = 0; - if self.has_key(key)?.0 { + if Self::has_key(&self, key)?.0 { self.block.tree.delete(key)?; deleted_bytes_len = self.db.delete_subspace_val(self.last_height, key)?; @@ -808,6 +808,44 @@ where } } +impl StorageWrite for &mut Storage +where + D: DB + for<'iter> DBIter<'iter>, + H: StorageHasher, +{ + fn write( + &mut self, + key: &crate::types::storage::Key, + val: T, + ) -> storage_api::Result<()> { + let val = val.try_to_vec().unwrap(); + self.write_bytes(key, val) + } + + fn write_bytes( + &mut self, + key: &crate::types::storage::Key, + val: impl AsRef<[u8]>, + ) -> storage_api::Result<()> { + let _ = self + .db + .write_subspace_val(self.block.height, key, val) + .into_storage_result()?; + Ok(()) + } + + fn delete( + &mut self, + key: &crate::types::storage::Key, + ) -> storage_api::Result<()> { + let _ = self + .db + .delete_subspace_val(self.block.height, key) + .into_storage_result()?; + Ok(()) + } +} + impl From for Error { fn from(error: MerkleTreeError) -> Self { Self::MerkleTreeError(error) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index e6a186c408..73989bf942 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -131,3 +131,45 @@ where self.key.push(&LEN_SUBKEY.to_owned()).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_vec_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_vec = LazyVec::::new(key); + + // The vec should be empty at first + assert!(lazy_vec.is_empty(&storage)?); + assert!(lazy_vec.len(&storage)? == 0); + assert!(lazy_vec.iter(&storage)?.next().is_none()); + assert!(lazy_vec.pop(&mut storage)?.is_none()); + assert!(lazy_vec.get(&storage, 0)?.is_none()); + assert!(lazy_vec.get(&storage, 1)?.is_none()); + + // Push a new value and check that it's added + lazy_vec.push(&mut storage, 15_u32)?; + assert!(!lazy_vec.is_empty(&storage)?); + assert!(lazy_vec.len(&storage)? == 1); + assert_eq!(lazy_vec.iter(&storage)?.next().unwrap()?, 15_u32); + assert_eq!(lazy_vec.get(&storage, 0)?.unwrap(), 15_u32); + assert!(lazy_vec.get(&storage, 1)?.is_none()); + + // Pop the last value and check that the vec is empty again + let popped = lazy_vec.pop(&mut storage)?.unwrap(); + assert_eq!(popped, 15_u32); + assert!(lazy_vec.is_empty(&storage)?); + assert!(lazy_vec.len(&storage)? == 0); + assert!(lazy_vec.iter(&storage)?.next().is_none()); + assert!(lazy_vec.pop(&mut storage)?.is_none()); + assert!(lazy_vec.get(&storage, 0)?.is_none()); + assert!(lazy_vec.get(&storage, 1)?.is_none()); + + Ok(()) + } +} From fff77348a1d18879786e44fdef1bb3d026f2d472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 17:47:36 +0200 Subject: [PATCH 301/373] storage_api/collections/lazy: add basic tests and missing methods --- .../storage_api/collections/lazy_hashmap.rs | 96 ++++++++++++++++++- .../storage_api/collections/lazy_hashset.rs | 67 ++++++++++++- .../storage_api/collections/lazy_map.rs | 92 +++++++++++++++++- .../storage_api/collections/lazy_set.rs | 72 +++++++++++++- 4 files changed, 314 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index d626b75451..f3330df8e3 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -6,7 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::hasher::hash_for_storage_key; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey corresponding to the data elements of the LazyMap @@ -125,14 +125,51 @@ where Self::read_key_val(storage, &data_key) } + /// Returns whether the set contains a value. + pub fn contains(&self, storage: &S, key: &K) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + storage.has_key(&self.get_data_key(key)) + } + + /// Reads the number of elements in the map. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + iter.count().try_into().into_storage_result() + } + + /// Returns whether the map contains no elements. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let mut iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + Ok(iter.next().is_none()) + } + /// An iterator visiting all key-value elements. The iterator element type /// is `Result<(K, V)>`, because iterator's call to `next` may fail with /// e.g. out of gas or data decoding error. /// /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the + /// on unbounded maps to avoid gas usage increasing with the length of the /// map. - pub fn iter<'iter, S>( + pub fn iter<'iter>( &self, storage: &'iter impl StorageRead<'iter>, ) -> Result> + 'iter> { @@ -177,3 +214,56 @@ where self.get_data_prefix().push(&hash_str).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_hash_map_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_map = LazyHashMap::::new(key); + + // The map should be empty at first + assert!(lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 0); + assert!(!lazy_map.contains(&storage, &0)?); + assert!(!lazy_map.contains(&storage, &1)?); + assert!(lazy_map.iter(&storage)?.next().is_none()); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert!(lazy_map.get(&storage, &1)?.is_none()); + assert!(lazy_map.remove(&mut storage, &0)?.is_none()); + assert!(lazy_map.remove(&mut storage, &1)?.is_none()); + + // Insert a new value and check that it's added + let (key, val) = (123, "Test".to_string()); + lazy_map.insert(&mut storage, key, val.clone())?; + assert!(!lazy_map.contains(&storage, &0)?); + assert!(lazy_map.contains(&storage, &key)?); + assert!(!lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 1); + assert_eq!( + lazy_map.iter(&storage)?.next().unwrap()?, + (key, val.clone()) + ); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert_eq!(lazy_map.get(&storage, &key)?.unwrap(), val); + + // Remove the last value and check that the map is empty again + let removed = lazy_map.remove(&mut storage, &key)?.unwrap(); + assert_eq!(removed, val); + assert!(lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 0); + assert!(!lazy_map.contains(&storage, &0)?); + assert!(!lazy_map.contains(&storage, &1)?); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert!(lazy_map.get(&storage, &key)?.is_none()); + assert!(lazy_map.iter(&storage)?.next().is_none()); + assert!(lazy_map.remove(&mut storage, &key)?.is_none()); + + Ok(()) + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index 96a31ecef7..c9bd01a34c 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -6,7 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::hasher::hash_for_storage_key; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage; /// Subkey corresponding to the data elements of the LazySet @@ -47,14 +47,14 @@ where /// Adds a value to the set. If the set did not have this value present, /// `Ok(true)` is returned, `Ok(false)` otherwise. - pub fn insert(&self, storage: &mut S, val: &T) -> Result + pub fn insert(&self, storage: &mut S, val: T) -> Result where S: StorageWrite + for<'iter> StorageRead<'iter>, { - if self.contains(storage, val)? { + if self.contains(storage, &val)? { Ok(false) } else { - let data_key = self.get_data_key(val); + let data_key = self.get_data_key(&val); storage.write(&data_key, &val)?; Ok(true) } @@ -81,6 +81,21 @@ where Ok(value.is_some()) } + /// Reads the number of elements in the set. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + iter.count().try_into().into_storage_result() + } + /// Returns whether the set contains no elements. pub fn is_empty(&self, storage: &S) -> Result where @@ -119,3 +134,47 @@ where self.get_data_prefix().push(&hash_str).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_set_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_set = LazyHashSet::::new(key); + + // The set should be empty at first + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.iter(&storage)?.next().is_none()); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + // Insert a new value and check that it's added + let val = 1337; + lazy_set.insert(&mut storage, val)?; + assert!(!lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 1); + assert_eq!(lazy_set.iter(&storage)?.next().unwrap()?, val.clone()); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.contains(&storage, &val)?); + + // Remove the last value and check that the set is empty again + let is_removed = lazy_set.remove(&mut storage, &val)?; + assert!(is_removed); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.is_empty(&storage)?); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + Ok(()) + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 6da5c97031..a89cb02469 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -95,12 +95,49 @@ where Self::read_key_val(storage, &data_key) } + /// Returns whether the set contains a value. + pub fn contains(&self, storage: &S, key: &K) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + storage.has_key(&self.get_data_key(key)) + } + + /// Reads the number of elements in the map. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + iter.count().try_into().into_storage_result() + } + + /// Returns whether the map contains no elements. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// set. + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let mut iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + Ok(iter.next().is_none()) + } + /// An iterator visiting all key-value elements. The iterator element type /// is `Result<(K, V)>`, because iterator's call to `next` may fail with /// e.g. out of gas or data decoding error. /// /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the + /// on unbounded maps to avoid gas usage increasing with the length of the /// map. pub fn iter<'iter>( &self, @@ -150,3 +187,56 @@ where self.get_data_prefix().push(&key_str).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_map_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_map = LazyMap::::new(key); + + // The map should be empty at first + assert!(lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 0); + assert!(!lazy_map.contains(&storage, &0)?); + assert!(!lazy_map.contains(&storage, &1)?); + assert!(lazy_map.iter(&storage)?.next().is_none()); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert!(lazy_map.get(&storage, &1)?.is_none()); + assert!(lazy_map.remove(&mut storage, &0)?.is_none()); + assert!(lazy_map.remove(&mut storage, &1)?.is_none()); + + // Insert a new value and check that it's added + let (key, val) = (123, "Test".to_string()); + lazy_map.insert(&mut storage, key, val.clone())?; + assert!(!lazy_map.contains(&storage, &0)?); + assert!(lazy_map.contains(&storage, &key)?); + assert!(!lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 1); + assert_eq!( + lazy_map.iter(&storage)?.next().unwrap()?, + (key, val.clone()) + ); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert_eq!(lazy_map.get(&storage, &key)?.unwrap(), val); + + // Remove the last value and check that the map is empty again + let removed = lazy_map.remove(&mut storage, &key)?.unwrap(); + assert_eq!(removed, val); + assert!(lazy_map.is_empty(&storage)?); + assert!(lazy_map.len(&storage)? == 0); + assert!(!lazy_map.contains(&storage, &0)?); + assert!(!lazy_map.contains(&storage, &1)?); + assert!(lazy_map.get(&storage, &0)?.is_none()); + assert!(lazy_map.get(&storage, &key)?.is_none()); + assert!(lazy_map.iter(&storage)?.next().is_none()); + assert!(lazy_map.remove(&mut storage, &key)?.is_none()); + + Ok(()) + } +} diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 1e1f259c55..0bf533dea9 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -42,14 +42,14 @@ where /// Adds a value to the set. If the set did not have this value present, /// `Ok(true)` is returned, `Ok(false)` otherwise. - pub fn insert(&self, storage: &mut S, val: &T) -> Result + pub fn insert(&self, storage: &mut S, val: T) -> Result where S: StorageWrite + for<'iter> StorageRead<'iter>, { - if self.contains(storage, val)? { + if self.contains(storage, &val)? { Ok(false) } else { - let data_key = self.get_data_key(val); + let data_key = self.get_data_key(&val); // The actual value is written into the key, so the value written to // the storage is empty (unit) storage.write(&data_key, ())?; @@ -74,11 +74,29 @@ where where S: for<'iter> StorageRead<'iter>, { - let value: Option<()> = storage.read(&self.get_data_key(val))?; - Ok(value.is_some()) + storage.has_key(&self.get_data_key(val)) + } + + /// Reads the number of elements in the set. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let iter = + storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; + iter.count().try_into().into_storage_result() } /// Returns whether the set contains no elements. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. pub fn is_empty(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, @@ -122,3 +140,47 @@ where self.get_data_prefix().push(&key_str).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use crate::ledger::storage::testing::TestStorage; + + #[test] + fn test_lazy_set_basics() -> storage_api::Result<()> { + let mut storage = TestStorage::default(); + + let key = storage::Key::parse("test").unwrap(); + let lazy_set = LazySet::::new(key); + + // The set should be empty at first + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.iter(&storage)?.next().is_none()); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + // Insert a new value and check that it's added + let val = 1337; + lazy_set.insert(&mut storage, val)?; + assert!(!lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 1); + assert_eq!(lazy_set.iter(&storage)?.next().unwrap()?, val.clone()); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.contains(&storage, &val)?); + + // Remove the last value and check that the set is empty again + let is_removed = lazy_set.remove(&mut storage, &val)?; + assert!(is_removed); + assert!(lazy_set.is_empty(&storage)?); + assert!(lazy_set.len(&storage)? == 0); + assert!(!lazy_set.contains(&storage, &0)?); + assert!(lazy_set.is_empty(&storage)?); + assert!(!lazy_set.remove(&mut storage, &0)?); + assert!(!lazy_set.remove(&mut storage, &1)?); + + Ok(()) + } +} From ca50fb950cefd968f8e7b77bd8dc617388b5def1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 16 Aug 2022 17:52:44 +0200 Subject: [PATCH 302/373] fix clippy --- shared/src/ledger/storage/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/ledger/storage/mod.rs b/shared/src/ledger/storage/mod.rs index 3a1bfa697e..bd969fa464 100644 --- a/shared/src/ledger/storage/mod.rs +++ b/shared/src/ledger/storage/mod.rs @@ -459,7 +459,7 @@ where // Note that this method is the same as `StorageWrite::delete`, // but with gas and storage bytes len diff accounting let mut deleted_bytes_len = 0; - if Self::has_key(&self, key)?.0 { + if self.has_key(key)?.0 { self.block.tree.delete(key)?; deleted_bytes_len = self.db.delete_subspace_val(self.last_height, key)?; From 5819b23ce380ebe1a4980c306d738b41d72c5576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 23 Aug 2022 17:09:18 +0200 Subject: [PATCH 303/373] add lazy_vec validation --- .../storage_api/collections/lazy_vec.rs | 308 +++++++++++++++++- shared/src/ledger/storage_api/mod.rs | 1 + .../src/ledger/storage_api/validation/mod.rs | 53 +++ shared/src/types/storage.rs | 28 ++ 4 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 shared/src/ledger/storage_api/validation/mod.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 73989bf942..c9b836f774 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -1,11 +1,17 @@ //! Lazy dynamically-sized vector. +use std::collections::BTreeSet; use std::marker::PhantomData; +use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use derivative::Derivative; +use thiserror::Error; use super::super::Result; +use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::ledger::vp_env::VpEnv; use crate::types::storage; /// Subkey pointing to the length of the LazyVec @@ -13,6 +19,9 @@ pub const LEN_SUBKEY: &str = "len"; /// Subkey corresponding to the data elements of the LazyVec pub const DATA_SUBKEY: &str = "data"; +/// Using `u64` for vector's indices +pub type Index = u64; + /// Lazy dynamically-sized vector. /// /// This can be used as an alternative to `std::collections::Vec`. In the lazy @@ -24,6 +33,69 @@ pub struct LazyVec { phantom: PhantomData, } +/// Possible sub-keys of a [`LazyVec`] +pub enum SubKey { + /// Length sub-key + Len, + /// Data sub-key, further sub-keyed by its index + Data(Index), +} + +/// Possible sub-keys of a [`LazyVec`], together with their [`validation::Data`] +/// that contains prior and posterior state. +#[derive(Debug)] +pub enum SubKeyWithData { + /// Length sub-key + Len(Data), + /// Data sub-key, further sub-keyed by its index + Data(Index, Data), +} + +/// Possible actions that can modify a [`LazyVec`]. This roughly corresponds to +/// the methods that have `StorageWrite` access. +pub enum Action { + /// Push a value `T` into a [`LazyVec`] + Push(T), + /// Pop a value `T` from a [`LazyVec`] + Pop(T), +} + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ValidationError { + #[error("Incorrect difference in LazyVec's length")] + InvalidLenDiff, + #[error("An empty LazyVec must be deleted from storage")] + EmptyVecShouldBeDeleted, + #[error("Push at a wrong index. Got {got}, expected {expected}.")] + UnexpectedPushIndex { got: Index, expected: Index }, + #[error("Pop at a wrong index. Got {got}, expected {expected}.")] + UnexpectedPopIndex { got: Index, expected: Index }, + #[error( + "Update (combination of pop and push) at a wrong index. Got {got}, \ + expected {expected}." + )] + UnexpectedUpdateIndex { got: Index, expected: Index }, + #[error("An index has overflown its representation: {0}")] + IndexOverflow(>::Error), + #[error("Unexpected underflow in `{0} - {0}`")] + UnexpectedUnderflow(Index, Index), +} + +/// [`LazyVec`] validation result +pub type ValidationResult = std::result::Result; + +/// [`LazyVec`] validation builder from storage changes. The changes can be +/// accumulated with `LazyVec::validate()` and then turned into a list +/// of valid actions on the vector with `ValidationBuilder::build()`. +#[derive(Debug, Derivative)] +// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound +#[derivative(Default(bound = ""))] +pub struct ValidationBuilder { + /// The accumulator of found changes under the vector + pub changes: Vec>, +} + impl LazyVec where T: BorshSerialize + BorshDeserialize + 'static, @@ -73,7 +145,7 @@ where } /// Read an element at the index or `Ok(None)` if out of bounds. - pub fn get(&self, storage: &S, index: u64) -> Result> + pub fn get(&self, storage: &S, index: Index) -> Result> where S: for<'iter> StorageRead<'iter>, { @@ -116,13 +188,64 @@ where })) } + /// Check if the given storage key is a LazyVec sub-key and if so return + /// which one + pub fn is_sub_key(&self, key: &storage::Key) -> Option { + if let Some((prefix, storage::DbKeySeg::StringSeg(last))) = + key.split_last() + { + if let Ok(index) = Index::from_str(last) { + if let Some((prefix, storage::DbKeySeg::StringSeg(snd_last))) = + prefix.split_last() + { + if snd_last == DATA_SUBKEY && prefix.eq_owned(&self.key) { + return Some(SubKey::Data(index)); + } + } + } else if last == LEN_SUBKEY && prefix.eq_owned(&self.key) { + return Some(SubKey::Len); + } + } + None + } + + /// Accumulate storage changes inside a [`ValidationBuilder`] + pub fn validate( + &self, + builder: &mut Option>, + env: &ENV, + key_changed: storage::Key, + ) -> std::result::Result<(), ENV::Error> + where + ENV: VpEnv, + { + if let Some(sub) = self.is_sub_key(&key_changed) { + let change = match sub { + SubKey::Len => { + let data = validation::read_data(env, &key_changed)?; + data.map(SubKeyWithData::Len) + } + SubKey::Data(index) => { + let data = validation::read_data(env, &key_changed)?; + data.map(|data| SubKeyWithData::Data(index, data)) + } + }; + if let Some(change) = change { + let builder = + builder.get_or_insert(ValidationBuilder::default()); + builder.changes.push(change) + } + } + Ok(()) + } + /// Get the prefix of set's elements storage fn get_data_prefix(&self) -> storage::Key { self.key.push(&DATA_SUBKEY.to_owned()).unwrap() } /// Get the sub-key of vector's elements storage - fn get_data_key(&self, index: u64) -> storage::Key { + fn get_data_key(&self, index: Index) -> storage::Key { self.get_data_prefix().push(&index.to_string()).unwrap() } @@ -132,6 +255,187 @@ where } } +impl ValidationBuilder { + /// Validate storage changes and if valid, build from them a list of + /// actions. + /// + /// The validation rules for a [`LazyVec`] are: + /// - A difference in the vector's length must correspond to the + /// difference in how many elements where pushed versus how many + /// elements were popped. + /// - An empty vector must be deleted from storage + /// - In addition, we check that indices of any changes are within an + /// expected range (i.e. the vectors indices should always be + /// monotonically increasing from zero) + pub fn build(self) -> ValidationResult>> { + let mut actions = vec![]; + + // We need to accumlate some values for what's changed + let mut post_gt_pre = false; + let mut len_diff: u64 = 0; + let mut len_pre: u64 = 0; + let mut added = BTreeSet::::default(); + let mut updated = BTreeSet::::default(); + let mut deleted = BTreeSet::::default(); + + for change in self.changes { + match change { + SubKeyWithData::Len(data) => match data { + Data::Add { post } => { + if post == 0 { + return Err( + ValidationError::EmptyVecShouldBeDeleted, + ); + } + post_gt_pre = true; + len_diff = post; + } + Data::Update { pre, post } => { + if post == 0 { + return Err( + ValidationError::EmptyVecShouldBeDeleted, + ); + } + if post > pre { + post_gt_pre = true; + len_diff = post - pre; + } else { + len_diff = pre - post; + } + len_pre = pre; + } + Data::Delete { pre } => { + len_diff = pre; + len_pre = pre; + } + }, + SubKeyWithData::Data(index, data) => match data { + Data::Add { post } => { + actions.push(Action::Push(post)); + added.insert(index); + } + Data::Update { pre, post } => { + actions.push(Action::Pop(pre)); + actions.push(Action::Push(post)); + updated.insert(index); + } + Data::Delete { pre } => { + actions.push(Action::Pop(pre)); + deleted.insert(index); + } + }, + } + } + let added_len: u64 = deleted + .len() + .try_into() + .map_err(ValidationError::IndexOverflow)?; + let deleted_len: u64 = deleted + .len() + .try_into() + .map_err(ValidationError::IndexOverflow)?; + + if len_diff != 0 + && !(if post_gt_pre { + deleted_len + len_diff == added_len + } else { + added_len + len_diff == deleted_len + }) + { + return Err(ValidationError::InvalidLenDiff); + } + + let mut last_added = Option::None; + // Iterate additions in increasing order of indices + for index in added { + if let Some(last_added) = last_added { + // Following additions should be at monotonically increasing + // indices + let expected = last_added + 1; + if expected != index { + return Err(ValidationError::UnexpectedPushIndex { + got: index, + expected, + }); + } + } else if index != len_pre { + // The first addition must be at the pre length value. + // If something is deleted and a new value is added + // in its place, it will go through `Data::Update` + // instead. + return Err(ValidationError::UnexpectedPushIndex { + got: index, + expected: len_pre, + }); + } + last_added = Some(index); + } + + let mut last_deleted = Option::None; + // Also iterate deletions in increasing order of indices + for index in deleted { + if let Some(last_added) = last_deleted { + // Following deletions should be at monotonically increasing + // indices + let expected = last_added + 1; + if expected != index { + return Err(ValidationError::UnexpectedPopIndex { + got: index, + expected, + }); + } + } + last_deleted = Some(index); + } + if let Some(index) = last_deleted { + if len_pre > 0 { + let expected = len_pre - 1; + if index != expected { + // The last deletion must be at the pre length value minus 1 + return Err(ValidationError::UnexpectedPopIndex { + got: index, + expected: len_pre, + }); + } + } + } + + // And finally iterate updates in increasing order of indices + let mut last_updated = Option::None; + for index in updated { + if let Some(last_updated) = last_updated { + // Following additions should be at monotonically increasing + // indices + let expected = last_updated + 1; + if expected != index { + return Err(ValidationError::UnexpectedUpdateIndex { + got: index, + expected, + }); + } + } + last_updated = Some(index); + } + if let Some(index) = last_updated { + let expected = len_pre.checked_sub(deleted_len).ok_or( + ValidationError::UnexpectedUnderflow(len_pre, deleted_len), + )?; + if index != expected { + // The last update must be at the pre length value minus + // deleted_len. + // If something is added and then deleted in a + // single tx, it will never be visible here. + return Err(ValidationError::UnexpectedUpdateIndex { + got: index, + expected: len_pre, + }); + } + } + + Ok(actions) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/shared/src/ledger/storage_api/mod.rs b/shared/src/ledger/storage_api/mod.rs index 8cb04434e2..b806f35801 100644 --- a/shared/src/ledger/storage_api/mod.rs +++ b/shared/src/ledger/storage_api/mod.rs @@ -3,6 +3,7 @@ pub mod collections; mod error; +pub mod validation; use borsh::{BorshDeserialize, BorshSerialize}; pub use error::{CustomError, Error, OptionExt, Result, ResultExt}; diff --git a/shared/src/ledger/storage_api/validation/mod.rs b/shared/src/ledger/storage_api/validation/mod.rs new file mode 100644 index 0000000000..30c1e2566b --- /dev/null +++ b/shared/src/ledger/storage_api/validation/mod.rs @@ -0,0 +1,53 @@ +//! Storage change validation helpers + +use std::fmt::Debug; + +use borsh::BorshDeserialize; + +use crate::ledger::vp_env::VpEnv; +use crate::types::storage; + +/// Data update with prior and posterior state. +#[derive(Clone, Debug)] +pub enum Data { + /// Newly added value + Add { + /// Posterior state + post: T, + }, + /// Updated value prior and posterior state + Update { + /// Prior state + pre: T, + /// Posterior state + post: T, + }, + /// Deleted value + Delete { + /// Prior state + pre: T, + }, +} + +/// Read the prior and posterior state for the given key. +pub fn read_data( + env: &ENV, + key: &storage::Key, +) -> Result>, ENV::Error> +where + T: BorshDeserialize, + ENV: VpEnv, +{ + let pre = env.read_pre(key)?; + let post = env.read_post(key)?; + Ok(match (pre, post) { + (None, None) => { + // If the key was inserted and then deleted in the same tx, we don't + // need to validate it as it's not visible to any VPs + None + } + (None, Some(post)) => Some(Data::Add { post }), + (Some(pre), None) => Some(Data::Delete { pre }), + (Some(pre), Some(post)) => Some(Data::Update { pre, post }), + }) +} diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 289f115c8f..49abe7197d 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -210,6 +210,13 @@ pub struct Key { pub segments: Vec, } +/// A [`Key`] made of borrowed key segments [`DbKeySeg`]. +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct KeyRef<'a> { + /// Reference of key segments + pub segments: &'a [DbKeySeg], +} + impl From for Key { fn from(seg: DbKeySeg) -> Self { Self { @@ -283,6 +290,13 @@ impl Key { self.segments.last() } + /// Returns the prefix before the last segment and last segment of the key, + /// or `None` if it is empty. + pub fn split_last(&self) -> Option<(KeyRef<'_>, &DbKeySeg)> { + let (last, prefix) = self.segments.split_last()?; + Some((KeyRef { segments: prefix }, last)) + } + /// Returns a key of the validity predicate of the given address /// Only this function can push "?" segment for validity predicate pub fn validity_predicate(addr: &Address) -> Self { @@ -372,6 +386,20 @@ impl Display for Key { } } +impl KeyRef<'_> { + /// Check if [`KeyRef`] is equal to a [`Key`]. + pub fn eq_owned(&self, other: &Key) -> bool { + self.segments == other.segments + } + + /// Returns the prefix before the last segment and last segment of the key, + /// or `None` if it is empty. + pub fn split_last(&self) -> Option<(KeyRef<'_>, &DbKeySeg)> { + let (last, prefix) = self.segments.split_last()?; + Some((KeyRef { segments: prefix }, last)) + } +} + // TODO use std::convert::{TryFrom, Into}? /// Represents a segment in a path that may be used as a database key pub trait KeySeg { From a474c802b755ff7f133a2ca0829ba836ac70ef52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 29 Aug 2022 14:16:59 +0200 Subject: [PATCH 304/373] storage_api/collections/lazy: allow nested lazy collections in LazyMap --- .../storage_api/collections/lazy_hashmap.rs | 15 ++-- .../storage_api/collections/lazy_hashset.rs | 13 +-- .../storage_api/collections/lazy_map.rs | 72 +++++++++++----- .../storage_api/collections/lazy_set.rs | 14 ++-- .../storage_api/collections/lazy_vec.rs | 83 ++++++++++--------- .../src/ledger/storage_api/collections/mod.rs | 15 ++++ 6 files changed, 134 insertions(+), 78 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index f3330df8e3..4098e6d8c9 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -6,6 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::hasher::hash_for_storage_key; +use super::LazyCollection; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage; @@ -41,20 +42,22 @@ struct KeyVal { val: V, } -impl LazyHashMap -where - K: BorshDeserialize + BorshSerialize + 'static, - V: BorshDeserialize + BorshSerialize + 'static, -{ +impl LazyCollection for LazyHashMap { /// Create or use an existing map with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, phantom_v: PhantomData, } } +} +impl LazyHashMap +where + K: BorshDeserialize + BorshSerialize + 'static, + V: BorshDeserialize + BorshSerialize + 'static, +{ /// Inserts a key-value pair into the map. /// /// If the map did not have this key present, `None` is returned. diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index c9bd01a34c..a110072371 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -6,6 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::hasher::hash_for_storage_key; +use super::LazyCollection; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage; @@ -33,18 +34,20 @@ pub struct LazyHashSet { phantom: PhantomData, } -impl LazyHashSet -where - T: BorshSerialize + BorshDeserialize + 'static, -{ +impl LazyCollection for LazyHashSet { /// Create or use an existing set with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom: PhantomData, } } +} +impl LazyHashSet +where + T: BorshSerialize + BorshDeserialize + 'static, +{ /// Adds a value to the set. If the set did not have this value present, /// `Ok(true)` is returned, `Ok(false)` otherwise. pub fn insert(&self, storage: &mut S, val: T) -> Result diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index a89cb02469..ddec03363e 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; -use super::ReadError; +use super::{LazyCollection, ReadError}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage::{self, KeySeg}; @@ -31,20 +31,65 @@ pub struct LazyMap { phantom_v: PhantomData, } -impl LazyMap +impl LazyCollection for LazyMap where K: storage::KeySeg, - V: BorshDeserialize + BorshSerialize + 'static, { /// Create or use an existing map with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, phantom_v: PhantomData, } } +} + +// Generic `LazyMap` methods that require no bounds on values `V` +impl LazyMap +where + K: storage::KeySeg, +{ + /// Returns whether the set contains a value. + pub fn contains(&self, storage: &S, key: &K) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + storage.has_key(&self.get_data_key(key)) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of a given element + fn get_data_key(&self, key: &K) -> storage::Key { + let key_str = key.to_db_key(); + self.get_data_prefix().push(&key_str).unwrap() + } +} + +// `LazyMap` methods with nested `LazyCollection`s `V` +impl LazyMap +where + K: storage::KeySeg, + V: LazyCollection, +{ + /// Get a nested collection at given key `key`. If there is no nested + /// collection at the given key, a new empty one will be provided. The + /// nested collection may be manipulated through its methods. + pub fn at(&self, key: &K) -> V { + V::new(self.get_data_key(key)) + } +} +// `LazyMap` methods with borsh encoded values `V` +impl LazyMap +where + K: storage::KeySeg, + V: BorshDeserialize + BorshSerialize + 'static, +{ /// Inserts a key-value pair into the map. /// /// The full storage key identifies the key in the pair, while the value is @@ -95,14 +140,6 @@ where Self::read_key_val(storage, &data_key) } - /// Returns whether the set contains a value. - pub fn contains(&self, storage: &S, key: &K) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - storage.has_key(&self.get_data_key(key)) - } - /// Reads the number of elements in the map. /// /// Note that this function shouldn't be used in transactions and VPs code @@ -175,17 +212,6 @@ where ) -> Result<()> { storage.write(storage_key, val) } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of a given element - fn get_data_key(&self, key: &K) -> storage::Key { - let key_str = key.to_db_key(); - self.get_data_prefix().push(&key_str).unwrap() - } } #[cfg(test)] diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 0bf533dea9..8918b55efd 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -3,7 +3,7 @@ use std::marker::PhantomData; use super::super::Result; -use super::ReadError; +use super::{LazyCollection, ReadError}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage::{self, KeySeg}; @@ -28,18 +28,20 @@ pub struct LazySet { phantom: PhantomData, } -impl LazySet -where - T: storage::KeySeg, -{ +impl LazyCollection for LazySet { /// Create or use an existing set with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom: PhantomData, } } +} +impl LazySet +where + T: storage::KeySeg, +{ /// Adds a value to the set. If the set did not have this value present, /// `Ok(true)` is returned, `Ok(false)` otherwise. pub fn insert(&self, storage: &mut S, val: T) -> Result diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index c9b836f774..f87d4c126f 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -9,6 +9,7 @@ use derivative::Derivative; use thiserror::Error; use super::super::Result; +use super::LazyCollection; use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; use crate::ledger::vp_env::VpEnv; @@ -96,18 +97,57 @@ pub struct ValidationBuilder { pub changes: Vec>, } -impl LazyVec -where - T: BorshSerialize + BorshDeserialize + 'static, -{ +impl LazyCollection for LazyVec { /// Create or use an existing vector with the given storage `key`. - pub fn new(key: storage::Key) -> Self { + fn new(key: storage::Key) -> Self { Self { key, phantom: PhantomData, } } +} + +// Generic `LazyVec` methods that require no bounds on values `T` +impl LazyVec { + /// Reads the number of elements in the vector. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let len = storage.read(&self.get_len_key())?; + Ok(len.unwrap_or_default()) + } + + /// Returns `true` if the vector contains no elements. + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + Ok(self.len(storage)? == 0) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of vector's elements storage + fn get_data_key(&self, index: Index) -> storage::Key { + self.get_data_prefix().push(&index.to_string()).unwrap() + } + + /// Get the sub-key of vector's length storage + fn get_len_key(&self) -> storage::Key { + self.key.push(&LEN_SUBKEY.to_owned()).unwrap() + } +} +// `LazyVec` methods with borsh encoded values `T` +impl LazyVec +where + T: BorshSerialize + BorshDeserialize + 'static, +{ /// Appends an element to the back of a collection. pub fn push(&self, storage: &mut S, val: T) -> Result<()> where @@ -152,24 +192,6 @@ where storage.read(&self.get_data_key(index)) } - /// Reads the number of elements in the vector. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let len = storage.read(&self.get_len_key())?; - Ok(len.unwrap_or_default()) - } - - /// Returns `true` if the vector contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - Ok(self.len(storage)? == 0) - } - /// An iterator visiting all elements. The iterator element type is /// `Result`, because iterator's call to `next` may fail with e.g. out of /// gas or data decoding error. @@ -238,21 +260,6 @@ where } Ok(()) } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of vector's elements storage - fn get_data_key(&self, index: Index) -> storage::Key { - self.get_data_prefix().push(&index.to_string()).unwrap() - } - - /// Get the sub-key of vector's length storage - fn get_len_key(&self) -> storage::Key { - self.key.push(&LEN_SUBKEY.to_owned()).unwrap() - } } impl ValidationBuilder { diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index 156615b9de..01bcae439e 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -22,9 +22,24 @@ pub use lazy_map::LazyMap; pub use lazy_set::LazySet; pub use lazy_vec::LazyVec; +use crate::types::storage; + #[allow(missing_docs)] #[derive(Error, Debug)] pub enum ReadError { #[error("A storage key was unexpectedly empty")] UnexpectedlyEmptyStorageKey, } + +/// A lazy collection of storage values is a handler with some storage prefix +/// that is given to its `fn new()`. The values are typically nested under this +/// prefix and they can be changed individually (e.g. without reading in the +/// whole collection) and their changes directly indicated to the validity +/// predicates, which do not need to iterate the whole collection pre/post to +/// find diffs. +/// +/// An empty collection must be deleted from storage. +pub trait LazyCollection { + /// Create or use an existing vector with the given storage `key`. + fn new(key: storage::Key) -> Self; +} From 51036681d046e5b8100605d68cd8376b048dbbb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 1 Sep 2022 10:43:32 +0200 Subject: [PATCH 305/373] impl LazyCollection trait for all the collections + refactor --- .../storage_api/collections/lazy_hashmap.rs | 23 +++---- .../storage_api/collections/lazy_hashset.rs | 1 + .../storage_api/collections/lazy_map.rs | 61 ++++++++++++++---- .../storage_api/collections/lazy_set.rs | 23 +++---- .../storage_api/collections/lazy_vec.rs | 62 +++++++++++++++---- 5 files changed, 120 insertions(+), 50 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index 4098e6d8c9..46bc55fa6e 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -29,6 +29,7 @@ pub const DATA_SUBKEY: &str = "data"; /// /// Additionally, [`LazyHashMap`] also writes the unhashed values into the /// storage together with the values (using an internal `KeyVal` type). +#[derive(Debug)] pub struct LazyHashMap { key: storage::Key, phantom_k: PhantomData, @@ -136,33 +137,29 @@ where storage.has_key(&self.get_data_key(key)) } - /// Reads the number of elements in the map. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded maps to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result + /// Returns whether the map contains no elements. + pub fn is_empty(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let iter = + let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() + Ok(iter.next().is_none()) } - /// Returns whether the map contains no elements. + /// Reads the number of elements in the map. /// /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded maps to avoid gas usage increasing with the length of the /// set. - pub fn is_empty(&self, storage: &S) -> Result + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let mut iter = + let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) + iter.count().try_into().into_storage_result() } /// An iterator visiting all key-value elements. The iterator element type diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index a110072371..06a6ef2295 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -29,6 +29,7 @@ pub const DATA_SUBKEY: &str = "data"; /// /// Additionally, [`LazyHashSet`] also writes the unhashed values into the /// storage. +#[derive(Debug)] pub struct LazyHashSet { key: storage::Key, phantom: PhantomData, diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index ddec03363e..103508404e 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -6,6 +6,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use super::super::Result; use super::{LazyCollection, ReadError}; +use crate::ledger::storage_api::validation::Data; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::types::storage::{self, KeySeg}; @@ -25,12 +26,50 @@ pub const DATA_SUBKEY: &str = "data"; /// /// This is different from [`super::LazyHashMap`], which hashes borsh encoded /// key. +#[derive(Debug)] pub struct LazyMap { key: storage::Key, phantom_k: PhantomData, phantom_v: PhantomData, } +/// Possible sub-keys of a [`LazyMap`] +#[derive(Debug)] +pub enum SubKey { + /// Data sub-key, further sub-keyed by its literal map key + Data(K), +} + +/// Possible sub-keys of a [`LazyMap`], together with their [`validation::Data`] +/// that contains prior and posterior state. +#[derive(Debug)] +pub enum SubKeyWithData { + /// Data sub-key, further sub-keyed by its literal map key + Data(K, Data), +} + +/// Possible actions that can modify a [`LazyMap`]. This roughly corresponds to +/// the methods that have `StorageWrite` access. +/// TODO: In a nested collection, `V` may be an action inside the nested +/// collection. +#[derive(Debug)] +pub enum Action { + /// Insert or update a value `V` at key `K` in a [`LazyMap`]. + Insert(K, V), + /// Remove a value `V` at key `K` from a [`LazyMap`]. + Remove(K, V), +} + +/// TODO: In a nested collection, `V` may be an action inside the nested +/// collection. +#[derive(Debug)] +pub enum Nested { + /// Insert or update a value `V` at key `K` in a [`LazyMap`]. + Insert(K, V), + /// Remove a value `V` at key `K` from a [`LazyMap`]. + Remove(K, V), +} + impl LazyCollection for LazyMap where K: storage::KeySeg, @@ -140,33 +179,29 @@ where Self::read_key_val(storage, &data_key) } - /// Reads the number of elements in the map. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded maps to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result + /// Returns whether the map contains no elements. + pub fn is_empty(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let iter = + let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() + Ok(iter.next().is_none()) } - /// Returns whether the map contains no elements. + /// Reads the number of elements in the map. /// /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded maps to avoid gas usage increasing with the length of the /// set. - pub fn is_empty(&self, storage: &S) -> Result + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let mut iter = + let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) + iter.count().try_into().into_storage_result() } /// An iterator visiting all key-value elements. The iterator element type diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 8918b55efd..fc301b09f1 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -23,6 +23,7 @@ pub const DATA_SUBKEY: &str = "data"; /// /// This is different from [`super::LazyHashSet`], which hashes borsh encoded /// values. +#[derive(Debug)] pub struct LazySet { key: storage::Key, phantom: PhantomData, @@ -79,33 +80,29 @@ where storage.has_key(&self.get_data_key(val)) } - /// Reads the number of elements in the set. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result + /// Returns whether the set contains no elements. + pub fn is_empty(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let iter = + let mut iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() + Ok(iter.next().is_none()) } - /// Returns whether the set contains no elements. + /// Reads the number of elements in the set. /// /// Note that this function shouldn't be used in transactions and VPs code /// on unbounded sets to avoid gas usage increasing with the length of the /// set. - pub fn is_empty(&self, storage: &S) -> Result + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result where S: for<'iter> StorageRead<'iter>, { - let mut iter = + let iter = storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) + iter.count().try_into().into_storage_result() } /// An iterator visiting all elements. The iterator element type is diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index f87d4c126f..6dd57ca556 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -11,7 +11,7 @@ use thiserror::Error; use super::super::Result; use super::LazyCollection; use crate::ledger::storage_api::validation::{self, Data}; -use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::vp_env::VpEnv; use crate::types::storage; @@ -29,12 +29,14 @@ pub type Index = u64; /// vector, the elements do not reside in memory but are instead read and /// written to storage sub-keys of the storage `key` used to construct the /// vector. +#[derive(Debug)] pub struct LazyVec { key: storage::Key, phantom: PhantomData, } /// Possible sub-keys of a [`LazyVec`] +#[derive(Debug)] pub enum SubKey { /// Length sub-key Len, @@ -54,11 +56,21 @@ pub enum SubKeyWithData { /// Possible actions that can modify a [`LazyVec`]. This roughly corresponds to /// the methods that have `StorageWrite` access. +#[derive(Debug)] pub enum Action { /// Push a value `T` into a [`LazyVec`] Push(T), /// Pop a value `T` from a [`LazyVec`] Pop(T), + /// Update a value `T` at index from pre to post state in a [`LazyVec`] + Update { + /// index at which the value is updated + index: Index, + /// value before the update + pre: T, + /// value after the update + post: T, + }, } #[allow(missing_docs)] @@ -83,6 +95,15 @@ pub enum ValidationError { UnexpectedUnderflow(Index, Index), } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum UpdateError { + #[error( + "Invalid index into a LazyVec. Got {index}, but the length is {len}" + )] + InvalidIndex { index: Index, len: u64 }, +} + /// [`LazyVec`] validation result pub type ValidationResult = std::result::Result; @@ -184,6 +205,23 @@ where } } + /// Update an element at the given index. + /// + /// The index must be smaller than the length of the vector, otherwise this + /// will fail with `UpdateError::InvalidIndex`. + pub fn update(&self, storage: &mut S, index: Index, val: T) -> Result<()> + where + S: StorageWrite + for<'iter> StorageRead<'iter>, + { + let len = self.len(storage)?; + if index >= len { + return Err(UpdateError::InvalidIndex { index, len }) + .into_storage_result(); + } + let data_key = self.get_data_key(index); + storage.write(&data_key, val) + } + /// Read an element at the index or `Ok(None)` if out of bounds. pub fn get(&self, storage: &S, index: Index) -> Result> where @@ -231,24 +269,27 @@ where None } - /// Accumulate storage changes inside a [`ValidationBuilder`] - pub fn validate( + /// Accumulate storage changes inside a [`ValidationBuilder`]. This is + /// typically done by the validity predicate while looping through the + /// changed keys. If the resulting `builder` is not `None`, one must + /// call `fn build()` on it to get the validation result. + pub fn accumulate( &self, - builder: &mut Option>, env: &ENV, - key_changed: storage::Key, + builder: &mut Option>, + key_changed: &storage::Key, ) -> std::result::Result<(), ENV::Error> where ENV: VpEnv, { - if let Some(sub) = self.is_sub_key(&key_changed) { + if let Some(sub) = self.is_sub_key(key_changed) { let change = match sub { SubKey::Len => { - let data = validation::read_data(env, &key_changed)?; + let data = validation::read_data(env, key_changed)?; data.map(SubKeyWithData::Len) } SubKey::Data(index) => { - let data = validation::read_data(env, &key_changed)?; + let data = validation::read_data(env, key_changed)?; data.map(|data| SubKeyWithData::Data(index, data)) } }; @@ -277,7 +318,7 @@ impl ValidationBuilder { pub fn build(self) -> ValidationResult>> { let mut actions = vec![]; - // We need to accumlate some values for what's changed + // We need to accumulate some values for what's changed let mut post_gt_pre = false; let mut len_diff: u64 = 0; let mut len_pre: u64 = 0; @@ -322,8 +363,7 @@ impl ValidationBuilder { added.insert(index); } Data::Update { pre, post } => { - actions.push(Action::Pop(pre)); - actions.push(Action::Push(post)); + actions.push(Action::Update { index, pre, post }); updated.insert(index); } Data::Delete { pre } => { From 255b43be1e337e6838855187864bce59a611c313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 12 Sep 2022 18:18:26 +0200 Subject: [PATCH 306/373] storage/lazy_vec/validation: disallow unrecognized keys matching prefix --- .../storage_api/collections/lazy_vec.rs | 84 ++++++++++++++----- shared/src/types/storage.rs | 19 +++++ 2 files changed, 80 insertions(+), 23 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 6dd57ca556..c13d40855d 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -13,7 +13,7 @@ use super::LazyCollection; use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; use crate::ledger::vp_env::VpEnv; -use crate::types::storage; +use crate::types::storage::{self, DbKeySeg}; /// Subkey pointing to the length of the LazyVec pub const LEN_SUBKEY: &str = "len"; @@ -76,6 +76,8 @@ pub enum Action { #[allow(missing_docs)] #[derive(Error, Debug)] pub enum ValidationError { + #[error("Storage error in reading key {0}")] + StorageError(storage::Key), #[error("Incorrect difference in LazyVec's length")] InvalidLenDiff, #[error("An empty LazyVec must be deleted from storage")] @@ -93,6 +95,8 @@ pub enum ValidationError { IndexOverflow(>::Error), #[error("Unexpected underflow in `{0} - {0}`")] UnexpectedUnderflow(Index, Index), + #[error("Invalid storage key {0}")] + InvalidSubKey(storage::Key), } #[allow(missing_docs)] @@ -155,6 +159,7 @@ impl LazyVec { /// Get the sub-key of vector's elements storage fn get_data_key(&self, index: Index) -> storage::Key { + // TODO rebase on ordered prefix iter (https://github.com/anoma/namada/pull/458) and remove `.to_string()` self.get_data_prefix().push(&index.to_string()).unwrap() } @@ -248,58 +253,91 @@ where })) } - /// Check if the given storage key is a LazyVec sub-key and if so return - /// which one - pub fn is_sub_key(&self, key: &storage::Key) -> Option { - if let Some((prefix, storage::DbKeySeg::StringSeg(last))) = - key.split_last() - { - if let Ok(index) = Index::from_str(last) { - if let Some((prefix, storage::DbKeySeg::StringSeg(snd_last))) = - prefix.split_last() - { - if snd_last == DATA_SUBKEY && prefix.eq_owned(&self.key) { - return Some(SubKey::Data(index)); - } + /// Check if the given storage key is a valid LazyVec sub-key and if so + /// return which one + pub fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> ValidationResult> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub)] if sub == LEN_SUBKEY => { + Ok(Some(SubKey::Len)) + } + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + // TODO rebase on ordered prefix iter (https://github.com/anoma/namada/pull/458) and parse with `KeySeg::parse` + if let Ok(index) = Index::from_str(sub_b) { + Ok(Some(SubKey::Data(index))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) } - } else if last == LEN_SUBKEY && prefix.eq_owned(&self.key) { - return Some(SubKey::Len); } + _ => Err(ValidationError::InvalidSubKey(key.clone())), } - None } /// Accumulate storage changes inside a [`ValidationBuilder`]. This is /// typically done by the validity predicate while looping through the /// changed keys. If the resulting `builder` is not `None`, one must /// call `fn build()` on it to get the validation result. + /// This function will return `Ok(true)` if the storage key is a valid + /// sub-key of this collection, `Ok(false)` if the storage key doesn't match + /// the prefix of this collection, or fail with + /// [`ValidationError::InvalidSubKey`] if the prefix matches this + /// collection, but the key itself is not recognized. pub fn accumulate( &self, env: &ENV, builder: &mut Option>, key_changed: &storage::Key, - ) -> std::result::Result<(), ENV::Error> + ) -> ValidationResult where ENV: VpEnv, { - if let Some(sub) = self.is_sub_key(key_changed) { + if let Some(sub) = self.is_valid_sub_key(key_changed)? { let change = match sub { SubKey::Len => { - let data = validation::read_data(env, key_changed)?; + let data = validation::read_data(env, key_changed) + // TODO this has to propagate errors from VpEnv rather + // then replace them (e.g. it could be out-of-gas), but + // VpEnv::Error is generic, maybe we should make it + // concrete (only the VpEnv impls have extensible error + // types) + .map_err(|_| { + ValidationError::StorageError(key_changed.clone()) + })?; data.map(SubKeyWithData::Len) } SubKey::Data(index) => { - let data = validation::read_data(env, key_changed)?; + let data = validation::read_data(env, key_changed) + .map_err(|_| { + ValidationError::StorageError(key_changed.clone()) + })?; data.map(|data| SubKeyWithData::Data(index, data)) } }; if let Some(change) = change { let builder = builder.get_or_insert(ValidationBuilder::default()); - builder.changes.push(change) + builder.changes.push(change); + return Ok(true); } } - Ok(()) + Ok(false) } } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 49abe7197d..b5d72b7b02 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -372,6 +372,25 @@ impl Key { }), } } + + /// Check if the key begins with the given prefix and returns: + /// - `Some(Some(suffix))` the suffix after the match with, if any, or + /// - `Some(None)` if the prefix is matched, but it has no suffix, or + /// - `None` if it doesn't match + pub fn split_prefix(&self, prefix: &Self) -> Option> { + if self.segments.len() < prefix.len() { + return None; + } else if self == prefix { + return Some(None); + } + let mut self_prefix = self.segments.clone(); + let rest = self_prefix.split_off(prefix.len()); + if self_prefix == prefix.segments { + Some(Some(Key { segments: rest })) + } else { + None + } + } } impl Display for Key { From f854d8285c7b5e80aa8131d9a14cbbb99b5fa0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 13 Sep 2022 18:56:32 +0200 Subject: [PATCH 307/373] storage/lazy_vec: add state machine test for lazy vec API --- .../storage_api/collections/lazy_hashmap.rs | 4 +- .../storage_api/collections/lazy_hashset.rs | 4 +- .../storage_api/collections/lazy_map.rs | 6 +- .../storage_api/collections/lazy_set.rs | 4 +- .../storage_api/collections/lazy_vec.rs | 257 +++++++++++++++++- .../src/ledger/storage_api/collections/mod.rs | 2 +- 6 files changed, 264 insertions(+), 13 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs index 46bc55fa6e..9a60fd18f0 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs @@ -45,7 +45,7 @@ struct KeyVal { impl LazyCollection for LazyHashMap { /// Create or use an existing map with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, @@ -225,7 +225,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_map = LazyHashMap::::new(key); + let lazy_map = LazyHashMap::::open(key); // The map should be empty at first assert!(lazy_map.is_empty(&storage)?); diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs index 06a6ef2295..63ac5c845c 100644 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ b/shared/src/ledger/storage_api/collections/lazy_hashset.rs @@ -37,7 +37,7 @@ pub struct LazyHashSet { impl LazyCollection for LazyHashSet { /// Create or use an existing set with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom: PhantomData, @@ -149,7 +149,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_set = LazyHashSet::::new(key); + let lazy_set = LazyHashSet::::open(key); // The set should be empty at first assert!(lazy_set.is_empty(&storage)?); diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 103508404e..a47ff0734a 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -75,7 +75,7 @@ where K: storage::KeySeg, { /// Create or use an existing map with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, @@ -119,7 +119,7 @@ where /// collection at the given key, a new empty one will be provided. The /// nested collection may be manipulated through its methods. pub fn at(&self, key: &K) -> V { - V::new(self.get_data_key(key)) + V::open(self.get_data_key(key)) } } @@ -259,7 +259,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_map = LazyMap::::new(key); + let lazy_map = LazyMap::::open(key); // The map should be empty at first assert!(lazy_map.is_empty(&storage)?); diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index fc301b09f1..9635724543 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -31,7 +31,7 @@ pub struct LazySet { impl LazyCollection for LazySet { /// Create or use an existing set with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom: PhantomData, @@ -150,7 +150,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_set = LazySet::::new(key); + let lazy_set = LazySet::::open(key); // The set should be empty at first assert!(lazy_set.is_empty(&storage)?); diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index c13d40855d..f3a8c2bac8 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -29,7 +29,7 @@ pub type Index = u64; /// vector, the elements do not reside in memory but are instead read and /// written to storage sub-keys of the storage `key` used to construct the /// vector. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct LazyVec { key: storage::Key, phantom: PhantomData, @@ -124,7 +124,7 @@ pub struct ValidationBuilder { impl LazyCollection for LazyVec { /// Create or use an existing vector with the given storage `key`. - fn new(key: storage::Key) -> Self { + fn open(key: storage::Key) -> Self { Self { key, phantom: PhantomData, @@ -523,6 +523,12 @@ impl ValidationBuilder { #[cfg(test)] mod test { + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + use super::*; use crate::ledger::storage::testing::TestStorage; @@ -531,7 +537,7 @@ mod test { let mut storage = TestStorage::default(); let key = storage::Key::parse("test").unwrap(); - let lazy_vec = LazyVec::::new(key); + let lazy_vec = LazyVec::::open(key); // The vec should be empty at first assert!(lazy_vec.is_empty(&storage)?); @@ -561,4 +567,249 @@ mod test { Ok(()) } + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + /// A `StateMachineTest` implemented on `LazyVec` that manipulates + /// it with `Transition`s and checks its state against an in-memory + /// `std::collections::Vec`. + fn lazy_vec_api_state_machine_test(sequential 1..100 => ConcreteLazyVecState); + + } + + /// Some borsh-serializable type with arbitrary fields to be used inside + /// LazyVec state machine test + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + )] + struct TestVecItem { + x: u64, + y: bool, + } + + #[derive(Debug)] + struct ConcreteLazyVecState { + // The eager vec in `AbstractLazyVecState` is not visible in `impl + // StateMachineTest for ConcreteLazyVecState`, it's only used to drive + // transition generation, so we duplicate it here and apply the + // transitions on it the same way (with + // `fn apply_transition_on_eager_vec`) + eager_vec: Vec, + lazy_vec: LazyVec, + storage: TestStorage, + } + + #[derive(Clone, Debug)] + struct AbstractLazyVecState(Vec); + + /// Possible transitions that can modify a [`LazyVec`]. This roughly + /// corresponds to the methods that have `StorageWrite` access and is very + /// similar to [`Action`] + #[derive(Clone, Debug)] + pub enum Transition { + /// Push a value `T` into a [`LazyVec`] + Push(T), + /// Pop a value from a [`LazyVec`] + Pop, + /// Update a value `T` at index from pre to post state in a + /// [`LazyVec`] + Update { + /// index at which the value is updated + index: Index, + /// value to update the element to + value: T, + }, + } + + impl AbstractStateMachine for AbstractLazyVecState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self(vec![])).boxed() + } + + fn transitions(state: &Self::State) -> BoxedStrategy { + if state.0.is_empty() { + prop_oneof![arb_test_vec_item().prop_map(Transition::Push)] + .boxed() + } else { + let indices: Vec = + (0_usize..state.0.len()).map(|ix| ix as Index).collect(); + let arb_index = proptest::sample::select(indices); + prop_oneof![ + Just(Transition::Pop), + arb_test_vec_item().prop_map(Transition::Push), + (arb_index, arb_test_vec_item()).prop_map( + |(index, value)| Transition::Update { index, value } + ) + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + apply_transition_on_eager_vec(&mut state.0, transition); + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + if state.0.is_empty() { + !matches!( + transition, + Transition::Pop | Transition::Update { .. } + ) + } else if let Transition::Update { index, .. } = transition { + *index < (state.0.len() - 1) as Index + } else { + true + } + } + } + + impl StateMachineTest for ConcreteLazyVecState { + type Abstract = AbstractLazyVecState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + Self { + eager_vec: vec![], + lazy_vec: LazyVec::open( + storage::Key::parse("key_path/arbitrary").unwrap(), + ), + storage: TestStorage::default(), + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Transition application on lazy vec and post-conditions: + match dbg!(&transition) { + Transition::Push(value) => { + let old_len = state.lazy_vec.len(&state.storage).unwrap(); + + state + .lazy_vec + .push(&mut state.storage, value.clone()) + .unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(&state.storage).unwrap(); + let stored_value = state + .lazy_vec + .get(&state.storage, new_len - 1) + .unwrap() + .unwrap(); + assert_eq!( + &stored_value, value, + "the new item must be added to the back" + ); + assert_eq!(old_len + 1, new_len, "length must increment"); + } + Transition::Pop => { + let old_len = state.lazy_vec.len(&state.storage).unwrap(); + + let popped = state + .lazy_vec + .pop(&mut state.storage) + .unwrap() + .unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(&state.storage).unwrap(); + assert_eq!(old_len, new_len + 1, "length must decrement"); + assert_eq!( + &popped, + state.eager_vec.last().unwrap(), + "popped element matches the last element in eager vec \ + before it's updated" + ); + } + Transition::Update { index, value } => { + state + .lazy_vec + .update(&mut state.storage, *index, value.clone()) + .unwrap(); + } + } + + // Apply transition in the eager vec for comparison + apply_transition_on_eager_vec(&mut state.eager_vec, &transition); + + // Global post-conditions: + + // All items in eager vec must be present in lazy vec + for (ix, expected_item) in state.eager_vec.iter().enumerate() { + let got = state + .lazy_vec + .get(&state.storage, ix as Index) + .unwrap() + .expect("The expected item must be present in lazy vec"); + assert_eq!(expected_item, &got, "at index {ix}"); + } + + // All items in lazy vec must be present in eager vec + for (ix, expected_item) in + state.lazy_vec.iter(&state.storage).unwrap().enumerate() + { + let expected_item = expected_item.unwrap(); + let got = state + .eager_vec + .get(ix) + .expect("The expected item must be present in eager vec"); + assert_eq!(&expected_item, got, "at index {ix}"); + } + + state + } + } + + /// Generate an arbitrary `TestVecItem` + fn arb_test_vec_item() -> impl Strategy { + (any::(), any::()).prop_map(|(x, y)| TestVecItem { x, y }) + } + + /// Apply `Transition` on an eager `Vec`. + fn apply_transition_on_eager_vec( + vec: &mut Vec, + transition: &Transition, + ) { + match transition { + Transition::Push(value) => vec.push(value.clone()), + Transition::Pop => { + let _popped = vec.pop(); + } + Transition::Update { index, value } => { + let entry = vec.get_mut(*index as usize).unwrap(); + *entry = value.clone(); + } + } + } } diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index 01bcae439e..b3e1b4af0c 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -41,5 +41,5 @@ pub enum ReadError { /// An empty collection must be deleted from storage. pub trait LazyCollection { /// Create or use an existing vector with the given storage `key`. - fn new(key: storage::Key) -> Self; + fn open(key: storage::Key) -> Self; } From 8c4e2ca33466a0a54ae751ecf693ca042eb29345 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 14 Sep 2022 10:46:45 +0200 Subject: [PATCH 308/373] update after rebase on #458, #465 handle TODOs to fix LazyVec's iter order to fix the API SM test --- .../storage_api/collections/lazy_vec.rs | 35 +++++++------------ .../src/ledger/storage_api/validation/mod.rs | 5 +-- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index f3a8c2bac8..0c91b0dbbc 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -2,7 +2,6 @@ use std::collections::BTreeSet; use std::marker::PhantomData; -use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; use derivative::Derivative; @@ -159,8 +158,7 @@ impl LazyVec { /// Get the sub-key of vector's elements storage fn get_data_key(&self, index: Index) -> storage::Key { - // TODO rebase on ordered prefix iter (https://github.com/anoma/namada/pull/458) and remove `.to_string()` - self.get_data_prefix().push(&index.to_string()).unwrap() + self.get_data_prefix().push(&index).unwrap() } /// Get the sub-key of vector's length storage @@ -258,7 +256,7 @@ where pub fn is_valid_sub_key( &self, key: &storage::Key, - ) -> ValidationResult> { + ) -> storage_api::Result> { let suffix = match key.split_prefix(&self.key) { None => { // not matching prefix, irrelevant @@ -266,7 +264,8 @@ where } Some(None) => { // no suffix, invalid - return Err(ValidationError::InvalidSubKey(key.clone())); + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); } Some(Some(suffix)) => suffix, }; @@ -279,14 +278,15 @@ where [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] if sub_a == DATA_SUBKEY => { - // TODO rebase on ordered prefix iter (https://github.com/anoma/namada/pull/458) and parse with `KeySeg::parse` - if let Ok(index) = Index::from_str(sub_b) { + if let Ok(index) = storage::KeySeg::parse(sub_b.clone()) { Ok(Some(SubKey::Data(index))) } else { Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() } } - _ => Err(ValidationError::InvalidSubKey(key.clone())), + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), } } @@ -304,29 +304,18 @@ where env: &ENV, builder: &mut Option>, key_changed: &storage::Key, - ) -> ValidationResult + ) -> storage_api::Result where - ENV: VpEnv, + ENV: for<'a> VpEnv<'a>, { if let Some(sub) = self.is_valid_sub_key(key_changed)? { let change = match sub { SubKey::Len => { - let data = validation::read_data(env, key_changed) - // TODO this has to propagate errors from VpEnv rather - // then replace them (e.g. it could be out-of-gas), but - // VpEnv::Error is generic, maybe we should make it - // concrete (only the VpEnv impls have extensible error - // types) - .map_err(|_| { - ValidationError::StorageError(key_changed.clone()) - })?; + let data = validation::read_data(env, key_changed)?; data.map(SubKeyWithData::Len) } SubKey::Data(index) => { - let data = validation::read_data(env, key_changed) - .map_err(|_| { - ValidationError::StorageError(key_changed.clone()) - })?; + let data = validation::read_data(env, key_changed)?; data.map(|data| SubKeyWithData::Data(index, data)) } }; diff --git a/shared/src/ledger/storage_api/validation/mod.rs b/shared/src/ledger/storage_api/validation/mod.rs index 30c1e2566b..ca0e779a75 100644 --- a/shared/src/ledger/storage_api/validation/mod.rs +++ b/shared/src/ledger/storage_api/validation/mod.rs @@ -4,6 +4,7 @@ use std::fmt::Debug; use borsh::BorshDeserialize; +use crate::ledger::storage_api; use crate::ledger::vp_env::VpEnv; use crate::types::storage; @@ -33,10 +34,10 @@ pub enum Data { pub fn read_data( env: &ENV, key: &storage::Key, -) -> Result>, ENV::Error> +) -> Result>, storage_api::Error> where T: BorshDeserialize, - ENV: VpEnv, + ENV: for<'a> VpEnv<'a>, { let pre = env.read_pre(key)?; let post = env.read_post(key)?; From 35b5f4013719d224895235e105d0e336c64a05fd Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 14 Sep 2022 14:38:29 +0200 Subject: [PATCH 309/373] quick bug and documentation fix --- shared/src/ledger/storage_api/collections/lazy_vec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 0c91b0dbbc..c086297ea7 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -336,7 +336,7 @@ impl ValidationBuilder { /// /// The validation rules for a [`LazyVec`] are: /// - A difference in the vector's length must correspond to the - /// difference in how many elements where pushed versus how many + /// difference in how many elements were pushed versus how many /// elements were popped. /// - An empty vector must be deleted from storage /// - In addition, we check that indices of any changes are within an @@ -400,7 +400,7 @@ impl ValidationBuilder { }, } } - let added_len: u64 = deleted + let added_len: u64 = added .len() .try_into() .map_err(ValidationError::IndexOverflow)?; From 5396df98e460a30471c6785661b6d3b333f97cb1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 14 Sep 2022 16:47:22 +0200 Subject: [PATCH 310/373] post-conditions for Transition::Update + some comments --- .../storage_api/collections/lazy_vec.rs | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index c086297ea7..79967cb9c3 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -336,8 +336,8 @@ impl ValidationBuilder { /// /// The validation rules for a [`LazyVec`] are: /// - A difference in the vector's length must correspond to the - /// difference in how many elements were pushed versus how many - /// elements were popped. + /// difference in how many elements were pushed versus how many elements + /// were popped. /// - An empty vector must be deleted from storage /// - In addition, we check that indices of any changes are within an /// expected range (i.e. the vectors indices should always be @@ -635,6 +635,7 @@ mod test { Just(Self(vec![])).boxed() } + // Apply a random transition to the state fn transitions(state: &Self::State) -> BoxedStrategy { if state.0.is_empty() { prop_oneof![arb_test_vec_item().prop_map(Transition::Push)] @@ -667,11 +668,14 @@ mod test { transition: &Self::Transition, ) -> bool { if state.0.is_empty() { + // Ensure that the pop or update transitions are not applied to + // an empty state !matches!( transition, Transition::Pop | Transition::Update { .. } ) } else if let Transition::Update { index, .. } = transition { + // Ensure that the update index is a valid one *index < (state.0.len() - 1) as Index } else { true @@ -742,10 +746,37 @@ mod test { ); } Transition::Update { index, value } => { + let old_len = state.lazy_vec.len(&state.storage).unwrap(); + let old_val = state + .lazy_vec + .get(&state.storage, *index) + .unwrap() + .unwrap(); + state .lazy_vec .update(&mut state.storage, *index, value.clone()) .unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(&state.storage).unwrap(); + let new_val = state + .lazy_vec + .get(&state.storage, *index) + .unwrap() + .unwrap(); + assert_eq!(old_len, new_len, "length must not change"); + assert_eq!( + &old_val, + state.eager_vec.get(*index as usize).unwrap(), + "old value must match the value at the same index in \ + the eager vec before it's updated" + ); + assert_eq!( + &new_val, value, + "new value must match that which was passed into the \ + Transition::Update" + ); } } From 073a7081c3d89a062c48fc15e50e186a859f3fe6 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 20 Aug 2022 17:33:47 +0200 Subject: [PATCH 311/373] multitoken transfer and query --- apps/src/lib/cli.rs | 15 +++ apps/src/lib/client/rpc.rs | 128 ++++++++++++++++++---- apps/src/lib/client/tx.rs | 15 ++- shared/src/types/intent.rs | 4 + shared/src/types/token.rs | 68 ++++++++++++ tests/src/e2e/ledger_tests.rs | 1 + vm_env/src/governance.rs | 1 + vm_env/src/ibc.rs | 2 +- vm_env/src/proof_of_stake.rs | 2 +- vm_env/src/token.rs | 108 ++++++++++++++---- wasm/wasm_source/src/tx_from_intent.rs | 3 +- wasm/wasm_source/src/tx_transfer.rs | 3 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 8 +- wasm/wasm_source/src/vp_user.rs | 20 +++- wasm_for_tests/wasm_source/src/lib.rs | 1 + 15 files changed, 323 insertions(+), 56 deletions(-) diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f546860cfd..6eb6454bc4 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1465,6 +1465,7 @@ pub mod args { const SOURCE: Arg = arg("source"); const SOURCE_OPT: ArgOpt = SOURCE.opt(); const STORAGE_KEY: Arg = arg("storage-key"); + const SUB_PREFIX: ArgOpt = arg_opt("sub-prefix"); const TARGET: Arg = arg("target"); const TO_STDOUT: ArgFlag = flag("stdout"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); @@ -1610,6 +1611,8 @@ pub mod args { pub target: WalletAddress, /// Transferred token address pub token: WalletAddress, + /// Transferred token address + pub sub_prefix: Option, /// Transferred token amount pub amount: token::Amount, } @@ -1620,12 +1623,14 @@ pub mod args { let source = SOURCE.parse(matches); let target = TARGET.parse(matches); let token = TOKEN.parse(matches); + let sub_prefix = SUB_PREFIX.parse(matches); let amount = AMOUNT.parse(matches); Self { tx, source, target, token, + sub_prefix, amount, } } @@ -1638,6 +1643,7 @@ pub mod args { )) .arg(TARGET.def().about("The target account address.")) .arg(TOKEN.def().about("The transfer token.")) + .arg(SUB_PREFIX.def().about("The token's sub prefix.")) .arg(AMOUNT.def().about("The amount to transfer in decimal.")) } } @@ -2185,6 +2191,8 @@ pub mod args { pub owner: Option, /// Address of a token pub token: Option, + /// Sub prefix of an account + pub sub_prefix: Option, } impl Args for QueryBalance { @@ -2192,10 +2200,12 @@ pub mod args { let query = Query::parse(matches); let owner = OWNER.parse(matches); let token = TOKEN_OPT.parse(matches); + let sub_prefix = SUB_PREFIX.parse(matches); Self { query, owner, token, + sub_prefix, } } @@ -2211,6 +2221,11 @@ pub mod args { .def() .about("The token's address whose balance to query."), ) + .arg( + SUB_PREFIX.def().about( + "The token's sub prefix whose balance to query.", + ), + ) } } diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..7d7a859138 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -27,7 +27,7 @@ use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalVote, TallyResult, }; use namada::types::key::*; -use namada::types::storage::{Epoch, PrefixValue}; +use namada::types::storage::{Epoch, Key, KeySeg, PrefixValue}; use namada::types::token::{balance_key, Amount}; use namada::types::{address, storage, token}; use tendermint::abci::Code; @@ -101,15 +101,29 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { (Some(token), Some(owner)) => { let token = ctx.get(&token); let owner = ctx.get(&owner); - let key = token::balance_key(&token, &owner); + let key = match &args.sub_prefix { + Some(sub_prefix) => { + let sub_prefix = Key::parse(sub_prefix).unwrap(); + let prefix = + token::multitoken_balance_prefix(&token, &sub_prefix); + token::multitoken_balance_key(&prefix, &owner) + } + None => token::balance_key(&token, &owner), + }; let currency_code = tokens .get(&token) .map(|c| Cow::Borrowed(*c)) .unwrap_or_else(|| Cow::Owned(token.to_string())); match query_storage_value::(&client, &key).await { - Some(balance) => { - println!("{}: {}", currency_code, balance); - } + Some(balance) => match &args.sub_prefix { + Some(sub_prefix) => { + println!( + "{} with {}: {}", + currency_code, sub_prefix, balance + ); + } + None => println!("{}: {}", currency_code, balance), + }, None => { println!("No {} balance found for {}", currency_code, owner) } @@ -119,12 +133,44 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { let owner = ctx.get(&owner); let mut found_any = false; for (token, currency_code) in tokens { - let key = token::balance_key(&token, &owner); - if let Some(balance) = - query_storage_value::(&client, &key).await - { - println!("{}: {}", currency_code, balance); - found_any = true; + let prefix = token.to_db_key().into(); + let balances = query_storage_prefix::( + client.clone(), + prefix, + ) + .await; + if let Some(balances) = balances { + let stdout = io::stdout(); + let mut w = stdout.lock(); + for (key, balance) in balances { + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, o)) if *o == owner => { + writeln!( + w, + "{} with {}: {}", + currency_code, sub_prefix, balance + ) + .unwrap(); + found_any = true; + } + Some(_) => {} + None => { + if let Some(o) = + token::is_any_token_balance_key(&key) + { + if *o == owner { + writeln!( + w, + "{}: {}", + currency_code, balance + ) + .unwrap(); + found_any = true; + } + } + } + } + } } } if !found_any { @@ -133,9 +179,9 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { } (Some(token), None) => { let token = ctx.get(&token); - let key = token::balance_prefix(&token); + let prefix = token.to_db_key().into(); let balances = - query_storage_prefix::(client, key).await; + query_storage_prefix::(client, prefix).await; match balances { Some(balances) => { let currency_code = tokens @@ -144,12 +190,30 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { .unwrap_or_else(|| Cow::Owned(token.to_string())); let stdout = io::stdout(); let mut w = stdout.lock(); - writeln!(w, "Token {}:", currency_code).unwrap(); + writeln!(w, "Token {}", currency_code).unwrap(); for (key, balance) in balances { - let owner = - token::is_any_token_balance_key(&key).unwrap(); - writeln!(w, " {}, owned by {}", balance, owner) - .unwrap(); + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => { + writeln!( + w, + " with {}: {}, owned by {}", + sub_prefix, balance, owner + ) + .unwrap(); + } + None => { + if let Some(owner) = + token::is_any_token_balance_key(&key) + { + writeln!( + w, + ": {}, owned by {}", + balance, owner + ) + .unwrap(); + } + } + } } } None => { @@ -167,12 +231,30 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { .await; match balances { Some(balances) => { - writeln!(w, "Token {}:", currency_code).unwrap(); + writeln!(w, "Token {}", currency_code).unwrap(); for (key, balance) in balances { - let owner = - token::is_any_token_balance_key(&key).unwrap(); - writeln!(w, " {}, owned by {}", balance, owner) - .unwrap(); + match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => { + writeln!( + w, + " with {}: {}, owned by {}", + sub_prefix, balance, owner + ) + .unwrap(); + } + None => { + if let Some(owner) = + token::is_any_token_balance_key(&key) + { + writeln!( + w, + ": {}, owned by {}", + balance, owner + ) + .unwrap() + } + } + } } } None => { diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1d41ebbc77..a1b089fab9 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -16,7 +16,7 @@ use namada::types::governance::{ }; use namada::types::key::*; use namada::types::nft::{self, Nft, NftToken}; -use namada::types::storage::Epoch; +use namada::types::storage::{Epoch, Key}; use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, @@ -415,7 +415,17 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { } } // Check source balance - let balance_key = token::balance_key(&token, &source); + let (sub_prefix, balance_key) = match args.sub_prefix { + Some(sub_prefix) => { + let sub_prefix = Key::parse(sub_prefix).unwrap(); + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); + ( + Some(sub_prefix), + token::multitoken_balance_key(&prefix, &source), + ) + } + None => (None, token::balance_key(&token, &source)), + }; let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); match rpc::query_storage_value::(&client, &balance_key).await { @@ -447,6 +457,7 @@ pub async fn submit_transfer(ctx: Context, args: args::TxTransfer) { source, target, token, + sub_prefix, amount: args.amount, }; tracing::debug!("Transfer data {:?}", transfer); diff --git a/shared/src/types/intent.rs b/shared/src/types/intent.rs index c3effbb4fc..3acb43f0c0 100644 --- a/shared/src/types/intent.rs +++ b/shared/src/types/intent.rs @@ -316,12 +316,14 @@ mod tests { source: bertha_addr.clone(), target: albert_addr.clone(), token: Address::from_str(BTC).unwrap(), + sub_prefix: None, amount: token::Amount::from(100), }, token::Transfer { source: albert_addr, target: bertha_addr, token: Address::from_str(XAN).unwrap(), + sub_prefix: None, amount: token::Amount::from(1), }, ] @@ -424,12 +426,14 @@ mod tests { source: bertha_addr.clone(), target: albert_addr.clone(), token: Address::from_str(BTC).unwrap(), + sub_prefix: None, amount: token::Amount::from(100), }, token::Transfer { source: albert_addr, target: bertha_addr, token: Address::from_str(XAN).unwrap(), + sub_prefix: None, amount: token::Amount::from(1), }, ] diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index f3e4cd4ed2..4d03138e60 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -250,6 +250,23 @@ pub fn balance_prefix(token_addr: &Address) -> Key { .expect("Cannot obtain a storage key") } +/// Obtain a storage key prefix for multitoken balances. +pub fn multitoken_balance_prefix( + token_addr: &Address, + sub_prefix: &Key, +) -> Key { + Key::from(token_addr.to_db_key()).join(sub_prefix) +} + +/// Obtain a storage key for user's multitoken balance. +pub fn multitoken_balance_key(prefix: &Key, owner: &Address) -> Key { + prefix + .push(&BALANCE_STORAGE_KEY.to_owned()) + .expect("Cannot obtain a storage key") + .push(&owner.to_db_key()) + .expect("Cannot obtain a storage key") +} + /// Check if the given storage key is balance key for the given token. If it is, /// returns the owner. pub fn is_balance_key<'a>( @@ -297,6 +314,54 @@ pub fn is_non_owner_balance_key(key: &Key) -> Option<&Address> { } } +/// Check if the given storage key is multitoken balance key for the given +/// token. If it is, returns the sub prefix and the owner. +pub fn is_multitoken_balance_key<'a>( + token_addr: &Address, + key: &'a Key, +) -> Option<(Key, &'a Address)> { + match key.segments.first() { + Some(DbKeySeg::AddressSeg(addr)) if addr == token_addr => { + multitoken_balance_owner(key) + } + _ => None, + } +} + +/// Check if the given storage key is multitoken balance key for unspecified +/// token. If it is, returns the sub prefix and the owner. +pub fn is_any_multitoken_balance_key(key: &Key) -> Option<(Key, &Address)> { + match key.segments.first() { + Some(DbKeySeg::AddressSeg(_)) => multitoken_balance_owner(key), + _ => None, + } +} + +fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { + let len = key.segments.len(); + if len < 4 { + // the key of a multitoken should have 1 or more segments other than + // token, balance, owner + return None; + } + match key.get_at(len - 2) { + Some(DbKeySeg::StringSeg(balance)) + if balance == BALANCE_STORAGE_KEY => + { + match key.segments.last() { + Some(DbKeySeg::AddressSeg(owner)) => { + let sub_prefix = Key { + segments: key.segments[1..(len - 2)].to_vec(), + }; + Some((sub_prefix, owner)) + } + _ => None, + } + } + _ => None, + } +} + /// A simple bilateral token transfer #[derive( Debug, @@ -318,6 +383,8 @@ pub struct Transfer { pub target: Address, /// Token's address pub token: Address, + /// Source token's sub prefix + pub sub_prefix: Option, /// The amount of tokens pub amount: Amount, } @@ -354,6 +421,7 @@ impl TryFrom for Transfer { source, target, token, + sub_prefix: None, amount, }) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 22eb4f4ac5..1f19433ca1 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -425,6 +425,7 @@ fn invalid_transactions() -> Result<()> { source: find_address(&test, DAEWON)?, target: find_address(&test, ALBERT)?, token: find_address(&test, XAN)?, + sub_prefix: None, amount: token::Amount::whole(1), }; let data = transfer diff --git a/vm_env/src/governance.rs b/vm_env/src/governance.rs index db4ea7916f..cfe16e2acb 100644 --- a/vm_env/src/governance.rs +++ b/vm_env/src/governance.rs @@ -63,6 +63,7 @@ pub mod tx { &data.author, &governance_address, &m1t(), + None, min_proposal_funds, ); } diff --git a/vm_env/src/ibc.rs b/vm_env/src/ibc.rs index febaa78560..c7abb82ec5 100644 --- a/vm_env/src/ibc.rs +++ b/vm_env/src/ibc.rs @@ -37,7 +37,7 @@ impl IbcActions for Ibc { token: &Address, amount: Amount, ) { - transfer(src, dest, token, amount) + transfer(src, dest, token, None, amount) } fn get_height(&self) -> BlockHeight { diff --git a/vm_env/src/proof_of_stake.rs b/vm_env/src/proof_of_stake.rs index 8e4bba4223..afabd85ed7 100644 --- a/vm_env/src/proof_of_stake.rs +++ b/vm_env/src/proof_of_stake.rs @@ -256,6 +256,6 @@ impl namada_proof_of_stake::PosActions for PoS { src: &Self::Address, dest: &Self::Address, ) { - crate::token::tx::transfer(src, dest, token, amount) + crate::token::tx::transfer(src, dest, token, None, amount) } } diff --git a/vm_env/src/token.rs b/vm_env/src/token.rs index 8a7367afb9..784b838707 100644 --- a/vm_env/src/token.rs +++ b/vm_env/src/token.rs @@ -20,7 +20,12 @@ pub mod vp { ) -> bool { let mut change: Change = 0; let all_checked = keys_changed.iter().all(|key| { - match token::is_balance_key(token, key) { + let owner: Option<&Address> = + match token::is_multitoken_balance_key(token, key) { + Some((_, o)) => Some(o), + None => token::is_balance_key(token, key), + }; + match owner { None => { // Unknown changes to this address space are disallowed, but // unknown changes anywhere else are permitted @@ -73,38 +78,101 @@ pub mod tx { src: &Address, dest: &Address, token: &Address, + sub_prefix: Option, amount: Amount, ) { - let src_key = token::balance_key(token, src); - let dest_key = token::balance_key(token, dest); + let src_key = match &sub_prefix { + Some(sub_prefix) => { + let prefix = + token::multitoken_balance_prefix(token, sub_prefix); + token::multitoken_balance_key(&prefix, src) + } + None => token::balance_key(token, src), + }; + let dest_key = match &sub_prefix { + Some(sub_prefix) => { + let prefix = + token::multitoken_balance_prefix(token, sub_prefix); + token::multitoken_balance_key(&prefix, dest) + } + None => token::balance_key(token, dest), + }; let src_bal: Option = tx::read(&src_key.to_string()); - let mut src_bal = src_bal.unwrap_or_else(|| match src { - Address::Internal(InternalAddress::IbcMint) => Amount::max(), - _ => { + match src_bal { + None => { tx::log_string(format!("src {} has no balance", src)); unreachable!() } - }); - src_bal.spend(&amount); - let mut dest_bal: Amount = - tx::read(&dest_key.to_string()).unwrap_or_default(); - dest_bal.receive(&amount); - match src { - Address::Internal(InternalAddress::IbcMint) => { - tx::write_temp(&src_key.to_string(), src_bal) + Some(mut src_bal) => { + src_bal.spend(&amount); + let mut dest_bal: Amount = + tx::read(&dest_key.to_string()).unwrap_or_default(); + dest_bal.receive(&amount); + tx::write(&src_key.to_string(), src_bal); + tx::write(&dest_key.to_string(), dest_bal); } - Address::Internal(InternalAddress::IbcBurn) => { + } + } + + /// A token transfer with storage keys that can be used in a transaction. + pub fn transfer_with_keys(src_key: &Key, dest_key: &Key, amount: Amount) { + let src_owner = is_any_multitoken_balance_key(src_key).map(|(_, o)| o); + let src_bal: Option = match src_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { + Some(Amount::max()) + } + Some(Address::Internal(InternalAddress::IbcBurn)) => { tx::log_string("invalid transfer from the burn address"); unreachable!() } - _ => tx::write(&src_key.to_string(), src_bal), - } - match dest { - Address::Internal(InternalAddress::IbcMint) => { + Some(_) => tx::read(&src_key.to_string()), + None => { + // the key is not a multitoken key + match is_any_token_balance_key(src_key) { + Some(_) => tx::read(src_key.to_string()), + None => { + tx::log_string(format!( + "invalid balance key: {}", + src_key + )); + unreachable!() + } + } + } + }; + let mut src_bal = src_bal.unwrap_or_else(|| { + tx::log_string(format!("src {} has no balance", src_key)); + unreachable!() + }); + src_bal.spend(&amount); + let dest_owner = + is_any_multitoken_balance_key(dest_key).map(|(_, o)| o); + let mut dest_bal: Amount = match dest_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { tx::log_string("invalid transfer to the mint address"); unreachable!() } - Address::Internal(InternalAddress::IbcBurn) => { + Some(_) => tx::read(dest_key.to_string()).unwrap_or_default(), + None => match is_any_token_balance_key(dest_key) { + Some(_) => tx::read(dest_key.to_string()).unwrap_or_default(), + None => { + tx::log_string(format!( + "invalid balance key: {}", + dest_key + )); + unreachable!() + } + }, + }; + dest_bal.receive(&amount); + match src_owner { + Some(Address::Internal(InternalAddress::IbcMint)) => { + tx::write_temp(&src_key.to_string(), src_bal) + } + _ => tx::write(&src_key.to_string(), src_bal), + } + match dest_owner { + Some(Address::Internal(InternalAddress::IbcBurn)) => { tx::write_temp(&dest_key.to_string(), dest_bal) } _ => tx::write(&dest_key.to_string(), dest_bal), diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs index e39963fae7..f86c412353 100644 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ b/wasm/wasm_source/src/tx_from_intent.rs @@ -20,10 +20,11 @@ fn apply_tx(tx_data: Vec) { source, target, token, + sub_prefix, amount, } in tx_data.matches.transfers { - token::transfer(&source, &target, &token, amount); + token::transfer(&source, &target, &token, sub_prefix, amount); } tx_data diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index f0ab0ad2d0..059a3296e1 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -14,7 +14,8 @@ fn apply_tx(tx_data: Vec) { source, target, token, + sub_prefix, amount, } = transfer; - token::transfer(&source, &target, &token, amount) + token::transfer(&source, &target, &token, sub_prefix, amount) } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 553288926e..a2f9a6267b 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -136,7 +136,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + &source, address, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -251,7 +253,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(address, &target, &token, None, amount); }); let vp_env = vp_host_env::take(); @@ -284,7 +286,7 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer(address, &target, &token, None, amount); }); let vp_env = vp_host_env::take(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..e1815cc0b1 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -34,6 +34,10 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { fn from(key: &'a storage::Key) -> KeyType<'a> { if let Some(address) = token::is_any_token_balance_key(key) { Self::Token(address) + } else if let Some((_, address)) = + token::is_any_multitoken_balance_key(key) + { + Self::Token(address) } else if proof_of_stake::is_pos_key(key) { Self::PoS } else if let Some(address) = intent::is_invalid_intent_key(key) { @@ -412,7 +416,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(&source, address, &token, amount); + tx_host_env::token::transfer( + &source, address, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -445,7 +451,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + address, &target, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); @@ -482,7 +490,9 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(address, &target, &token, amount); + tx_host_env::token::transfer( + address, &target, &token, None, amount, + ); }); let mut vp_env = vp_host_env::take(); @@ -520,7 +530,9 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { tx_host_env::insert_verifier(address); // Apply transfer in a transaction - tx_host_env::token::transfer(&source, &target, &token, amount); + tx_host_env::token::transfer( + &source, &target, &token, None, amount, + ); }); let vp_env = vp_host_env::take(); diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 674fb2a2d9..1062d1ce43 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -140,6 +140,7 @@ pub mod main { source: _, target, token, + sub_prefix: _, amount, } = transfer; let target_key = token::balance_key(&token, &target); From beb6f8e69c180588d8db8bc648d61a568c06e988 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 20 Aug 2022 16:33:02 +0000 Subject: [PATCH 312/373] [ci skip] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++-------------- wasm_for_tests/wasm_source/Cargo.lock | 24 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 898f6e9763..1cf95608b2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.16097490afa7378c79e6216751b20796cde3a9026c34255c3f1e5ec5a4c9482e.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d1937b17a3abaf7ea595526c870b3d57ddef8e0c1bc96f8e0a448864b186c7.wasm", - "tx_ibc.wasm": "tx_ibc.378b10551c0b22c2c892d24e2676ee5160d654e2e53a50e7925e0f2c6321497b.wasm", - "tx_init_account.wasm": "tx_init_account.adab66c2b4d635e9c42133936aafb143363f91dddff2a60f94df504ffec951a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.d1065ebd80ba6ea97f29bc2268becf9ba3ba2952641992464f3e9e868df17447.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.184131576a579f9ece96460d1eb20e5970fcd149b0527c8e56b711e5c535aa5f.wasm", - "tx_init_validator.wasm": "tx_init_validator.2990747d24d467b56e19724c5d13df826a3aab83f7e1bf26558dbdf44e260f8a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.33db14dea4a03ff7508ca44f3ae956d83c0abceb3dae5be844668e54ac22b273.wasm", - "tx_transfer.wasm": "tx_transfer.a601d62296f56f6b4dabb0a2ad082478d195e667c7469f363bdfd5fe41349bd8.wasm", - "tx_unbond.wasm": "tx_unbond.014cbf5b0aa3ac592c0a6940dd502ec8569a3af4d12782e3a5931c15dc13042f.wasm", - "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bcb5280be9dfeed0a7650ba5e4a3cebc2c19b76780fd74dcb345be3da766b64a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8fc0a3439ee9ae66047c520519877bc1f540e0cb02abfa31afa8cce8cd069b6f.wasm", - "vp_nft.wasm": "vp_nft.2c820c728d241b82bf0ed3c552ee9e7c046bceaa4f7b6f12d3236a1a3d7c1589.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e762f3fda8aa7a252e2b29a4a312db91ded062d6c18b8b489883733c89dc227.wasm", - "vp_token.wasm": "vp_token.c45cc3848f12fc47713702dc206d1312ad740a6bbee7f141556714f6f89d4985.wasm", - "vp_user.wasm": "vp_user.d6cd2f4b5bc26f96df6aa300fddf4d25e1656920d59896209bd54ae8d407ecde.wasm" + "tx_bond.wasm": "tx_bond.23637103c6b0bb5604e51a522d125c102ce8faa958a73e31fe9ec495a87b5d5e.wasm", + "tx_from_intent.wasm": "tx_from_intent.b4d18f001d7f50cebf13c613dc76cd2157784d2ca38cdac7bae089ee76750aae.wasm", + "tx_ibc.wasm": "tx_ibc.459621fe0df4389118360ecfb333e58682d6efa465fb2743051b3b034de36f2f.wasm", + "tx_init_account.wasm": "tx_init_account.bb9a3c0d941e815a5363b5edcaf12e7a77aeefc7d041826c80f746a71daea964.wasm", + "tx_init_nft.wasm": "tx_init_nft.44e5c200985c93c442b122ecd334d7d6f33babfafa17b8263ebdbcc76f19b417.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.19e24f2b040d737f69c3e838e9a0d1ff33d87709cd1378c80561b32af1bc5718.wasm", + "tx_init_validator.wasm": "tx_init_validator.d62938cc345f4ae9f082ed5a92f7ad36992ff294800dfec63fed8a40207886e2.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.867743e04fba226e0794c9f70c039918e6cecc9aa17e89a9609d4782d9feca92.wasm", + "tx_transfer.wasm": "tx_transfer.119cfb69971a648c1363d6d466590bcc4c45b992a3f65ca51b47b8f4a8637f8b.wasm", + "tx_unbond.wasm": "tx_unbond.94884377c96b8045c1c965badc321275401e2ab9edc09f66e766a40e924e06c7.wasm", + "tx_update_vp.wasm": "tx_update_vp.cb21644aed96971122eb1a52f3db96da377cd19a7f028951f755bb76c0701558.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.854851a5af0c67b53c3165ccbc2355fd96a1cea29a48437c1c3e1ab4b4660dac.wasm", + "tx_withdraw.wasm": "tx_withdraw.eb07fbbde79b5c2355a4f73a30355aba1e64e803eff29cfc8161fbda946b2de5.wasm", + "vp_nft.wasm": "vp_nft.1613c09ad9ecac50584160bc8f4eb1b1acc2d96c5b51e91ac69ad17a439c01d6.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.598942468d0cb42d987be7b93f2efaeba98d837441c5a6971217c0dff6161064.wasm", + "vp_token.wasm": "vp_token.4988b829c7825aa101ac47903d3e363ab38b6887df95eec739e1726e3b29234f.wasm", + "vp_user.wasm": "vp_user.bf58f7316e142118328e4c63486e53003812b142b9b5927c83681cd0de0f74d8.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..876455fcaa 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1619,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From fa3d72d26525f7e621471cb68a1ff75b7ebedd31 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sat, 20 Aug 2022 18:41:52 +0200 Subject: [PATCH 313/373] remove an error message --- apps/src/lib/client/rpc.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7d7a859138..d39aeaddb9 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1414,14 +1414,7 @@ where Ok(values) => { let decode = |PrefixValue { key, value }: PrefixValue| { match T::try_from_slice(&value[..]) { - Err(err) => { - eprintln!( - "Skipping a value for key {}. Error in \ - decoding: {}", - key, err - ); - None - } + Err(_) => None, Ok(value) => Some((key, value)), } }; From c503031ce7f86317ec80f81957976f19c06d5a14 Mon Sep 17 00:00:00 2001 From: yito88 Date: Sun, 21 Aug 2022 09:39:48 +0200 Subject: [PATCH 314/373] update a test wasm --- wasm_for_tests/tx_mint_tokens.wasm | Bin 226185 -> 229247 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index c9a102773e28ced047f374606582082da3d839d0..b6f8f074bf213c9a7bcd33fbf811e213ff4bab37 100755 GIT binary patch delta 78238 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 z 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 delta 75499 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
j?xxwrg2Y z$N@bBwJM(u39n`4+g6XWov=p`R(tz709oE|vnohLWt(0Z~mUq)ZP% z_V?E?b~g+l=6dNYR%R2haT0mW^bYlDmRNPhU?<2& zIavB(UkpJMv;?x{xSq)R6iz5>i3EDpAtD;o)+PE~~#WJZuaHCKIlK_UhEEI!L;AAJ7q5ZP$?G%RaR#crS0R{f52nMJwp zsI*!+aY8Ck@ThTm+lfX`r3J&^SF* zfMIE@t~2j^k$p%|UUUM&yQWD_Db$=F)$ZN4)hsWvC=DU9Nx8Z+PI`G7@qn%3=qxW$ zHaO(Y%GbeiPTO~#YCOj3X|8W1-#8rO_S96PNaQ2x7Y|!SdAV9cglgqd)jSQ9 zqC3j1u0a+WP)^U{k{byrXU(algJ*N2r@C3sI#Z8=>U+_8s)<+2sn%OtUM12au))(z zZ^nw;l;W;(q7
MF-5FLsqv`=b)xR!@1eyQzAaNRFD8*Viq^W4d}t-o{3?iSbHe zxV*NlEnNPE%bzN3ZPD^8l9Z{e>L$;YGTUD1CU4493SW|Q;;M6!#+D{!EU0aE4XVn_ z%E=g*oiRT%-IFzV;Nbbnzh07)+OC@|A9pCPza|e1$;r;i%Bq-OkvTs*%QI-OM}Z0v zuAEsWHz_OU$(FV=Rr2O8O8&d@fY^YHy&lhkimZxk@BG0Fa=e+!gDrB`woR>aXYDPPM-EG1^t#lNM$lnsIbKrEWn{ec z)YR0MgA#10OX)F@M=BSeltVbK+E*U?L>}k;6l7dpjfZ*@GhHhf9hPWn@Rqylysgbc zTZja?ETc@1l9923?SK-fCU6Z}!ImRIc};ztw}RC`H7c+2mM>)V7^wP0Egt%9W?R#h z-Mx5ZGCgWSJTi2)$aqLX=GPvYGtLbVD#=5-j%NLl0e+rO5(DwyQui&s&> zP|A&;%iV?zBSeU2q%TG58$1>0oz33LMr=N&O{bNO^-J7@W?a5ZH1aewpf|glTIMr6 zB0!`VUEo@g%jjYSwPG1rE7L{(dzHw-BQ!^+`N8eOGjpWz|*0$la z?36fGrOOU*+T^UKBgf z)h{8#<;u-vb3ut>&(itcsdd$L)y-_pAmy47e5dl|K;G5<*B+o{Z8O7;a%I)$a$k;9 zG0OPUa$i>#^lz|5P5lyYW4Wiv%WO%E4R5T3SgC8C=3+lVbJPepcm=znh$*#S$S!_E zJX5}m;nB*TNZwgKldbIS#}h}=C9I7tb*vDwMMExl;INY#ZmMIMq{%esNg=@{@KvdR3&(!HysudjGoex(=VYVCpDKT zsDkL3?uJJ1g6dX=BUj42P(C~H5}otz0>HOwLp4(|KHyElsaCSY`#IcfBg=-J&yz z{@29b8K-Pd=iTK`bCrYXypQ~2sd8`#k5!^weDHXBYOA5X3Dl@7cjI;OMu-ZuAumJE zbr1x?hfrO?)NH?BdS!vkYyH^F1Vf~aQZ~4Fq&z?qlwbZJCv^#$uV;f??#sJnAdhjz z8Y(<+1#29nc#8Scf~7egR8+S9Eca4E>^!I~J%isOb)$t9A+lPU7vw<12q_7PiK`_P zPYAE@%__uYEuJOQyyXmYO30B$FQcnIRgNJU#OM@nlc%`_omUqeS7T&E?{T+eXEL(3 zm3z=v>y}j4Rj`*KB-Jt3UFoeukFRlyUUVEa(bxwJL#K<0)g12mp7MnPYu^o04rKAy zt#cs%r~@eP=U~X#^rLJKp+z}#N$?FS!1fGNj%4#e+%=dfA)m-)a%qur$snH6<88NI z1ZepRNkytY-CkSctz&rTN;&wQ9O*cO3O6mSD`z+wr_{vp=-A$ftEwMtI-GtL0y9UZ z^!QFrkT=+ryd1uU4?ZqmEe3Ee$nZ41Y^llRse=F$a3M7)n(s-g~Obf%^xe z=42{#1JTWpZ&N&azTgZTbWvXJ#!KYd!OFTZyt`6&5sy!$Tc15mnpk}oEJ))*6U+2- z)xd%1zc}Kktn0=je8bvDC$);87^6L*ORAfzK-LCNd3Ezr)(L`w$OxUR+{;iSHk~v9 zb_bm38I4|&qW7Z1V@ztO^45CY$j1v_$p)#3MosK6&<-uNs=lI$(YrI~Rn^VPlb^_m z$^#$CVX0TPV+10FE-}&ie@n)|Ts8&h2hTCQiLMO$TuzoP0!MN3G?#L62p>H4*$Fyo zzu`fTia5@D*Tcd0_Y1>5RO)^&Et{L&w@JCO)J>DBWYMyRS|4YA}KLj@lEG8 zejQSr{#2eMd)isbu58NZow{Z~NH;IoHWXHFZmsqIqCZZL;7LZjcQorDH} zVL3D!e=aT}KtgY;5CS1$6faiJbm6uEFQVsa`ZuG~F<>OpDA^JCs~rf;N^9(@7AHo) z+&UmY&_Nd$wMB%ix(f6yOrN}si@mJY&+o255EXRaI(0j23spjF{FFSZuTnOeCve!_ zmF*wORfB0?s2CZE%^7Wk4(I`*>U0^s&Xnp{Q-nSkAIoQj@?|z|Shd zGwstYhIcfTRo}|tovs7{Cr>Y`2cpLhYTH)8E2T(!WnNS3NiB0%d0QE7Eh05$l5%Mw z4|e*{iV~Wtj;mWx&+vSfiq0>7lshT=3wbYJEttkzM;f+UGzU67dz$2aed7{OV})UC zq!WN{cVnfyzP^^lpf)PTA$MT4VH4`>CbR&_SE5&|0j@z1D#L*QQeuRH`wjXK^(J?9 zWnF!vXMT;Bh0f46pDsAUWHFPWIR-d7FG(42PpheHk9k zLV&D_`WEEmhTuduEmY?NHS)n6QUOl&Kd2F12t-25db0 zt1LP$_jc0h5%9gnCv6za626f84Y~&MOJlMX>^XE5OeZk#HdU8np6*v|T!X-YPl1tC zp*K!wE+c&OQgRcT?e+kxUiUI@V?Efjg3;Aaf(uDr)6R2J+9I2Y+w~^gIAyY zI;2^7?3f%;VFjKccF{vbOnT5qmjzG==}EfU>N8$AQi2dUiJkM`2ZB zc)omhDZes#1`U@qF#<-#%xER#Pym7%_OjBHteP~-&@OB?s#n_rK?A}4WFJME#``#D zwF}W~=rMO%m7&vkLgK%`sZ9&38{A8(s=@d4G__ytbc0f?X@Jr&jmLzVz$s(u8!^`) zzNght<*Tpcxx=TRLx|ydinp1Ss=7dchONr5hd~!1SE!c`KwUi0IjR`8a%)myIuGtv z2d>vx8N|H}WSA^0Xzasb%J%7efV?P2d4D?Z&T+a#onC(y$7?xWMWMM|io7pfd2j~z zIL8{=ii~c}RK9BBe<8_Z>zm8yg=t8~I@ls~8Wf^&s=yLFE(BIog!0u)-ft+qenDM< zrqTX+iLZ!wLLHtiM?FDbg~aJ+Ac?P4faWYrD`_MOUdiN)FPt{5PC{8 zrV{0vkbh>P^73q+lt!nmv~gP1c2&XhBec|-W=3z&dB{LmM|HnqoYG?skIdK)7DpQz z!N|}>7eX!$2BjLI3eqB$W}Tqw(=s^}Urd0Ny=6RxCBRo1D-CUs>yhP5op#wBD)|X@G zW&;Y9y>ofj!DjRwZJdPpOUxyLAn-(gZ1Q>T5;&%M$}rFbXxAkZ&Ud` zX*e)8AM7DCT~Qc4Q!Lm*t*ix{LBlweIUs?<~iy(2LE z)0ilF2i{JmnO%e<4Z0p7i0#tF{Ox5hz+meI>4LF^x$Gcj3PM7a(u@rk2nDLzWLc}x zx=yUo# zC>I8s(DCL&Jggt2KxgaT1|!6WQr~ctFiPkxqe*Qco)H?-S}Z;Y6iyLr!|+s(a?6); z(lokh3{x;S#%R)X1ihDljbL4Au3pHTsLR-z`uV6lU6mQYQUm)X3`!rA%jk_^s2oBk zcxeLa-dL}!`&^Ed&$yK@ygb={3wjAPf_>jd>ArwZowM8ybBmzo7sf@I4@xY z7t0?kbetwJ8Qrg?8d=C>-~#v8H#D)Q(d$$JBrLx~dNOOT0|uZGduqsBw@l9igoLg; z26P4%NdgCt!a~q;$cUzvMsH&JiuP{SmZ4l)#bag1R5G$8Dd{fW(>E5mHdhHfk=|nU zE&{g6Xqe01KoOu5h7J~{564AhP(LaRZC==4x1(20Y^-2(?WU$ssrE(8d21Qnqgew5 zl%}4f12(ar(3>$ry6Z@Jrboc*`hhX^PT3vI33S&lfjM8${2NGiO}&QgnqTOhEC{z{*jZmSS|3vU@5eVhcywV+bNv^fVOpD0Y`H8@{%XuIap8#GA~k!OGXbd3glggV#>Z zCDj#B7Z0}^iHBD*dbC)u7rH<{WM=3}Yz_k*n*_-Rv^N9<3aZqb<-$Dh8+2=RKosUY zMtkJ6VN@M{He_hJBTOpMx$Jhdt6pv5i)i~~C^HPN3aU#9CmVQ|T)Kc3jMz>I1?@I0 zud=H^naM=$adqW2U=tWP9O%yFuxWX+lH|y24?g`7uv}-p~MO-KQ&gX zHJCv3VRN%!@QmhCc;*)nN9WzW&Us7 z=<+##8zDRW9dvsEA#rx{D&Se|RU6D&p9G&gf&M_0byuOMu&Nrx)xsO(lf$qA%foA@ z2U94fbq$~lTQvav12W@2NO?`T!Gg6B)G#hN6o@{%Bw(#_Il4Vf2AddNYoO}Kl`XJa zFdy(IEN_{D*6Gu7aW}3YuPPR92D8G(foT?_10$*hjgD;rFRFF3eWi<@t%P?=4r`6q zYnX|?iWgA*Yg_hSDj%1&)0V7>?W>wFDcuZR>9eoo`5AQfjRb<|HVc7Zb Date: Tue, 23 Aug 2022 12:52:12 +0200 Subject: [PATCH 315/373] change the balance format --- apps/src/lib/client/rpc.rs | 168 ++++++++++++++----------------------- shared/src/types/token.rs | 23 +++-- 2 files changed, 71 insertions(+), 120 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d39aeaddb9..7bbc0feaa8 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -131,8 +131,7 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { } (None, Some(owner)) => { let owner = ctx.get(&owner); - let mut found_any = false; - for (token, currency_code) in tokens { + for (token, _) in tokens { let prefix = token.to_db_key().into(); let balances = query_storage_prefix::( client.clone(), @@ -140,132 +139,87 @@ pub async fn query_balance(ctx: Context, args: args::QueryBalance) { ) .await; if let Some(balances) = balances { - let stdout = io::stdout(); - let mut w = stdout.lock(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, o)) if *o == owner => { - writeln!( - w, - "{} with {}: {}", - currency_code, sub_prefix, balance - ) - .unwrap(); - found_any = true; - } - Some(_) => {} - None => { - if let Some(o) = - token::is_any_token_balance_key(&key) - { - if *o == owner { - writeln!( - w, - "{}: {}", - currency_code, balance - ) - .unwrap(); - found_any = true; - } - } - } - } - } + print_balances(balances, &token, Some(&owner)); } } - if !found_any { - println!("No balance found for {}", owner); - } } (Some(token), None) => { let token = ctx.get(&token); let prefix = token.to_db_key().into(); let balances = query_storage_prefix::(client, prefix).await; - match balances { - Some(balances) => { - let currency_code = tokens - .get(&token) - .map(|c| Cow::Borrowed(*c)) - .unwrap_or_else(|| Cow::Owned(token.to_string())); - let stdout = io::stdout(); - let mut w = stdout.lock(); - writeln!(w, "Token {}", currency_code).unwrap(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => { - writeln!( - w, - " with {}: {}, owned by {}", - sub_prefix, balance, owner - ) - .unwrap(); - } - None => { - if let Some(owner) = - token::is_any_token_balance_key(&key) - { - writeln!( - w, - ": {}, owned by {}", - balance, owner - ) - .unwrap(); - } - } - } - } - } - None => { - println!("No balances for token {}", token.encode()) - } + if let Some(balances) = balances { + print_balances(balances, &token, None); } } (None, None) => { - let stdout = io::stdout(); - let mut w = stdout.lock(); - for (token, currency_code) in tokens { + for (token, _) in tokens { let key = token::balance_prefix(&token); let balances = query_storage_prefix::(client.clone(), key) .await; - match balances { - Some(balances) => { - writeln!(w, "Token {}", currency_code).unwrap(); - for (key, balance) in balances { - match token::is_any_multitoken_balance_key(&key) { - Some((sub_prefix, owner)) => { - writeln!( - w, - " with {}: {}, owned by {}", - sub_prefix, balance, owner - ) - .unwrap(); - } - None => { - if let Some(owner) = - token::is_any_token_balance_key(&key) - { - writeln!( - w, - ": {}, owned by {}", - balance, owner - ) - .unwrap() - } - } - } - } - } - None => { - println!("No balances for token {}", token.encode()) - } + if let Some(balances) = balances { + print_balances(balances, &token, None); } } } } } +fn print_balances( + balances: impl Iterator, + token: &Address, + target: Option<&Address>, +) { + let stdout = io::stdout(); + let mut w = stdout.lock(); + + // Token + let tokens = address::tokens(); + let currency_code = tokens + .get(token) + .map(|c| Cow::Borrowed(*c)) + .unwrap_or_else(|| Cow::Owned(token.to_string())); + writeln!(w, "Token {}", currency_code).unwrap(); + + let print_num = balances + .filter_map( + |(key, balance)| match token::is_any_multitoken_balance_key(&key) { + Some((sub_prefix, owner)) => Some(( + owner.clone(), + format!( + "with {}: {}, owned by {}", + sub_prefix, balance, owner + ), + )), + None => token::is_any_token_balance_key(&key).map(|owner| { + ( + owner.clone(), + format!(": {}, owned by {}", balance, owner), + ) + }), + }, + ) + .filter_map(|(o, s)| match target { + Some(t) if o == *t => Some(s), + Some(_) => None, + None => Some(s), + }) + .map(|s| { + writeln!(w, "{}", s).unwrap(); + }) + .count(); + + if print_num == 0 { + match target { + Some(t) => writeln!(w, "No balances owned by {}", t).unwrap(), + None => { + writeln!(w, "No balances for token {}", currency_code).unwrap() + } + } + } +} + /// Query Proposals pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { async fn print_proposal( diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 4d03138e60..a4a123da12 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -344,19 +344,16 @@ fn multitoken_balance_owner(key: &Key) -> Option<(Key, &Address)> { // token, balance, owner return None; } - match key.get_at(len - 2) { - Some(DbKeySeg::StringSeg(balance)) - if balance == BALANCE_STORAGE_KEY => - { - match key.segments.last() { - Some(DbKeySeg::AddressSeg(owner)) => { - let sub_prefix = Key { - segments: key.segments[1..(len - 2)].to_vec(), - }; - Some((sub_prefix, owner)) - } - _ => None, - } + match &key.segments[..] { + [ + .., + DbKeySeg::StringSeg(balance), + DbKeySeg::AddressSeg(owner), + ] if balance == BALANCE_STORAGE_KEY => { + let sub_prefix = Key { + segments: key.segments[1..(len - 2)].to_vec(), + }; + Some((sub_prefix, owner)) } _ => None, } From a360b0282a1c6e150108a4e1e14cd16f9de99b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 16 Sep 2022 18:44:51 +0200 Subject: [PATCH 316/373] changelog: add #359 --- .changelog/unreleased/features/132-multitoken-transfer.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/132-multitoken-transfer.md diff --git a/.changelog/unreleased/features/132-multitoken-transfer.md b/.changelog/unreleased/features/132-multitoken-transfer.md new file mode 100644 index 0000000000..2b913d7d19 --- /dev/null +++ b/.changelog/unreleased/features/132-multitoken-transfer.md @@ -0,0 +1,2 @@ +- Added multitoken transfer and query for bridges + ([#132](https://github.com/anoma/namada/issues/132)) \ No newline at end of file From 42231dc0eedb8e4a996cc9a832c7c8c02baad834 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 16 Sep 2022 17:16:56 +0000 Subject: [PATCH 317/373] [ci] wasm checksums update --- wasm/checksums.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 1cf95608b2..9c1ce45fe8 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -14,6 +14,6 @@ "tx_withdraw.wasm": "tx_withdraw.eb07fbbde79b5c2355a4f73a30355aba1e64e803eff29cfc8161fbda946b2de5.wasm", "vp_nft.wasm": "vp_nft.1613c09ad9ecac50584160bc8f4eb1b1acc2d96c5b51e91ac69ad17a439c01d6.wasm", "vp_testnet_faucet.wasm": "vp_testnet_faucet.598942468d0cb42d987be7b93f2efaeba98d837441c5a6971217c0dff6161064.wasm", - "vp_token.wasm": "vp_token.4988b829c7825aa101ac47903d3e363ab38b6887df95eec739e1726e3b29234f.wasm", - "vp_user.wasm": "vp_user.bf58f7316e142118328e4c63486e53003812b142b9b5927c83681cd0de0f74d8.wasm" + "vp_token.wasm": "vp_token.e3df9b76b573bf1a4d722073f7573822a7504e9ccba65c641ca54067943c009f.wasm", + "vp_user.wasm": "vp_user.42678c7c959fca64e6003b9b84dbe66cfd22797e7fd22047d352b3e61f58b17c.wasm" } \ No newline at end of file From 8b3b1406456d433141ee47fb4739cf1b96bbd5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 20 Sep 2022 19:01:00 +0200 Subject: [PATCH 318/373] fixup! Sync with 'main' --- Cargo.lock | 12 +++++------ Cargo.toml | 4 ++++ apps/src/lib/node/ledger/tendermint_node.rs | 6 ++---- shared/src/ledger/storage_api/error.rs | 2 +- wasm_for_tests/wasm_source/Cargo.lock | 24 ++++++++++----------- 5 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02fd1365bd..ac8e31770d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,8 +311,7 @@ dependencies = [ [[package]] name = "async-io" version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" +source = "git+https://github.com/heliaxdev/async-io.git?rev=9285dad39c9a37ecd0dbd498c5ce5b0e65b02489#9285dad39c9a37ecd0dbd498c5ce5b0e65b02489" dependencies = [ "autocfg 1.1.0", "concurrent-queue", @@ -322,6 +321,7 @@ dependencies = [ "once_cell", "parking", "polling", + "rustversion", "slab", "socket2 0.4.7", "waker-fn", @@ -340,8 +340,7 @@ dependencies = [ [[package]] name = "async-process" version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c" +source = "git+https://github.com/heliaxdev/async-process.git?rev=e42c527e87d937da9e01aaeb563c0b948580dc89#e42c527e87d937da9e01aaeb563c0b948580dc89" dependencies = [ "async-io", "autocfg 1.1.0", @@ -351,6 +350,7 @@ dependencies = [ "futures-lite", "libc", "once_cell", + "rustversion", "signal-hook", "winapi 0.3.9", ] @@ -4697,13 +4697,13 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" +source = "git+https://github.com/heliaxdev/polling.git?rev=02a655775282879459a3460e2646b60c005bca2c#02a655775282879459a3460e2646b60c005bca2c" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", + "rustversion", "wepoll-ffi", "winapi 0.3.9", ] diff --git a/Cargo.toml b/Cargo.toml index 2bb0d8ad10..3ce019636e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,10 @@ borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4 borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +# The following 3 crates patch a work-around for https://github.com/smol-rs/polling/issues/38 breaking namada tooling build with nightly 2022-05-20 +polling = {git = "https://github.com/heliaxdev/polling.git", rev = "02a655775282879459a3460e2646b60c005bca2c"} +async-io = {git = "https://github.com/heliaxdev/async-io.git", rev = "9285dad39c9a37ecd0dbd498c5ce5b0e65b02489"} +async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = "e42c527e87d937da9e01aaeb563c0b948580dc89"} # borsh = {path = "../borsh-rs/borsh"} # borsh-derive = {path = "../borsh-rs/borsh-derive"} # borsh-derive-internal = {path = "../borsh-rs/borsh-derive-internal"} diff --git a/apps/src/lib/node/ledger/tendermint_node.rs b/apps/src/lib/node/ledger/tendermint_node.rs index ffdf3b5b9c..181f72cb4b 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -185,11 +185,9 @@ pub fn reset(tendermint_dir: impl AsRef) -> Result<()> { /// Convert a common signing scheme validator key into JSON for /// Tendermint fn validator_key_to_json( - address: &Address, sk: &common::SecretKey, ) -> std::result::Result { - let address = address.raw_hash().unwrap(); - + let raw_hash = tm_consensus_key_raw_hash(&sk.ref_to()); let (id_str, pk_arr, kp_arr) = match sk { common::SecretKey::Ed25519(_) => { let sk_ed: ed25519::SecretKey = sk.try_to_sk().unwrap(); @@ -211,7 +209,7 @@ fn validator_key_to_json( }; Ok(json!({ - "address": address, + "address": raw_hash, "pub_key": { "type": format!("tendermint/PubKey{}",id_str), "value": base64::encode(pk_arr), diff --git a/shared/src/ledger/storage_api/error.rs b/shared/src/ledger/storage_api/error.rs index d01fbfd287..c5e602c464 100644 --- a/shared/src/ledger/storage_api/error.rs +++ b/shared/src/ledger/storage_api/error.rs @@ -15,7 +15,7 @@ pub enum Error { /// Result of a storage API call. pub type Result = std::result::Result; -/// Result extension to easily wrap custom errors into [`Error`]. +/// Result extension to easily wrap custom errors into [`enum@Error`]. // This is separate from `ResultExt`, because the implementation requires // different bounds for `T`. pub trait ResultExt { diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 98263054ce..cd7a6a1e67 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "derivative", @@ -1594,7 +1594,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1613,7 +1613,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "namada", @@ -1625,7 +1625,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "namada", @@ -1633,7 +1633,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "namada", @@ -1645,7 +1645,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From dd13e3300f215f25a5a59951e2631af054919df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 21 Sep 2022 09:48:12 +0200 Subject: [PATCH 319/373] rustdoc: fix more broken links picked from #324 merged earlier --- tests/src/native_vp/pos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 0cf9382673..528b8e9492 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -23,7 +23,7 @@ //! ## Pos Parameters //! //! Arbitrary valid PoS parameters are provided from its module via -//! [`namada_vm_env::proof_of_stake::parameters::testing::arb_pos_params`]. +//! [`proof_of_stake::parameters::testing::arb_pos_params`]. //! //! ## Valid transitions //! From 1ef0ef0d1e9abcac1b5460ae54127232363ca283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 21 Sep 2022 10:32:52 +0200 Subject: [PATCH 320/373] fixup! Sync with 'main' --- shared/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- wasm/checksums.json | 34 ++++++++++---------- wasm_for_tests/wasm_source/Cargo.lock | 45 ++++++++++++++++++++++----- 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 6094550024..f183a385c5 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -57,7 +57,7 @@ ark-serialize = "0.3" arse-merkle-tree = {package = "sparse-merkle-tree", git = "https://github.com/heliaxdev/sparse-merkle-tree", rev = "b03d8df11a6a0cfbd47a1a33cda5b73353bb715d", default-features = false, features = ["std", "borsh"]} bech32 = "0.8.0" borsh = "0.9.0" -chrono = "0.4.19" +chrono = {version = "0.4.22", default-features = false, features = ["clock", "std"]} # Using unreleased commit on top of version 0.5.0 that adds Sync to the CLruCache clru = {git = "https://github.com/marmeladema/clru-rs.git", rev = "71ca566"} derivative = "2.2.0" diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4aed0b5e85..79c1fab152 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -15,7 +15,7 @@ wasm-runtime = ["namada/wasm-runtime"] namada = {path = "../shared", features = ["testing", "ibc-mocks"]} namada_vp_prelude = {path = "../vp_prelude"} namada_tx_prelude = {path = "../tx_prelude"} -chrono = "0.4.19" +chrono = "0.4.22" concat-idents = "1.1.2" prost = "0.9.0" serde_json = {version = "1.0.65"} diff --git a/wasm/checksums.json b/wasm/checksums.json index e2d1296d87..e2f2d87b50 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.e57051db9c794067b0203363e135d0e0fda3be4ee6af1d283df0e921029e1efd.wasm", - "tx_from_intent.wasm": "tx_from_intent.479d4a2caf6b8c6b569db2fb8ec741a1304b58c167338fd6e1099b08d41bb046.wasm", - "tx_ibc.wasm": "tx_ibc.99da4fe30ebe90427103fadf999936c3d60b1d6ea25c68ff0d3b9a7d479173fc.wasm", - "tx_init_account.wasm": "tx_init_account.983c1a26ef1337b843ab243662490f56cc9e4a35bd6854959a678e57e4bcfa8b.wasm", - "tx_init_nft.wasm": "tx_init_nft.12330bcfbc19bab368b643504b5a40473ea19fc936407e4e79b7259a1b58951d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.6397eb2a01f5574ef745a38ea1f8fcbfaa86f68015cccc21ff455f63be215f43.wasm", - "tx_init_validator.wasm": "tx_init_validator.f557a23db47bb5b306cf89f259eb46702492b4df49cc6418dc405df78b72332d.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.766dbf72a8441689c9b03f3564c59c73747729c9231f5ab30d37f7146cb4bb99.wasm", - "tx_transfer.wasm": "tx_transfer.f75183c2dc5bbab3af9044327bc22b1c38f633ffe959e6170d82943cf4924a94.wasm", - "tx_unbond.wasm": "tx_unbond.41556156ddb543a121b70b8517d7c9ab821c1591923b15ec16cf9d7a01e6396d.wasm", - "tx_update_vp.wasm": "tx_update_vp.f2632c6b949a5383284278bfd059aa538d7b26e521a7aebc823989d78d7354ad.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.7646435ae6f38cc6efcf50eff0763139a632808fcd1ff7951c784b5631f54836.wasm", - "tx_withdraw.wasm": "tx_withdraw.a893bc7fc00c3d670d53c25235d50df98aaaacc2877c500adafa182a658a477f.wasm", - "vp_nft.wasm": "vp_nft.8172eefc935351b9b04bf094270198ee76e9557df230d00e3ff859f0d5e67375.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.229b3e44ff0458b5ee5f58b962f90b46568c09faee20f8c0ea279ed3c774c056.wasm", - "vp_token.wasm": "vp_token.6838583ee06be081a0643754acab19aadb865a9167386b301c69991756437235.wasm", - "vp_user.wasm": "vp_user.a6d7344a8a78360b50a86e0a1401ab509d1cb6110180c0e2b1ce9089b18eb54a.wasm" + "tx_bond.wasm": "tx_bond.fd431211cffe6678a4aab29c220d95f34bf44f9dfe5b7deeadb48f389c494003.wasm", + "tx_from_intent.wasm": "tx_from_intent.4f1c180362177f07a37e24e836dc15fcb1c98c0ffb3613312ff0647f3a6cc881.wasm", + "tx_ibc.wasm": "tx_ibc.0006a82894609d9f9bf474f77a62dca4dd58f8a7ceac6ccebd396391418eb75f.wasm", + "tx_init_account.wasm": "tx_init_account.44bd4e179a6a81ac725caabab958d366b33be6c31f8ba884116ef7cc1398eb7f.wasm", + "tx_init_nft.wasm": "tx_init_nft.878b9dded41ec4d89b81e4b4fa89c5d24359e20eb49d1936d47884cd155ecb39.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.8387f70ea09f674af60e9134af351f698ff8a183a9a1568c34ec7a6074292e22.wasm", + "tx_init_validator.wasm": "tx_init_validator.b090e9ba8ea94214d7099d43d2af7d46a1587e6d9ce73183211f33b454830880.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.adbcd0a341d04538279c55c083cb4ac4c8630bfd3d3cb90e721af5a26212ad57.wasm", + "tx_transfer.wasm": "tx_transfer.ff667b75188859a0c8803beb2d9b3dd3c2d2753191003f4ff1d5fe7fca56f3e9.wasm", + "tx_unbond.wasm": "tx_unbond.a52c41c58da4e54f10a423a0f6519dfca15c96757ea72a8168e945900329d34f.wasm", + "tx_update_vp.wasm": "tx_update_vp.18fbdba6e4a7d158daaf8e9c7aab4fd122a95bafc50e486a903ad03c287c6b88.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.5600006579e70de5ae488c47721cd6366f52550da59eac9a6452fc9056ac5065.wasm", + "tx_withdraw.wasm": "tx_withdraw.044f4ca72eebbef1d3f7ec95f6c557e279eb4c3e4c994eac6fbc5e76b7afe1be.wasm", + "vp_nft.wasm": "vp_nft.6b40c4d28bf92db475cb3d3634cde01c2a0aa4fa7b7764091244dc2cb402efa0.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.3dc90813260f06bb64ba3bf8f0c39c0a45aaa1e0466edd0f8e03a16647e202ec.wasm", + "vp_token.wasm": "vp_token.155172a21c3b958ffb9c10899c4411fc2fb12c6cb882b4f8b6aaddeee0331d48.wasm", + "vp_user.wasm": "vp_user.b764c772d0d5083c0450aa69bcc672046ffbb4c0ec1b3977e90d391249fd8c08.wasm" } \ No newline at end of file diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index cd7a6a1e67..d69927cade 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -37,6 +37,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.59" @@ -383,14 +392,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -421,6 +432,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.2" @@ -1184,6 +1201,20 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "iana-time-zone" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi", +] + [[package]] name = "ibc" version = "0.12.0" @@ -1340,9 +1371,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" [[package]] name = "libloading" @@ -1747,9 +1778,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" From d5d2ec671bbbaac9ac33ca5ee54b6f8134c727ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 21 Sep 2022 10:35:27 +0200 Subject: [PATCH 321/373] fixup! rustdoc: fix more broken links --- tests/src/native_vp/pos.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/src/native_vp/pos.rs b/tests/src/native_vp/pos.rs index 528b8e9492..ec6f75a15f 100644 --- a/tests/src/native_vp/pos.rs +++ b/tests/src/native_vp/pos.rs @@ -23,7 +23,7 @@ //! ## Pos Parameters //! //! Arbitrary valid PoS parameters are provided from its module via -//! [`proof_of_stake::parameters::testing::arb_pos_params`]. +//! [`namada_tx_prelude::proof_of_stake::parameters::testing::arb_pos_params`]. //! //! ## Valid transitions //! From 9eb27059642db42d524b67bf95b9a9bb72115726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 21 Sep 2022 16:25:22 +0200 Subject: [PATCH 322/373] fixup! Merge branch 'yuji/multitoken' (#359) --- wasm_for_tests/tx_memory_limit.wasm | Bin 135517 -> 135517 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 226159 -> 241603 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 213719 -> 213875 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152558 -> 157244 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 218648 -> 230399 bytes wasm_for_tests/vp_always_false.wasm | Bin 160766 -> 160758 bytes wasm_for_tests/vp_always_true.wasm | Bin 160766 -> 161094 bytes wasm_for_tests/vp_eval.wasm | Bin 160961 -> 161715 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 162919 -> 163755 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170873 -> 176873 bytes 10 files changed, 0 insertions(+), 0 deletions(-) diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index c31821bdf45cc9da95e876de939171236563b048..ff162b561b3d58cc4013c777fd8a13b2ec3351b0 100755 GIT binary patch delta 620 zcmZ`#OK1~O6n%FxX;NOalQh4kiJ3_g9TROPY12>AN?%19l%gmSms&w3f`U>I6)7mC zu1vWo2wJ2?(?xJIil0_UX%H!h1{60IDs-nCK@jT3+Xk0z&b#-Wd)_(kuFl$5XYHl3 z)NGqBho;|N(Zf4D9Z~2_wz_YMUYDwr>7AES+ z3}un24;Mv+YDLGf!Be1{wZS#Xsf)!;-bRD92Kjtw1$3McAsU$xU#yl+{uzUYf%79l e>(**;Fdx98ah$^T=P&(dJ2#i|FP8G~;N4$18>AHg delta 607 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

{6HWwQ>+63Ojxu%B` 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 Ktg;7q>Hh-)`-ndP diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 4f19c38cdb3b54224d2cebd493660c22186e1c39..33fb94cdb95d4aa4c1d36af188d3bee4dace9107 100755 GIT binary patch delta 30526 zcma*w2Yl6J+xYR5)Amqk3uSLOEgRWeL?s{yGQ|brLPVh;TNKwp2}40d62yTB8Uz6m zXk;iVNKo9MQHBH1fGD`5;(Gl5uB!#B&-;GfKR#)$Z|)>la_7!{C#U1tJ0s`EnK|

mrA6k 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 delta 30494 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 P9#zXn7gxvCKO6o7POmLC delta 38548 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

nRMNdMP=d33QCtd+{ zGjJ^J4NMW0DY_*facl*~kApsqxUhLt8@|g1m8zZS^!6{<+F7m^-lAR6nm>FQ)Fkyt zgko-;W~w^3bgX9Uf3lcI3qEelaSIrp2o@!sq9eO#*)#k{WV7rZZC7Lw5lwMN%p!(|wWr zPKa5NU_Umto~0{54IKWaY3$!qK1KcIHXkmS70=#{M3VGV z;FkV%gSoBiHV^dY`nT-ZJVcAZd*}aLuH#_ndUUq8G=iA)CuYZ01OttdEj|wgnWV>z zXK9V{Vu_}80f6J|61@aYa@cW9{=fCM74$T?+in3;n{pL)DmCjx`XY3I5$RJUb!+rA zP>OcwD1Lk=9|j^RRgw0?{M-h=CH;Gh*ctRrx97sFe$XsfszlKw;z(y; z<3`X^t5#A3w7CZ2K-vwIGF7GZG3YSx?0^*+D;_x^w!5ovQ}IJ9UWfVPpt%}UMabJM z{Q{U}u?a}?1o)#u4j>e~!t`k{PE|gqx`PXkjnVQ?g>Dc+p2bYwI2)b+tnKK&8fhbH zHZ3v~R4lm;Ul`7N4fp2=7$@A&tqaUjb2RXq$C;xb_tkWBk_tAu6=Ym#jGN$^KSg&y z@t!RG#_=Gt!{L|M2PIe}d&p)KKNLYF92TNuA)b^)Mc;}5#+;q3VT5*;%8uem_W?h2 zqZSp-P@4ajBBpYf5Vc)Mmd*}{AoCNw`GxaAa=NAYO(1opH5AWOl8#N!65O=cnImLE;ikFF_Ww7dX~2kcGPyUxDIl3=lQ{mFZbDFj!wEM4K3d+Q zAmr7+S-0!T)nM@)_HyehSA%JCYD}GewFMG{t4eZXO(fCMFzAGpa2l8~pUct@&KILk zX=>RWc#?$wsSG|MFQM0gal;Fss!An0nR9EY=UnB7?xh3Z;#xvNJGmzrPgkPrVNim) zwHs{Uo(s*l6w>ue9*aOa;M#3U%@V=68EEJ59QsnAV319kC&B!S*SanC4*(@Nr)XYN z8X(;X3)Ca6s~*HMY!QA8H64z)wW4p0 z9&a8vIpw?c_7ix*_u|&~J&Wk0&twHdw^FPW$E1E&RfhjFell4sE1$0TX6ykDEV{pW zVuFF(<{wmmHR5?XJ)GW<70U2 zlepEyb9uHqo$ov;vpb)1ADi2Yvei1h(HYCrhz9!C)GA`z;)<$xB12bjBIYCup5wnh zBf&tP{G~~r$TL5~08oKxqXCoN2@TA@KS|!(W+gnsk3cWMQg|e7N^}=b_ae2fHqYnz zUX1xF#sQWyjW4pt{N{rB!Vi48S%=wfy^tRQB@?zAPt#k?6ZysaLrzc$CfCp zxE2o|W}3Yb8=vqky|ga*;2wGz_LVg<`o8b3Sit2{bP$uruAt>J?ZT#bbBn(*P4~}W zbPxSya_FY$by$j)gwSXE46Gs0-;ZA`AL2-1H_#*LoO|h?a660I14-BqUbjP!!^XYK z_abH{#WMO*RQM1IsaLm0)Bvdek$iBP-eXq_v41MQcQoK)x67MP z&sJU6wJhR!Ug#L-`pqBn&Oa|2i8`UM6Luoa9R3%Hg=67x*a?*G#*PU_tx#yjK_BjVty#9UoJb!4|i{$>f#(z4zQ)Kx+ z*DQ%g+Bp{RyG7?X(IrntUv1`W=HJ=;c&D=;Yth~5;`eNEbj=pkPAZ%Vxs`SO!j&n< zz4fOxAvcx^v%&p-(&|uh3*TM5lk=!wJ-LhXh~M(Q*8b|1ZTu~gog!cVy2jr#SsQO3 zN_>4!c#74#^AI6d7FAEERD()VZ(zeNAYaP-vG>2q){@=WtIi z6zZ=H<#p~8wxPkR8sS#DQ4SQd1H}g2GJgE{8Ik_sXeyS9^b1oAij7)q)NL~$RL;kS z&(?(cu?sudE>3mlN+&^~buMMACe|kus)-K>Ep)t8%ysIj$*t>*u8uaGX#JVRn^Pad ziSR&<89jj0jJdJW&M|gQ#cD%NO^Ygr1lqWv>K2t@oqOr5Tl|}|ZaB&EI`2Q9b&G#{ z)-C?*tSieBoPUd<(Sde7{wz=4#!Im=Q~zJ{)WY?KSCyw)C{Jy?!Bx#In^v4{xH>J8 z7k07_N}22ru5!3)5xZIuyIQG8RhaX2M!S0qtBP(sud4s5tpDh=Jga^iYgpC4E$ga3 zEo(9xc0UN!xeoO$6&@7sA0Ek;X`fg<96^Yxy4KDm?5=ibx5p^B(5dtKg;%rPt*ztN z`cPdmQJ)#wO(g{p?is1=&mkjjx$UuSU2T{nHsqKBs@#~T*OZ#odTSV!(I%BK@n;3( zHEDAHO%qJ~+XCW}{c#gi^J`i_PN{%adyOR&{gV>3vpv_HMry#IHPw16D#O3_ zR@P0m9T4*DbK!SQyDQ6<bg^H*h}@ZhTd+I8tP>(U0q*A=_-3uEnAcdTy?UcqN>i;vWXhYZ`IROxHi1d zq35Jh*M^sK*qLgq{lYUG`pK}8ad-HaCc#)+Y7%^-cghR9S#9Cyn>nW&ch@>A7YprF=UM|Kv_s=IXBTHU*>9r^ z+m4i@?Nwi5ZggyQSGK$ss;h3xI6-$;6RWOBwW*GWy@8Q|c9CqT8pcUf46_T*$&9XM zNVnYXRb%avuU46Mt!`!?+wyfjcBrmQ#j7I`JJ)C(2d<*4tmdk7=H)hCg{iqIszfT5 zY-3+Y+V!dpx3P*IqZnK-R2`wFI0^DqX4&8Xsl6Pl(o#Lkl<yPF)Q>$xfVd`A~jUQl8;dxf?rxR!&!tDj!`F zt4nQ`DoPIY={(>HuA( zit0#ZC_!YnPiViYxP1&C>{3HGbd8F;cTH?aXNz&20ipdYV%t=tlu*aW=mgi2j+|?6 zGb=8F8m;GllW*sxDmb}{hMs6MyUeMI-Nw|! z-T1hg^0DYQXACF0!5Lc9tSZ#0bW(eaNhR1}EW5|u%5k>a*UnIjj=QbKF|+?VG{*Lf zxD9)bO}O3nt}4}LHgfTs*)$`PDj!=Nvl*OKdXjRx52^CPohswXdDy-mv6T3N(xB+6 zE|w-=qdEOSdfI<1c^*H0En`Z&bnNlUpKEPseA2S&MqFzkKDm!ew189M_VZ*>gXX8rI=KtI?mxp ztp4=lHrKblg1qg>0*EuDFsGH5t+yLD*hM(Ze!m`+1<)2GKQyW{xlm)y2{Fjc_rsks!-O?B`6Q-B^Sj?s#IGVkK-v~ zV|9r#whW{*EGPGL0o(NppOOj>T2vY*kDjXXil3`jMhi|V{PSXrtWBO1GeH{B)t2fIX??&3vV+Ty<)hRWSErRX2*LhEh1 zOY9GJiEp|~{10~V%4kMS&Z%tUKC~`7r>I+Azs*TdtlYoUM6JuP?dmHipEzMPLf1Q+ z3bp9HwO2&Wb}yYUseJSpwKa>3TV#CG$g%#HeOh?Yp;hG^njwmNulg>?3OiyWr82j- z+EK*yhN$YL%7=}$DVDC0?O{XWDGVXpg`~>&t8!>`)R6(98BS%nRVeDDT~WYwH^364d)HE;B5Cn(-62 z3PawhK~^c~{l?g!&)33MVa9YG5b6M#)izEiLi=0Jbx$9q?j$$k=(H#9pQBYFOUZ7o zY>L2yf#OdQYqzYn*$bCfa%ya=14O%PIX-KFn3Mk z)>N~x)y+7Borvo6n8C`m-40M_gW~-o6k3IiHqMy(X>8*;99W&CAF1NJ+pD2OYA6Fc zon}=|Drt?fJe3^8omZ&g#3&G37+{x%Xq)jwQ%T)HrkeTfwoQ4JT!$Ji?!Hzqc0Tm~ zm1NNJUTHH822pM~-EL#7&ANZJOT_GLT#9M0?HO*^Q)6gUW2o(amg*_H+7N0xMzmvW zet(c(n3 zKE90ls$#OHM}b%ol{M^41^zcXw`km%5}|`p%PJe=#=UXOSeSQ|ra8@31a^wnnwU7TR)=i;fG^avvO;N@YBhxRP)0AQ zj9R6$Hp&oPW*@^yChSp297a)5w`N!sQzwpA-O7EHTMxoDR`jjhbi>ixp3B%oep=EJ ztQ)Q=vpW%Tc0g4HA5Amz+i$;x##FadAHX3RGuZZAE9$fB>;ihtQ!PhVx8hK*#`SAh zXAVu1B$K3K!&Wraa)?$-qVBXiIY(L_lc-cH@=xDyjj1MTeN?K<&JT|m0TlZ~Z1QG8Yrtc8$ayYx}Dkmg*(QVZcas)ex1 zWi7lTvVVzksGJEpS_&xH zRbQ~WDYNiFM@>Ve<)$JY3+6P>`i^eALj|S&Mctbdw0eHg0I0)dsh0IkoeMvXdXQUG zDz}=J*3x6-j9utA$ocB}C@N!fcWuLdk*cWO^J)xj=mo5xJi39>OuekhK;_29rON2G zt>Z|h%91rUX7IbYyRPAYWz00|BNM6ismjrIbKRb)aVP7(n%PL{c6Vpo!4r$kt!;wU z4QNSYcNa#1ccpCs(rGZ|7pL@UTprL-<*kqQj-ToCdCGY6B*IJ|DkQLHC+~!+#*WyIK?Ia$qtrP zK5eDI1_17Um6dRcWxfQLRZGBK8W-3)uxXuBI+ESTRY&d7ONeoz2Yr!SW+Qu~v^2~r z7!!)}`sH6vGS)L3>K{oFvWF|SBo`{>OOK!)xl&iup;3Vnpq8^=EpHNG>E^gFq zE@M4aDyEx-K~pq;5AVRSnAET|VJl($uC41CAq}#Uiu8=6`bTWa5~IJ>VsykEI6Ao< zRgVL6P`gti)QtOoih`vnt7{P!tgS{RJ&tOtR#B@j3NfLx^(%_2PpE@iX3G|FOQr*h zrIt)-NyJu1S;Di5Np|Z}<;t-tX$|X@J89u1WTOp<)*pz8e$xY4igC~GOl(3`Nq%hN z#+IXNwcEvc05w>9JDmY35wyjHY zRaGhN9H=d^aqb3&ipZF=Ys%fl3+B8TIh&gYZsa=JoPsGoo4?3BvN&v;>3p@}=OvSV za7r7Z+VIxLQ$EudxsTF@C67J7zWyu8F`Y?N{Uo{9oe*bhjh!_x*J0l4-u>>3x5Gm^ z+dx6KN7lqVPF3p;wj3RC!`3*$wi-CYNmUY;v>Elr)oUgry4?1}OZA_dST^(wkF!_Y?&JwJ?xc%9Tg&Ckora#mz7|>NIW@FozRF`;7La?gR$mhul$ui0ZcywX^3u#r ze_>CNSrk>0+$OmZt@HKdTiSJ2bw~SlYpd-Pd%0p$A$5v&*DxaG45A|$f7ZlQyGhk# zvS(yO;}<=BGhF&+Y2Bj!lx=vl)*Eixe%R9RaJh;VlK5!*HMDb0Yuz>3v@gmJcco$e zqmCqX!xeYC*;p5`u_<0!r(-dVmVQ8$=U6QvEsBxnzH7*y8yyf@?b!03)p{o9v`Us} zxm?_VHdg%d-?zwewMdT9v_;anj7p?hTMcNKYO=1)lCxcGn*L~SoJ|WgAckGKT@u#S zTF2FDOO;mzFBMt4{*l#|J!{47X4TLk^t5Lj-MCt_lC5b|WN`$iX| zrFPugZG3*n^HewZz(y-hhm*8KBKFX!oKlF)FAawMBMa^Mb-5c;C&sO?az^jt_F-c0 zHmCmSDZU*oUgy5|b(6ZWId$!N>AyjvUZq;$MgT!c^aua z@@Oe-CG_o=c5YKF*r#835}($ik2kZ+`Kj=rKK&zS@!|CMBDPRv)stcDnEIfRXFqh> z_E5}5bVk|IGLv1DHuMx+&WPK`Zq`G#soWN(xP`HBvZncHrZa@PP<`7nR93g6D{ZS8 zcRR{ETDM&&G-Z%8k%5Hq*A}a6*^^q#0r=5<3=bsgsyn4xH!O>BC&RVn#wx73%>gMS ztJQ7k>g_hA9}jP+#u4>9ZukH?3YJXjIB1^(Sfr1rRIEABELz(qt3-*E$JbNM>uRW| zZBp%7bxH9cs$aN^O?T*y+@QcT#%5t0#w|3!4s3^|OGtI9;~uR3=+#{;eH*>HSVFBD zQYmA9d$3V%jk*shwzk_aD?qO^$IUk!xBgr#GcMMvD&fQI09=OH^MC+g#gw$9?R-B*t+Yfv2G-Jfvti@ z-L;Lf*9s(YA z8yhoU;0(WQ>dzXn=@~BJ3>U_mwDdndLC*ib?R|J{p;o8H3qS3w*y||jbI&gAM*Kmi9ZvYiZrPf7&P`UQ z`y?-*&xpF8S(OH*>b{~;R^6{_lr@aU=uAs{+N#axHXG3Q>^^2?qih9cdgGq9m~nk$ z9OInpS=FRubxPJK-O`I;rPUSpHmaP;S%W^+c3S+U0U zQff=IZSLxHM`uf#jy;0)G~M~5@~Lc6t-Aids#bP!tu|S$8Z?2Xz+#168}4q|5LS@B zwiL&!1jN`qr*>r^f=oY3+*V|ESmOH9acuUky}vG*8FIe`!L4I;yHvMXTKVlr!E5Td$vm{3O*feFwPVJ|w9h7+ zJPj}Hs#I-SYYWFV8)-N~xTnY5g*oNM@A3jAWA-M8*3oEdHiu-{gpY;;CsGM}gq@_$ zRY%Axw>Natt$9A)F;Th+o6egZmgKb3Q-Ak5Mj^ZZN^s+xUIR~~;i6lzhTa-KS9##|e)PQVG=Pr>8QXA&|5*~A(#^lh30eeM@ z88f4k-Ll()JR$B(D@)tx=PE2@Pl)X;nCXr^K+&rO_5z3sU?lP~O>}uOa z8-#7w1T$*Bz@yokg!NEXFg(ap6=EISo?Gycc(gsE=N3>EWE#1!U85?@QrP+x-cm}? z{aPj0bhf`eJhaznn2V)a%(qK2ITf13Ee4l&AhUxW)>-g4ky7*z7`lCUeaN3UbeP}b zuuh)6qd{zGy+sAUEY~VZ=^l(LLCs=8G}VGiTmQP3WiMuR*vfl0rEC{RZ!vLIXJD6C z`mEmQYBVeWZMd}I4W@YMjVXJ3kN5bF;rn4%?=965__wc0O{-5#_2R#LRqD^)l%fsv z3$wtY=_|kKdy}dJtN%x@N-?U2UG5^etfflGjob`66W;r^DrbYHRY+UsAopRnz$#eq z9q$opZ0?IJ+aViVHEW4d(=DgBn$J>#kC!HE+kar-D zetF-RMz>Gt=@}K4-lBz7M6L-VWD8#UO;AyiEU*#Q8$n@JYWvMj_3!NRJfkAjg2_=$ z{ewMDB|^2cBSlgb_C)f3`!1dRCa1YNaooXqU+bU~sJ~+D7w-C=CH4drc zXTuK6cqxUDuEp2g>^8)zdT3DXlwCusIHsF->RipsbgXr@iDPP!GVHFhYPH(7*?X;} zI@<7F>y&Hjn*g!gGnK9{iwt${>hITt*k~8EGm{Q`;KmJa{Xsc+;{i8qQ!icfM+f}f z@$7M(-lC%y4ZFWz;;@^%5gVr!c4ch|!a7%*&Z^7fI@IsMrLwVR8rik(gzI3{x1#>; zxHjBd>r2*y)arU!H=e4fpKjDp#5{(j_HdYJ{hOlk!5*)m$LZ0VzA9?#j7?t^jl2Ky zswllEZ>%(VRg}SIlZ>(~UCLL}Iz=x?L2r^JZw5DN>Sr6W?99^h=g1JQe1-MX{AzfI z)PkjVNNt2rEOoB_9nuOFll9KDc_yMUb<*wzDkkS{S2!TpQF*ZU8Z}_CG-1VPuYAyd zj&P^(3ctG*`%)WtT(UakE~o2EcHwD4S^W@c`#0@)ZDFdKa^;=((ukriykVk2|6py$ z-XUXo*lM%es(vazX~OCdeel9f>+64f{q)cJR66fJd;2u<`?pUi9=kzi@=MmNQiN?t z*DylU146Ov)N*Wtx}aTQ9o(yQtE-pJ`?a#nEt6WXJjJBU+i!) zo%M%Dbk4dJao0P{zgr~S_14)mcfY6VBssAA8r{6xpW{Hc{a7<8u)l^o~aeAwxIo4A^z+L!EvY)-FE zL0jxTmm%0*g0MgQ8ZfGJ`x!b!O^EriRdE}sQcm)0|BO-F#!GSjJ)?R!Tlr6o8W7v6 zn(4A%oN^GkVEw>->q<_P1KP8l@tj*I6&juF%}x>jv?~rA_8)e#yGOpJ=a!9us2zxj zb|^uPLOU{Ia+Ks&{)WGP=S*Mn(lP79P6z*<iU+FvnxaFb|mDVs#Ev*=oXuhVw( zY)Pnol`LfQkvp22`O2)OW@T_eQ>W_OWlg<#ottYk3mq)%T5lGzrTvf=oSG)}EsIRM zH*T<1Mm99sRv{aY#kWc8%BCi*Yrpn)KfR-8OIM9w;}jliY7YBMQ*+pto0`ME)zlpJ zy+$(^*?0=A$b_wnHBD+d-sBW!G~I!;W;ZoyEo^Giy8kP`%^6!Xxlo&4$0=X=BhT3B zpvSn(O_P15sX4=!o0?SLYHCvbu&GJ)voHO(&*(vF4*)k7TyqM}77d#w^&R#aH;~pZO?M!ziH*l9UHhw=nzXKKG)k6vC&p~SeWmFRoY&h;O z;mA%b80pDuZAc6Iw*KN?_l5t~*;_dK`O$H^CM>`9l!tc;{5{S&)n7VpeE9k={C?+5 z2xmX_A3A5*-urV$U~^JB$z$j>vY*=2!q!TRVYN(Ytt+Z1^;DUU zEKNH;^FD51ZOrI_(jWPY&-*F7;B$Ze`8DAmKk}bHzk8RgJteX?YC`pYXsmZ}?b&T| zGe3F3kS;2qRB3&y*4`OlK;eY}7Ne6F`e$Eo1X+0Qf~7k&-hF%$Vu6CQc31OP!8Yc* z>CaJjH)ko?^q|rHvJ1P$+2gucg+F6v`z7CAIMHzu{<#;8;P(?31ueBg$NNM5d0-WD z(^szcmtMSGirntuI39~ssnInd8#~=DbuOoDA;vjBc1g-<@2~R@b-MY--rqNBJ-ok_ zzpeklC2h#=&zB5y4)&jYs85Hkr57PMd(D(WoEIYw=au$4#A;Z6e8->Tt>-8gj^BYl z4PP*Rp3~Jo^3skReHu+fP zS5DeFas?%^&!pbYa{ru3y_}3cW6~*G?acha9@@A>GD%6A?KX*3AMQabTRJx7;eC&7 z-G}|Ym+jcGHt~}aw!cL$t>yCP=P8F-sr#CL>18|geV3fLHeX@t_2U&(w=Jq|EM}pS zs$0oqs9-zrZi9BPZ2$3}!JjyJTjy#2#>s=7r~F{@R^+w(^1~Co_{%bD5SEwIF7NF0 z@~2#W0CBHfepsTH?lenr1E!>$75)G%^l$<{GvzPL&3>M8M7uwpi}`g|9KPcp zM8YRXi1Gzuu{PP@rvV|cl;x-tmgN{SN?_HZ(g}2zav-Gw$*IY%Gch)sdKkv z9Vyw{A9z*H>F57?)hK=+n!b?V&!;Cl{g#ZndUdpPxt{R5PTk3Q%s*^uUrz6;spmTn z`%!-j=QgV2Ueuy7{(NR3)KO=%B?nKN={SA;A8#1q?6PE!8xIcuZXz>9i#8jleA6*A ztCy#{&eA3AZ@${GiQ+BGop%1ww;smt2X0L{yZW!*n$ooC=Ucl~cBTf{6=X`atG{{X zGv`M?dD~G=(LepRULAj&yGhlJC&B}*8*_j37vAQw=eM^Vv(vnrx#+#z-|SKKEn&?vB>L&fT#$P) z+bluurw09U%gf(>JujQS#Y;rlnwa2}#*Jth(br_NT1aW#r6{ zEt4H=*tX@~QYJez)R_9~$R9-8b!@z6OMLeG4wai1yGlzrnu@TOzwOFId@ZE3|EL)G zmqVO+{>v-3W0i~dgZ+wCU3aDvrz2;ywSTFBJhJM=T*1-HhozUStjh8LTeZS&`q-(O z^-}-XRo&XQEoHJsHI859H2yvPJ`WA{Z(a7jv$gLnw`=~y^6L1UU&v)bm9EW$or&7^ z!9nHgY%p2=10qi{^gQ?A7WJQ-`E4KS=#PD9uMTS|f6DLot*SK64*#=p zw#~zb{Mk46_b+|8)~o*}(+-`0y{f1Y+Fn&;`IEuOt}B=N!`VO2S$fsFcb*C*=9g<+ z(>JQa3~yX+rU~JJP5-lJe4N?n<`E|xvFQ()*Mm6?Z+6vkKQf=@Zr@>c!DY5FG!&|~ zF_70f+u9cfgt!N@0#CA$+3HHZ;aX*+TCF9lL6aIfAk>YcT+tBQjY(xGHte?1-5jsc zaJO!U_?%^q`q`&LVS4WSY^a|hJMNns5gIg&M?R}HwNoBaoWKlAqKphMHGWA+bg~=c zHq}(%KB2a(;DmUilgnxA)b>}qoK>>Mv66`D@0(OzAJqgx=gr@1*Ppm8swno6`Wn%+ zR1!73Tgl&$v2Tx>u>Em0Ay&35Wb;>Bsb$rOBID09281ZC(y^A8ie!@#9uTT66>5s} z@F?n@e9{|O}1@9-m+9DIEK#Mdg*u9wBg?_B43-6P1Xy^msJ7FEu~(6k;66-D_!J< zi^5aBjaQ_HWxG#k^U`&`I(%N482xA9c1DK$M+ zn$mwf)s1J$sWjBT!RnHlUN25HtbY^0%7QM+cG%>C+EWfX0vmvp2{*L2UzKy2ZXA$1 zsx;kjdi!f~N0!3Xy?;Mj3~f5q2>I{K8y=qaorcdzEMn53pP6kKKJ(ei@ZY~}8rNlh zL)-=PE8`o!EqOJWW>*K5A%Pp>ba3f5*=A~u``RAApT4xItPBnM;G4UH8PES? z*1rCjhX!{juwtz0x9KeHL;uvZS1=X$Ztc9LQvrYdV|#b{ldnWVj=xXdb@ueH%_IG{#8#U{C%IsG5)A6G$;K_pJ^9gY{8Za-fh7a z3QpS?oM6FL+WJkgao-OuSm_`8Z2OpnnkhbS!O~&ZSTLcj%Pd%-;9Lt<`OfpDZnvC*zhA8f zYOOFYN!{-7t86e8a%F-*`uu zxInW88#~-}lOGA`7hPlxJ+ny@<7hh$BX|Suyta1*~pZlnn@cf%+TT zhyEr!%-{OuvJScWFEOccTxZx={>a##h|Ko)dwH*f?lA2a&o^#+`GTfiQuE5=(e5{V z>fD;37vfn2xsTT$D)XT_0CkD2oK*zjE2rdz7{W(_~x{qFDfZ!s{Q z)bS7EldU(6X&!EU)4fLTFml9_=s^2Fx$$$9WzAZmsYdg07%qQ29 zt-rmyi&N|G@?PKMQDw-+n5Twb1}3WP$B z@@YpZ=X~w@O!x12zwdU@X30=<)QV3#^LBh%E*kgZTb=Ql{KWW~?nge@vuDFGPhsyZ z*m@10<$T`AXPnQ*Z~JF{zzye;Ss&aTt3Ibi(&n9^Vf@%s5iGCXXcCf!qti%8*bkgeLQ5>6{lOU|Bpd+FBq3o0IE#b? ztI1I^c5}pK~l92Ee_>6=eu!NolpOcV~2L;j*o&jHwjwd~fz9b>xIq($;3G2bvBqTf! zz9Avu1@J8iEwhAP1mBU6@Dli*goKyD4KyF#E1T2sv%;$aj)ffPkCRFJ$lEQ3X{95qAf2kna$ zD1kCkC8|PMsX1zaa#9kpLhI(GR;V=!q&BE6DoE{6dsLJVh^y zS*a`PhH_F0RinIAgSw+Y>VY;#1!)VkB`Qj_s3%H4Z0YrKx`#qr!Hn3O$UZ15ZH=}; zIe*GOHuC~)+ktI6vaKNXL;X=v+8GT%>6{&U7qlzNNV}okQC8Xm4MaJ~MT1aY+7s=C z0%l$FjxV^B^Si_S)QX&gES1=6|bJXA2n&PTtIo}w-Q z7ZR0TZ3+AhU4$|wFMct+1ZEX^DLR-5R8E?JCK8vICZWqvAWcRi*|Q*Bj;0V-l&(OB z5|>_M30{c~LmBBRl(zIF?;7|P%!${c!`U@2U5AcDfpk5Z%AN(uN5>FXl%}EMQTh=} z@J4hh%1Ae%Q&3i#j&8Q}q>Q!GGl|TLx1q5pknTX|pn^05-N_e=(p~6olz!9_oQdv1 z8R=d$-V&5%q1l$8GzU$z^rWogg+lX*48;40oMH(|3(!L1iqaxXm-X5_|$ZNnA!+SFfz} z6v`8qlb)_uUU~*SOI#p5ht{Kl^gQxjfJN~|_!3G#ZVA4OUO^e@RrDIlO0T0gP)>Ri z1t>4Qjov|lv;n<~3etP%eN>b_Kp%4b-SiWd;79Odl#xC`pQ5bv8TuUMqyqW^<)tst zS16FaLEoW*^liP0()Z{G;?nCZy}zRyxc+WN{0Ce`WLEkS{e*JT&*&GFmx}0D6iEL> zzo9Qou`uH!7rUsaFp5}}dD7C0BI4bQ6h~#q%ZlZ&8O%u)D1q`)C8|P!)Eu=y1u2PI zqN3CawMOZuEWtLYEy_skPI-rgy=ZT$QXPB3|pv_Pqbw%A!K}w<9Io+aEgSr!! ze%cc3fi_1OX$!O^%1Yc6hWL9QHz)N%TcP~Zo_(=5>;nTuZjH7<1!-Hf9V$xOqrNDe zx9<%=C-x>mX&1CBaan0MbPE~HNxP$y*fTG=Xb^h_dG-H$!tv}{P~=|d3QJHLjMA3i zGnU{Gv^UB~`=FsHEA5Mhp`0`v?T7Nx{^$S{NM}VDQzE2?yhMB~JR24jc`iB+rJuC~ zJ@g&QNRv?(Wu+--KFUc~p!-l>x)Lowfs{shRFJMg&!D2oi(d<$h3V%k!Ryf?l#!;Q z#V9NJXbH+m)6i0smu^7!qd>Y5J%9?*O=uY^O4HGDlwPkBycs?SGvY1iA(WMFMGvE# zltDR^mu^EVP$1ooR-%G*2U>-S(hRg3rJuJ1??h`*M!MSy_)(Y@XQH(zC*6Y{LwV_5 z^f(HnS?CE=kY=NGs3^@rPone-mf&3U6v{~R(9_Aw|1XH;a1@b6sTn#P zrC+fGE6@=rBk{;IbR^13mFOsxld8~PP+n?|jz)ph0{s;gq$Jul%7e6`$UmnY+K$Nd ztCnCZv^~m5tx;c;mD->kP)=%#c0_rp9qNYysXgkC3Q`BO6Dmp_(ay+Azh((`f&*Yi z>Wp?lS*Z)!73HMO&~7L%bw#_QKD_Kni`1Bb($BKx5I zP+r;^?T-R!8*~6FNXMXKQBgV$9gos)T7oB_6H!Jw37w3x(kbXvlzUU-PaPZ$^NKtT zosI(O40I+cNN1rjs3?s^XQOmr364YOpp0}bIuB)~^U(z;CtZmChVqE<=OTD93>0|@ z@=!ql#}j4ccZ*C6WxOX>0UGo6(nyqoCAyETr>}* z-?0RfD16{O|pK~$6;qNqa;!}JDAFo#y4 zjIDk?~?q1RDSdIPCF*%1B*NHl90ndb) zPfY{SStu*HXbj3pgV0!%m-a+wqd?jVjY9=#Fggbnr6K5Cl>W?8*c+XPGM{Pu*$18v zvx*#wEBaAc{BwBxvq1L6nJ9_^=^n&C=*B-qWO4VRI4VlB5dXF|@5Ni(Y*dai(j3IUb?;`S zxu^o=q3$Ekg$3~e)D9J;WvD$$e`N_SM;%Z`dJuI)S?M9v3FV}RQD>Bw za)^JQ$ql3xXfsrhRwfyLy27HkipXv#{k0{y8l_N1T7#-lR(b^0pq%t5>W=c#TGRst z(qm|IRFIxPTcV=$Op@`Z4@`e!3C?Iu!<$Ef(w%4w%1U>ku_!0qjm}1SX(k$n0_h%f z4k}3ZqH|GEnuX3o>2EE)*~mK|X2d!00+f~Jq6<+@nuq>|@=_LEgaTFt`_Lt* zC@nx9N`GewE=1!|Mp}d}waBcv7*0SrX$hK$^3qZ?2?f&q=rUB09zc^(QCfyBN9pe^ z!R2TQ%195QD^ON?2wgdk@h>Mn46h3>*)>(LC9k)B6)qO9}+x(nr` z7t!4)FTI3jqCk2X-Gd6A_zJui7Nu9wER_Dy5_}EKMj7dKGzVp+H_%*^lio!0P+kg9 z76sB0Pu4Wu^DfVw98KM@vv%`T#9Of%GA| z9~GpJ&;zI_eTg7hU? ziHg!!XcbETVkvx$er!#8i1F_m_!G=3@>}#X%1Pg$Ur=899u-j_{eXT&1?jpjEbS*r zP`VyHkJ3d;a4LEMWh5WHh_WUxJ`KJEbBeqHy^QkGjp!8=NH?KZQ9+uHUPDFcX7oBr z|7r=|g5E$G=~nb6%1Rj&pq%902H%2t@pkk!3Zy&GJE$PdKpRj|x)Z&N(*Lvs??UgP zjC418A7!PP=mV6K?m-`-ymYSzKZ1cc3w?|V(rokzDoS(ErzriKB{&y-hBDGT^f}5( zSyVtdX+HV_<)!=3mne`Hbn&<$fdz3Pkzb>tvG zbU*q5<)sJE-%%hfL;pYpX?YjMzaL>ye2~bWQ2J6!@FDaw%1A5FuP7_6ME^uNX%+en zJ!6V>WvL*cJsda@O*9gBj?Jv+>C@OW5IfD1<)!85K@><2p@&gH z%ApmgD6K@RQ2J_1a5Y+kGSVaHQIwU|(*J}WgE{eWBA-BcX&rhJ1=3UKX;hH%=owU$ zo<+~0^fi{?dh|TXNH3rlQC4~hy^M0wEA&61S7Ba!jmXzgAiaU!Lpm$M5dJnyiveF0WLzI&~LLZ~N^a=7lg@O1P{2Ude0{Q|Kr7zJ}D1DtJ_%-?l zWu$M>cPJ};kA6To>F?+tC@=kpenNrtGy26^OhGKdUr|x|C;APgueSutyRn?mjRd7; z$U#}D0)iGEzs>5@n@Ms1?dd zol$F)m%5-fD3CToZBaq0MJZI2dZOxXj6Z4L66^(Qh|EY^q3$Rv^+r8VPU?d;M|o*$ zv;_*JZP1peAnk;i%Mv-Ad_?U9!ecY%FjR@xQqfO67qXh)Qnc1QhCAnk$r zqk^;#8ixskRUN9%^hX$j(bT~Q?1=5k|U{sL)f(}7N z>96Q8l)ljtJOv$tGSaE&Sd^9O&~YetqsE`n@OYS4rqy^1x-ac=~m>Uyp%!HP=FYJZi6?#f+BB6H=?3+2f7KRZ?*(yqZue8 z%|UmftTY$hg>uq7bT`UNSu_&`(tLCeDww?ZeehmbROA9Q3#D(d1ec?QC?h?H7NM;4 z5IPIxq=(TMl$Ua72@0fjXe=s7PolF?QSzRG<6!z$OYmuQ4$4S*^eD^hAbo;1prZ6CdKaZLmfmL`ybxx@&(YsdRw|$mQBL|1osaU;Pv`;^ zNI#>CP(k_yU5tuS5q*c!w^@Q)RF~e3$e3cisy${X2qgp-I2qz8I%t$UE5v%FFl6dL4ou*+JL-*_yl|x7NvFQJ(Rx75_}T9 zk22Cz=mV6Mo<<*{oRmi&p}h1A`WOY$v*;64ke)-IqF+6u{Xb6Imbb&wcUyW6+Rol8 zlfr0wl$9c=FUm<#v;)dZc9b1aAjMHXRFKM0e^ivp(M~8mQ~hr3b}NmdHgJME~0g4uV-lwnlrRoYV&G zh4NBcG#CX^J2V6pr1ofURFpcPeNg&dORysviZW6sv@gnz$QY{*V(z7kWp6GCtk$RycP*&Os9f@*MZ*&yOOMN`}7Z`|JqoYwl+6Mg<6{Ta) zu_!&q5G`@Gp3th^tMR5X= z6H$7eB{&IPhBDG*bUDgOQ_vMCCtZoILU}2Tu10}$4Z0Q;r0dZ2s3=WEo)6PmOK=*z z0cE5c(M>2TO-DDQoOBDi73HN2x(x-=?dT3vkY=DeQBk@J-Hp=oExnoO9ty_y6N-wkoUqUaVjPwe66=kK@(Ca8Cy@B3DUS15~TQHE` zM(?14v;n<~iqd=NeUx5g34V?WC?kD=zC>B+Tl5{uNdxbt)4i7ylm?+aQQ(Pt!NIT~ z4MBUOqO=bhiqeZM!F|y%l#zy`{ZLlgA02>l(g<`Q%1Z~KgHa%jM2DclV$TkGC_D@n z6*&qWj?zmk?g(@w%1B3{zo4viH2N#bNyngLQC>O@9ghO(1au-QNGG9_QE`dJzf<6; zFul|gtV5$wMmi0hjH=>(RL7I+kMn&lsbSp|fU@6qh;^ul5>6yHE z54btZDsl_7CCW*)s3*!xz0g)Dkb0v&s32{Pwn0T{d(;=Dmsx^4pdC?0>WBKHtmN$k zcZNA}0NMrRrCrf(D3EqXd!T|e5V@!*4MKaO^m0pZFEkisq#5Dd<#`lj_iDl$TCJr=vhRV;1Az znXn*UK;(s}DE$pxgwhXNf)}F;7~wP0C5V#a4+<@AJh~L+qzPyu%1e{bWhjs)qsvi2 zx(az|SQM{@Co(ptb9T_{(DlS+q^Zb9S?NY}6Us@`(ak6?-GXjKfs{eFp@MWfx&sxZ z8R$+si}VUh@Gf{a%1ATOJt!;Pi)NvmG#kx9d1)>>$tD=mJam*zFr+Lx78RxW=rlXQ zm6qOps4mR-mk}4j+lkCdi_qmLCoM*|p}e#N9n8brKw66KCo=`<0kjMirRC^BlwM^C zK7<}d87YTWaQ*o^LyKGqR}q<$R--j2FFk@DMS-*yO+y9gF*Jd!6{W|~HPn*yYCGl= zXdQcIq$kl+C@ZZ;&m%7Vft`S!r`L80Dlb&=8dO#4X|8Fpz4|KByq|L_<+g>V@`2=|?QVt zMhI?Y%!^Tiy^VnwBiP4S5belYM;(u3JC7KHno(~n!iNrF2WGh$1EI~%iND}n=zIk7dt zU5t6L4Z&TFf!LPdZpMPxj^OUbqS&6`9#IdcpRj~G;DHvM5j$GYezIaGf`g1Xu`|Iv zjd`&P!M%)uxEaC0#)8_%{JV|tyZy5{X0|ehT7R6-*-!Z12vV@lt++fT+W%a-8LBj8vvxr+Ft|IucG5xe9yqe%A#*Dazp!cacD?dW;GhBfRMmf+3CqIfpJTa4*vZRc?WZ#8Dba|mXPS@B$g zw;6Mud>-N3&3W;Bf_E4L@dAP~j0N#Rf_EB=;zb1SHm0AmgfAvI)0h!2A$X55D|!U) zHRiI1o zoH&W#T4P?kjNoJZu>LGhCj7WX7sSg6K4C11QwXjzreCy#uORrOF(Y0{@F`FbB>6b0xn+d*d%!s!Te8ZR(ZzcGq zF(+mS2FAR28^O1Xfp|N?w~Ym(S9S;Ccg#ga&mg$Ln100)zLVg)#*BCu!S{?=@os|e z8*}1Jf*%<3;ynaEGzQ|m1V1ts#90JCuC(#DD9^^9SakYTOLz{!PmLLIF2T=?S#ci0 z&y6`TOR!+fi}MM7VGP9k2!3fShzkgQWh_F*--U#~Hm6^+gclL~#+VTo6a3bg6=!sy z3Gl;TL#lWPt}*7ty9hpF48*$$K58t8GYPIW7R7rAKIWOzuiLTiCHS~8BhDiDgfT15 zCb-U+6Xy_o(wGWBLt^zxNS-)|`36^09#6bH=Q= zkl=b_PFzIrd1GE&Oz;I`ATA;JqOl+@CHRuDDBe%-Wn=nHOXvZD-Ye#eyo}(h#;mxU z;A_U5_#na8jd}4Qf^QfD@nM2*8Vh2MU|=kYD+s=2Ob3?mN`h}2Gl9n6RfOL$XB9p0 z3@3C9Km56*?d=jg)|eLu5j@Tqh&-WVp)K;TXS?H_a21wYsyDwM;WO7H;%YH)TX@UQ{} zoZSfgOo28yyAya?fkAMl5xD*+xc)uLI26t-0=FnI49;upKqoc#%0|2}0L1Lpt&w&@2?4l=5PVp{9pM~G;AaX0!C64y76m4g zlYToCm;&br0{1AeGn^v{JV4;b@gD5xa$^3A+2H7gpW&h4?uK7cLpV!=I~~6s`1QoE z7k(6%($NpI{O#aO!*89Dwm0BD_+{ajjbFL9uFl_-(%`H0FZQL#TgUJ&1D`}(N_WNb zaCOrlPOPe{Db4F1&Ht&0W6e89mW}0oI$OQ^BFa=>ZN0z2+pr97l~isl1(R{p8^>d}N1L5H%S=|DyjgaTHG;-W8Wz zCcrBfzYzS07kNK}`x|~^5tsNl0Kb9w4Z?4*+*8PV4Yej80`H;t<>O~1Vi@28{DvF; z_{TVYG16JcXN)ti+k6pzIqv7@`n>JeE&48nR22;4TBsp@`DE?`TS)UC{^lLo5b$)i?61jRbe=Ky=(r_k_ z%-7h&N|wsvbNHpxtSI^+Y+T)9Z%uW@@QR8CUt{Cwx^jO-bzNoqtVST-1b^MMrUe6x zcN6D#Ucv`@k+h|}J>LDiPIMPE*9j82;>lXY z3ORQYPil8fZ84&aY;J7w*S1G7p=H}7-b+(8g(!*?63G($CgEr0=+FJBozibIepB$9 z%65Lo-MuS(^P4M6tLql{v;2Al{N+EoIj+;a9)Ib3-uR#16!Uo-^W4!{h_rjTP>aP%2kM?1|CH{Ok0qGn^}|223$#DPfvY{!|l z^%c17?~+C^zU-Uh91hYB`sZYsS7|++-wlH*wpHBD5fKKZINc zgA!`V>T}>y8+=<=L9(8tT=&}75g&|C+vjqeBpw={=dNIvW4YC1vT!*bvUtSoak}ia zlfVu0i<{jPqhE3a2CAkxzYy z@x{QynQifiwWkRWb8eU4&fwic1~y}36qWADkgH{{nY@$z&8vvZa>7jBefquIv2&BS zv$vHkisA2+8614lPP{k2?7zGIQ#G{K!m~4Ziv7)b#CdsQCXWroJJ8aO?#H$_)Ohwx zI}gRt0=kWYWp8)z&<;z`twI8_=KzR?@Y0P-+3z^GI5HDEN}dP6f_P}kd8j@LIUmt3 z--e^c1Ox!kW=6WGA-fBJ+t7Dy>Hwe_2);uDkL`rgWxo`}!_0!1b-xbm_sE-P@g9M9 zsO1}9_QaqV`@rEQh-RAYvS(W0!W5VNIqJNOukhj^`!MRzjBnf&mt%_s9`$aB%bsgV za8Az%dyXZ-*uV;xyDkTN4MFOtfv!&W)7d66H2tfSLdILFc;wsyY!aVh!8dAv0l( z=5QBUu=|5joZBp%7_&yWm?O>_r9P;$bEkZK4)5|u6sO53&Iu6RS{x^2wY4}Y1e~!r z+OZyT{<%CgFrWY{oFtLkf*jj>T8eX1P>Az7i$~9CS}=3G-KN=C_MJhgj)NADqA{Yk zF~6?wXfAt89}~n`g9+XU=NvUrRd$6XF* zL0i&M3A}^-6N}u8Ums?_zzS!0LP&1SZ*=$2h)WT^6kHiSyjuvl&2RL;$Uu9K91p@A zR;e2m&(Bio+L2kvU1vpRpp_Pb&dq10^9*D%$2XR0$letc;V3FF(P6>yA?|X2f*8Aj zu~^_mh!3ol^j4VjRak7T`FEYkT`XjrrS6OV84qmE2P|GHlXi;f(V*0yD<;njH1mVV zw6KfI{-q_)!a-@yhb?WT>!J?H0bE>R(F%oQqW@q$W~i}apoi3$ht!x8S^_F80xB%P zQV5s}#<@v<4FE*C%R{=W3IOz$hxFEFxS_KmNN4SbTj{F^(pRTctZ0MtKPyh|Zxko+ z-|FQRu5c3XbhKV7k0N?30j)#>Fo|9Vw@LJNxZ8-{uVO`;_3sHsJsqJKg&xQ7j5aBn zur8>hC4dY4q3EDdMBRdd{SwR`RFCP0skqYcw&Z|GLJ*Y+VxY>(@hy? z5r9nHl<8OkkfED0EF%DUxhb!u0RB@xXdU)c3)zJw@%9``xhBN9oKY5KLz)gJqRJsJ z8-coTYR8b}mS9Tmw@UkLPsrDn9;Ogi(RkJ(!EQ=ip5O_j05}4Gq;)2Eb&A|1tSXOi z*bjvj9oyqv5c3P1?PYRQ_;1K$n@FPkkgVUd6^u4k0CLXGR8Ue|M$t{F-3Y*x+P!e= zQj7dg0B952lyHg`ZHUOvbjKfV1Fe{=+dw7AwN-i412B{aTSsoH4pkt!+o!ca2LYG` zq6a_BW|#%HQ68CI!MpuFo4`#b&PJfhekLedH*W|q&71uITJ3>j1f0BzQ{+O?YzsOWK5IF~bF*Q#kD)wAXk6O`hp4|Bm-YM`M;=ycJhTkORTOO1kiH0p zz6iU6(Y@Qq597R9>kX=|qjBEjAUgCkbUlR!a^4}oZ$Yd^u$b4_nlW3m!b;>E z5ZTg}Lx7cJhO6$KZttNi!XE?%W)2bp^PBV_@988#Fh5-wezJ7dlUjJ9;|ZGF zG`_T_gF1xlvUp5RfRLg$y|4fRRf3FiFl##tKpEV{!(HwoxR;WTryyq`GGO)1#qc1|8f;~#LFUva&G{&fm0{1t1}jD2Qf)$v7v838zo zc_)V&bihVXU$K`b8l#SL(Gqj1Ig)x2lWsDaC?2UcT5Yc$hL;~+OlyJG6R}eb#j%XH z4cwv6#MY5l8u~1syC2Sw9TxI6&}f?$@-C6--3@J23q<0f(b$hGpIXS1((Xl1p7=6w zPj$waRI90u?9JHudsGED4os5n8oq$fmkl+%JHJZ)rG_sA>Y!Raf^U;!Yx!tL?}?cG z{(;Fq*Yc?W+`7Q_Vy-Xuk!TODKwz)Kop)VCL{M+^q~x9$AA-~JtzX`x#E{eDT0xdK zDIz2eam_EUBs%0Xi$LW~iSiT{LL?zIdA=xjCd4lV?m;Qs6~S`RfCm7gmc#6eEV7?h z-QB*{qKJlg-OgnecyPX^X4pJ9OV3J>I``3N&vkHD!EF{`6}eFWo@|l_xLpB||Jjyc zGa)ldunTUZ1TgLa{N4h{RZF-{`WNwtl!upNXFuk-vVUH`K1c-B*R_7s*VN7^3W}L$ z)K?t2jrz(W_a9VWV+og0+A_G?>T4;W-&V-kLMs&*TF|Pkx8q;K|Z~m?V>xl9>0v@3m)SfFTG7X zCcsv-mI5Uu4+YK=Eega~+v40Dv zhjq6Ge)KTh`{&}=G&oOjzPuP`_mvyV;o2BtxZ`eYxkq*6T@0UnAR}`Ba7&g{(UHkF zTjJo22o|}drL~$OZ@dt@=IzL!CoohJhKD=0h0fn<3H>W!J4r?IP?l7tI`VR@g_qpY z%oBoc{R=rCY36x>>|}%)UtR)_W}b=gNsmKHs2AK&!wSd^_0kKKK9k%~??bJrCZ+#o z_u$De^FVr|JE$jI0i$)U1JK$!w*mOQtrP0S3*vSYa;O*FhJ*b+a2o|UMs7<10z_9H zO7QFb1BFL~oT&u#9HOaS;^1yAKo)?rD*#_6{Y!Yq*+-wW$iqR<6gl`oi_g{M^W3Et zp9=EX4V#3TxvJ!!AQg~*{Zm%Kn2iXQA1vYB1J&Sz`Q<(y?OECxEwPK<=i_!CHWqH; zQ9dfUjNIT+@_$*$bllkd%t~HLxO`Co)#+~p(8{Cj0M3p_XYBJYMFI8#1@-y3Nr=5p z85spQLGE@1KsBFDC7-DT&Qw(b-D-imwEzPEoNWPQ{SqG4hiTQJP}-ZJ*=VeqolT}= zGU`mE7NIs_FJ{wjXRQagsCiuS&_W*BH+=@IFp50`#*SEqk>S(O*xA^GtsTSyrO3S( zTMU3Bdp;S9(-wkS8NQrH2kyMdz`4cd+=G10FZ+61y!-n>u)#b?b+fHg#FaT{8!2Ggbv_(6gwt9SMi2f;@ps%3Wv;X9b z_0W>VF#uSu@TldAM7T3N5j2+@2RD`}em=#omMyX^BVsWZtF zIvgSd0OLZt1~=uezT(%zJhaq6cYPM`2tVUWgBkbCV>gwY?zJr5dG^T-B}EGr_5{#l ze#Q58YPa50{ERX1>k_!s8KHz>FO}&sXMz%j*8#K&!wCXRVQ|8Pvj{^9!0h$TXvZ$J zEc460+?j}_eiOvu*D06jVx15g4W0M?y(+*^gvKX%+}5Tp-#jg}{O zU%->Okgs3ByZSzY4N6^3_Fc~QtAKYq&$J5IZok95&PeNq2Xm#}$P@oESJ}fbP88A` z;Auf8Hc|JrA;3EY?yNU*-R$JhjmpqH0+*;of7OV=x;Pxf#pb;@9!kIin6kU>A#7%X zK`gGcb5Q}QTTDQ@#zjt7OsA0|#>o{iV=8V~Lz*5#5N!rjNN0rf_znnexBy2_Ddc$` z;G%vM&cOmhk0VHAV(38_=#JYUN<>Gfw5L%F@hCLGozOj@zYT4xZDt)z0k15K}e=|Gzi8`j}O=V6$pIB_P5Z^Z3Br?7+X zQs94T7xtN9QlKY}vxr+(L9f37pa%+CgfT({0aF3oyc;`YUIO4kn%uu&?)EtVgy7=- zxLSq~oItdc^*Dg~ZzCiEUC0AJVuG*d7vRn_utM?xsd&ZL| z;uavbxF{QCC!a)6`@>jVdj}vb3rrJzfItP|!|N&XdX8ef-Psze7kPbD zXYpd%abvf+IRtH;d4;Y`B1fcQG*J|d;oJv&l^DZhp{R0paJ|HCFp4afeXWN z^Q?hEZJGoPs3n?Sf9+4WTM*3dd&(X@5uQgpaDQtjjt-|kr$`jLB0~JPt8qvdym!CP zgH?L`F)N<-o*qKj(D&^%7JGo^gRjbBa@QK3Vh@yqB%Azl4Ue{OzNXb3*k40#N^8s8 zhqY7X-*j!qv~>a*&g}3?v*tVkhUFx+6TZU za2*7jgr?koHiQma72tL8wGfVr1^m9EQ`=lwV}-RiZ2GI`c#@xo3qutVDf1Y$q* z|7kSqDf=inP4Ks}Mp@#!d+QSgQ|5&14>e1K=yO37aFp9&!*$$%_H`LVEF{at#i^bs zCK_AFP$w62AJT#00FaaZnLO|C1eTKQa^6Ke#{S5?t?t0|IpoG3;VhT4?1%IO#S3q9e8ol5AO`hm4?*t($bB2tZa(EyU?`L`>KK&69~rwe22) zD!^62Fc+ULxn!R1NUuwifs3CvF}!>oTJJOS}#E}1kVt_#ndv~_-uqg zYv`XTFrSZuMTVL~OotPw&5u$#zRxm29hEy8G3d5D3UyZnN?_fl=ur53#o#}H(zkhc z$4P{kU+FtMCM0!|3Bnru%Ai8@sY7=_^i$y;87RkeqaS|y$5F~ z&NWx*X-6V$+6dgQgN!Lo!L_)M9B#y6T0TeiUBJ(5WFNMc+oq=BLILCwwl=_RVQ4@E zj6}e+ze1*v4Y}jy;u!$S2JV|+RXhVRC3oMYxH#^h@)vt&K)JsNcM2+L*=87f(Ao;P zE&`SEoB7+R^@VE|K+ zmp_1>X6)BkL!1pA^;#H$z6G!yz%7-S9zwjXS8%Q!AS(KugIkbJ!DlCey?s#$|2BXh zH^4CYP6wDcj#f67Qc?~fLvZH=IHWSbeJP5Nu0&NW%;#nzwT%}>DMK&5mI0xk@?NZ1I$ zWN5a@Zvr5CZcl)WquWrxPTVlXZu<%l6@TsZ;0l$GivPlWXc*wBLifepxKH2Y($L3o zX9^V-(U{f~J&Ib;we5Kr+8ta@eu3M@KZYBDL0S5n+?E)Jd;iD{{|Q6&IOJY@8F(6i z?xIL5Mq)A+^@RX^?OB|d4`uKTd}v>?{~2^H;6eHl06Y`3%s=QMhBOTBj6A@w^ayU- zo{ve5_jPvxy3AV*_k$AG;_GhK$!yB<;APyIbO{RkCNxueT9O%{x;bA4x;MnGrZ@%dW|KBXW|K7Ft!2iwS`zlDoSuSOtdF_33rvQro zM=ZXd-P-%EUl5G^&%7Vm`J0RHA+Mp1%&%lD`ZYCdQ5!&PcTGXx@DtR>WXJ(2R@*-> zz?+2R6A$q)y0?fROmzGcKo4p|=XSuKH&7nixA0FtjVrj{=3;O+G{zHf&q23vu}O456kUY=;7j<^ zv?Xx{ZzURiQBxRn+iMteMJdY4(N?sl3Gbt8ape&LW-m~@ObJ53ooJrAK3(1wcE*P| zYPOWyc7t!zkE2^zihB4fa<%vjbTDguoDFw^xpI}(plWX+Cv|_yNFYEDY<;U>t!)-_VAjZ4Ojm(0sff#^=h>ys< zTS?ppLHyebC|#hKYon>aV^BETzv{t<7vWt(<-(LOWlVzz+rb7rIrS}OX2JS<3UJ5` zNa05Cd&Ei=M>`*V(odM|NTbyHpyXya#lvfGvlRNiUNtn%QTp{|5Jomipx0C62Qg!| z{;{yDP{pes+Pdwar z^S~HmGMZ;1jyPPjr;2z&&!0b8P6SJzs zp~)`yex#;}>7E-!r;u+=R8;=d$r)HIqiySZDem-8b;#j$xPR@81o-eQqMKjlE}rQ; z7kQaq=GE9;<@|1p38TX9#eUp&Y3{???YTWo{v)PJ#9E~(6Ve0&_byCuK@#EvFxyNy z%!C}ahGZf{*B6mrG~=u(B2U{4AGOGP5lkrMV~4TuH<+yB(blHLK%J-s@X&t;CK(Yb zIS*}^ZbUebBH&eY%o)z52?*$rX7c(LWQKk>2cipgIBg_(sh0PrE{503XgS*DXft{< zIq)dPb3xdn(E%dAsu(ww5pa&+ww>V3T67D6q?fobqZL`B6CaB>P_hq z0AvOgTZ4+bC;|y({uNkV1mjZ*xGi%N&>QYo^K}1W_@7>(`;+_YN~}V@kFi(*h@v(r z7Ed%XABXHF&Nniz0}v1pgdk|e6dJ*q@rteSiYcBMtyqoi(BClI!aX|D;ybvlZl#X1 z_*HImpdk{*;=dCIh>wx$hT{rG^d*HDh1RbE)nH3u0@s$i%Ydi^bw$Ebh)oHRb$`KD zI|}ZO!iHgf0s>IjfbsP$=r+l{9EEz$VN{#=^ahj_?4EQj2^xAe{lg%@wzb1DOh(Fx z&Dg|u(_)w}V0h7dd-yOj62OXR$DtexZ0DWaT@c|!6wI=G9v4nWO2Sd45ablZQwu#= z1TTyJFV=~amZ60n59L0V(w`sW9-WKMgxtv0GZ&S)3IG;zoilp3k(B-hcZL6enA<22 z0Ce9kNd5t3*p2X!LwGlL27qG()bOMT+7uIx{U>Z>9V9`OZ9t5rY-IrSvN^+0RP!qe z!ZrlY#sYNu6k%nk=RLT$!TqaCK)WgMSGGQen|SptzaiuFt(CN_c+kL4%Zex9#|IO(4zu=FD`r+o?PH)wGbiEa9V%bP0Lb3g3X5V45I-B~{&;j~b@# zTuKW|r>^CBLE`eMp7voGq9vq$uZb+jXmvkld5GEqVclvP}2?a}!+yp!;uX#-JCYc#6LOoF7SU z3>R#%+hLozkbi1FZhH#7Mm!X0Y8KZ@(Jn*pN`*vG#{FPZo%$Qb-ykQo5EGx|5bt%@ z8*}P-bf3(&F%xqjlp*NefYO9csW#}wj1sd&z~HQ&dXe0i= zJu_f$4M;|2r(E`H`0y~6>3?Bmf&VDlS(#Y^nXDqlz|1-V8aKn;-pnFYtyb19ifUL{`{6#bl@*}; znMOz9A`hT3JP*f=v|p~Ycp+whQx=deM z7(i2kOdNvc$zH)IY9-9c{ue^34;jGKk7E%1Db`^qq^JZc;0+ys)C6SPJIeO{1W`fsHwjF9yrIsxX|8!oub`K%K1_J zu?^dXfhy7u%jA*&9)=}KsH91%)cE* zr*jda>>=#$;*Yo#Cg`8Ql7-zcV^W85i)=`5HG&r2>`*wj6X$p0j~G*|v(LYYb7Aj{ zIeUzg{qu=sOnZ<}euO_5Sm+;^z_uQT803loY+jq@1m@O{2VrZv_Bj%W(_rpRuOiC% zn9J`16JrLdlApjx2JGnf54dX(aQ`68MiT(5?)F7kk0bXQtg@lq{77yOsA_%$Qb?(* zEQ4p;aa>%nHFynP4xo5FxcggFTmi`DJ$antTC`vD<7;u4VS~j(Jc!|cVsV@7?R4S3v4(^yA-^hD~&&ojeNW^dCkxq{_ zbQ6p)-2H{8lk~E=95+DX@qz#NaO+hs$8{DHE_e+Ob>K1j)-QyPDQ(o#C{OqML5|fH zI5@yNI3mki6R_YLF??kf57$f{SC)yk8W79?{Zmb=t_B*GBqmO;OA6whP%rDsL+(LP+`I9aN;*mq@9$D5 z=Unt&eLe~#%)wZ)c6eA_M9|K2p;_JzMo9+$}P<9*aTQ$kCC>J}01%kijwpbJ; z*A4ea+;$;K(iiSBh`Z@u_7RNB;|m$v0nLZJZQoSmrE+p(iFMo)c+nj0vh&*f%h1%d zPvEJ2i9h1rzoDrFP`;Q7Sh-{S^6M}ha~fjoDDq}LZS)-1i-vDY^7M$GafmS?%m zeF?a|A9d$B-xe0W1i%ph@oIbDHGoc0&`MjT`}j~;^gtg^xBgH(^NUxz;2eLp$c<-( z&Eu@hGGl(5@$IdvSegl|I?b&uipNX z4${rre<*+b<0*g4^*{RUKNf9{8-hSQsvzFOKtor8)A4>I)Ax(_vfilf_3;~e)FoxYStnN*-u?yfuUv1dxP+ICc1s- z%uc&-72Uj>;m6eVQ|)P;1ws5+F1iz1X@tef*S7Pnj@PrzY%qYM+fu3#+594LS<1N< zIMkTOJ_+WSpTg3B0j>Fk)1mzyutdx+0vG&zsHf1YI2t!eOovG_7BRxBL>q_g9ryPDWPI8Kyo2+0i-+i;fjN#3Ez;LvsutszV)5|o;IWQ#$ioz| zm{+k4xmS^i6*B14qU^~Q4}PI5q@CKnKhoY#<35yWZ>OXG=?WnQW`4nUj>Cx2uq~7) zf2^aU@_%GnP;V43*Y3pZ*|H3bDM3JoX)q>@`Q>i`Yh$2xMc_)z9fUI0@p`DDhhd{6 z9szJ0H2h=GZK690@%p>)*^b~sR7}+&9e|{lLg5pH$Zc)IoPl}gdJHa*LC3(KxSJ|x z9YnJuHtV^mVzvXIN^w)A><2*g;imdHMgZ!=hvG>|O))G?swvJkqleKpB9H!7k5Uw1}l}_Zj1I-w*djxVuzhK{27Ajn*z7 zbPCvKTiDoXs>go9%EqZtjuV!;4#V7Uh_!CRX!^H(l6kRbktAe+k*g|yVwtl&j`EKG z4Gt<$qA#R9imK$ZK`|j$CCN6P6}Cmg33bm$AatI?23h%=aR+yx@5;QkLYj;koD zG2=+9nZ}Hx{Ng^Gg<4vGWj=UzyoqOd9sxiX8B%M%3vTmEp&J&cwI88CT4wm~Tf1Yw z6(QZQK-y3VHf57SH!N7&Oq)Ku7!q$ri(I&Zd&I#YAC5O*wM>;>l30a~Q{S?I*q z2^g=r+&vI-0su)-_#3^Tp)tflNCp*hh^2#1%?oIu&OR2O-jg`#SYy0B^)5^thhP-- z9t=i(&-Vo`xYT6B8;JNE2ICb7v}GfI1>B7RliH>H(oHxngE4%?H!x}|3t*`J1VG)t zv>q9V)#>PK=;T4gEDR2rwj23qbKw#%q~U2(Ag+81i;M6aOd%(rTeMt^$+Xu{m@b{5 zmXbk=qYLsYK`XWOjKH>B_+XDQ_K3nqn?a;j;E!$f4!q)racFHMA%rh34`Q8%RO0Lw zb#cyMC(_W|N6Ikd;F<=eoILwPk!cHv~s8+YLC&aICPYhayP*nOEinvDhS`5cTssZ6NY+ z2aQ(EZ*Jtz5vh?YrTmNB1rh>Rk2Q$Ap2)u&BcvpB%Jb;-53=?6F zQ%GihUb$-zPYb*-A45+HA0@haj-q6n0W1g&a=!rgF1Y92;&QoD5oVwporY>$Gg7^6vJ2t zP)-A&&jG9hP5@IdYI#bk-+F9RHvZi}88Kh;&)@IPZ~o3`hua<1J=1VQgaY^N#La5URK?GQELM z2>qF~@V&gR-8)wFHi{u{*vms4-g87Z$IJ?O&tBes>q9=(OiGaZ^|AjGm^y6r{jW0oS$?hg|IbR?o`AOe|NjsW`2SGnRt^MyL+B{3 zTHEbvrZQ)A_CZBg{Zn)F^qV1oKhazvoh%;J-O+u;nGwvS=b@Xdg|4rP(xRMnTcPx`_*;APNDl3@~jIf^tGJzmw7n&Z8iR!UgaW-Z0N z4y2T%BCwQq2wx1K9>Bs|9SO{>A>Bp_D&rm8U)Q0}08n!iI)JZ$9aOVa#5yy2=|1$gCP~CF$9^M@@>woAIl2{Eq6`-C&b#EMr+L(HM=d%X zR9(%(Jlt_NXfwa6`8>jLlf}bdjTWG2cF#6y9s8H$-zHW;1nzlGM%^9uy*EwowhiiZtc{^fbC4X6(XheMu1j=W}yuO zHZg2CMs-#1qX_X>qS^Zdw!Izzkg6;(x}Ht@2yw-~?m;(60SzkPDEY-b#Mst1;CDal zy0Qy#wLbwjP!%KD!vcQ}AjkNp!eO2Qs>WJPck-$3j3~v3WMhv3kuBYL$uMj$Uv(bM(#_IY;6y$ZBwM9T%-SD;k|fHn>^mf=@s<6uaWyRE;k+EkwKqa zhR2^<_C3qr=RtdiA*KB50p3w#Y4WK9JR>wN4RN1$IbFy;EA5)+`PJIkv=;e1U#SJX zw;M9o5`U0q@IH?}hBGL{>5yM;g>p@2>_$CIOo!~h`6H9{FX43g9p~T>NwB=)5Kl-d zLq_(JG44LdXdT=^`htkqI1|UZnDoEIWAZn2qk9)Q<6a(g6bQ}Feuc|fj&!>W9Mj=` z-GU>p7iI%A9Y@SF*dh+opK#F7!u;%4xtuGJj$U6PI_lw0hufs%;Xk7yxWZxo7h1qE zyS(fro{(H(#J?2rms;ZYeH7--PfTum83kQ>3yy#U%Xg7rz&1*-V7w~melvk{2$IvK zX7(~YBo38mX1(ra9OPg!`(+#;-*f{;b-}XuWnd~m9KuA!tuo^)Mr@=IV4{#+W(Xu< z)%_^Erehkh1hwasj}WGV1|}->DKoxeM+&5wFi}VW+$K$a%%E)J3R7TGJyy=p3W%j? z!6rkIZimY$k*3aHgO9Z#^O(vhhhYJuu96Bj+J2FV&C8+i*la zq$g%5>8QR6xpx2>I8?VSKtb>X9`C-4Roi7Pi8lGPwUwoB!_w_TZC2)*sj39IDru$s<3!hhi z;gbeY{wsu$K0Vbz)VGO;T|T2__G?^Akq2JmOIs38@>7~z@i8x>KTGA1PkGk={hyEz z_yEFR)RMxrwH@S3q1p&}Tc}psGA&Fy5EOu#Z)|G716;iu8d(aCMSGW&F7}lhkhiAB zZ@PW-j1>8IMl4@_Luqqey|;X!uY&x#z;N^{7kZh-n(A_&fdR-3m%ou5=q%>@m+Fa- zUCXv;?MI85=ok9X6`wT1;P-tOAz=$5@hw2UzM z;6s|HrL0IR3YPyY(t64I=d`Gn(KEE%h?ZBYv3n~4T?IWy zM^CscX{c`U4I%%0Mq_=&A8(V7Ipxt@5hs^_rG;yE|W17v*<5tV+`eABaU-<2#HU2%Dhzs^^|{Lq+s?Ckz4nO*MaA>s(A3Rz0_OYGzI({2UdEU{0*#pIFomG)l#(gilxjH zy#yAsWU^gsN$?K`6Ps&G=PzsWHR92+QpHGy2_pH(ms&>h2;i#)2@Cu+6)X}0Rqq9N zO1<;_4NchWEBj0rMICdpGgA6yXXod$e~*Kn@-Cb2n^sp{SKY)?Wo94of_n>E85qSp zMNDq`QtQPh$u9~;*Q-|wSD&lVTcUbv{7ZZd<=#dgdjgGactd4#t*@?WdN#WsJ-<;+ z16Q&nd3l!T%de0f&KKQ-P_@tH$>UifbvRw{*U((Y3X{#~ee+f_xHkHKN_8@h@1= z*vg|jqfm{g*TyQgOy1L1q-ZAx$W4u6r7XdlDEw);Xrve}uj?l|rT!z+q#S~=lFb0x z3V&%i)K8OYP>pP{e5;>G5f>IPdE`eeN$&blbK7VETu!Rc66EMyF;F|5Em!4==wdgD zqVu6Y+V2GemCUkzZM;HtPH%;Ydp6ltG{@H$2UXADmBxLHyxob z%By_Tcd$n3ohu^c#=mMdd1Q$gEuV`L!GW*F!xr!^neHoR-JzDt{S7`CW2V@U{$*ik z*p1$%W;9n*QC!?fwVBf9ync-4A@Eu$a#}(!A6^G7SL>@SuWBf*^wpt_*QlywYe1GE zN)R48;YIR>Ibrl{N{p|N8%o4EvTT6Z=w$Sm669arQvh90+z+FE^g6bN`7W;CL95h5cEbv;5-1W5Kb~ej80CjgSXjEYmS%M!vwg0` zC(PXQyDqCA; zeohV=>ygp&NWO^e@mgEIqCf128ez0GX-RbxwLn-M)lJLrLbOa7CSnF^uqaG9R@cVaC6_B ze0Ch#8FIrehITLhQtNK3G&!NMF0x~R7{trv^a7FHm6pKJ^?4i1tE)?!>+22G{GO@o z*cPMNk_u0ved*Aas&*J1@iy9n>hPA!69rJLm&&l=A}J)j03z1lt!`>$_qPljE*|B< zd8oT8NGL11W~9jAaWZqHNa(qfwC1u#M!W9tJZ}Sx7|4EQX>D^2t4K0y3GIF@X`OlT z<=_?RvkJBZP2cRou}m!&MTt2w-y^WEYnCh?C8lWK55kMLB3WLWFWfRTMs$dL9x9~) zs;z=ewUg*ouqiE{jS~0pc$#O_d%HueLP?7DHR{r(d`RhP`6QjMd>n!U9n#mRVy(*P*lwqzkyw0O`CymA9^<##f5R zWql2ceQaQFGhKFnw7xyC!|QxY*xrG1(^&B>50{sX6P*QS`sChYT2=o9a9edbqyZT1 zfomsqid=eS$rKVYy7F0^oKPsvjkq2Sc%-kUhINBo;#-8*hS3UAWJ;K@hoVQJyHQo4 z*=Xv_N7^0&7vw#cY8|WD2Z<(`15iUrXkZO^I~ArLYzMZPlo{crJ)ho+3Znz2$J(en zRz0guNI|Q5MkA&fT=Rif+D}2{l826Kv9jcQZDBG!Q>L^rS!{mvGGx)Pcgz+b!~7yX z@d(Cvtws^8D4T`{i>Z|xi$tu41GA6pr&1z;;Wan8>68{7`XMT3@{A%s*oaM~Efbt# z0hc2th$w;E6{x?Tq;!2)Jg6+8VvfW6t!$t&bL5XdY8~WP6GXRw1NH8sM}lExQr7_k zn9(DQ3;YdBybTqWu7u8Zl$JJBmiqm*Y&mi_`oERz04fa62~TJS3nxLx>jByQq4M8C z`KZfRM&Aas1J%Pe{tP!&$fPtaMm{%Dgz*~r)&)TLO^`l}W^26VKGW+IRZR^WeMnJ0&VTtj0uGMdXL^UoMv-H?mkx{H?JNdoJt{j2~Q(c94AUB))c?`Mj& z-A_^nL*_rYr|ka(D2aB=WY}v&BadnsK3ha~NJDXIo1qz?Q7@NSvqdl7U7kBzWOZ4F zhS<2Uy1sNtRW&5{7`lO0JF66Rz=p}YW{Vy+TKtgw8*Q!}Fh_LgNAJhgqna?3DTg70 zv873TB)WE`g{VP&4+GDO;cpeg<~hCj4)q9EM^@K5d>DZ0sswu{`x={Tn%D*M^Eo1m zyJhmZB86W^qrf%dxg;ikN)qXKsOF1v#r*I{2&~Z@^N@T2nS;0T?b0z%OwFWAzey-* zD2Ks=uHxb+P>t!zeIjy$n{vBZuA3(^I%*(u0gByJ4e5B2Dp#M-V2@3dFU}KPd(!on z!|^C}b@?=7c7>tq>r3wv?J7jS+BupH);> z!VH=xyX;-?@ovBXHuos@{1ufCUf&`(WaR(8eBumRgT+ejKS-aU8M(_GGz=WZLsQTzw z<(VOGEEA~$SH8=^$F)@XNtsBRH4D_w_f=w0s}EZB%o~^&qSfGA%myK6-_m*?ls%c0 z&~S7}&9q%e#^{W2JMmSSEhFx9?o7`vn)44^G7>guy33hVdK1N9C5a=c;Ao*945e390DE0l?91NwN4(J=}&qSI#s=xr=e zcePh^6L>m^B(ZB3)FZubq|fb8tsXLKTc_*gZ28E1ktlY;DxZ5?>(+e-stek{Tf=DY zswKN2eRDHX2IS`EGn}%P6FZB9_gVA})arN>jJ2%5x0eN4dYI z2JB@!NuiCA-H&TA@i(EP#z0-~{}w}fpo2-go35!414piakd&5MxGG> zmm?eOQiB3^7}ZI=i)sLP@sb7bj208j087}#kWyzwuyvip=;!P?w=AH0~)=B0r5PfRtl}?Nf z%Bz~|7B=d{W~B7XXLM>^@um%PCAySpQ^w66U0OVR^7zq{+1)VE8X=FStxtzFmF)>_ zrdmb~OF&`072bM)jTr14S|BpJ&}F4Yqi6KGX6t+t*~)bBM}u56Ohm~I$8dn|AjEM# zns`pjqDo=Y_}y}GwMf%W4wPG~MYmpb69^_UO3{Z=udizIHAo0lX;bw=b|;uIrp7-X z(lS7PQ7yVUs^C-6;FsIJ)Di`DAIXrDS~ofPJmGTE%Wvp-hg`i6nU|d>as^(*l8X{V z0>4Xsex4YRPN)A-G=Hg*>Nd{Y4f_!zzah|+`3%o25W~C5;)R%G*djMJihMPMO=7ft ztip$R7j_)QYOJbZRggMwd3kd!+aXJ8M6Bxvh^)W95h|^|M#Q9_Kv`hLd2489CJNe9 zR}IxN;;c8k2LM#RAqdb&n*(rRYN%S%AOsxhK-BD);I_s_KEs{W;1@tZ>jfjky#w(@EFOogO5rHN$=zR&w7u~Q~ zA6Epo<}pr2)QhwpbbSh}Al1|vJ<|tX(L`@Xiay;t8l-sZma!SqTMusEE|=Gf0)Zpo z@~4wpPrgnbs~3qAF9!$IEIWJK?2hz_Bu2LxqI>Xp4Qqc!Z$q5)*@6adU8RprHyNrc z%^`BiBGG^P3e=4;NGw%GAiXi6O!5WQ4N%Z(^zii1`0v0{5`VU5V(H zuV@f`hkXwN#i&?JKKLOf6YG^`r1!}&s%iAswpw*>g9e%ffMTe?@LQzQtM0S0ROlrcNyjP$l>6zzXTVUMec+Tmn})7@$)MjJ!v zRV@y$ublytbZY%5BYJM*k+X6ij(Q0m5w8blVcN5qXW} z4b^(y57wcHW7vTfKCTY4W6UpmFBU`i4!LkKOtzb-)co`#0Ki6ClbQ;jK3`ni+lpB_aSg{e-tt+{ zoU~j-c6|$!(#W8Z(IG^=7p`o^z?8*9ud4YGtlBq=>~8st9?^jmDJ<-3h!4iFnAv8u zD^#E47$VfKNkc!VuG>j8GLlo!MvMx;pzii=CX|gaKQ=ST(_d@z zlTI2fismL+5KZ-{NtH$z4vlPsTz$TXOrlpleT$mCHLdy>ldY^jezf8#H>?n;a?lD9 jqg~{elUHDN`BmjPP)nEYu660&|_^~Bg*~{?iyWW diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 7f7bb4247381400745a635febf5a295801e3748b..373c93f533844fe4db0630dd96d5766dff3e560d 100755 GIT binary patch delta 5944 zcma)A32YqKd7d{jq;_}a&Cc$UyS({kc$lPQQ-?)Lq7DwJB}Gw;t&6o}9hGER@+>Kt zk}NqoOvz?sI$>iO&uQG|qL0?K6SXPmB2uj&ETASvqNGBbAZ{QOMH0j^VAu`%+4HS|Jh5F-`J?VeJ8^5xHMJ%t zTZ5I-?oTjB33DKjM8;^>u!%Q!m4{(V@GTOu^1ibQ-eIQ<;kOoW|e#^-tWJH@opJCO_A+IW_&_ ztS0o}4J~x%Yn=vm^{&d;T^t@{T2Q z*?hTi$qBYp{@ap^Y@vK==_`2hgO&5;o@Mvp>0Rq;}2kYfEIHg86*f0ZG0D>pt{nht)Xi?n=m<6>F9X#ulk|E9Aw(=W`z zC{rXw4DRnSJk8Ep!VX?#!j^AuT2^WYtl-(Qb@Qw~{epaYxI!Ak6_hKr!w-{xpB@(S z{BV1U2bvdy`ZlvfOt?c_j%+?v+%jHeiMVh@tk9uP8MurIE9}rG4G{~pH5_NFEa7=x zA-KSPcmz9wStG=K%xx9%7Jb$rn8ZcARljWbTBkl?lB5p(baDQGB=ZjaH<&3>?_=&^ zoyK(|h6fX7=*8se`|8@iXoi7FSW~;7!|ni5o+T;q;2#Vqc8;F?&bQa-zylY-wg(@~xdB1$XlXEpQ8dwo%i&^=^?6iGYQ^ zGsrcOk`HWM@QJL@Uxqtfb6mLRdnz7#+2bPBqH86AW(n>t6}~>EH~U>)Y~``&&m-LVFp=u%WGezQ=~Pu5fN>>q1*n9imgWYl~;57gLfV zH5~hx?KLM48G6 zUhthjAAl)Nz>Y$0Qq;gsc!SGRJNE7*O!zj`CJnIJts;m~&ElR##P0<0!CQBbOX9Jc zeCZ|ODV#wvtO0J2*M&tVvLV2P&IoocZO#E5iB=t{A35I3l951Z1PH>a3#yvlSwEg8 zvvMMP)1!n*wV2+}%3GomFe6f5@O~6i9p=Zp1Ogzk9SCniq=+FCB8PpJu<{&q2|ps9 z7F_BCi2<@3Hr_NL2_NFF+%2aK0LajvAKk&pf0{~c-H(MIEZ=<`QceIyw89*cLqjw2m3nv^xgt+u}ZEjP`nA(RBeQb4Pl8qQ5GIDy?o@9=c zxvncLn|@ZNcQXe|K#2N+pP~4b!EqV9^=)_9=K`y3S{AT1uySm7 zt|bj$0%OSt)qr+Ps9<1--HL{vkze0kHBKR2O7%Lp--%)IL%innZz-zdr|7;;NBKH9g-_zR8qMd-y1lY zJV#&yt&4q{LDCQQcA$?ddGHa)JMrLM7+-yG!9rxc;Gs``?^oGmQzE)I*jA*YtA1Wm zRLPcnUz3+c9=TnFiLdFtP4i27Kc9objmv2rRcPbBhE=y#T`I164Xa9{RbR&{cQY>~ z&$v-qLK1CJx|Bf1Kkf5yl3hIMN>RpT$S8?q%+g8!v#$ZG%;KtR#Z^DVDl=Mjl~#4_ zCr54HzaMQ%1^7pPZ~yxHZ8+Rg^h|_@$om$ow}Ofh&n3Fl#})CyB`O>|at6LP7*STF zYGA@~xUoaBgdQqhlgA#a%kiKBYz*^oq6>IL6wBq;9%{6SVNI&oRI{%=)IKKz?iC=V zbhs7ue>7}bl%w*O1uca9Lrbs|}n- zX+-qnCyKmB^@Ul%B?c=@AKywzXqS;!N6M-d=q3R=;Shg1WEh!3*8O&*)>Rbh63#s0 zPcG+;ZU8pNM_VyoeX3W!Gg^tO(gXW29yn0D28DNuDr1CY%p?|VWEaCJaHL{sI2wf* z-iq?D9k7R))&R~)(3=oU@*f8pmZJI|34<3r1)I=(CDaX8=> zwW{%88vWrs`K7U(3$}=mM8~5om6yjF(TBf1mdDtBsIGmfqD7cdOpd7eLdsG*lLQ{3 zb|&h}(VUz-vBgqQk(v~#h*uW$x3M2Ed+tbcjHAGMh=ct0kvfl~i&FK8tVjr# zCQEIC8_R$mS@Ot2D@~;w4F`QE^gQwmv;kjyz)+pQn}gNvW)LQq*B{v)y_wQK_GatR zuISA>AK&xp(cMs)A3K&)rRoy%vmz^UN+#+tX!r}qI-+-1e*fL|V;iG)*M9%qhDTQ_ z2LM1Q`o!`0^pMX#I-l}{4Eyn;&G6HQkA7x76$n=NNU2UzsRqOTp;C=^RH|v$A1>8N zlKOVZo-YSoXV3HV$?>Y9Qs*$4!Y>(kGs^7o{umHR0TLi9kaD$G@ z1}`>C_CRI6Q8=nWbVORUNVaH&2r`3=h)i&VuPS>A*z{5WX#ywMFoZf<^cS`qJ+%xe z@pq>-V*KFLAjUP1Ey8&6u}`+4vk1ofm8+mgRMZ`H3pH;PbyCrm_2UijaPN3Cy!~H$ zd^t7VU7Mz=&XAj`s@LHHPFJ5ZsAlr*aSv{@PWLaQY*Fo~O84Roa_^?g0ti7XOPJ8B zs6&?%r`J~EV4^OCJSr>}{7^>KN4|A>!3_k~ovFqt%g!ur1~E|t`~<#2)Q19n8&H)& zZd1EPd5rY^+?ji}+QH8w?pAH8MQr+0hMZO5izCq!WJ!m9g+7)+XK&C9s=ZvipT(@c zm9v;n;L#2(D82`4_eTAqL!&IERx*<$$kuU z8kCKqQqX53oecX|o>}mx8npL`hnsRyef48xdk$WJ(aQF6`u)s;>~arT@Wg1Gi(0Y` zT0J~b0~D1}QO%*Y(nA-cpG8qk!GfzOs%d$Cq6WHuccN}@PSnsMKNp1!GIJ5eL4>Zz z6~pR8H8mNPlz1=+j;0o?SdPhCg0~u)kKY`K^KUQDsO@s-Q?&qR;Zy%h0j6dpJWJo3 z&z^0=<8Pf^n?*RT8q{ZkkfO#+`MCbcyA49!@RKXl{FhHI*p!a?wE0M^c+e7xTy%do zm=>;|*ALgI2CfCao@~Yq5whVWfivvLMimB&Zuype?x~*qGf>{)Nxu@Mnf#1ohY`Oq z6vBxL((>;oUu5>ohNpJ3aj5(AV*LihiuKzj#Qis<-KgJ`h_E)Ph#)P>;GmoCxo)W5 zCD9Iy0x89+P6X^H6>b3>6&OkY`e_H~zo}G0H8@JEnH8UToUv7McB(DDN(i(lRJMOL zwZ}mBAb0NX`(uDZGe;SOW)6MUUjzyKJj_)+uOc_b1zq;@{*)&&` delta 5938 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 diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 131746904204cc3faa563e28912e7372e759f5d6..e61db822b6762d0cb5272178d5d7dd6abc135219 100755 GIT binary patch delta 16765 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 T4uT&(7ya3vMt>IkpDX_liiWTQ delta 16532 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 diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 58dff40f048c99f93b6798bf1de26963f302c4ba..e3c02138b403c00bed0215d39817b1911f8517d8 100755 GIT binary patch delta 17567 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 diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 3e476d5ec4aa9dcad450d17a744e092d10a0ad1b..bbe3d0bf3f77459fc19e8678a92134d6d24733d7 100755 GIT binary patch delta 19575 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 Cs6wIu diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index 4a91dfb6fa7a597bfddbf2d0998380faf73486c3..3313aacbb7141bd7c52996d84b6f6c49da189dc7 100755 GIT binary patch delta 54096 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⪼SA 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 Date: Thu, 26 May 2022 16:41:38 +0200 Subject: [PATCH 323/373] [fix]: governance overflow, proposal validation --- apps/src/lib/client/rpc.rs | 121 +++++------ apps/src/lib/client/tx.rs | 62 ++++-- .../lib/node/ledger/shell/finalize_block.rs | 184 +---------------- apps/src/lib/node/ledger/shell/governance.rs | 195 ++++++++++++++++++ apps/src/lib/node/ledger/shell/mod.rs | 1 + shared/src/ledger/governance/parameters.rs | 18 ++ shared/src/ledger/governance/utils.rs | 35 ++-- shared/src/types/token.rs | 6 + 8 files changed, 347 insertions(+), 275 deletions(-) create mode 100644 apps/src/lib/node/ledger/shell/governance.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 34652ac825..597205656b 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -13,11 +13,12 @@ use async_std::prelude::*; use borsh::BorshDeserialize; use itertools::Itertools; use namada::ledger::governance::storage as gov_storage; -use namada::ledger::governance::utils::Votes; +use namada::ledger::governance::utils::{Votes, VotePower}; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ Epoch as PosEpoch, VotingPower, WeightedValidator, }; +use namada::ledger::governance::parameters::GovParams; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; @@ -414,7 +415,7 @@ pub async fn query_proposal_result( } }; } else { - eprintln!("Either id or offline should be used as arguments."); + eprintln!("Either --proposal-id or --data-path should be provided as arguments."); cli::safe_exit(1) } } @@ -427,45 +428,8 @@ pub async fn query_protocol_parameters( ) { let client = HttpClient::new(args.query.ledger_address).unwrap(); - println!("Goveranance parameters"); - let key = gov_storage::get_max_proposal_code_size_key(); - let max_proposal_code_size = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Max. proposal code size: {}", - "", max_proposal_code_size - ); - - let key = gov_storage::get_max_proposal_content_key(); - let max_proposal_content = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Max. proposal content size: {}", - "", max_proposal_content - ); - - let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!("{:4}Min. proposal funds: {}", "", min_proposal_fund); - - let key = gov_storage::get_min_proposal_grace_epoch_key(); - let min_proposal_grace_epoch = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Min. proposal grace epoch: {}", - "", min_proposal_grace_epoch - ); - - let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!("{:4}Min. proposal period: {}", "", min_proposal_period); + let gov_parameters = get_governance_parameters(&client).await; + println!("Governance Parameters\n {:4}", gov_parameters); println!("Protocol parameters"); let key = param_storage::get_epoch_storage_key(); @@ -1558,9 +1522,9 @@ pub async fn get_proposal_votes( query_storage_prefix::(client.clone(), vote_prefix_key) .await; - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1570,7 +1534,7 @@ pub async fn get_proposal_votes( if vote.is_yay() && validators.contains(&voter_address) { let amount = get_validator_stake(client, epoch, &voter_address).await; - yay_validators.insert(voter_address, amount); + yay_validators.insert(voter_address, VotePower::from(amount)); } else if !validators.contains(&voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key) @@ -1587,9 +1551,9 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators.insert(voter_address, amount); + yay_delegators.insert(voter_address, VotePower::from(amount)); } else { - nay_delegators.insert(voter_address, amount); + nay_delegators.insert(voter_address, VotePower::from(amount)); } } } @@ -1612,9 +1576,9 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1641,7 +1605,7 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators.insert(proposal_vote.address, amount); + yay_validators.insert(proposal_vote.address, VotePower::from(amount)); } else if is_delegator_at( client, &proposal_vote.address, @@ -1669,9 +1633,9 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators.insert(validator_address, amount); + yay_delegators.insert(validator_address, VotePower::from(amount)); } else { - nay_delegators.insert(validator_address, amount); + nay_delegators.insert(validator_address, VotePower::from(amount)); } } } @@ -1701,7 +1665,7 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_stacked_tokens = Amount::from(0); + let mut total_yay_stacked_tokens = VotePower::from(0 as u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -1788,8 +1752,8 @@ pub async fn get_total_staked_tokes( client: &HttpClient, epoch: Epoch, validators: &[Address], -) -> token::Amount { - let mut total = Amount::from(0); +) -> VotePower { + let mut total = VotePower::from(0 as u64); for validator in validators { total += get_validator_stake(client, epoch, validator).await; @@ -1801,7 +1765,7 @@ async fn get_validator_stake( client: &HttpClient, epoch: Epoch, validator: &Address, -) -> token::Amount { +) -> VotePower { let total_voting_power_key = pos::validator_total_deltas_key(validator); let total_voting_power = query_storage_value::( client, @@ -1811,9 +1775,12 @@ async fn get_validator_stake( .expect("Total deltas should be defined"); let epoched_total_voting_power = total_voting_power.get(epoch); if let Some(epoched_total_voting_power) = epoched_total_voting_power { - token::Amount::from_change(epoched_total_voting_power) + match VotePower::try_from(epoched_total_voting_power) { + Ok(voting_power) => voting_power, + Err(_) => VotePower::from(0 as u64), + } } else { - token::Amount::from(0) + VotePower::from(0 as u64) } } @@ -1836,3 +1803,39 @@ pub async fn get_delegators_delegation( } delegation_addresses } + +pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { + let key = gov_storage::get_max_proposal_code_size_key(); + let max_proposal_code_size = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_max_proposal_content_key(); + let max_proposal_content_size = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_fund_key(); + let min_proposal_fund = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_grace_epoch_key(); + let min_proposal_grace_epochs = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + let key = gov_storage::get_min_proposal_period_key(); + let min_proposal_period = query_storage_value::(&client, &key) + .await + .expect("Parameter should be definied."); + + return GovParams { + min_proposal_fund: u64::from(min_proposal_fund), + max_proposal_code_size: u64::from(max_proposal_code_size), + min_proposal_period: u64::from(min_proposal_period), + max_proposal_content_size: u64::from(max_proposal_content_size), + min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), + } + +} \ No newline at end of file diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1d41ebbc77..29519ec8b2 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -532,7 +532,43 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let proposal: Proposal = serde_json::from_reader(file).expect("JSON was not well-formatted"); + let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let signer = WalletAddress::new(proposal.clone().author.to_string()); + let goverance_parameters = rpc::get_governance_parameters(&client).await; + let current_epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; + + if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { + eprintln!( + "Invalid proposal start epoch: {} must be greater than current \ + epoch {} and a multiple of 3", + proposal.voting_start_epoch, current_epoch + ); + safe_exit(1) + } else if proposal.voting_end_epoch <= proposal.voting_start_epoch + || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 + < goverance_parameters.min_proposal_period || proposal.voting_end_epoch.0 % 3 == 0 + { + eprintln!( + "Invalid proposal end epoch: difference between proposal start \ + and end epoch must be at least {} and end epoch must be a multiple of 3", + goverance_parameters.min_proposal_period + ); + safe_exit(1) + } else if proposal.grace_epoch <= proposal.voting_end_epoch + || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 + < goverance_parameters.min_proposal_grace_epochs + { + eprintln!( + "Invalid proposal grace epoch: difference between proposal grace \ + and end epoch must be at least {}", + goverance_parameters.min_proposal_grace_epochs + ); + safe_exit(1) + } if args.offline { let signer = ctx.get(&signer); @@ -556,8 +592,6 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } } } else { - let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let tx_data: Result = proposal.clone().try_into(); let init_proposal_data = if let Ok(data) = tx_data { data @@ -566,35 +600,21 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { safe_exit(1) }; - let min_proposal_funds_key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = - rpc::query_storage_value(&client, &min_proposal_funds_key) - .await - .unwrap(); let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < min_proposal_funds { + if balance < token::Amount::from(goverance_parameters.min_proposal_fund) { eprintln!( "Address {} doesn't have enough funds.", &proposal.author ); safe_exit(1); } - let min_proposal_funds_key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_funds: Amount = - rpc::query_storage_value(&client, &min_proposal_funds_key) - .await - .unwrap(); - let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) - .await - .unwrap_or_default(); - if balance < min_proposal_funds { - eprintln!( - "Address {} doesn't have enough funds.", - &proposal.author - ); + if init_proposal_data.content.len() + > goverance_parameters.max_proposal_content_size as usize + { + eprintln!("Proposal content size too big.",); safe_exit(1); } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 1f758a2f2e..a52876492c 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,20 +1,11 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell -use namada::ledger::governance::storage as gov_storage; -use namada::ledger::governance::utils::{ - compute_tally, get_proposal_votes, ProposalEvent, -}; -use namada::ledger::governance::vp::ADDRESS as gov_address; -use namada::ledger::storage::types::encode; -use namada::ledger::treasury::ADDRESS as treasury_address; -use namada::types::address::{xan as m1t, Address}; -use namada::types::governance::TallyResult; -use namada::types::storage::{BlockHash, Epoch, Header}; +use namada::types::storage::{BlockHash, Header}; use tendermint_proto::abci::Misbehavior as Evidence; use tendermint_proto::crypto::PublicKey as TendermintPublicKey; +use super::governance::execute_governance_proposals; use super::*; -use crate::node::ledger::events::EventType; impl Shell where @@ -53,175 +44,8 @@ where let (height, new_epoch) = self.update_state(req.header, req.hash, req.byzantine_validators); - if new_epoch { - for id in std::mem::take(&mut self.proposal_data) { - let proposal_funds_key = gov_storage::get_funds_key(id); - let proposal_start_epoch_key = - gov_storage::get_voting_start_epoch_key(id); - - let funds = self - .read_storage_key::(&proposal_funds_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal funds.".to_string(), - ) - })?; - let proposal_start_epoch = self - .read_storage_key::(&proposal_start_epoch_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal start_epoch.".to_string(), - ) - })?; - - let votes = - get_proposal_votes(&self.storage, proposal_start_epoch, id); - let tally_result = - compute_tally(&self.storage, proposal_start_epoch, votes); - - let transfer_address = match tally_result { - TallyResult::Passed => { - let proposal_author_key = - gov_storage::get_author_key(id); - let proposal_author = self - .read_storage_key::

(&proposal_author_key) - .ok_or_else(|| { - Error::BadProposal( - id, - "Invalid proposal author.".to_string(), - ) - })?; - - let proposal_code_key = - gov_storage::get_proposal_code_key(id); - let proposal_code = - self.read_storage_key_bytes(&proposal_code_key); - match proposal_code { - Some(proposal_code) => { - let tx = - Tx::new(proposal_code, Some(encode(&id))); - let tx_type = TxType::Decrypted( - DecryptedTx::Decrypted(tx), - ); - let pending_execution_key = - gov_storage::get_proposal_execution_key(id); - self.storage - .write(&pending_execution_key, "") - .expect( - "Should be able to write to storage.", - ); - let tx_result = protocol::apply_tx( - tx_type, - 0, /* this is used to compute the fee - * based on the code size. We dont - * need it here. */ - &mut BlockGasMeter::default(), - &mut self.write_log, - &self.storage, - &mut self.vp_wasm_cache, - &mut self.tx_wasm_cache, - ); - self.storage - .delete(&pending_execution_key) - .expect( - "Should be able to delete the storage.", - ); - match tx_result { - Ok(tx_result) => { - if tx_result.is_accepted() { - self.write_log.commit_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed, - id, - true, - true, - ) - .into(); - response - .events - .push(proposal_event); - - proposal_author - } else { - self.write_log.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal - .to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response - .events - .push(proposal_event); - - treasury_address - } - } - Err(_e) => { - self.write_log.drop_tx(); - let proposal_event: Event = - ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - true, - false, - ) - .into(); - response.events.push(proposal_event); - - treasury_address - } - } - } - None => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Passed, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - - proposal_author - } - } - } - TallyResult::Rejected | TallyResult::Unknown => { - let proposal_event: Event = ProposalEvent::new( - EventType::Proposal.to_string(), - TallyResult::Rejected, - id, - false, - false, - ) - .into(); - response.events.push(proposal_event); - - treasury_address - } - }; - - // transfer proposal locked funds - self.storage.transfer( - &m1t(), - funds, - &gov_address, - &transfer_address, - ); - } - } + let _proposals_result = + execute_governance_proposals(self, new_epoch, &mut response)?; for processed_tx in &req.txs { let tx = if let Ok(tx) = Tx::try_from(processed_tx.tx.as_ref()) { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs new file mode 100644 index 0000000000..e65ede6c07 --- /dev/null +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -0,0 +1,195 @@ +use anoma::ledger::governance::storage as gov_storage; +use anoma::ledger::governance::utils::{ + compute_tally, get_proposal_votes, ProposalEvent, +}; +use anoma::ledger::governance::vp::ADDRESS as gov_address; +use anoma::ledger::storage::types::encode; +use anoma::ledger::storage::{DBIter, StorageHasher, DB}; +use anoma::ledger::treasury::ADDRESS as treasury_address; +use anoma::types::address::{xan as m1t, Address}; +use anoma::types::governance::TallyResult; +use anoma::types::storage::Epoch; +use anoma::types::token; + +use super::*; +use crate::node::ledger::events::EventType; + +pub struct ProposalsResult { + passed: Vec, + rejected: Vec, +} + +pub fn execute_governance_proposals( + shell: &mut Shell, + new_epoch: bool, + response: &mut shim::response::FinalizeBlock, +) -> Result +where + D: DB + for<'iter> DBIter<'iter> + Sync + 'static, + H: StorageHasher + Sync + 'static, +{ + let mut proposals_result = ProposalsResult { + passed: Vec::new(), + rejected: Vec::new(), + }; + + if !new_epoch { + return Ok(proposals_result); + } + + for id in std::mem::take(&mut shell.proposal_data) { + let proposal_funds_key = gov_storage::get_funds_key(id); + let proposal_start_epoch_key = + gov_storage::get_voting_start_epoch_key(id); + + let funds = shell + .read_storage_key::(&proposal_funds_key) + .ok_or_else(|| { + Error::BadProposal(id, "Invalid proposal funds.".to_string()) + })?; + let proposal_start_epoch = shell + .read_storage_key::(&proposal_start_epoch_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal start_epoch.".to_string(), + ) + })?; + + let votes = + get_proposal_votes(&shell.storage, proposal_start_epoch, id); + let tally_result = + compute_tally(&shell.storage, proposal_start_epoch, votes); + + let transfer_address = match tally_result { + TallyResult::Passed => { + let proposal_author_key = gov_storage::get_author_key(id); + let proposal_author = shell + .read_storage_key::
(&proposal_author_key) + .ok_or_else(|| { + Error::BadProposal( + id, + "Invalid proposal author.".to_string(), + ) + })?; + + let proposal_code_key = gov_storage::get_proposal_code_key(id); + let proposal_code = + shell.read_storage_key_bytes(&proposal_code_key); + match proposal_code { + Some(proposal_code) => { + let tx = Tx::new(proposal_code, Some(encode(&id))); + let tx_type = + TxType::Decrypted(DecryptedTx::Decrypted(tx)); + let pending_execution_key = + gov_storage::get_proposal_execution_key(id); + shell + .storage + .write(&pending_execution_key, "") + .expect("Should be able to write to storage."); + let tx_result = protocol::apply_tx( + tx_type, + 0, /* this is used to compute the fee + * based on the code size. We dont + * need it here. */ + &mut BlockGasMeter::default(), + &mut shell.write_log, + &shell.storage, + &mut shell.vp_wasm_cache, + &mut shell.tx_wasm_cache, + ); + shell + .storage + .delete(&pending_execution_key) + .expect("Should be able to delete the storage."); + match tx_result { + Ok(tx_result) => { + if tx_result.is_accepted() { + shell.write_log.commit_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + true, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.passed.push(id); + + proposal_author + } else { + shell.write_log.drop_tx(); + let proposal_event: Event = + ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + } + Err(_e) => { + shell.write_log.drop_tx(); + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + true, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + } + } + None => { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Passed, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.passed.push(id); + + proposal_author + } + } + } + TallyResult::Rejected | TallyResult::Unknown => { + let proposal_event: Event = ProposalEvent::new( + EventType::Proposal.to_string(), + TallyResult::Rejected, + id, + false, + false, + ) + .into(); + response.events.push(proposal_event.into()); + proposals_result.rejected.push(id); + + treasury_address + } + }; + + // transfer proposal locked funds + shell + .storage + .transfer(&m1t(), funds, &gov_address, &transfer_address); + } + + Ok(proposals_result) +} diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 1f9759a2a2..4495cacc28 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -10,6 +10,7 @@ mod init_chain; mod prepare_proposal; mod process_proposal; mod queries; +mod governance; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; diff --git a/shared/src/ledger/governance/parameters.rs b/shared/src/ledger/governance/parameters.rs index 79c3b4d5b6..5b280491b9 100644 --- a/shared/src/ledger/governance/parameters.rs +++ b/shared/src/ledger/governance/parameters.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use borsh::{BorshDeserialize, BorshSerialize}; use super::storage as gov_storage; @@ -30,6 +32,22 @@ pub struct GovParams { pub min_proposal_grace_epochs: u64, } +impl Display for GovParams { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Min. proposal fund: {}\nMax. proposal code size: {}\nMin. \ + proposal period: {}\nMax. proposal content size: {}\nMin. \ + proposal grace epochs: {}", + self.min_proposal_fund, + self.max_proposal_code_size, + self.min_proposal_period, + self.max_proposal_content_size, + self.min_proposal_grace_epochs + ) + } +} + impl Default for GovParams { fn default() -> Self { Self { diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index aaca277f91..7b03ee1bd6 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -15,15 +15,17 @@ use crate::types::governance::{ProposalVote, TallyResult}; use crate::types::storage::{Epoch, Key}; use crate::types::token; +pub type VotePower = u128; + /// Proposal structure holding votes information necessary to compute the /// outcome pub struct Votes { /// Map from validators who votes yay to their total stake amount - pub yay_validators: HashMap, + pub yay_validators: HashMap, /// Map from delegation who votes yay to their bond amount - pub yay_delegators: HashMap, + pub yay_delegators: HashMap, /// Map from delegation who votes nay to their bond amount - pub nay_delegators: HashMap, + pub nay_delegators: HashMap, } /// Proposal errors @@ -93,7 +95,7 @@ where nay_delegators, } = votes; - let mut total_yay_stacked_tokens = token::Amount::from(0); + let mut total_yay_stacked_tokens = VotePower::from(0 as u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -110,7 +112,7 @@ where if yay_validators.contains_key(&validator_address) { total_yay_stacked_tokens -= amount; } - } + } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { TallyResult::Passed @@ -205,9 +207,9 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap = HashMap::new(); + let mut nay_delegators: HashMap = HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -236,12 +238,12 @@ where if vote.is_yay() { yay_delegators.insert( address.clone(), - amount, + VotePower::from(amount), ); } else { nay_delegators.insert( address.clone(), - amount, + VotePower::from(amount), ); } } @@ -300,14 +302,14 @@ fn get_total_stacked_tokens( storage: &Storage, epoch: Epoch, validators: &[Address], -) -> token::Amount +) -> VotePower where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { return validators .iter() - .fold(token::Amount::from(0), |acc, validator| { + .fold(VotePower::from(0 as u64), |acc, validator| { acc + get_validator_stake(storage, epoch, validator) }); } @@ -316,7 +318,7 @@ fn get_validator_stake( storage: &Storage, epoch: Epoch, validator: &Address, -) -> token::Amount +) -> VotePower where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, @@ -331,9 +333,12 @@ where if let Some(total_delta) = total_delta { let epoched_total_delta = total_delta.get(epoch); if let Some(epoched_total_delta) = epoched_total_delta { - return token::Amount::from_change(epoched_total_delta); + match VotePower::try_from(epoched_total_delta) { + Ok(voting_power) => return voting_power, + Err(_) => return VotePower::from(0 as u64), + } } } } - token::Amount::from(0) + VotePower::from(0 as u64) } diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index f3e4cd4ed2..4942051826 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -139,6 +139,12 @@ impl From for u64 { } } +impl From for u128 { + fn from(amount: Amount) -> Self { + u128::from(amount.micro) + } +} + impl Add for Amount { type Output = Amount; From 2aee155197527cf2b00ee0cf0ac447bc03cd1b62 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 26 May 2022 17:23:36 +0200 Subject: [PATCH 324/373] [feat]: added total votes to query --- apps/src/lib/client/rpc.rs | 21 ++++++++++++++++----- shared/src/ledger/governance/utils.rs | 4 +--- shared/src/types/governance.rs | 25 ++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 597205656b..eef53cf7f5 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -13,7 +13,8 @@ use async_std::prelude::*; use borsh::BorshDeserialize; use itertools::Itertools; use namada::ledger::governance::storage as gov_storage; -use namada::ledger::governance::utils::{Votes, VotePower}; +use namada::ledger::governance::utils::Votes; +use namada::types::governance::VotePower; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ Epoch as PosEpoch, VotingPower, WeightedValidator, @@ -25,7 +26,7 @@ use namada::ledger::pos::{ use namada::ledger::treasury::storage as treasury_storage; use namada::types::address::Address; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalVote, TallyResult, + OfflineProposal, OfflineVote, ProposalVote, TallyResult, ProposalResult, }; use namada::types::key::*; use namada::types::storage::{Epoch, PrefixValue}; @@ -1654,7 +1655,7 @@ pub async fn compute_tally( client: &HttpClient, epoch: Epoch, votes: Votes, -) -> TallyResult { +) -> ProposalResult { let validators = get_all_validators(client, epoch).await; let total_stacked_tokens = get_total_staked_tokes(client, epoch, &validators).await; @@ -1685,9 +1686,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - TallyResult::Passed + return ProposalResult{ + result: TallyResult::Passed, + total_voting_power: total_stacked_tokens, + total_yay_power: total_yay_stacked_tokens, + total_nay_power: 0, + } } else { - TallyResult::Rejected + return ProposalResult{ + result: TallyResult::Rejected, + total_voting_power: total_stacked_tokens, + total_yay_power: total_yay_stacked_tokens, + total_nay_power: 0, + } } } diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 7b03ee1bd6..93671f99fa 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -11,12 +11,10 @@ use crate::ledger::pos; use crate::ledger::pos::{BondId, Bonds, ValidatorSets, ValidatorTotalDeltas}; use crate::ledger::storage::{DBIter, Storage, StorageHasher, DB}; use crate::types::address::Address; -use crate::types::governance::{ProposalVote, TallyResult}; +use crate::types::governance::{ProposalVote, TallyResult, VotePower}; use crate::types::storage::{Epoch, Key}; use crate::types::token; -pub type VotePower = u128; - /// Proposal structure holding votes information necessary to compute the /// outcome pub struct Votes { diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index c8bf57469f..8ff1d96993 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -15,6 +15,8 @@ use super::key::SigScheme; use super::storage::Epoch; use super::transaction::governance::InitProposalData; +pub type VotePower = u128; + #[derive( Debug, Clone, @@ -83,7 +85,28 @@ pub enum TallyResult { Unknown, } -impl fmt::Display for TallyResult { +/// The result with votes of a proposal +pub struct ProposalResult { + pub result: TallyResult, + pub total_voting_power: VotePower, + pub total_yay_power: VotePower, + pub total_nay_power: VotePower, +} + +impl Display for ProposalResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} with {} yay votes over {} ({:.2}%)", + self.result, + self.total_yay_power, + self.total_voting_power, + (self.total_yay_power / self.total_voting_power) * 100 + ) + } +} + +impl Display for TallyResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { TallyResult::Passed => write!(f, "passed"), From 9a08d61d4a29e1c5d767125c7276b9ade0190e4b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Thu, 26 May 2022 17:54:34 +0200 Subject: [PATCH 325/373] [misc]: clippy, fmt --- apps/src/lib/client/rpc.rs | 41 ++++++++++++++++----------- apps/src/lib/client/tx.rs | 13 ++++++--- apps/src/lib/node/ledger/shell/mod.rs | 2 +- shared/src/ledger/governance/utils.rs | 10 +++---- shared/src/types/governance.rs | 5 ++++ 5 files changed, 44 insertions(+), 27 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index eef53cf7f5..029da55866 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -416,7 +416,10 @@ pub async fn query_proposal_result( } }; } else { - eprintln!("Either --proposal-id or --data-path should be provided as arguments."); + eprintln!( + "Either --proposal-id or --data-path should be provided \ + as arguments." + ); cli::safe_exit(1) } } @@ -1552,9 +1555,11 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators.insert(voter_address, VotePower::from(amount)); + yay_delegators + .insert(voter_address, VotePower::from(amount)); } else { - nay_delegators.insert(voter_address, VotePower::from(amount)); + nay_delegators + .insert(voter_address, VotePower::from(amount)); } } } @@ -1606,7 +1611,8 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators.insert(proposal_vote.address, VotePower::from(amount)); + yay_validators + .insert(proposal_vote.address, VotePower::from(amount)); } else if is_delegator_at( client, &proposal_vote.address, @@ -1634,9 +1640,11 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators.insert(validator_address, VotePower::from(amount)); + yay_delegators + .insert(validator_address, VotePower::from(amount)); } else { - nay_delegators.insert(validator_address, VotePower::from(amount)); + nay_delegators + .insert(validator_address, VotePower::from(amount)); } } } @@ -1666,7 +1674,7 @@ pub async fn compute_tally( nay_delegators, } = votes; - let mut total_yay_stacked_tokens = VotePower::from(0 as u64); + let mut total_yay_stacked_tokens = VotePower::from(0_u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -1686,19 +1694,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - return ProposalResult{ + return ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - } + }; } else { - return ProposalResult{ + return ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - } + }; } } @@ -1764,7 +1772,7 @@ pub async fn get_total_staked_tokes( epoch: Epoch, validators: &[Address], ) -> VotePower { - let mut total = VotePower::from(0 as u64); + let mut total = VotePower::from(0_u64); for validator in validators { total += get_validator_stake(client, epoch, validator).await; @@ -1788,10 +1796,10 @@ async fn get_validator_stake( if let Some(epoched_total_voting_power) = epoched_total_voting_power { match VotePower::try_from(epoched_total_voting_power) { Ok(voting_power) => voting_power, - Err(_) => VotePower::from(0 as u64), + Err(_) => VotePower::from(0_u64), } } else { - VotePower::from(0 as u64) + VotePower::from(0_u64) } } @@ -1847,6 +1855,5 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { min_proposal_period: u64::from(min_proposal_period), max_proposal_content_size: u64::from(max_proposal_content_size), min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), - } - -} \ No newline at end of file + }; +} diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 29519ec8b2..a4660f2284 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -541,7 +541,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { + if proposal.voting_start_epoch <= current_epoch + || proposal.voting_start_epoch.0 % 3 == 0 + { eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of 3", @@ -550,11 +552,13 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { safe_exit(1) } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - < goverance_parameters.min_proposal_period || proposal.voting_end_epoch.0 % 3 == 0 + < goverance_parameters.min_proposal_period + || proposal.voting_end_epoch.0 % 3 == 0 { eprintln!( "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and end epoch must be a multiple of 3", + and end epoch must be at least {} and end epoch must be a \ + multiple of 3", goverance_parameters.min_proposal_period ); safe_exit(1) @@ -603,7 +607,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < token::Amount::from(goverance_parameters.min_proposal_fund) { + if balance < token::Amount::from(goverance_parameters.min_proposal_fund) + { eprintln!( "Address {} doesn't have enough funds.", &proposal.author diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 4495cacc28..336fc781ea 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -6,11 +6,11 @@ //! (unless we can simply overwrite them in the next block). //! More info in . mod finalize_block; +mod governance; mod init_chain; mod prepare_proposal; mod process_proposal; mod queries; -mod governance; use std::collections::HashSet; use std::convert::{TryFrom, TryInto}; diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 93671f99fa..aeb75c53d0 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -93,7 +93,7 @@ where nay_delegators, } = votes; - let mut total_yay_stacked_tokens = VotePower::from(0 as u64); + let mut total_yay_stacked_tokens = VotePower::from(0_u64); for (_, amount) in yay_validators.clone().into_iter() { total_yay_stacked_tokens += amount; } @@ -110,7 +110,7 @@ where if yay_validators.contains_key(&validator_address) { total_yay_stacked_tokens -= amount; } - } + } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { TallyResult::Passed @@ -307,7 +307,7 @@ where { return validators .iter() - .fold(VotePower::from(0 as u64), |acc, validator| { + .fold(VotePower::from(0_u64), |acc, validator| { acc + get_validator_stake(storage, epoch, validator) }); } @@ -333,10 +333,10 @@ where if let Some(epoched_total_delta) = epoched_total_delta { match VotePower::try_from(epoched_total_delta) { Ok(voting_power) => return voting_power, - Err(_) => return VotePower::from(0 as u64), + Err(_) => return VotePower::from(0_u64), } } } } - VotePower::from(0 as u64) + VotePower::from(0_u64) } diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index 8ff1d96993..abe65f9d37 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -15,6 +15,7 @@ use super::key::SigScheme; use super::storage::Epoch; use super::transaction::governance::InitProposalData; +/// Type alias for vote power pub type VotePower = u128; #[derive( @@ -87,9 +88,13 @@ pub enum TallyResult { /// The result with votes of a proposal pub struct ProposalResult { + /// The result of a proposal pub result: TallyResult, + /// The total voting power during the proposal tally pub total_voting_power: VotePower, + /// The total voting power from yay votes pub total_yay_power: VotePower, + /// The total voting power from nay votes (unused at the moment) pub total_nay_power: VotePower, } From afe779a6a08fce87d450b0710f57a185e0b1d325 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 11:25:09 +0200 Subject: [PATCH 326/373] [fix]: clippy, fmt --- apps/src/lib/client/rpc.rs | 17 ++++++++--------- apps/src/lib/client/tx.rs | 11 +++++++---- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 029da55866..3167895bcc 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1538,7 +1538,7 @@ pub async fn get_proposal_votes( if vote.is_yay() && validators.contains(&voter_address) { let amount = get_validator_stake(client, epoch, &voter_address).await; - yay_validators.insert(voter_address, VotePower::from(amount)); + yay_validators.insert(voter_address, amount); } else if !validators.contains(&voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key) @@ -1611,8 +1611,7 @@ pub async fn get_proposal_offline_votes( &proposal_vote.address, ) .await; - yay_validators - .insert(proposal_vote.address, VotePower::from(amount)); + yay_validators.insert(proposal_vote.address, amount); } else if is_delegator_at( client, &proposal_vote.address, @@ -1694,19 +1693,19 @@ pub async fn compute_tally( } if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { - return ProposalResult { + ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - }; + } } else { - return ProposalResult { + ProposalResult { result: TallyResult::Rejected, total_voting_power: total_stacked_tokens, total_yay_power: total_yay_stacked_tokens, total_nay_power: 0, - }; + } } } @@ -1849,11 +1848,11 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { .await .expect("Parameter should be definied."); - return GovParams { + GovParams { min_proposal_fund: u64::from(min_proposal_fund), max_proposal_code_size: u64::from(max_proposal_code_size), min_proposal_period: u64::from(min_proposal_period), max_proposal_content_size: u64::from(max_proposal_content_size), min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), - }; + } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index a4660f2284..8f286215f8 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -541,13 +541,15 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch <= current_epoch + if proposal.voting_start_epoch >= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { eprintln!( "Invalid proposal start epoch: {} must be greater than current \ - epoch {} and a multiple of 3", - proposal.voting_start_epoch, current_epoch + epoch {} and a multiple of {}", + proposal.voting_start_epoch, + current_epoch, + goverance_parameters.min_proposal_period ); safe_exit(1) } else if proposal.voting_end_epoch <= proposal.voting_start_epoch @@ -558,7 +560,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { eprintln!( "Invalid proposal end epoch: difference between proposal start \ and end epoch must be at least {} and end epoch must be a \ - multiple of 3", + multiple of {}", + goverance_parameters.min_proposal_period, goverance_parameters.min_proposal_period ); safe_exit(1) From ee303e5419d550b1d51777c7d46247827c9e4d63 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 11:48:03 +0200 Subject: [PATCH 327/373] [fix]: clippy, fmt --- apps/src/lib/client/rpc.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 3167895bcc..237f5d32de 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1824,35 +1824,35 @@ pub async fn get_delegators_delegation( pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { let key = gov_storage::get_max_proposal_code_size_key(); - let max_proposal_code_size = query_storage_value::(&client, &key) + let max_proposal_code_size = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_max_proposal_content_key(); - let max_proposal_content_size = query_storage_value::(&client, &key) + let max_proposal_content_size = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_fund_key(); - let min_proposal_fund = query_storage_value::(&client, &key) + let min_proposal_fund = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_grace_epoch_key(); - let min_proposal_grace_epochs = query_storage_value::(&client, &key) + let min_proposal_grace_epochs = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); let key = gov_storage::get_min_proposal_period_key(); - let min_proposal_period = query_storage_value::(&client, &key) + let min_proposal_period = query_storage_value::(client, &key) .await .expect("Parameter should be definied."); GovParams { min_proposal_fund: u64::from(min_proposal_fund), - max_proposal_code_size: u64::from(max_proposal_code_size), - min_proposal_period: u64::from(min_proposal_period), - max_proposal_content_size: u64::from(max_proposal_content_size), - min_proposal_grace_epochs: u64::from(min_proposal_grace_epochs), + max_proposal_code_size, + min_proposal_period, + max_proposal_content_size, + min_proposal_grace_epochs, } } From 124b2d975e0af7921a3c52e438f21d26bcc256d6 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 12:47:28 +0200 Subject: [PATCH 328/373] [fix]: bad validation condition --- apps/src/lib/client/tx.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8f286215f8..9decbc127c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -541,7 +541,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { }) .await; - if proposal.voting_start_epoch >= current_epoch + if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 % 3 == 0 { eprintln!( From 4373e5d2fbb70a10479e0a18f2dc831f24a21973 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:37:28 +0200 Subject: [PATCH 329/373] [fix]: governance vp author address, proposal submission validation --- apps/src/lib/client/tx.rs | 6 ++++-- shared/src/ledger/governance/vp.rs | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 9decbc127c..84270e773a 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -542,8 +542,10 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; if proposal.voting_start_epoch <= current_epoch - || proposal.voting_start_epoch.0 % 3 == 0 + || proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period != 0 { + println!("{}", proposal.voting_start_epoch <= current_epoch); + println!("{}", proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period == 0); eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of {}", @@ -555,7 +557,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 < goverance_parameters.min_proposal_period - || proposal.voting_end_epoch.0 % 3 == 0 + || proposal.voting_end_epoch.0 % 3 != 0 { eprintln!( "Invalid proposal end epoch: difference between proposal start \ diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 6ccfc7a33e..aa66fa95d6 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -88,15 +88,18 @@ where let has_pre_author = ctx.has_key_pre(&author_key).ok(); match (has_pre_author, author) { (Some(has_pre_author), Some(author)) => { - // TODO: if author is an implicit address, we should asssume its - // existence we should reuse the same logic as in - // check_address_existence in shared/src/vm/host_env.rs - let address_exist_key = Key::validity_predicate(&author); - let address_exist = ctx.has_key_post(&address_exist_key).ok(); - if let Some(address_exist) = address_exist { - !has_pre_author && verifiers.contains(&author) && address_exist - } else { - false + match author { + Address::Established(_) => { + let address_exist_key = Key::validity_predicate(&author); + let address_exist = ctx.has_key_post(&address_exist_key).ok(); + if let Some(address_exist) = address_exist { + !has_pre_author && verifiers.contains(&author) && address_exist + } else { + false + } + }, + Address::Implicit(_) => !has_pre_author && verifiers.contains(&author), + Address::Internal(_) => return false, } } _ => false, From f7cb4c09348bb4b8d0d5684815956bd11056d840 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:44:50 +0200 Subject: [PATCH 330/373] [feat]: vote transaction validation --- apps/src/lib/client/tx.rs | 28 +++++++++++++++++++++++++--- shared/src/ledger/governance/vp.rs | 30 ++++++++++++++++-------------- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 84270e773a..68e7dced01 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -542,10 +542,17 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; if proposal.voting_start_epoch <= current_epoch - || proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period != 0 + || proposal.voting_start_epoch.0 + % goverance_parameters.min_proposal_period + != 0 { println!("{}", proposal.voting_start_epoch <= current_epoch); - println!("{}", proposal.voting_start_epoch.0 % goverance_parameters.min_proposal_period == 0); + println!( + "{}", + proposal.voting_start_epoch.0 + % goverance_parameters.min_proposal_period + == 0 + ); eprintln!( "Invalid proposal start epoch: {} must be greater than current \ epoch {} and a multiple of {}", @@ -692,6 +699,8 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); + let current_epoch = + rpc::query_epoch(args::Query { ledger_address }).await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); @@ -705,6 +714,14 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { match proposal_start_epoch { Some(epoch) => { + if current_epoch < epoch { + eprintln!( + "Current epoch {} is not greater than proposal start \ + epoch ", + current_epoch, epoch + ); + safe_exit(1) + } let mut delegation_addresses = rpc::get_delegators_delegation( &client, &voter_address, @@ -752,7 +769,12 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { process_tx(ctx, &args.tx, tx, Some(signer)).await; } None => { - eprintln!("Proposal start epoch is not in the storage.") + eprintln!( + "Proposal start epoch is for proposal id {} is not \ + definied.", + proposal_id + ); + safe_exit(1) } } } diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index aa66fa95d6..7f50f85fc7 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -87,21 +87,23 @@ where let author = read(ctx, &author_key, ReadType::POST).ok(); let has_pre_author = ctx.has_key_pre(&author_key).ok(); match (has_pre_author, author) { - (Some(has_pre_author), Some(author)) => { - match author { - Address::Established(_) => { - let address_exist_key = Key::validity_predicate(&author); - let address_exist = ctx.has_key_post(&address_exist_key).ok(); - if let Some(address_exist) = address_exist { - !has_pre_author && verifiers.contains(&author) && address_exist - } else { - false - } - }, - Address::Implicit(_) => !has_pre_author && verifiers.contains(&author), - Address::Internal(_) => return false, + (Some(has_pre_author), Some(author)) => match author { + Address::Established(_) => { + let address_exist_key = Key::validity_predicate(&author); + let address_exist = ctx.has_key_post(&address_exist_key).ok(); + if let Some(address_exist) = address_exist { + !has_pre_author + && verifiers.contains(&author) + && address_exist + } else { + false + } } - } + Address::Implicit(_) => { + !has_pre_author && verifiers.contains(&author) + } + Address::Internal(_) => return false, + }, _ => false, } } From 4903744b272e3bcfd5bd9d3e72fd50f55f53553a Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 15:56:46 +0200 Subject: [PATCH 331/373] [fix]: error println --- apps/src/lib/client/tx.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 68e7dced01..53bcef6047 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -700,7 +700,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let current_epoch = - rpc::query_epoch(args::Query { ledger_address }).await; + rpc::query_epoch(args::Query { ledger_address: args.tx.ledger_address.clone() }).await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); @@ -717,7 +717,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { if current_epoch < epoch { eprintln!( "Current epoch {} is not greater than proposal start \ - epoch ", + epoch {}", current_epoch, epoch ); safe_exit(1) From 3ca66b288051639f0b59e94b2242923b4d0ff5d0 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 16:08:45 +0200 Subject: [PATCH 332/373] [misc]: clippy, fmt --- apps/src/lib/client/tx.rs | 6 ++++-- shared/src/ledger/governance/vp.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 53bcef6047..2131410839 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -699,8 +699,10 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } } else { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); - let current_epoch = - rpc::query_epoch(args::Query { ledger_address: args.tx.ledger_address.clone() }).await; + let current_epoch = rpc::query_epoch(args::Query { + ledger_address: args.tx.ledger_address.clone(), + }) + .await; let voter_address = ctx.get(signer); let proposal_id = args.proposal_id.unwrap(); diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 7f50f85fc7..300787fba5 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -102,7 +102,7 @@ where Address::Implicit(_) => { !has_pre_author && verifiers.contains(&author) } - Address::Internal(_) => return false, + Address::Internal(_) => false, }, _ => false, } From 94500543274c1fcbea368f75c7d77010b8481055 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 16:52:07 +0200 Subject: [PATCH 333/373] [fix]: e2e test --- tests/src/e2e/ledger_tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 22eb4f4ac5..f438bbad85 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1174,7 +1174,10 @@ fn proposal_submission() -> Result<()> { &validator_one_rpc, ]; let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; - client.exp_string("Transaction is invalid.")?; + client.exp_string( + "Invalid proposal end epoch: difference between proposal start and \ + end epoch must be at least 3 and end epoch must be a multiple of 3", + )?; client.assert_success(); // 7. Check invalid proposal was not accepted From 6cbf9dcc242c632c641b825d16ba26638c1c423b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Fri, 27 May 2022 17:36:49 +0200 Subject: [PATCH 334/373] [fix]: e2e test --- tests/src/e2e/ledger_tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f438bbad85..fd08f3a3df 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1402,8 +1402,8 @@ fn proposal_offline() -> Result<()> { }, "author": albert, "voting_start_epoch": 3, - "voting_end_epoch": 6, - "grace_epoch": 6 + "voting_end_epoch": 9, + "grace_epoch": 18 } ); generate_proposal_json( From ecbefd0ebfa87401d7655029a2250b19e9bbab4e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 30 May 2022 18:17:20 +0200 Subject: [PATCH 335/373] Fixes e2e tests --- tests/src/e2e/ledger_tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index fd08f3a3df..63c99efecb 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1079,7 +1079,6 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 3. Query the proposal - let proposal_query_args = vec![ "query-proposal", "--proposal-id", @@ -1161,6 +1160,7 @@ fn proposal_submission() -> Result<()> { "grace_epoch": 10009, } ); + generate_proposal_json( invalid_proposal_json_path.clone(), invalid_proposal_json, @@ -1178,7 +1178,7 @@ fn proposal_submission() -> Result<()> { "Invalid proposal end epoch: difference between proposal start and \ end epoch must be at least 3 and end epoch must be a multiple of 3", )?; - client.assert_success(); + client.assert_failure(); // 7. Check invalid proposal was not accepted let proposal_query_args = vec![ @@ -1337,7 +1337,7 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, query_protocol_parameters, Some(30))?; - client.exp_regex(".*Min. proposal grace epoch: 9.*")?; + client.exp_regex(".*Min. proposal grace epochs: 9.*")?; client.assert_success(); Ok(()) From e49d8b06449ba38725fb22f179fdc82368bba30e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 31 May 2022 17:16:56 +0200 Subject: [PATCH 336/373] Fixes test artifacts folder persistence --- apps/src/lib/client/rpc.rs | 13 ++++-- apps/src/lib/client/tx.rs | 22 +++++++--- tests/src/e2e/ledger_tests.rs | 83 +++++++++++------------------------ 3 files changed, 53 insertions(+), 65 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 237f5d32de..7c744b9693 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -349,7 +349,14 @@ pub async fn query_proposal_result( if entry.file_name().eq(&"proposal") { is_proposal_present = true - } else { + } else if entry + .file_name() + .to_string_lossy() + .starts_with("proposal-vote-") + { + // Folder may contain other + // files than just the proposal + // and the votes files.insert(entry.path()); } } @@ -371,8 +378,8 @@ pub async fn query_proposal_result( if !is_proposal_present { eprintln!( - "The folder must contain a the offline \ - proposal in a file named proposal" + "The folder must contain the offline proposal \ + in a file named \"proposal\"" ); cli::safe_exit(1) } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 2131410839..feb5194340 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -596,11 +596,18 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { .await; let offline_proposal = OfflineProposal::new(proposal, signer, &signing_key); - let proposal_filename = "proposal".to_string(); + let proposal_filename = args + .proposal_data + .parent() + .expect("No parent found") + .join("proposal"); let out = File::create(&proposal_filename).unwrap(); match serde_json::to_writer_pretty(out, &offline_proposal) { Ok(_) => { - println!("Proposal created: {}.", proposal_filename); + println!( + "Proposal created: {}.", + proposal_filename.to_string_lossy() + ); } Err(e) => { eprintln!("Error while creating proposal file: {}.", e); @@ -685,12 +692,17 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { &signing_key, ); - let proposal_vote_filename = - format!("proposal-vote-{}", &signer.to_string()); + let proposal_vote_filename = proposal_file_path + .parent() + .expect("No parent found") + .join(format!("proposal-vote-{}", &signer.to_string())); let out = File::create(&proposal_vote_filename).unwrap(); match serde_json::to_writer_pretty(out, &offline_vote) { Ok(_) => { - println!("Proposal vote created: {}.", proposal_vote_filename); + println!( + "Proposal vote created: {}.", + proposal_vote_filename.to_string_lossy() + ); } Err(e) => { eprintln!("Error while creating proposal vote file: {}.", e); diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 63c99efecb..feefeb9d46 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -9,8 +9,6 @@ //! To keep the temporary files created by a test, use env var //! `ANOMA_E2E_KEEP_TEMP=true`. -use std::fs::{self, OpenOptions}; -use std::path::PathBuf; use std::process::Command; use std::sync::Arc; use std::time::{Duration, Instant}; @@ -24,7 +22,6 @@ use namada_apps::config::genesis::genesis_config::{ use serde_json::json; use setup::constants::*; -use super::setup::working_dir; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; @@ -1034,8 +1031,7 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let valid_proposal_json_path = - test.test_dir.path().join("valid_proposal.json"); + let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); let albert = find_address(&test, ALBERT)?; @@ -1059,18 +1055,18 @@ fn proposal_submission() -> Result<()> { "proposal_code_path": proposal_code.to_str().unwrap() } ); - - generate_proposal_json( - valid_proposal_json_path.clone(), - valid_proposal_json, - ); + let proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); + let proposal_path = proposal_file.path(); + let proposal_ref = proposal_path.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", "--data-path", - valid_proposal_json_path.to_str().unwrap(), + &proposal_ref, "--ledger-address", &validator_one_rpc, ]; @@ -1123,8 +1119,6 @@ fn proposal_submission() -> Result<()> { // 6. Submit an invalid proposal // proposal is invalid due to voting_end_epoch - voting_start_epoch < 3 - let invalid_proposal_json_path = - test.test_dir.path().join("invalid_proposal.json"); let albert = find_address(&test, ALBERT)?; let invalid_proposal_json = json!( { @@ -1160,16 +1154,17 @@ fn proposal_submission() -> Result<()> { "grace_epoch": 10009, } ); - - generate_proposal_json( - invalid_proposal_json_path.clone(), - invalid_proposal_json, - ); + let invalid_proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&invalid_proposal_file, &invalid_proposal_json) + .unwrap(); + let invalid_proposal_path = invalid_proposal_file.path(); + let invalid_proposal_ref = invalid_proposal_path.to_string_lossy(); let submit_proposal_args = vec![ "init-proposal", "--data-path", - invalid_proposal_json_path.to_str().unwrap(), + &invalid_proposal_ref, "--ledger-address", &validator_one_rpc, ]; @@ -1384,8 +1379,8 @@ fn proposal_offline() -> Result<()> { client.assert_success(); // 2. Create an offline proposal - let valid_proposal_json_path = - test.test_dir.path().join("valid_proposal.json"); + let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); + let albert = find_address(&test, ALBERT)?; let valid_proposal_json = json!( { @@ -1406,17 +1401,18 @@ fn proposal_offline() -> Result<()> { "grace_epoch": 18 } ); - generate_proposal_json( - valid_proposal_json_path.clone(), - valid_proposal_json, - ); + let proposal_file = + tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); + serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); + let proposal_path = proposal_file.path(); + let proposal_ref = proposal_path.to_string_lossy(); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let offline_proposal_args = vec![ "init-proposal", "--data-path", - valid_proposal_json_path.to_str().unwrap(), + &proposal_ref, "--offline", "--ledger-address", &validator_one_rpc, @@ -1433,7 +1429,7 @@ fn proposal_offline() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let proposal_path = working_dir().join("proposal"); + let proposal_path = test_dir.path().join("proposal"); let proposal_ref = proposal_path.to_string_lossy(); let submit_proposal_vote = vec![ "vote-proposal", @@ -1453,31 +1449,17 @@ fn proposal_offline() -> Result<()> { client.assert_success(); let expected_file_name = format!("proposal-vote-{}", albert); - let expected_path_vote = working_dir().join(&expected_file_name); + let expected_path_vote = test_dir.path().join(&expected_file_name); assert!(expected_path_vote.exists()); - let expected_path_proposal = working_dir().join("proposal"); + let expected_path_proposal = test_dir.path().join("proposal"); assert!(expected_path_proposal.exists()); // 4. Compute offline tally - let proposal_data_folder = working_dir().join("proposal-test-data"); - fs::create_dir_all(&proposal_data_folder) - .expect("Should create a new folder."); - fs::copy( - expected_path_proposal, - &proposal_data_folder.join("proposal"), - ) - .expect("Should copy proposal file."); - fs::copy( - expected_path_vote, - &proposal_data_folder.join(&expected_file_name), - ) - .expect("Should copy proposal vote file."); - let tally_offline = vec![ "query-proposal-result", "--data-path", - proposal_data_folder.to_str().unwrap(), + test_dir.path().to_str().unwrap(), "--offline", "--ledger-address", &validator_one_rpc, @@ -1490,19 +1472,6 @@ fn proposal_offline() -> Result<()> { Ok(()) } -fn generate_proposal_json( - proposal_path: PathBuf, - proposal_content: serde_json::Value, -) { - let intent_writer = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(proposal_path) - .unwrap(); - serde_json::to_writer(intent_writer, &proposal_content).unwrap(); -} - /// In this test we: /// 1. Setup 2 genesis validators /// 2. Initialize a new network with the 2 validators From 2a385bd918291be19ad2c44f67af8b2721952e7e Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Mon, 13 Jun 2022 16:32:24 +0200 Subject: [PATCH 337/373] [fix]: votes accumulation --- apps/src/lib/client/rpc.rs | 72 ++++++++++++++++++++------- apps/src/lib/client/tx.rs | 2 + shared/src/ledger/governance/utils.rs | 67 +++++++++++++++---------- shared/src/types/governance.rs | 5 +- 4 files changed, 99 insertions(+), 47 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 7c744b9693..d31059ff35 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -232,6 +232,8 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Status: on-going", ""); } else { let votes = get_proposal_votes(client, start_epoch, id).await; + println!("{:?}", votes.yay_delegators); + println!("{:?}", votes.yay_validators); let proposal_result = compute_tally(client, start_epoch, votes).await; println!("{:4}Status: done", ""); @@ -1534,8 +1536,8 @@ pub async fn get_proposal_votes( .await; let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1562,11 +1564,25 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - yay_delegators - .insert(voter_address, VotePower::from(amount)); + match yay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + yay_delegators.insert(voter_address, delegations_map); + } + } } else { - nay_delegators - .insert(voter_address, VotePower::from(amount)); + match nay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + nay_delegators.insert(voter_address, delegations_map); + } + } } } } @@ -1590,8 +1606,8 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1646,11 +1662,25 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - yay_delegators - .insert(validator_address, VotePower::from(amount)); + match yay_delegators.get_mut(&proposal_vote.address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + yay_delegators.insert(proposal_vote.address.clone(), delegations_map); + } + } } else { - nay_delegators - .insert(validator_address, VotePower::from(amount)); + match nay_delegators.get_mut(&proposal_vote.address) { + Some(map) => { + map.insert(validator_address, VotePower::from(amount)); + }, + None => { + let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); + nay_delegators.insert(proposal_vote.address.clone(), delegations_map); + } + } } } } @@ -1686,20 +1716,24 @@ pub async fn compute_tally( } // YAY: Add delegator amount whose validator didn't vote / voted nay - for (validator_address, amount) in yay_delegators.into_iter() { - if !yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens += amount; + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if !yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens += vote_power; + } } } // NAY: Remove delegator amount whose validator validator vote yay - for (validator_address, amount) in nay_delegators.into_iter() { - if yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens -= amount; + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens -= vote_power; + } } } - if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { + if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 { ProposalResult { result: TallyResult::Passed, total_voting_power: total_stacked_tokens, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index feb5194340..1d68780aa3 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -767,6 +767,8 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { .await; } + println!("{:?}", delegation_addresses); + let tx_data = VoteProposalData { id: proposal_id, vote: args.vote, diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index aeb75c53d0..790ce3ec1f 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -21,9 +21,9 @@ pub struct Votes { /// Map from validators who votes yay to their total stake amount pub yay_validators: HashMap, /// Map from delegation who votes yay to their bond amount - pub yay_delegators: HashMap, + pub yay_delegators: HashMap>, /// Map from delegation who votes nay to their bond amount - pub nay_delegators: HashMap, + pub nay_delegators: HashMap>, } /// Proposal errors @@ -98,21 +98,26 @@ where total_yay_stacked_tokens += amount; } + // YAY: Add delegator amount whose validator didn't vote / voted nay - for (validator_address, amount) in yay_delegators.into_iter() { - if !yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens += amount; + for (_, vote_map) in yay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if !yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens += vote_power; + } } } // NAY: Remove delegator amount whose validator validator vote yay - for (validator_address, amount) in nay_delegators.into_iter() { - if yay_validators.contains_key(&validator_address) { - total_yay_stacked_tokens -= amount; + for (_, vote_map) in nay_delegators.iter() { + for (validator_address, vote_power) in vote_map.into_iter() { + if yay_validators.contains_key(&validator_address) { + total_yay_stacked_tokens -= vote_power; + } } } - if 3 * total_yay_stacked_tokens >= 2 * total_stacked_tokens { + if total_yay_stacked_tokens >= (total_stacked_tokens / 3) * 2 { TallyResult::Passed } else { TallyResult::Rejected @@ -205,9 +210,9 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap = HashMap::new(); - let mut nay_delegators: HashMap = HashMap::new(); + let mut yay_validators= HashMap::new(); + let mut yay_delegators: HashMap> = HashMap::new(); + let mut nay_delegators: HashMap> = HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -216,33 +221,43 @@ where (Some(key), Some(vote)) => { let voter_address = gov_storage::get_voter_address(&key); match voter_address { - Some(address) => { - if vote.is_yay() && validators.contains(address) { + Some(voter_address) => { + if vote.is_yay() && validators.contains(voter_address) { let amount = - get_validator_stake(storage, epoch, address); - yay_validators.insert(address.clone(), amount); - } else if !validators.contains(address) { + get_validator_stake(storage, epoch, voter_address); + yay_validators.insert(voter_address.clone(), amount); + } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key); match validator_address { Some(validator_address) => { let amount = get_bond_amount_at( storage, - address, + voter_address, validator_address, epoch, ); if let Some(amount) = amount { if vote.is_yay() { - yay_delegators.insert( - address.clone(), - VotePower::from(amount), - ); + match yay_delegators.get_mut(&voter_address) { + Some(map) => { + map.insert(validator_address.clone(), VotePower::from(amount)); + }, + None => { + let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); + yay_delegators.insert(voter_address.clone(), delegations_map); + } + } } else { - nay_delegators.insert( - address.clone(), - VotePower::from(amount), - ); + match nay_delegators.get_mut(&voter_address.clone()) { + Some(map) => { + map.insert(validator_address.clone(), VotePower::from(amount)); + }, + None => { + let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); + nay_delegators.insert(voter_address.clone(), delegations_map); + } + } } } } diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index abe65f9d37..8a8577abdc 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -13,6 +13,7 @@ use super::hash::Hash; use super::key::common::{self, Signature}; use super::key::SigScheme; use super::storage::Epoch; +use super::token::SCALE; use super::transaction::governance::InitProposalData; /// Type alias for vote power @@ -104,8 +105,8 @@ impl Display for ProposalResult { f, "{} with {} yay votes over {} ({:.2}%)", self.result, - self.total_yay_power, - self.total_voting_power, + self.total_yay_power / SCALE as u128, + self.total_voting_power / SCALE as u128, (self.total_yay_power / self.total_voting_power) * 100 ) } From 20de724b8416d0863a927aea7a52e6e650331b6d Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli Date: Mon, 13 Jun 2022 16:33:24 +0200 Subject: [PATCH 338/373] [misc]: remove logs --- apps/src/lib/client/rpc.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d31059ff35..4e83841890 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -232,8 +232,6 @@ pub async fn query_proposal(_ctx: Context, args: args::QueryProposal) { println!("{:4}Status: on-going", ""); } else { let votes = get_proposal_votes(client, start_epoch, id).await; - println!("{:?}", votes.yay_delegators); - println!("{:?}", votes.yay_validators); let proposal_result = compute_tally(client, start_epoch, votes).await; println!("{:4}Status: done", ""); From 2423a0217ad7a184e345c52a13dc356ce32005d6 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Jun 2022 15:32:02 +0200 Subject: [PATCH 339/373] Fixes `safe_exit` call only if `force` is not set --- apps/src/lib/client/tx.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 1d68780aa3..b5978b4824 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -560,7 +560,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { current_epoch, goverance_parameters.min_proposal_period ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 < goverance_parameters.min_proposal_period @@ -573,7 +575,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { goverance_parameters.min_proposal_period, goverance_parameters.min_proposal_period ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } else if proposal.grace_epoch <= proposal.voting_end_epoch || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 < goverance_parameters.min_proposal_grace_epochs @@ -583,7 +587,9 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { and end epoch must be at least {}", goverance_parameters.min_proposal_grace_epochs ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } if args.offline { @@ -734,7 +740,10 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { epoch {}", current_epoch, epoch ); - safe_exit(1) + + if !args.tx.force { + safe_exit(1) + } } let mut delegation_addresses = rpc::get_delegators_delegation( &client, @@ -786,11 +795,13 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } None => { eprintln!( - "Proposal start epoch is for proposal id {} is not \ + "Proposal start epoch for proposal id {} is not \ definied.", proposal_id ); - safe_exit(1) + if !args.tx.force { + safe_exit(1) + } } } } From a6bf03ac3c85fc1673acd54a79440dfd4a401b39 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 14 Jun 2022 18:16:16 +0200 Subject: [PATCH 340/373] Speeds up testing --- tests/src/e2e/ledger_tests.rs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index feefeb9d46..46e6bfca75 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -991,7 +991,19 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { - let test = setup::network(|genesis| genesis, None)?; + let test = setup::network(|genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 1, + min_duration: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; + + GenesisConfig { + parameters, + ..genesis + } + }, None)?; let anomac_help = vec!["--help"]; @@ -1049,9 +1061,9 @@ fn proposal_submission() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 6, - "voting_end_epoch": 18, - "grace_epoch": 24, + "voting_start_epoch": 12, + "voting_end_epoch": 24, + "grace_epoch": 30, "proposal_code_path": proposal_code.to_str().unwrap() } ); @@ -1205,7 +1217,7 @@ fn proposal_submission() -> Result<()> { // 9. Send a yay vote from a validator let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 7 { + while epoch.0 <= 13 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1270,7 +1282,7 @@ fn proposal_submission() -> Result<()> { // 11. Query the proposal and check the result let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 19 { + while epoch.0 <= 25 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1289,7 +1301,7 @@ fn proposal_submission() -> Result<()> { // 12. Wait proposal grace and check proposal author funds let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 < 26 { + while epoch.0 < 31 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } @@ -1424,7 +1436,7 @@ fn proposal_offline() -> Result<()> { // 3. Generate an offline yay vote let mut epoch = get_epoch(&test, &validator_one_rpc).unwrap(); - while epoch.0 <= 5 { + while epoch.0 <= 2 { sleep(1); epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } From 0a7f0f57f7480fe7852935aca36bfb1c4543072e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 1 Jul 2022 15:43:47 +0200 Subject: [PATCH 341/373] fmt and fix clippy --- apps/src/lib/client/rpc.rs | 96 +++++++++++++++----- apps/src/lib/client/tx.rs | 3 +- apps/src/lib/node/ledger/shell/governance.rs | 10 +- shared/src/ledger/governance/utils.rs | 79 +++++++++++----- tests/src/e2e/ledger_tests.rs | 27 +++--- 5 files changed, 151 insertions(+), 64 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4e83841890..5f9aa6385d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1534,8 +1534,10 @@ pub async fn get_proposal_votes( .await; let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); if let Some(vote_iter) = vote_iter { for (key, vote) in vote_iter { @@ -1564,21 +1566,41 @@ pub async fn get_proposal_votes( if vote.is_yay() { match yay_delegators.get_mut(&voter_address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - yay_delegators.insert(voter_address, delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + yay_delegators + .insert(voter_address, delegations_map); } } } else { match nay_delegators.get_mut(&voter_address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - nay_delegators.insert(voter_address, delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + nay_delegators + .insert(voter_address, delegations_map); } } } @@ -1604,8 +1626,10 @@ pub async fn get_proposal_offline_votes( let proposal_hash = proposal.compute_hash(); let mut yay_validators: HashMap = HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); for path in files { let file = File::open(&path).expect("Proposal file must exist."); @@ -1662,21 +1686,45 @@ pub async fn get_proposal_offline_votes( if proposal_vote.vote.is_yay() { match yay_delegators.get_mut(&proposal_vote.address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - yay_delegators.insert(proposal_vote.address.clone(), delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + yay_delegators.insert( + proposal_vote.address.clone(), + delegations_map, + ); } } } else { match nay_delegators.get_mut(&proposal_vote.address) { Some(map) => { - map.insert(validator_address, VotePower::from(amount)); - }, + map.insert( + validator_address, + VotePower::from(amount), + ); + } None => { - let delegations_map: HashMap = HashMap::from([(validator_address, VotePower::from(amount))]); - nay_delegators.insert(proposal_vote.address.clone(), delegations_map); + let delegations_map: HashMap< + Address, + VotePower, + > = HashMap::from([( + validator_address, + VotePower::from(amount), + )]); + nay_delegators.insert( + proposal_vote.address.clone(), + delegations_map, + ); } } } @@ -1715,8 +1763,8 @@ pub async fn compute_tally( // YAY: Add delegator amount whose validator didn't vote / voted nay for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if !yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if !yay_validators.contains_key(validator_address) { total_yay_stacked_tokens += vote_power; } } @@ -1724,8 +1772,8 @@ pub async fn compute_tally( // NAY: Remove delegator amount whose validator validator vote yay for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if yay_validators.contains_key(validator_address) { total_yay_stacked_tokens -= vote_power; } } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index b5978b4824..8df5869727 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -795,8 +795,7 @@ pub async fn submit_vote_proposal(mut ctx: Context, args: args::VoteProposal) { } None => { eprintln!( - "Proposal start epoch for proposal id {} is not \ - definied.", + "Proposal start epoch for proposal id {} is not definied.", proposal_id ); if !args.tx.force { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index e65ede6c07..6c88ffd591 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -115,7 +115,7 @@ where true, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.passed.push(id); proposal_author @@ -130,7 +130,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address @@ -146,7 +146,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address @@ -162,7 +162,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.passed.push(id); proposal_author @@ -178,7 +178,7 @@ where false, ) .into(); - response.events.push(proposal_event.into()); + response.events.push(proposal_event); proposals_result.rejected.push(id); treasury_address diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 790ce3ec1f..3e75f99e93 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -98,11 +98,10 @@ where total_yay_stacked_tokens += amount; } - // YAY: Add delegator amount whose validator didn't vote / voted nay for (_, vote_map) in yay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if !yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if !yay_validators.contains_key(validator_address) { total_yay_stacked_tokens += vote_power; } } @@ -110,8 +109,8 @@ where // NAY: Remove delegator amount whose validator validator vote yay for (_, vote_map) in nay_delegators.iter() { - for (validator_address, vote_power) in vote_map.into_iter() { - if yay_validators.contains_key(&validator_address) { + for (validator_address, vote_power) in vote_map.iter() { + if yay_validators.contains_key(validator_address) { total_yay_stacked_tokens -= vote_power; } } @@ -210,9 +209,11 @@ where gov_storage::get_proposal_vote_prefix_key(proposal_id); let (vote_iter, _) = storage.iter_prefix(&vote_prefix_key); - let mut yay_validators= HashMap::new(); - let mut yay_delegators: HashMap> = HashMap::new(); - let mut nay_delegators: HashMap> = HashMap::new(); + let mut yay_validators = HashMap::new(); + let mut yay_delegators: HashMap> = + HashMap::new(); + let mut nay_delegators: HashMap> = + HashMap::new(); for (key, vote_bytes, _) in vote_iter { let vote_key = Key::from_str(key.as_str()).ok(); @@ -223,9 +224,13 @@ where match voter_address { Some(voter_address) => { if vote.is_yay() && validators.contains(voter_address) { - let amount = - get_validator_stake(storage, epoch, voter_address); - yay_validators.insert(voter_address.clone(), amount); + let amount = get_validator_stake( + storage, + epoch, + voter_address, + ); + yay_validators + .insert(voter_address.clone(), amount); } else if !validators.contains(voter_address) { let validator_address = gov_storage::get_vote_delegation_address(&key); @@ -239,23 +244,55 @@ where ); if let Some(amount) = amount { if vote.is_yay() { - match yay_delegators.get_mut(&voter_address) { + match yay_delegators + .get_mut(voter_address) + { Some(map) => { - map.insert(validator_address.clone(), VotePower::from(amount)); - }, + map.insert( + validator_address + .clone(), + VotePower::from(amount), + ); + } None => { - let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); - yay_delegators.insert(voter_address.clone(), delegations_map); + let delegations_map = + HashMap::from([( + validator_address + .clone(), + VotePower::from( + amount, + ), + )]); + yay_delegators.insert( + voter_address.clone(), + delegations_map, + ); } } } else { - match nay_delegators.get_mut(&voter_address.clone()) { + match nay_delegators + .get_mut(&voter_address.clone()) + { Some(map) => { - map.insert(validator_address.clone(), VotePower::from(amount)); - }, + map.insert( + validator_address + .clone(), + VotePower::from(amount), + ); + } None => { - let delegations_map = HashMap::from([(validator_address.clone(), VotePower::from(amount))]); - nay_delegators.insert(voter_address.clone(), delegations_map); + let delegations_map = + HashMap::from([( + validator_address + .clone(), + VotePower::from( + amount, + ), + )]); + nay_delegators.insert( + voter_address.clone(), + delegations_map, + ); } } } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 46e6bfca75..037bdb64d8 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -991,19 +991,22 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { - let test = setup::network(|genesis| { - let parameters = ParametersConfig { - min_num_of_blocks: 1, - min_duration: 1, - max_expected_time_per_block: 1, - ..genesis.parameters - }; + let test = setup::network( + |genesis| { + let parameters = ParametersConfig { + min_num_of_blocks: 1, + min_duration: 1, + max_expected_time_per_block: 1, + ..genesis.parameters + }; - GenesisConfig { - parameters, - ..genesis - } - }, None)?; + GenesisConfig { + parameters, + ..genesis + } + }, + None, + )?; let anomac_help = vec!["--help"]; From a8f6c641dd29f21478a8649bee69a7afde17d4b0 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Tue, 20 Sep 2022 12:41:52 +0200 Subject: [PATCH 342/373] Rename `Treasury` to `SlashFund` --- apps/src/lib/client/rpc.rs | 6 ++-- apps/src/lib/config/genesis.rs | 20 +++++------ apps/src/lib/node/ledger/protocol/mod.rs | 16 ++++----- apps/src/lib/node/ledger/shell/governance.rs | 8 ++--- apps/src/lib/node/ledger/shell/init_chain.rs | 2 +- .../src/explore/design/ledger/governance.md | 8 ++--- genesis/dev.toml | 2 +- genesis/e2e-tests-single-node.toml | 2 +- shared/src/ledger/mod.rs | 2 +- .../ledger/{treasury => slash_fund}/mod.rs | 34 +++++++++---------- .../{treasury => slash_fund}/parameters.rs | 12 +++---- .../{treasury => slash_fund}/storage.rs | 4 +-- shared/src/types/address.rs | 20 +++++------ vm_env/src/lib.rs | 2 +- wasm_for_tests/wasm_source/src/lib.rs | 4 +-- 15 files changed, 71 insertions(+), 71 deletions(-) rename shared/src/ledger/{treasury => slash_fund}/mod.rs (90%) rename shared/src/ledger/{treasury => slash_fund}/parameters.rs (79%) rename shared/src/ledger/{treasury => slash_fund}/storage.rs (91%) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 5f9aa6385d..4434e92d18 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -23,7 +23,7 @@ use namada::ledger::governance::parameters::GovParams; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; -use namada::ledger::treasury::storage as treasury_storage; +use namada::ledger::slash_fund::storage as slash_fund_storage; use namada::types::address::Address; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalVote, TallyResult, ProposalResult, @@ -474,8 +474,8 @@ pub async fn query_protocol_parameters( .expect("Parameter should be definied."); println!("{:4}Transactions whitelist: {:?}", "", tx_whitelist); - println!("Treasury parameters"); - let key = treasury_storage::get_max_transferable_fund_key(); + println!("Slash Fund parameters"); + let key = slash_fund_storage::get_max_transferable_fund_key(); let max_transferable_amount = query_storage_value::(&client, &key) .await .expect("Parameter should be definied."); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..e13e9bf970 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -9,7 +9,7 @@ use derivative::Derivative; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; -use namada::ledger::treasury::parameters::TreasuryParams; +use namada::ledger::slash_fund::parameters::SlashFundParams; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; @@ -31,7 +31,7 @@ pub mod genesis_config { use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::ledger::treasury::parameters::TreasuryParams; + use namada::ledger::slash_fund::parameters::SlashFundParams; use namada::types::address::Address; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; @@ -119,8 +119,8 @@ pub mod genesis_config { pub pos_params: PosParamsConfig, // Governance parameters pub gov_params: GovernanceParamsConfig, - // Treasury parameters - pub treasury_params: TreasuryParamasConfig, + // Slash Fund parameters + pub slash_fund_params: SlashFundParamasConfig, // Wasm definitions pub wasm: HashMap, } @@ -145,8 +145,8 @@ pub mod genesis_config { } #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct TreasuryParamasConfig { - // Maximum funds that can be moved from treasury in a single transfer + pub struct SlashFundParamasConfig { + // Maximum funds that can be moved from slash fund in a single transfer // XXX: u64 doesn't work with toml-rs! pub max_proposal_fund_transfer: u64, } @@ -558,7 +558,7 @@ pub mod genesis_config { .min_proposal_grace_epochs, }; - let treasury_params = TreasuryParams { + let slash_fund_params = SlashFundParams { max_proposal_fund_transfer: 10_000, }; @@ -588,7 +588,7 @@ pub mod genesis_config { parameters, pos_params, gov_params, - treasury_params, + slash_fund_params, }; genesis.init(); genesis @@ -623,7 +623,7 @@ pub struct Genesis { pub parameters: Parameters, pub pos_params: PosParams, pub gov_params: GovParams, - pub treasury_params: TreasuryParams, + pub slash_fund_params: SlashFundParams, } impl Genesis { @@ -859,7 +859,7 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), - treasury_params: TreasuryParams::default(), + slash_fund_params: SlashFundParams::default(), } } diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index e5f191d6e7..c8069d5a9c 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -11,7 +11,7 @@ use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; -use namada::ledger::treasury::TreasuryVp; +use namada::ledger::slash_fund::SlashFundVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; use namada::types::storage; @@ -49,8 +49,8 @@ pub enum Error { IbcTokenNativeVpError(namada::ledger::ibc::vp::IbcTokenError), #[error("Governance native VP error: {0}")] GovernanceNativeVpError(namada::ledger::governance::vp::Error), - #[error("Treasury native VP error: {0}")] - TreasuryNativeVpError(namada::ledger::treasury::Error), + #[error("SlashFund native VP error: {0}")] + SlashFundNativeVpError(namada::ledger::slash_fund::Error), #[error("Ethereum bridge native VP error: {0}")] EthBridgeNativeVpError(namada::ledger::eth_bridge::vp::Error), #[error("Access to an internal address {0} is forbidden")] @@ -325,12 +325,12 @@ where gas_meter = governance.ctx.gas_meter.into_inner(); result } - InternalAddress::Treasury => { - let treasury = TreasuryVp { ctx }; - let result = treasury + InternalAddress::SlashFund => { + let slash_fund = SlashFundVp { ctx }; + let result = slash_fund .validate_tx(tx_data, &keys_changed, &verifiers) - .map_err(Error::TreasuryNativeVpError); - gas_meter = treasury.ctx.gas_meter.into_inner(); + .map_err(Error::SlashFundNativeVpError); + gas_meter = slash_fund.ctx.gas_meter.into_inner(); result } InternalAddress::IbcEscrow(_) diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 6c88ffd591..75e021c0ee 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -5,7 +5,7 @@ use anoma::ledger::governance::utils::{ use anoma::ledger::governance::vp::ADDRESS as gov_address; use anoma::ledger::storage::types::encode; use anoma::ledger::storage::{DBIter, StorageHasher, DB}; -use anoma::ledger::treasury::ADDRESS as treasury_address; +use anoma::ledger::slash_fund::ADDRESS as slash_fund_address; use anoma::types::address::{xan as m1t, Address}; use anoma::types::governance::TallyResult; use anoma::types::storage::Epoch; @@ -133,7 +133,7 @@ where response.events.push(proposal_event); proposals_result.rejected.push(id); - treasury_address + slash_fund_address } } Err(_e) => { @@ -149,7 +149,7 @@ where response.events.push(proposal_event); proposals_result.rejected.push(id); - treasury_address + slash_fund_address } } } @@ -181,7 +181,7 @@ where response.events.push(proposal_event); proposals_result.rejected.push(id); - treasury_address + slash_fund_address } }; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index a989338751..904f509324 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -60,7 +60,7 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); - genesis.treasury_params.init_storage(&mut self.storage); + genesis.slash_fund_params.init_storage(&mut self.storage); // Depends on parameters being initialized self.storage diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index 21fdc55da1..2f3734c8c5 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -2,12 +2,12 @@ Namada introduce a governance mechanism to propose and apply protocol changes with and without the need for an hard fork. Anyone holding some M1T will be able to prosose some changes to which delegators and validator will cast their yay or nay votes. Governance on Namada supports both signaling and voting mechanism. The difference between the the two, is that the former is needed when the changes require an hard fork. In cases where the chain is not able to produce blocks anymore, Namada relies an off chain signaling mechanism to agree on a common strategy. -## Governance & Treasury addresses +## Governance & SlashFund addresses Governance introduce two internal address with their corresponding native vps: - Governance address, which is in charge of validating on-chain proposals and votes -- Treasury address, which is in charge of holding treasury funds +- SlashFund address, which is in charge of holding slashed funds Also, it introduces some protocol parameters: @@ -60,7 +60,7 @@ and follow these rules: - `content` should follow the `Namada Improvement Proposal schema` and must be less than `max_proposal_content_size` kibibytes. - `author` must be a valid address on-chain -A proposal gets accepted if, at least 2/3 of the total voting power (computed at the epoch definied in the `startEpoch` field) vote `yay`. If the proposal is accepted, the locked funds are returned to the address definied in the `proposal_author` field, otherwise are moved to the treasury address. +A proposal gets accepted if, at least 2/3 of the total voting power (computed at the epoch definied in the `startEpoch` field) vote `yay`. If the proposal is accepted, the locked funds are returned to the address definied in the `proposal_author` field, otherwise are moved to the slash fund address. The `proposal_code` field can execute arbitrary code in the form of a wasm transaction. If the proposal gets accepted, the code is executed in the first block of the epoch following the `graceEpoch`. @@ -98,7 +98,7 @@ Vote is valid if it follow this rules: The outcome of a proposal is compute at the epoch specific in the `endEpoch` field and executed at `graceEpoch` field (if it contains a non-empty `proposalCode` field). A proposal is accepted only if more than 2/3 of the voting power vote `yay`. -If a proposal gets accepted, the locked funds will be reimbursed to the author. In case it gets rejected, the locked funds will be moved to treasury. +If a proposal gets accepted, the locked funds will be reimbursed to the author. In case it gets rejected, the locked funds will be moved to slash fund. ## Off-chain proposal diff --git a/genesis/dev.toml b/genesis/dev.toml index 4c48bcc279..729d7a93bb 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -184,5 +184,5 @@ max_proposal_content_size = 5000 # minimum epochs between end and grace epoch min_proposal_grace_epochs = 6 -[treasury_params] +[slash_fund_params] max_proposal_fund_transfer = 10000 \ No newline at end of file diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 96fd787ca2..962502201a 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -192,5 +192,5 @@ max_proposal_content_size = 10000 # minimum epochs between end and grace epoch min_proposal_grace_epochs = 6 -[treasury_params] +[slash_fund_params] max_proposal_fund_transfer = 10000 diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 2d545f96f2..8f0f049683 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -8,5 +8,5 @@ pub mod native_vp; pub mod parameters; pub mod pos; pub mod storage; -pub mod treasury; +pub mod slash_fund; pub mod vp_env; diff --git a/shared/src/ledger/treasury/mod.rs b/shared/src/ledger/slash_fund/mod.rs similarity index 90% rename from shared/src/ledger/treasury/mod.rs rename to shared/src/ledger/slash_fund/mod.rs index 071019059b..f276b6f984 100644 --- a/shared/src/ledger/treasury/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -1,15 +1,15 @@ -//! Treasury VP +//! SlashFund VP use std::collections::BTreeSet; -/// treasury parameters +/// SlashFund parameters pub mod parameters; -/// treasury storage +/// SlashFund storage pub mod storage; use borsh::BorshDeserialize; use thiserror::Error; -use self::storage as treasury_storage; +use self::storage as slash_fund_storage; use super::governance::vp::is_proposal_accepted; use crate::ledger::native_vp::{self, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; @@ -18,8 +18,8 @@ use crate::types::storage::Key; use crate::types::token; use crate::vm::WasmCacheAccess; -/// Internal treasury address -pub const ADDRESS: Address = Address::Internal(InternalAddress::Treasury); +/// Internal SlashFund address +pub const ADDRESS: Address = Address::Internal(InternalAddress::SlashFund); #[allow(missing_docs)] #[derive(Error, Debug)] @@ -28,11 +28,11 @@ pub enum Error { NativeVpError(native_vp::Error), } -/// Treasury functions result +/// SlashFund functions result pub type Result = std::result::Result; -/// Treasury VP -pub struct TreasuryVp<'a, DB, H, CA> +/// SlashFund VP +pub struct SlashFundVp<'a, DB, H, CA> where DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: StorageHasher, @@ -42,7 +42,7 @@ where pub ctx: Ctx<'a, DB, H, CA>, } -impl<'a, DB, H, CA> NativeVp for TreasuryVp<'a, DB, H, CA> +impl<'a, DB, H, CA> NativeVp for SlashFundVp<'a, DB, H, CA> where DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: 'static + StorageHasher, @@ -50,7 +50,7 @@ where { type Error = Error; - const ADDR: InternalAddress = InternalAddress::Treasury; + const ADDR: InternalAddress = InternalAddress::SlashFund; fn validate_tx( &self, @@ -78,7 +78,7 @@ where return false; }; let is_max_funds_transfer_key = - treasury_storage::get_max_transferable_fund_key(); + slash_fund_storage::get_max_transferable_fund_key(); let balance_key = token::balance_key(&nam(), &ADDRESS); let max_transfer_amount = self.ctx.read_pre(&is_max_funds_transfer_key); @@ -141,7 +141,7 @@ where _ => false, } } - KeyType::UNKNOWN_TREASURY => false, + KeyType::UNKNOWN_SLASH_FUND => false, KeyType::UNKNOWN => true, } }); @@ -157,17 +157,17 @@ enum KeyType { PARAMETER, #[allow(clippy::upper_case_acronyms)] #[allow(non_camel_case_types)] - UNKNOWN_TREASURY, + UNKNOWN_SLASH_FUND, #[allow(clippy::upper_case_acronyms)] UNKNOWN, } impl From<&Key> for KeyType { fn from(value: &Key) -> Self { - if treasury_storage::is_parameter_key(value) { + if slash_fund_storage::is_parameter_key(value) { KeyType::PARAMETER - } else if treasury_storage::is_treasury_key(value) { - KeyType::UNKNOWN_TREASURY + } else if slash_fund_storage::is_slash_fund_key(value) { + KeyType::UNKNOWN_SLASH_FUND } else if token::is_any_token_balance_key(value).is_some() { match token::is_balance_key(&nam(), value) { Some(addr) => KeyType::BALANCE(addr.clone()), diff --git a/shared/src/ledger/treasury/parameters.rs b/shared/src/ledger/slash_fund/parameters.rs similarity index 79% rename from shared/src/ledger/treasury/parameters.rs rename to shared/src/ledger/slash_fund/parameters.rs index 2ccb142d09..07e3a027a3 100644 --- a/shared/src/ledger/treasury/parameters.rs +++ b/shared/src/ledger/slash_fund/parameters.rs @@ -1,6 +1,6 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use super::storage as treasury_storage; +use super::storage as slash_fund_storage; use crate::ledger::storage::types::encode; use crate::ledger::storage::{self, Storage}; use crate::types::token::Amount; @@ -17,12 +17,12 @@ use crate::types::token::Amount; BorshDeserialize, )] /// Governance parameter structure -pub struct TreasuryParams { +pub struct SlashFundParams { /// Maximum amount of token that can be moved in a single transfer pub max_proposal_fund_transfer: u64, } -impl Default for TreasuryParams { +impl Default for SlashFundParams { fn default() -> Self { Self { max_proposal_fund_transfer: 10_000, @@ -30,15 +30,15 @@ impl Default for TreasuryParams { } } -impl TreasuryParams { - /// Initialize treasury parameters into storage +impl SlashFundParams { + /// Initialize slash fund parameters into storage pub fn init_storage(&self, storage: &mut Storage) where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: storage::StorageHasher, { let max_proposal_fund_transfer_key = - treasury_storage::get_max_transferable_fund_key(); + slash_fund_storage::get_max_transferable_fund_key(); let amount = Amount::whole(self.max_proposal_fund_transfer); storage .write(&max_proposal_fund_transfer_key, encode(&amount)) diff --git a/shared/src/ledger/treasury/storage.rs b/shared/src/ledger/slash_fund/storage.rs similarity index 91% rename from shared/src/ledger/treasury/storage.rs rename to shared/src/ledger/slash_fund/storage.rs index e4a8733240..0884fdf687 100644 --- a/shared/src/ledger/treasury/storage.rs +++ b/shared/src/ledger/slash_fund/storage.rs @@ -3,8 +3,8 @@ use crate::types::storage::{DbKeySeg, Key, KeySeg}; const MAX_TRANSFERABLE_FUND_KEY: &str = "max_fund"; -/// Check if a key is a treasury key -pub fn is_treasury_key(key: &Key) -> bool { +/// Check if a key is a slash fund key +pub fn is_slash_fund_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &ADDRESS) } diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index 704dcdbdb2..a1ff9702ea 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -59,8 +59,8 @@ mod internal { "ano::Protocol Parameters "; pub const GOVERNANCE: &str = "ano::Governance "; - pub const TREASURY: &str = - "ano::Treasury "; + pub const SLASH_FUND: &str = + "ano::Slash Fund "; pub const IBC_BURN: &str = "ano::IBC Burn Address "; pub const IBC_MINT: &str = @@ -185,7 +185,7 @@ impl Address { InternalAddress::Governance => { internal::GOVERNANCE.to_string() } - InternalAddress::Treasury => internal::TREASURY.to_string(), + InternalAddress::SlashFund => internal::SLASH_FUND.to_string(), InternalAddress::IbcEscrow(hash) => { format!("{}::{}", PREFIX_INTERNAL, hash) } @@ -245,8 +245,8 @@ impl Address { internal::GOVERNANCE => { Ok(Address::Internal(InternalAddress::Governance)) } - internal::TREASURY => { - Ok(Address::Internal(InternalAddress::Treasury)) + internal::SLASH_FUND => { + Ok(Address::Internal(InternalAddress::SlashFund)) } internal::IBC_MINT => { Ok(Address::Internal(InternalAddress::IbcMint)) @@ -447,8 +447,8 @@ pub enum InternalAddress { IbcMint, /// Governance address Governance, - /// Treasury address - Treasury, + /// SlashFund address for governance + SlashFund, /// Bridge to Ethereum EthBridge, } @@ -475,7 +475,7 @@ impl Display for InternalAddress { Self::Ibc => "IBC".to_string(), Self::Parameters => "Parameters".to_string(), Self::Governance => "Governance".to_string(), - Self::Treasury => "Treasury".to_string(), + Self::SlashFund => "SlashFund".to_string(), Self::IbcEscrow(hash) => format!("IbcEscrow: {}", hash), Self::IbcBurn => "IbcBurn".to_string(), Self::IbcMint => "IbcMint".to_string(), @@ -712,7 +712,7 @@ pub mod testing { InternalAddress::PosSlashPool => {} InternalAddress::Ibc => {} InternalAddress::Governance => {} - InternalAddress::Treasury => {} + InternalAddress::SlashFund => {} InternalAddress::Parameters => {} InternalAddress::IbcEscrow(_) => {} InternalAddress::IbcBurn => {} @@ -730,7 +730,7 @@ pub mod testing { Just(InternalAddress::IbcBurn), Just(InternalAddress::IbcMint), Just(InternalAddress::Governance), - Just(InternalAddress::Treasury), + Just(InternalAddress::SlashFund), Just(InternalAddress::EthBridge), ] } diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 578079d695..2d3cab9e8c 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -19,7 +19,7 @@ pub mod tx_prelude { pub use namada::ledger::governance::storage; pub use namada::ledger::parameters::storage as parameters_storage; pub use namada::ledger::storage::types::encode; - pub use namada::ledger::treasury::storage as treasury_storage; + pub use namada::ledger::slash_fund::storage as slash_fund_storage; pub use namada::proto::{Signed, SignedTxData}; pub use namada::types::address::Address; pub use namada::types::storage::Key; diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index 674fb2a2d9..f622e9c9f7 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -33,8 +33,8 @@ pub mod main { let target_key = storage::get_min_proposal_grace_epoch_key(); write(&target_key.to_string(), 9_u64); - // treasury - let target_key = treasury_storage::get_max_transferable_fund_key(); + // slash fund + let target_key = slash_fund_storage::get_max_transferable_fund_key(); write(&target_key.to_string(), token::Amount::whole(20_000)); // parameters From ff951ffabcb9c78bd8f56bf970b6ae88bdd5443e Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 21 Sep 2022 10:50:41 +0200 Subject: [PATCH 343/373] Skip tx whitelist for proposal code --- shared/src/ledger/governance/vp.rs | 2 +- vp_prelude/src/lib.rs | 21 +++++++++++++++++++++ wasm/wasm_source/src/vp_nft.rs | 2 +- wasm/wasm_source/src/vp_testnet_faucet.rs | 2 +- wasm/wasm_source/src/vp_token.rs | 4 ++-- wasm/wasm_source/src/vp_user.rs | 2 +- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index 300787fba5..a1f7457ecc 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -479,7 +479,7 @@ pub enum ReadType { POST, } -/// Check if a proposal id is beign executed +/// Check if a proposal id is being executed pub fn is_proposal_accepted( context: &Ctx, proposal_id: u64, diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 957354d848..daf35b6d06 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -33,6 +33,27 @@ pub fn is_vp_whitelisted(vp_bytes: &[u8]) -> bool { whitelist.is_empty() || whitelist.contains(&vp_hash.to_string()) } +/// Checks if a proposal id is being executed +pub fn is_proposal_accepted(proposal_id: u64) -> bool { + let proposal_execution_key = + gov_storage::get_proposal_execution_key(proposal_id); + + has_key_pre(&proposal_execution_key.to_string()) +} + +/// Checks whether a transaction is valid, which happens in two cases: +/// - tx is whitelisted, or +/// - tx is executed by an approved governance proposal (no need to be whitelisted) +pub fn is_valid_tx(tx_data: &[u8]) -> bool { + if is_tx_whitelisted() { + return true + } else { + let proposal_id = u64::try_from_slice(tx_data).ok(); + + proposal_id.map_or(false, |id| is_proposal_accepted(id)) + } +} + /// Log a string in a debug build. The message will be printed at the /// `tracing::Level::Info`. Any `debug_log!` statements are only enabled in /// non optimized builds by default. An optimized build will not execute diff --git a/wasm/wasm_source/src/vp_nft.rs b/wasm/wasm_source/src/vp_nft.rs index f1e6dd587b..4f57d543d6 100644 --- a/wasm/wasm_source/src/vp_nft.rs +++ b/wasm/wasm_source/src/vp_nft.rs @@ -15,7 +15,7 @@ fn validate_tx( addr, keys_changed, verifiers )); - if !is_tx_whitelisted() { + if !is_valid_tx(&tx_data) { return false; } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index 553288926e..962027c0f4 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -40,7 +40,7 @@ fn validate_tx( _ => false, }); - if !is_tx_whitelisted() { + if !is_valid_tx(&tx_data) { return false; } diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index 60513ce808..3f13cf0aaf 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -5,7 +5,7 @@ use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( - _tx_data: Vec, + tx_data: Vec, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, @@ -18,7 +18,7 @@ fn validate_tx( verifiers ); - if !is_tx_whitelisted() { + if !is_valid_tx(&tx_data) { return false; } diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..e412ffec97 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -88,7 +88,7 @@ fn validate_tx( _ => false, }); - if !is_tx_whitelisted() { + if !is_valid_tx(&tx_data) { return false; } From 4a3d0970e059074e8d547bec53285d07d896cea9 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 21 Sep 2022 11:49:13 +0200 Subject: [PATCH 344/373] Use proposal `end_epoch` instead of `start_epoch` for voting power --- apps/src/lib/node/ledger/shell/governance.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 75e021c0ee..180c0a9af4 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -39,27 +39,26 @@ where for id in std::mem::take(&mut shell.proposal_data) { let proposal_funds_key = gov_storage::get_funds_key(id); - let proposal_start_epoch_key = - gov_storage::get_voting_start_epoch_key(id); + let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(id); let funds = shell .read_storage_key::(&proposal_funds_key) .ok_or_else(|| { Error::BadProposal(id, "Invalid proposal funds.".to_string()) })?; - let proposal_start_epoch = shell - .read_storage_key::(&proposal_start_epoch_key) + let proposal_end_epoch = shell + .read_storage_key::(&proposal_end_epoch_key) .ok_or_else(|| { Error::BadProposal( id, - "Invalid proposal start_epoch.".to_string(), + "Invalid proposal end_epoch.".to_string(), ) })?; let votes = - get_proposal_votes(&shell.storage, proposal_start_epoch, id); + get_proposal_votes(&shell.storage, proposal_end_epoch, id); let tally_result = - compute_tally(&shell.storage, proposal_start_epoch, votes); + compute_tally(&shell.storage, proposal_end_epoch, votes); let transfer_address = match tally_result { TallyResult::Passed => { From de18d0fc57adf1679826ebc945beef92b0042415 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 21 Sep 2022 17:05:26 +0200 Subject: [PATCH 345/373] Removes `max_proposal_fund_transfer` parameter --- apps/src/lib/client/rpc.rs | 11 --- apps/src/lib/config/genesis.rs | 18 ---- apps/src/lib/node/ledger/shell/init_chain.rs | 1 - .../src/explore/design/ledger/governance.md | 2 - genesis/dev.toml | 3 - genesis/e2e-tests-single-node.toml | 3 - shared/src/ledger/slash_fund/mod.rs | 88 ++----------------- shared/src/ledger/slash_fund/parameters.rs | 47 ---------- shared/src/ledger/slash_fund/storage.rs | 28 +----- wasm_for_tests/wasm_source/src/lib.rs | 4 - 10 files changed, 8 insertions(+), 197 deletions(-) delete mode 100644 shared/src/ledger/slash_fund/parameters.rs diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 4434e92d18..afe399a2b4 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -23,7 +23,6 @@ use namada::ledger::governance::parameters::GovParams; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; -use namada::ledger::slash_fund::storage as slash_fund_storage; use namada::types::address::Address; use namada::types::governance::{ OfflineProposal, OfflineVote, ProposalVote, TallyResult, ProposalResult, @@ -474,16 +473,6 @@ pub async fn query_protocol_parameters( .expect("Parameter should be definied."); println!("{:4}Transactions whitelist: {:?}", "", tx_whitelist); - println!("Slash Fund parameters"); - let key = slash_fund_storage::get_max_transferable_fund_key(); - let max_transferable_amount = query_storage_value::(&client, &key) - .await - .expect("Parameter should be definied."); - println!( - "{:4}Max. transferable amount: {}", - "", max_transferable_amount - ); - println!("PoS parameters"); let key = pos::params_key(); let pos_params = query_storage_value::(&client, &key) diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index e13e9bf970..2a74dbc52a 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -9,7 +9,6 @@ use derivative::Derivative; use namada::ledger::governance::parameters::GovParams; use namada::ledger::parameters::Parameters; use namada::ledger::pos::{GenesisValidator, PosParams}; -use namada::ledger::slash_fund::parameters::SlashFundParams; use namada::types::address::Address; #[cfg(not(feature = "dev"))] use namada::types::chain::ChainId; @@ -31,7 +30,6 @@ pub mod genesis_config { use namada::ledger::parameters::{EpochDuration, Parameters}; use namada::ledger::pos::types::BasisPoints; use namada::ledger::pos::{GenesisValidator, PosParams}; - use namada::ledger::slash_fund::parameters::SlashFundParams; use namada::types::address::Address; use namada::types::key::dkg_session_keys::DkgPublicKey; use namada::types::key::*; @@ -119,8 +117,6 @@ pub mod genesis_config { pub pos_params: PosParamsConfig, // Governance parameters pub gov_params: GovernanceParamsConfig, - // Slash Fund parameters - pub slash_fund_params: SlashFundParamasConfig, // Wasm definitions pub wasm: HashMap, } @@ -144,13 +140,6 @@ pub mod genesis_config { pub min_proposal_grace_epochs: u64, } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct SlashFundParamasConfig { - // Maximum funds that can be moved from slash fund in a single transfer - // XXX: u64 doesn't work with toml-rs! - pub max_proposal_fund_transfer: u64, - } - /// Validator pre-genesis configuration can be created with client utils /// `init-genesis-validator` command and added to a genesis for /// `init-network` cmd and that can be subsequently read by `join-network` @@ -558,10 +547,6 @@ pub mod genesis_config { .min_proposal_grace_epochs, }; - let slash_fund_params = SlashFundParams { - max_proposal_fund_transfer: 10_000, - }; - let pos_params = PosParams { max_validator_slots: config.pos_params.max_validator_slots, pipeline_len: config.pos_params.pipeline_len, @@ -588,7 +573,6 @@ pub mod genesis_config { parameters, pos_params, gov_params, - slash_fund_params, }; genesis.init(); genesis @@ -623,7 +607,6 @@ pub struct Genesis { pub parameters: Parameters, pub pos_params: PosParams, pub gov_params: GovParams, - pub slash_fund_params: SlashFundParams, } impl Genesis { @@ -859,7 +842,6 @@ pub fn genesis() -> Genesis { parameters, pos_params: PosParams::default(), gov_params: GovParams::default(), - slash_fund_params: SlashFundParams::default(), } } diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index 904f509324..207f83fd0b 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -60,7 +60,6 @@ where genesis.parameters.init_storage(&mut self.storage); genesis.gov_params.init_storage(&mut self.storage); - genesis.slash_fund_params.init_storage(&mut self.storage); // Depends on parameters being initialized self.storage diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index 2f3734c8c5..1583ba4ccf 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -16,7 +16,6 @@ Also, it introduces some protocol parameters: - `min_proposal_period` - `max_proposal_content_size` - `min_proposal_grace_epochs` -- `max_proposal_fund_transfer` ## On-chain proposals @@ -29,7 +28,6 @@ On-chain proposals are created under the `governance_address` storage space and, /$GovernanceAddress/min_proposal_period: u64 /$GovernanceAddress/max_proposal_content_size: u64 /$GovernanceAddress/min_proposal_grace_epochs: u64 -/$GovernanceAddress/max_proposal_fund_transfer: u64 ``` In order to create a valid proposal, a transaction need to modify these storage keys: diff --git a/genesis/dev.toml b/genesis/dev.toml index 729d7a93bb..9af9c73e28 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -183,6 +183,3 @@ min_proposal_period = 3 max_proposal_content_size = 5000 # minimum epochs between end and grace epoch min_proposal_grace_epochs = 6 - -[slash_fund_params] -max_proposal_fund_transfer = 10000 \ No newline at end of file diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 962502201a..e2dcc80e9d 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -191,6 +191,3 @@ min_proposal_period = 3 max_proposal_content_size = 10000 # minimum epochs between end and grace epoch min_proposal_grace_epochs = 6 - -[slash_fund_params] -max_proposal_fund_transfer = 10000 diff --git a/shared/src/ledger/slash_fund/mod.rs b/shared/src/ledger/slash_fund/mod.rs index f276b6f984..0d452af2e5 100644 --- a/shared/src/ledger/slash_fund/mod.rs +++ b/shared/src/ledger/slash_fund/mod.rs @@ -1,8 +1,7 @@ //! SlashFund VP use std::collections::BTreeSet; -/// SlashFund parameters -pub mod parameters; + /// SlashFund storage pub mod storage; @@ -61,84 +60,15 @@ where let result = keys_changed.iter().all(|key| { let key_type: KeyType = key.into(); match key_type { - KeyType::PARAMETER => { - let proposal_id = u64::try_from_slice(tx_data).ok(); - match proposal_id { - Some(id) => is_proposal_accepted(&self.ctx, id), - _ => false, - } - } KeyType::BALANCE(addr) => { - let proposal_id = u64::try_from_slice(tx_data).ok(); - if let Some(id) = proposal_id { - if !is_proposal_accepted(&self.ctx, id) { - return false; - } - } else { - return false; - }; - let is_max_funds_transfer_key = - slash_fund_storage::get_max_transferable_fund_key(); - let balance_key = token::balance_key(&nam(), &ADDRESS); - let max_transfer_amount = - self.ctx.read_pre(&is_max_funds_transfer_key); - let pre_balance = self.ctx.read_pre(&balance_key); - let post_balance = self.ctx.read_post(&balance_key); if addr.ne(&ADDRESS) { return true; } - match (max_transfer_amount, pre_balance, post_balance) { - ( - Ok(max_transfer_amount), - Ok(pre_balance), - Ok(post_balance), - ) => { - match ( - max_transfer_amount, - pre_balance, - post_balance, - ) { - ( - Some(max_transfer_amount), - Some(pre_balance), - Some(post_balance), - ) => { - let max_transfer_amount = - token::Amount::try_from_slice( - &max_transfer_amount[..], - ) - .ok(); - let pre_balance = - token::Amount::try_from_slice( - &pre_balance[..], - ) - .ok(); - let post_balance = - token::Amount::try_from_slice( - &post_balance[..], - ) - .ok(); - match ( - max_transfer_amount, - pre_balance, - post_balance, - ) { - ( - Some(max_transfer_amount), - Some(pre_balance), - Some(post_balance), - ) => { - post_balance > pre_balance - || (pre_balance - post_balance - <= max_transfer_amount) - } - _ => false, - } - } - _ => false, - } - } - _ => false, + + let proposal_id = u64::try_from_slice(tx_data).ok(); + match proposal_id { + Some(id) => is_proposal_accepted(&self.ctx, id), + None => false, } } KeyType::UNKNOWN_SLASH_FUND => false, @@ -154,8 +84,6 @@ enum KeyType { #[allow(clippy::upper_case_acronyms)] BALANCE(Address), #[allow(clippy::upper_case_acronyms)] - PARAMETER, - #[allow(clippy::upper_case_acronyms)] #[allow(non_camel_case_types)] UNKNOWN_SLASH_FUND, #[allow(clippy::upper_case_acronyms)] @@ -164,9 +92,7 @@ enum KeyType { impl From<&Key> for KeyType { fn from(value: &Key) -> Self { - if slash_fund_storage::is_parameter_key(value) { - KeyType::PARAMETER - } else if slash_fund_storage::is_slash_fund_key(value) { + if slash_fund_storage::is_slash_fund_key(value) { KeyType::UNKNOWN_SLASH_FUND } else if token::is_any_token_balance_key(value).is_some() { match token::is_balance_key(&nam(), value) { diff --git a/shared/src/ledger/slash_fund/parameters.rs b/shared/src/ledger/slash_fund/parameters.rs deleted file mode 100644 index 07e3a027a3..0000000000 --- a/shared/src/ledger/slash_fund/parameters.rs +++ /dev/null @@ -1,47 +0,0 @@ -use borsh::{BorshDeserialize, BorshSerialize}; - -use super::storage as slash_fund_storage; -use crate::ledger::storage::types::encode; -use crate::ledger::storage::{self, Storage}; -use crate::types::token::Amount; - -#[derive( - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - BorshSerialize, - BorshDeserialize, -)] -/// Governance parameter structure -pub struct SlashFundParams { - /// Maximum amount of token that can be moved in a single transfer - pub max_proposal_fund_transfer: u64, -} - -impl Default for SlashFundParams { - fn default() -> Self { - Self { - max_proposal_fund_transfer: 10_000, - } - } -} - -impl SlashFundParams { - /// Initialize slash fund parameters into storage - pub fn init_storage(&self, storage: &mut Storage) - where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: storage::StorageHasher, - { - let max_proposal_fund_transfer_key = - slash_fund_storage::get_max_transferable_fund_key(); - let amount = Amount::whole(self.max_proposal_fund_transfer); - storage - .write(&max_proposal_fund_transfer_key, encode(&amount)) - .unwrap(); - } -} diff --git a/shared/src/ledger/slash_fund/storage.rs b/shared/src/ledger/slash_fund/storage.rs index 0884fdf687..60d29f0f48 100644 --- a/shared/src/ledger/slash_fund/storage.rs +++ b/shared/src/ledger/slash_fund/storage.rs @@ -1,33 +1,7 @@ use super::ADDRESS; -use crate::types::storage::{DbKeySeg, Key, KeySeg}; - -const MAX_TRANSFERABLE_FUND_KEY: &str = "max_fund"; +use crate::types::storage::{DbKeySeg, Key}; /// Check if a key is a slash fund key pub fn is_slash_fund_key(key: &Key) -> bool { matches!(&key.segments[0], DbKeySeg::AddressSeg(addr) if addr == &ADDRESS) } - -/// Check if key is max funds transfer key -pub fn is_max_funds_transfer_key(key: &Key) -> bool { - match &key.segments[..] { - [DbKeySeg::AddressSeg(addr), DbKeySeg::StringSeg(max_fund)] - if addr == &ADDRESS && max_fund == MAX_TRANSFERABLE_FUND_KEY => - { - true - } - _ => false, - } -} - -/// Check if key is any parameter key -pub fn is_parameter_key(key: &Key) -> bool { - is_max_funds_transfer_key(key) -} - -/// Get key of max funds transfer parameter -pub fn get_max_transferable_fund_key() -> Key { - Key::from(ADDRESS.to_db_key()) - .push(&MAX_TRANSFERABLE_FUND_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index f622e9c9f7..de82229d8f 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -33,10 +33,6 @@ pub mod main { let target_key = storage::get_min_proposal_grace_epoch_key(); write(&target_key.to_string(), 9_u64); - // slash fund - let target_key = slash_fund_storage::get_max_transferable_fund_key(); - write(&target_key.to_string(), token::Amount::whole(20_000)); - // parameters let target_key = parameters_storage::get_tx_whitelist_storage_key(); write(&target_key.to_string(), vec!["hash"]); From 172a93091598c389a9d64239f0c5a2211fb13b21 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Wed, 21 Sep 2022 18:02:39 +0200 Subject: [PATCH 346/373] Adds `max_proposal_period` governance parameter --- apps/src/lib/client/rpc.rs | 6 ++++ apps/src/lib/client/tx.rs | 29 ++++++++++--------- apps/src/lib/config/genesis.rs | 6 +++- .../src/explore/design/ledger/governance.md | 7 +++-- genesis/dev.toml | 4 ++- genesis/e2e-tests-single-node.toml | 4 ++- shared/src/ledger/governance/parameters.rs | 17 +++++++++-- shared/src/ledger/governance/storage.rs | 24 +++++++++++++++ shared/src/ledger/governance/vp.rs | 6 ++++ 9 files changed, 82 insertions(+), 21 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index afe399a2b4..ea86cfd314 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1924,10 +1924,16 @@ pub async fn get_governance_parameters(client: &HttpClient) -> GovParams { .await .expect("Parameter should be definied."); + let key = gov_storage::get_max_proposal_period_key(); + let max_proposal_period = query_storage_value::(client, &key) + .await + .expect("Parameter should be definied."); + GovParams { min_proposal_fund: u64::from(min_proposal_fund), max_proposal_code_size, min_proposal_period, + max_proposal_period, max_proposal_content_size, min_proposal_grace_epochs, } diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index 8df5869727..ab900fc732 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -535,7 +535,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let client = HttpClient::new(args.tx.ledger_address.clone()).unwrap(); let signer = WalletAddress::new(proposal.clone().author.to_string()); - let goverance_parameters = rpc::get_governance_parameters(&client).await; + let governance_parameters = rpc::get_governance_parameters(&client).await; let current_epoch = rpc::query_epoch(args::Query { ledger_address: args.tx.ledger_address.clone(), }) @@ -543,14 +543,14 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 - % goverance_parameters.min_proposal_period + % governance_parameters.min_proposal_period != 0 { println!("{}", proposal.voting_start_epoch <= current_epoch); println!( "{}", proposal.voting_start_epoch.0 - % goverance_parameters.min_proposal_period + % governance_parameters.min_proposal_period == 0 ); eprintln!( @@ -558,34 +558,37 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { epoch {} and a multiple of {}", proposal.voting_start_epoch, current_epoch, - goverance_parameters.min_proposal_period + governance_parameters.min_proposal_period ); if !args.tx.force { safe_exit(1) } } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - < goverance_parameters.min_proposal_period + < governance_parameters.min_proposal_period + || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 + > governance_parameters.max_proposal_period || proposal.voting_end_epoch.0 % 3 != 0 { eprintln!( "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and end epoch must be a \ - multiple of {}", - goverance_parameters.min_proposal_period, - goverance_parameters.min_proposal_period + and end epoch must be at least {} and at max {} and end epoch must \ + be a multiple of {}", + governance_parameters.min_proposal_period, + governance_parameters.max_proposal_period, + governance_parameters.min_proposal_period ); if !args.tx.force { safe_exit(1) } } else if proposal.grace_epoch <= proposal.voting_end_epoch || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 - < goverance_parameters.min_proposal_grace_epochs + < governance_parameters.min_proposal_grace_epochs { eprintln!( "Invalid proposal grace epoch: difference between proposal grace \ and end epoch must be at least {}", - goverance_parameters.min_proposal_grace_epochs + governance_parameters.min_proposal_grace_epochs ); if !args.tx.force { safe_exit(1) @@ -632,7 +635,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < token::Amount::from(goverance_parameters.min_proposal_fund) + if balance < token::Amount::from(governance_parameters.min_proposal_fund) { eprintln!( "Address {} doesn't have enough funds.", @@ -642,7 +645,7 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { } if init_proposal_data.content.len() - > goverance_parameters.max_proposal_content_size as usize + > governance_parameters.max_proposal_content_size as usize { eprintln!("Proposal content size too big.",); safe_exit(1); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 2a74dbc52a..45a7d7bab0 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -129,9 +129,12 @@ pub mod genesis_config { // Maximum size of proposal in kibibytes (KiB) // XXX: u64 doesn't work with toml-rs! pub max_proposal_code_size: u64, - // Proposal period length in epoch + // Minimum proposal period length in epochs // XXX: u64 doesn't work with toml-rs! pub min_proposal_period: u64, + // Maximum proposal period length in epochs + // XXX: u64 doesn't work with toml-rs! + pub max_proposal_period: u64, // Maximum number of characters in the proposal content // XXX: u64 doesn't work with toml-rs! pub max_proposal_content_size: u64, @@ -539,6 +542,7 @@ pub mod genesis_config { min_proposal_fund: config.gov_params.min_proposal_fund, max_proposal_code_size: config.gov_params.max_proposal_code_size, min_proposal_period: config.gov_params.min_proposal_period, + max_proposal_period: config.gov_params.max_proposal_period, max_proposal_content_size: config .gov_params .max_proposal_content_size, diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index 1583ba4ccf..63b466b23e 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -14,6 +14,7 @@ Also, it introduces some protocol parameters: - `min_proposal_fund` - `max_proposal_code_size` - `min_proposal_period` +- `max_proposal_period` - `max_proposal_content_size` - `min_proposal_grace_epochs` @@ -26,6 +27,7 @@ On-chain proposals are created under the `governance_address` storage space and, /$GovernanceAddress/min_proposal_fund: u64 /$GovernanceAddress/max_proposal_code_size: u64 /$GovernanceAddress/min_proposal_period: u64 +/$GovernanceAddress/max_proposal_period: u64 /$GovernanceAddress/max_proposal_content_size: u64 /$GovernanceAddress/min_proposal_grace_epochs: u64 ``` @@ -49,8 +51,9 @@ and follow these rules: - be grater than `currentEpoch`, where current epoch is the epoch in which the transaction is executed and included in a block - be a multiple of `min_proposal_period`. - `endEpoch` must: - - be at least `min_proposal_period` epoch greater than `startEpoch` - - be a multiple of `min_proposal_period` + - be at least `min_proposal_period` epochs greater than `startEpoch` + - be at most `max_proposal_period` epochs greater than `startEpoch` + - be a multiple of `min_proposal_period` - `graceEpoch` must: - be at least `min_grace_epoch` epochs greater than `endEpoch` - `proposalCode` can be empty and must be a valid transaction with size less than `max_proposal_code_size` kibibytes. diff --git a/genesis/dev.toml b/genesis/dev.toml index 9af9c73e28..cb3f9bbcb2 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -177,8 +177,10 @@ light_client_attack_slash_rate = 500 min_proposal_fund = 500 # proposal code size in bytes max_proposal_code_size = 300000 -# proposal period length in epoch +# min proposal period length in epochs min_proposal_period = 3 +# max proposal period length in epochs +max_proposal_period = 27 # maximum number of characters in the proposal content max_proposal_content_size = 5000 # minimum epochs between end and grace epoch diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index e2dcc80e9d..3933279ba0 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -185,8 +185,10 @@ light_client_attack_slash_rate = 500 min_proposal_fund = 500 # proposal code size in bytes max_proposal_code_size = 300000 -# proposal period length in epoch +# min proposal period length in epochs min_proposal_period = 3 +# max proposal period length in epochs +max_proposal_period = 27 # maximum number of characters in the proposal content max_proposal_content_size = 10000 # minimum epochs between end and grace epoch diff --git a/shared/src/ledger/governance/parameters.rs b/shared/src/ledger/governance/parameters.rs index 5b280491b9..71dca8c91b 100644 --- a/shared/src/ledger/governance/parameters.rs +++ b/shared/src/ledger/governance/parameters.rs @@ -26,7 +26,9 @@ pub struct GovParams { pub max_proposal_code_size: u64, /// Minimum proposal voting period in epochs pub min_proposal_period: u64, - /// Maximimum number of characters for proposal content + /// Maximum proposal voting period in epochs + pub max_proposal_period: u64, + /// Maximum number of characters for proposal content pub max_proposal_content_size: u64, /// Minimum epochs between end and grace epochs pub min_proposal_grace_epochs: u64, @@ -37,11 +39,12 @@ impl Display for GovParams { write!( f, "Min. proposal fund: {}\nMax. proposal code size: {}\nMin. \ - proposal period: {}\nMax. proposal content size: {}\nMin. \ - proposal grace epochs: {}", + proposal period: {}\nMax. proposal period: {}\nMax. proposal \ + content size: {}\nMin. proposal grace epochs: {}", self.min_proposal_fund, self.max_proposal_code_size, self.min_proposal_period, + self.max_proposal_period, self.max_proposal_content_size, self.min_proposal_grace_epochs ) @@ -54,6 +57,7 @@ impl Default for GovParams { min_proposal_fund: 500, max_proposal_code_size: 300_000, min_proposal_period: 3, + max_proposal_period: 27, max_proposal_content_size: 10_000, min_proposal_grace_epochs: 6, } @@ -71,6 +75,7 @@ impl GovParams { min_proposal_fund, max_proposal_code_size, min_proposal_period, + max_proposal_period, max_proposal_content_size, min_proposal_grace_epochs, } = self; @@ -93,6 +98,12 @@ impl GovParams { .write(&min_proposal_period_key, encode(min_proposal_period)) .unwrap(); + let max_proposal_period_key = + gov_storage::get_max_proposal_period_key(); + storage + .write(&max_proposal_period_key, encode(max_proposal_period)) + .unwrap(); + let max_proposal_content_size_key = gov_storage::get_max_proposal_content_key(); storage diff --git a/shared/src/ledger/governance/storage.rs b/shared/src/ledger/governance/storage.rs index 50e9dc05cb..9d2f0a4e4a 100644 --- a/shared/src/ledger/governance/storage.rs +++ b/shared/src/ledger/governance/storage.rs @@ -16,6 +16,7 @@ const PROPOSAL_COMMITTING_EPOCH: &str = "epoch"; const MIN_PROPOSAL_FUND_KEY: &str = "min_fund"; const MAX_PROPOSAL_CODE_SIZE_KEY: &str = "max_code_size"; const MIN_PROPOSAL_PERIOD_KEY: &str = "min_period"; +const MAX_PROPOSAL_PERIOD_KEY: &str = "max_period"; const MAX_PROPOSAL_CONTENT_SIZE_KEY: &str = "max_content"; const MIN_GRACE_EPOCH_KEY: &str = "min_grace_epoch"; const COUNTER_KEY: &str = "counter"; @@ -242,6 +243,21 @@ pub fn is_min_proposal_period_key(key: &Key) -> bool { } } +/// Check if key is a max proposal period param key +pub fn is_max_proposal_period_key(key: &Key) -> bool { + match &key.segments[..] { + [ + DbKeySeg::AddressSeg(addr), + DbKeySeg::StringSeg(max_proposal_period_param), + ] if addr == &ADDRESS + && max_proposal_period_param == MAX_PROPOSAL_PERIOD_KEY => + { + true + } + _ => false, + } +} + /// Check if key is a min grace epoch key pub fn is_commit_proposal_key(key: &Key) -> bool { match &key.segments[..] { @@ -282,6 +298,7 @@ pub fn is_parameter_key(key: &Key) -> bool { || is_max_content_size_key(key) || is_max_proposal_code_size_key(key) || is_min_proposal_period_key(key) + || is_max_proposal_period_key(key) || is_min_grace_epoch_key(key) } @@ -318,6 +335,13 @@ pub fn get_min_proposal_period_key() -> Key { .expect("Cannot obtain a storage key") } +/// Get maximum proposal period key +pub fn get_max_proposal_period_key() -> Key { + Key::from(ADDRESS.to_db_key()) + .push(&MAX_PROPOSAL_PERIOD_KEY.to_owned()) + .expect("Cannot obtain a storage key") +} + /// Get maximum proposal content key pub fn get_max_proposal_content_key() -> Key { Key::from(ADDRESS.to_db_key()) diff --git a/shared/src/ledger/governance/vp.rs b/shared/src/ledger/governance/vp.rs index a1f7457ecc..3251f5d2d9 100644 --- a/shared/src/ledger/governance/vp.rs +++ b/shared/src/ledger/governance/vp.rs @@ -263,12 +263,16 @@ where let min_period_parameter_key = gov_storage::get_min_proposal_period_key(); let min_period: Option = read(ctx, &min_period_parameter_key, ReadType::PRE).ok(); + let max_period_parameter_key = gov_storage::get_max_proposal_period_key(); + let max_period: Option = + read(ctx, &max_period_parameter_key, ReadType::PRE).ok(); let has_pre_start_epoch = ctx.has_key_pre(&start_epoch_key).ok(); let has_pre_end_epoch = ctx.has_key_pre(&end_epoch_key).ok(); match ( has_pre_start_epoch, has_pre_end_epoch, min_period, + max_period, start_epoch, end_epoch, current_epoch, @@ -277,6 +281,7 @@ where Some(has_pre_start_epoch), Some(has_pre_end_epoch), Some(min_period), + Some(max_period), Some(start_epoch), Some(end_epoch), Some(current_epoch), @@ -288,6 +293,7 @@ where && !has_pre_end_epoch && (end_epoch - start_epoch) % min_period == 0 && (end_epoch - start_epoch).0 >= min_period + && (end_epoch - start_epoch).0 <= max_period } _ => false, } From da57772607dc508dbedf87df4c1aabee92f59487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 10:48:00 +0200 Subject: [PATCH 347/373] remove intent gossiper, matchmaker and their deps --- CONTRIBUTING.md | 2 +- Cargo.lock | 1591 +---------------- Cargo.toml | 2 - Makefile | 2 +- README.md | 18 +- apps/Cargo.toml | 6 - apps/build.rs | 45 +- apps/src/bin/anoma-client/cli.rs | 9 +- apps/src/bin/anoma-node/cli.rs | 53 +- apps/src/bin/anoma/cli.rs | 10 +- apps/src/lib/cli.rs | 450 +---- apps/src/lib/cli/context.rs | 2 +- apps/src/lib/client/gossip.rs | 116 -- apps/src/lib/client/mod.rs | 1 - apps/src/lib/client/utils.rs | 164 +- apps/src/lib/config/genesis.rs | 23 +- apps/src/lib/config/mod.rs | 162 +- apps/src/lib/mod.rs | 1 - apps/src/lib/node/gossip/intent_gossiper.rs | 123 -- apps/src/lib/node/gossip/mempool.rs | 26 - apps/src/lib/node/gossip/mod.rs | 116 -- .../node/gossip/p2p/behaviour/discovery.rs | 517 ------ apps/src/lib/node/gossip/p2p/behaviour/mod.rs | 412 ----- apps/src/lib/node/gossip/p2p/identity.rs | 123 -- apps/src/lib/node/gossip/p2p/mod.rs | 139 -- apps/src/lib/node/gossip/rpc/client.rs | 166 -- apps/src/lib/node/gossip/rpc/matchmakers.rs | 847 --------- apps/src/lib/node/gossip/rpc/mod.rs | 2 - apps/src/lib/node/ledger/shell/init_chain.rs | 1 - apps/src/lib/node/matchmaker.rs | 364 ---- apps/src/lib/node/mod.rs | 2 - apps/src/lib/proto/README.md | 3 - apps/src/lib/proto/generated.rs | 1 - apps/src/lib/proto/generated/.gitignore | 1 - apps/src/lib/proto/mod.rs | 5 - apps/src/lib/proto/types.rs | 148 -- apps/src/lib/wallet/defaults.rs | 22 +- .../docs/src/testnets/internal-testnet-1.md | 2 +- .../docs/src/user-guide/getting-started.md | 2 +- .../intent-gossiper-and-matchmaker.md | 124 -- genesis/dev.toml | 5 - genesis/e2e-tests-single-node.toml | 15 +- macros/src/lib.rs | 117 +- proto/services.proto | 30 - proto/types.proto | 22 +- shared/build.rs | 3 - shared/src/proto/mod.rs | 4 +- shared/src/proto/types.rs | 146 -- shared/src/types/intent.rs | 475 ----- shared/src/types/matchmaker.rs | 30 - shared/src/types/mod.rs | 2 - shared/src/vm/mod.rs | 3 +- shared/src/vm/types.rs | 3 - tests/Cargo.toml | 1 - tests/src/e2e.rs | 1 - tests/src/e2e/gossip_tests.rs | 348 ---- tests/src/e2e/helpers.rs | 14 +- tests/src/e2e/setup.rs | 10 - vm_env/src/intent.rs | 39 - vm_env/src/lib.rs | 3 - wasm/wasm_source/src/lib.rs | 2 - wasm/wasm_source/src/tx_from_intent.rs | 35 - wasm/wasm_source/src/vp_user.rs | 186 +- wasm_for_tests/wasm_source/Cargo.lock | 24 +- 64 files changed, 163 insertions(+), 7158 deletions(-) delete mode 100644 apps/src/lib/client/gossip.rs delete mode 100644 apps/src/lib/node/gossip/intent_gossiper.rs delete mode 100644 apps/src/lib/node/gossip/mempool.rs delete mode 100644 apps/src/lib/node/gossip/mod.rs delete mode 100644 apps/src/lib/node/gossip/p2p/behaviour/discovery.rs delete mode 100644 apps/src/lib/node/gossip/p2p/behaviour/mod.rs delete mode 100644 apps/src/lib/node/gossip/p2p/identity.rs delete mode 100644 apps/src/lib/node/gossip/p2p/mod.rs delete mode 100644 apps/src/lib/node/gossip/rpc/client.rs delete mode 100644 apps/src/lib/node/gossip/rpc/matchmakers.rs delete mode 100644 apps/src/lib/node/gossip/rpc/mod.rs delete mode 100644 apps/src/lib/node/matchmaker.rs delete mode 100644 apps/src/lib/proto/README.md delete mode 100644 apps/src/lib/proto/generated.rs delete mode 100644 apps/src/lib/proto/generated/.gitignore delete mode 100644 apps/src/lib/proto/mod.rs delete mode 100644 apps/src/lib/proto/types.rs delete mode 100644 documentation/docs/src/user-guide/intent-gossiper-and-matchmaker.md delete mode 100644 proto/services.proto delete mode 100644 shared/src/types/intent.rs delete mode 100644 shared/src/types/matchmaker.rs delete mode 100644 tests/src/e2e/gossip_tests.rs delete mode 100644 vm_env/src/intent.rs delete mode 100644 wasm/wasm_source/src/tx_from_intent.rs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 42e29950d8..c00d3f08ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,4 +47,4 @@ for i in $(ls -d .changelog/*/*/); do basename "$i"; done ## Development priorities -If you’d like to follow the development or contribute with new or unimplemented features, we recommend to check [the pinned issues](https://github.com/anoma/anoma/issues) that are set to tracking issues in current focus of the ledger, intent gossiper and matchmaker team. +If you’d like to follow the development or contribute with new or unimplemented features, we recommend to check [the issues](https://github.com/anoma/namada/issues) that are in current focus of the ledger team. diff --git a/Cargo.lock b/Cargo.lock index eafd05b2ff..0874744e39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,41 +17,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", -] - -[[package]] -name = "aes-gcm" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle 2.4.1", -] - [[package]] name = "ahash" version = "0.7.6" @@ -233,12 +198,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" -[[package]] -name = "asn1_der" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" - [[package]] name = "assert_cmd" version = "1.0.8" @@ -314,7 +273,7 @@ dependencies = [ "parking", "polling", "slab", - "socket2 0.4.4", + "socket2", "waker-fn", "winapi 0.3.9", ] @@ -376,26 +335,12 @@ dependencies = [ "memchr", "num_cpus", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] -[[package]] -name = "async-std-resolver" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf3e776afdf3a2477ef4854b85ba0dff3bd85792f685fb3c68948b4d304e4f0" -dependencies = [ - "async-std", - "async-trait", - "futures-io", - "futures-util", - "pin-utils", - "trust-dns-resolver", -] - [[package]] name = "async-stream" version = "0.3.3" @@ -443,35 +388,13 @@ dependencies = [ "futures-io", "futures-util", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tokio-rustls", - "tungstenite 0.12.0", + "tungstenite", "webpki-roots", ] -[[package]] -name = "asynchronous-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" -dependencies = [ - "bytes 1.1.0", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite 0.2.9", -] - -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg 1.1.0", -] - [[package]] name = "atomic-waker" version = "1.0.0" @@ -544,12 +467,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -611,17 +528,6 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" -[[package]] -name = "blake2" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e37d16930f5459780f5621038b6382b9bb37c19016f39fb6b5808d831f174" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - [[package]] name = "blake2" version = "0.10.4" @@ -742,7 +648,7 @@ source = "git+https://github.com/heliaxdev/borsh-rs.git?rev=cd5223e5103c4f139e0c dependencies = [ "borsh-derive-internal", "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", + "proc-macro-crate", "proc-macro2", "syn", ] @@ -767,12 +673,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - [[package]] name = "bstr" version = "0.2.17" @@ -842,12 +742,6 @@ dependencies = [ "iovec", ] -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.1.0" @@ -914,18 +808,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chacha20" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" -dependencies = [ - "cfg-if 1.0.0", - "cipher", - "cpufeatures 0.1.5", - "zeroize", -] - [[package]] name = "chacha20" version = "0.8.1" @@ -934,20 +816,7 @@ checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", -] - -[[package]] -name = "chacha20poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" -dependencies = [ - "aead", - "chacha20 0.7.1", - "cipher", - "poly1305", - "zeroize", + "cpufeatures", ] [[package]] @@ -1135,15 +1004,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" -[[package]] -name = "cpufeatures" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" -dependencies = [ - "libc", -] - [[package]] name = "cpufeatures" version = "0.2.2" @@ -1292,7 +1152,7 @@ checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ "generic-array 0.14.5", "rand_core 0.6.3", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1306,16 +1166,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - [[package]] name = "crypto-mac" version = "0.8.0" @@ -1323,7 +1173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ "generic-array 0.14.5", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1333,7 +1183,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ "generic-array 0.14.5", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -1361,32 +1211,12 @@ dependencies = [ "syn", ] -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher", -] - [[package]] name = "cty" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" -[[package]] -name = "cuckoofilter" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b810a8449931679f64cd7eef1bbd0fa315801b6d5d9cdc1ace2804d6529eee18" -dependencies = [ - "byteorder", - "fnv", - "rand 0.7.3", -] - [[package]] name = "curl" version = "0.4.43" @@ -1398,7 +1228,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2 0.4.4", + "socket2", "winapi 0.3.9", ] @@ -1426,7 +1256,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1512,12 +1342,6 @@ dependencies = [ "syn", ] -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - [[package]] name = "der" version = "0.5.1" @@ -1618,37 +1442,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common", - "subtle 2.4.1", -] - -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi 0.3.9", -] - -[[package]] -name = "dns-parser" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" -dependencies = [ - "byteorder", - "quick-error 1.2.3", + "subtle", ] [[package]] @@ -1762,7 +1556,7 @@ dependencies = [ "group", "rand_core 0.6.3", "sec1", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1776,7 +1570,7 @@ dependencies = [ "rustc_version 0.4.0", "toml", "vswhom", - "winreg 0.10.1", + "winreg", ] [[package]] @@ -1788,18 +1582,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "enum-as-inner" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" -dependencies = [ - "heck 0.4.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "enum-iterator" version = "0.7.0" @@ -1925,7 +1707,7 @@ dependencies = [ "ark-serialize", "ark-std", "bincode", - "blake2 0.10.4", + "blake2", "blake2b_simd", "borsh", "digest 0.10.3", @@ -1934,7 +1716,7 @@ dependencies = [ "ferveo-common", "group-threshold-cryptography", "hex", - "itertools 0.10.3", + "itertools", "measure_time", "miracl_core", "num", @@ -1944,7 +1726,7 @@ dependencies = [ "serde_bytes", "serde_json", "subproductdomain", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -1968,7 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ "rand_core 0.6.3", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -2006,12 +1788,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "fixedbitset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" - [[package]] name = "fixedbitset" version = "0.4.1" @@ -2025,7 +1801,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "libz-sys", "miniz_oxide", ] @@ -2163,7 +1938,6 @@ dependencies = [ "futures-core", "futures-task", "futures-util", - "num_cpus", ] [[package]] @@ -2183,7 +1957,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.9", + "pin-project-lite", "waker-fn", ] @@ -2198,17 +1972,6 @@ dependencies = [ "syn", ] -[[package]] -name = "futures-rustls" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1387e07917c711fb4ee4f48ea0adb04a3c9739e53ef85bf43ae1edc2937a8b" -dependencies = [ - "futures-io", - "rustls", - "webpki", -] - [[package]] name = "futures-sink" version = "0.3.21" @@ -2221,12 +1984,6 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - [[package]] name = "futures-util" version = "0.3.21" @@ -2240,7 +1997,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", ] @@ -2286,16 +2043,6 @@ dependencies = [ "wasi 0.10.0+wasi-snapshot-preview1", ] -[[package]] -name = "ghash" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" -dependencies = [ - "opaque-debug 0.3.0", - "polyval", -] - [[package]] name = "gimli" version = "0.25.0" @@ -2367,7 +2114,7 @@ checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff", "rand_core 0.6.3", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -2383,9 +2130,9 @@ dependencies = [ "ark-serialize", "ark-std", "blake2b_simd", - "chacha20 0.8.1", + "chacha20", "hex", - "itertools 0.10.3", + "itertools", "miracl_core", "rand 0.8.5", "rand_core 0.6.3", @@ -2499,12 +2246,6 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - [[package]] name = "hermit-abi" version = "0.1.19" @@ -2520,22 +2261,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex_fmt" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" - -[[package]] -name = "hmac" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" -dependencies = [ - "crypto-mac 0.7.0", - "digest 0.8.1", -] - [[package]] name = "hmac" version = "0.8.1" @@ -2556,17 +2281,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac-drbg" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e570451493f10f6581b48cdd530413b63ea9e780f544bfd3bdcaa0d89d1a7b" -dependencies = [ - "digest 0.8.1", - "generic-array 0.12.4", - "hmac 0.7.1", -] - [[package]] name = "hmac-drbg" version = "0.3.0" @@ -2578,17 +2292,6 @@ dependencies = [ "hmac 0.8.1", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi 0.3.9", -] - [[package]] name = "http" version = "0.2.8" @@ -2608,7 +2311,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes 1.1.0", "http", - "pin-project-lite 0.2.9", + "pin-project-lite", ] [[package]] @@ -2658,8 +2361,8 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.9", - "socket2 0.4.4", + "pin-project-lite", + "socket2", "tokio", "tower-service", "tracing 0.1.35", @@ -2710,7 +2413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper 0.14.19", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tokio-io-timeout", ] @@ -2739,8 +2442,8 @@ dependencies = [ "ibc-proto", "ics23", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "safe-regex", "serde 1.0.137", "serde_derive", @@ -2761,8 +2464,8 @@ version = "0.16.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ "bytes 1.1.0", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "tendermint-proto", "tonic", @@ -2777,7 +2480,7 @@ dependencies = [ "anyhow", "bytes 1.1.0", "hex", - "prost 0.9.0", + "prost", "ripemd160", "sha2 0.9.9", "sha3", @@ -2812,43 +2515,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "if-addrs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2273e421f7c4f0fc99e1934fe4776f59d8df2972f4199d703fc0da9f2a9f73de" -dependencies = [ - "if-addrs-sys", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "if-addrs-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de74b9dd780476e837e5eb5ab7c88b49ed304126e412030a0adba99c8efe79ea" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "if-watch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" -dependencies = [ - "async-io", - "futures 0.3.21", - "futures-lite", - "if-addrs", - "ipnet", - "libc", - "log 0.4.17", - "winapi 0.3.9", -] - [[package]] name = "indenter" version = "0.3.3" @@ -2907,12 +2573,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "integer-encoding" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" - [[package]] name = "iovec" version = "0.1.4" @@ -2923,32 +2583,11 @@ dependencies = [ ] [[package]] -name = "ipconfig" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" -dependencies = [ - "socket2 0.3.19", - "widestring", - "winapi 0.3.9", - "winreg 0.6.2", -] - -[[package]] -name = "ipnet" -version = "2.5.0" +name = "ipnet" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.10.3" @@ -3104,417 +2743,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "libp2p" -version = "0.38.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "atomic", - "bytes 1.1.0", - "futures 0.3.21", - "lazy_static 1.4.0", - "libp2p-core", - "libp2p-deflate", - "libp2p-dns", - "libp2p-floodsub", - "libp2p-gossipsub", - "libp2p-identify", - "libp2p-kad", - "libp2p-mdns", - "libp2p-mplex", - "libp2p-noise", - "libp2p-ping", - "libp2p-plaintext", - "libp2p-pnet", - "libp2p-relay", - "libp2p-request-response", - "libp2p-swarm", - "libp2p-swarm-derive", - "libp2p-tcp", - "libp2p-uds", - "libp2p-wasm-ext", - "libp2p-websocket", - "libp2p-yamux", - "parity-multiaddr", - "parking_lot 0.11.2", - "pin-project 1.0.10", - "smallvec 1.8.0", - "wasm-timer", -] - -[[package]] -name = "libp2p-core" -version = "0.28.3" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asn1_der", - "bs58", - "ed25519-dalek", - "either", - "fnv", - "futures 0.3.21", - "futures-timer", - "lazy_static 1.4.0", - "libsecp256k1 0.3.5", - "log 0.4.17", - "multihash", - "multistream-select", - "parity-multiaddr", - "parking_lot 0.11.2", - "pin-project 1.0.10", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "ring", - "rw-stream-sink", - "sha2 0.9.9", - "smallvec 1.8.0", - "thiserror", - "unsigned-varint 0.7.1", - "void", - "zeroize", -] - -[[package]] -name = "libp2p-deflate" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "flate2", - "futures 0.3.21", - "libp2p-core", -] - -[[package]] -name = "libp2p-dns" -version = "0.28.1" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-std-resolver", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "smallvec 1.8.0", - "trust-dns-resolver", -] - -[[package]] -name = "libp2p-floodsub" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "cuckoofilter", - "fnv", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "smallvec 1.8.0", -] - -[[package]] -name = "libp2p-gossipsub" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "base64 0.13.0", - "byteorder", - "bytes 1.1.0", - "fnv", - "futures 0.3.21", - "hex_fmt", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "regex", - "sha2 0.9.9", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "wasm-timer", -] - -[[package]] -name = "libp2p-identify" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "smallvec 1.8.0", - "wasm-timer", -] - -[[package]] -name = "libp2p-kad" -version = "0.30.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "arrayvec 0.5.2", - "asynchronous-codec", - "bytes 1.1.0", - "either", - "fnv", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "sha2 0.9.9", - "smallvec 1.8.0", - "uint", - "unsigned-varint 0.7.1", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-mdns" -version = "0.30.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-io", - "data-encoding", - "dns-parser", - "futures 0.3.21", - "if-watch", - "lazy_static 1.4.0", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "rand 0.8.5", - "smallvec 1.8.0", - "socket2 0.4.4", - "void", -] - -[[package]] -name = "libp2p-mplex" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "nohash-hasher", - "parking_lot 0.11.2", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", -] - -[[package]] -name = "libp2p-noise" -version = "0.31.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "bytes 1.1.0", - "curve25519-dalek", - "futures 0.3.21", - "lazy_static 1.4.0", - "libp2p-core", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.8.5", - "sha2 0.9.9", - "snow", - "static_assertions", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "libp2p-ping" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "rand 0.7.3", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-plaintext" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "prost 0.7.0", - "prost-build 0.7.0", - "unsigned-varint 0.7.1", - "void", -] - -[[package]] -name = "libp2p-pnet" -version = "0.21.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "log 0.4.17", - "pin-project 1.0.10", - "rand 0.7.3", - "salsa20", - "sha3", -] - -[[package]] -name = "libp2p-relay" -version = "0.2.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", - "futures-timer", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "pin-project 1.0.10", - "prost 0.7.0", - "prost-build 0.7.0", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-request-response" -version = "0.11.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-trait", - "bytes 1.1.0", - "futures 0.3.21", - "libp2p-core", - "libp2p-swarm", - "log 0.4.17", - "lru", - "minicbor", - "rand 0.7.3", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", - "wasm-timer", -] - -[[package]] -name = "libp2p-swarm" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "either", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", - "rand 0.7.3", - "smallvec 1.8.0", - "void", - "wasm-timer", -] - -[[package]] -name = "libp2p-swarm-derive" -version = "0.23.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "libp2p-tcp" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-io", - "futures 0.3.21", - "futures-timer", - "if-watch", - "ipnet", - "libc", - "libp2p-core", - "log 0.4.17", - "socket2 0.4.4", -] - -[[package]] -name = "libp2p-uds" -version = "0.28.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "async-std", - "futures 0.3.21", - "libp2p-core", - "log 0.4.17", -] - -[[package]] -name = "libp2p-wasm-ext" -version = "0.28.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "js-sys", - "libp2p-core", - "parity-send-wrapper", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "libp2p-websocket" -version = "0.29.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "either", - "futures 0.3.21", - "futures-rustls", - "libp2p-core", - "log 0.4.17", - "quicksink", - "rw-stream-sink", - "soketto", - "url 2.2.2", - "webpki-roots", -] - -[[package]] -name = "libp2p-yamux" -version = "0.32.0" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "futures 0.3.21", - "libp2p-core", - "parking_lot 0.11.2", - "thiserror", - "yamux", -] - [[package]] name = "librocksdb-sys" version = "0.6.1+6.28.2" @@ -3530,22 +2758,6 @@ dependencies = [ "zstd-sys", ] -[[package]] -name = "libsecp256k1" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc1e2c808481a63dc6da2074752fdd4336a3c8fcc68b83db6f1fd5224ae7962" -dependencies = [ - "arrayref", - "crunchy", - "digest 0.8.1", - "hmac-drbg 0.2.0", - "rand 0.7.3", - "sha2 0.8.2", - "subtle 2.4.1", - "typenum", -] - [[package]] name = "libsecp256k1" version = "0.7.0" @@ -3554,7 +2766,7 @@ dependencies = [ "arrayref", "base64 0.13.0", "digest 0.9.0", - "hmac-drbg 0.3.0", + "hmac-drbg", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -3571,7 +2783,7 @@ source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d dependencies = [ "crunchy", "digest 0.9.0", - "subtle 2.4.1", + "subtle", ] [[package]] @@ -3685,24 +2897,6 @@ dependencies = [ "syn", ] -[[package]] -name = "lru" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91" -dependencies = [ - "hashbrown 0.11.2", -] - -[[package]] -name = "lru-cache" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "mach" version = "0.3.2" @@ -3726,12 +2920,6 @@ dependencies = [ "serde_yaml", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matchers" version = "0.1.0" @@ -3799,24 +2987,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "message-io" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" -dependencies = [ - "crossbeam-channel", - "crossbeam-utils 0.8.8", - "integer-encoding", - "lazy_static 1.4.0", - "log 0.4.17", - "mio 0.7.14", - "serde 1.0.137", - "strum", - "tungstenite 0.16.0", - "url 2.2.2", -] - [[package]] name = "mime" version = "0.2.6" @@ -3842,26 +3012,6 @@ dependencies = [ "unicase 2.6.0", ] -[[package]] -name = "minicbor" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51aa5bb0ca22415daca596a227b507f880ad1b2318a87fa9325312a5d285ca0d" -dependencies = [ - "minicbor-derive", -] - -[[package]] -name = "minicbor-derive" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54999f917cd092b13904737e26631aa2b2b88d625db68e4bab461dcd8006c788" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -3890,25 +3040,12 @@ dependencies = [ "kernel32-sys", "libc", "log 0.4.17", - "miow 0.2.2", + "miow", "net2", "slab", "winapi 0.2.8", ] -[[package]] -name = "mio" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" -dependencies = [ - "libc", - "log 0.4.17", - "miow 0.3.7", - "ntapi", - "winapi 0.3.9", -] - [[package]] name = "mio" version = "0.8.3" @@ -3945,15 +3082,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "miracl_core" version = "2.3.0" @@ -3975,52 +3103,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" -[[package]] -name = "multihash" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" -dependencies = [ - "digest 0.9.0", - "generic-array 0.14.5", - "multihash-derive", - "sha2 0.9.9", - "unsigned-varint 0.5.1", -] - -[[package]] -name = "multihash-derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" -dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "multimap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "multistream-select" -version = "0.10.3" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "log 0.4.17", - "pin-project 1.0.10", - "smallvec 1.8.0", - "unsigned-varint 0.7.1", -] - [[package]] name = "namada" version = "0.7.1" @@ -4043,15 +3131,15 @@ dependencies = [ "ibc", "ibc-proto", "ics23", - "itertools 0.10.3", - "libsecp256k1 0.7.0", + "itertools", + "libsecp256k1", "loupe", "namada_proof_of_stake", "parity-wasm", "pretty_assertions", "proptest", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "pwasm-utils", "rand 0.8.5", "rand_core 0.6.3", @@ -4099,7 +3187,6 @@ dependencies = [ "config", "curl", "derivative", - "directories", "ed25519-consensus", "eyre", "ferveo", @@ -4109,22 +3196,19 @@ dependencies = [ "futures 0.3.21", "git2", "hex", - "itertools 0.10.3", + "itertools", "jsonpath_lib", "libc", "libloading", - "libp2p", - "message-io", "namada", "num-derive", "num-traits 0.2.15", "num_cpus", "once_cell", "orion", - "pathdiff", "proptest", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "rand 0.8.5", "rand_core 0.6.3", "rayon", @@ -4136,7 +3220,6 @@ dependencies = [ "serde 1.0.137", "serde_bytes", "serde_json", - "serde_regex", "sha2 0.9.9", "signal-hook", "sparse-merkle-tree", @@ -4153,7 +3236,6 @@ dependencies = [ "tokio-test", "toml", "tonic", - "tonic-build", "tower", "tower-abci", "tracing 0.1.35", @@ -4168,7 +3250,7 @@ name = "namada_encoding_spec" version = "0.7.1" dependencies = [ "borsh", - "itertools 0.10.3", + "itertools", "lazy_static 1.4.0", "madato", "namada", @@ -4207,14 +3289,13 @@ dependencies = [ "file-serve", "fs_extra", "hex", - "itertools 0.10.3", - "libp2p", + "itertools", "namada", "namada_apps", "namada_vm_env", "pretty_assertions", "proptest", - "prost 0.9.0", + "prost", "rand 0.8.5", "serde_json", "sha2 0.9.9", @@ -4330,12 +3411,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nohash-hasher" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" - [[package]] name = "nom" version = "5.1.2" @@ -4588,7 +3663,7 @@ checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", "getrandom 0.2.6", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -4613,29 +3688,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2386b4ebe91c2f7f51082d4cefa145d030e33a1842a96b12e4885cc3c01f7a55" -[[package]] -name = "parity-multiaddr" -version = "0.11.2" -source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" -dependencies = [ - "arrayref", - "bs58", - "byteorder", - "data-encoding", - "multihash", - "percent-encoding 2.1.0", - "serde 1.0.137", - "static_assertions", - "unsigned-varint 0.7.1", - "url 2.2.2", -] - -[[package]] -name = "parity-send-wrapper" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" - [[package]] name = "parity-wasm" version = "0.42.2" @@ -4659,17 +3711,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api 0.4.7", - "parking_lot_core 0.8.5", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -4695,20 +3736,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", - "winapi 0.3.9", -] - [[package]] name = "parking_lot_core" version = "0.9.3" @@ -4728,12 +3755,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "peeking_take_while" version = "0.1.2" @@ -4788,53 +3809,23 @@ dependencies = [ "ucd-trie", ] -[[package]] -name = "petgraph" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" -dependencies = [ - "fixedbitset 0.2.0", - "indexmap", -] - [[package]] name = "petgraph" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset 0.4.1", + "fixedbitset", "indexmap", ] -[[package]] -name = "pin-project" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" -dependencies = [ - "pin-project-internal 0.4.29", -] - [[package]] name = "pin-project" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ - "pin-project-internal 1.0.10", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "pin-project-internal", ] [[package]] @@ -4848,12 +3839,6 @@ dependencies = [ "syn", ] -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -4885,29 +3870,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "poly1305" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" -dependencies = [ - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", - "universal-hash", -] - -[[package]] -name = "polyval" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures 0.2.2", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "ppv-lite86" version = "0.2.16" @@ -4921,7 +3883,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", - "itertools 0.10.3", + "itertools", "predicates-core", ] @@ -4962,16 +3924,6 @@ dependencies = [ "toml", ] -[[package]] -name = "proc-macro-crate" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" -dependencies = [ - "thiserror", - "toml", -] - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -5025,41 +3977,13 @@ dependencies = [ ] [[package]] -name = "prost" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" -dependencies = [ - "bytes 1.1.0", - "prost-derive 0.7.0", -] - -[[package]] -name = "prost" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes 1.1.0", - "prost-derive 0.9.0", -] - -[[package]] -name = "prost-build" -version = "0.7.0" +name = "prost" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes 1.1.0", - "heck 0.3.3", - "itertools 0.9.0", - "log 0.4.17", - "multimap", - "petgraph 0.5.1", - "prost 0.7.0", - "prost-types 0.7.0", - "tempfile", - "which", + "prost-derive", ] [[package]] @@ -5069,32 +3993,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes 1.1.0", - "heck 0.3.3", - "itertools 0.10.3", + "heck", + "itertools", "lazy_static 1.4.0", "log 0.4.17", "multimap", - "petgraph 0.6.2", - "prost 0.9.0", - "prost-types 0.9.0", + "petgraph", + "prost", + "prost-types", "regex", "tempfile", "which", ] -[[package]] -name = "prost-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" -dependencies = [ - "anyhow", - "itertools 0.9.0", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "prost-derive" version = "0.9.0" @@ -5102,22 +4013,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.3", + "itertools", "proc-macro2", "quote", "syn", ] -[[package]] -name = "prost-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" -dependencies = [ - "bytes 1.1.0", - "prost 0.7.0", -] - [[package]] name = "prost-types" version = "0.9.0" @@ -5125,7 +4026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes 1.1.0", - "prost 0.9.0", + "prost", ] [[package]] @@ -5180,17 +4081,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quicksink" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77de3c815e5a160b1539c6592796801df2043ae35e123b46d73380cfa57af858" -dependencies = [ - "futures-core", - "futures-sink", - "pin-project-lite 0.1.12", -] - [[package]] name = "quote" version = "1.0.18" @@ -5443,17 +4333,6 @@ dependencies = [ "redox_syscall 0.2.13", ] -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom 0.2.6", - "redox_syscall 0.2.13", - "thiserror", -] - [[package]] name = "regalloc" version = "0.0.31" @@ -5544,7 +4423,7 @@ dependencies = [ "mime 0.3.16", "native-tls", "percent-encoding 2.1.0", - "pin-project-lite 0.2.9", + "pin-project-lite", "serde 1.0.137", "serde_json", "serde_urlencoded", @@ -5554,17 +4433,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winreg 0.10.1", -] - -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error 1.2.3", + "winreg", ] [[package]] @@ -5757,17 +4626,6 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "rw-stream-sink" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" -dependencies = [ - "futures 0.3.21", - "pin-project 0.4.29", - "static_assertions", -] - [[package]] name = "ryu" version = "1.0.10" @@ -5827,15 +4685,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -[[package]] -name = "salsa20" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" -dependencies = [ - "cipher", -] - [[package]] name = "same-file" version = "1.0.6" @@ -5885,7 +4734,7 @@ checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", "generic-array 0.14.5", - "subtle 2.4.1", + "subtle", "zeroize", ] @@ -6010,16 +4859,6 @@ dependencies = [ "serde 1.0.137", ] -[[package]] -name = "serde_regex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" -dependencies = [ - "regex", - "serde 1.0.137", -] - [[package]] name = "serde_repr" version = "0.1.8" @@ -6084,7 +4923,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6096,22 +4935,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.10.3", ] -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - [[package]] name = "sha2" version = "0.9.9" @@ -6120,7 +4947,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -6132,7 +4959,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures", "digest 0.10.3", ] @@ -6225,35 +5052,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "snow" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7" -dependencies = [ - "aes-gcm", - "blake2 0.9.2", - "chacha20poly1305", - "rand 0.8.5", - "rand_core 0.6.3", - "ring", - "rustc_version 0.3.3", - "sha2 0.9.9", - "subtle 2.4.1", - "x25519-dalek", -] - -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi 0.3.9", -] - [[package]] name = "socket2" version = "0.4.4" @@ -6264,22 +5062,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "soketto" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c71ed3d54db0a699f4948e1bb3e45b450fa31fe602621dee6680361d569c88" -dependencies = [ - "base64 0.12.3", - "bytes 0.5.6", - "flate2", - "futures 0.3.21", - "httparse", - "log 0.4.17", - "rand 0.7.3", - "sha-1 0.9.8", -] - [[package]] name = "sp-std" version = "3.0.0" @@ -6341,28 +5123,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" -[[package]] -name = "strum" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" -dependencies = [ - "heck 0.4.0", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subproductdomain" version = "0.1.0" @@ -6376,12 +5136,6 @@ dependencies = [ "ark-std", ] -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - [[package]] name = "subtle" version = "2.4.1" @@ -6485,8 +5239,8 @@ dependencies = [ "k256", "num-traits 0.2.15", "once_cell", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "ripemd160", "serde 1.0.137", "serde_bytes", @@ -6494,7 +5248,7 @@ dependencies = [ "serde_repr", "sha2 0.9.9", "signature", - "subtle 2.4.1", + "subtle", "subtle-encoding", "tendermint-proto", "time 0.3.9", @@ -6536,8 +5290,8 @@ dependencies = [ "flex-error", "num-derive", "num-traits 0.2.15", - "prost 0.9.0", - "prost-types 0.9.0", + "prost", + "prost-types", "serde 1.0.137", "serde_bytes", "subtle-encoding", @@ -6560,7 +5314,7 @@ dependencies = [ "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", + "pin-project", "serde 1.0.137", "serde_bytes", "serde_json", @@ -6768,9 +5522,9 @@ dependencies = [ "num_cpus", "once_cell", "parking_lot 0.12.1", - "pin-project-lite 0.2.9", + "pin-project-lite", "signal-hook-registry", - "socket2 0.4.4", + "socket2", "tokio-macros", "winapi 0.3.9", ] @@ -6813,7 +5567,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -6875,7 +5629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -6937,7 +5691,7 @@ dependencies = [ "futures-core", "futures-sink", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", ] @@ -6950,7 +5704,7 @@ dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", - "pin-project-lite 0.2.9", + "pin-project-lite", "tokio", "tracing 0.1.35", ] @@ -6982,9 +5736,9 @@ dependencies = [ "hyper 0.14.19", "hyper-timeout", "percent-encoding 2.1.0", - "pin-project 1.0.10", - "prost 0.9.0", - "prost-derive 0.9.0", + "pin-project", + "prost", + "prost-derive", "tokio", "tokio-stream", "tokio-util 0.6.10", @@ -7002,7 +5756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" dependencies = [ "proc-macro2", - "prost-build 0.9.0", + "prost-build", "quote", "syn", ] @@ -7017,8 +5771,8 @@ dependencies = [ "futures-util", "hdrhistogram", "indexmap", - "pin-project 1.0.10", - "pin-project-lite 0.2.9", + "pin-project", + "pin-project-lite", "rand 0.8.5", "slab", "tokio", @@ -7035,8 +5789,8 @@ source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503 dependencies = [ "bytes 1.1.0", "futures 0.3.21", - "pin-project 1.0.10", - "prost 0.9.0", + "pin-project", + "prost", "tendermint-proto", "tokio", "tokio-stream", @@ -7073,7 +5827,7 @@ version = "0.1.30" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "cfg-if 1.0.0", - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing-attributes 0.1.19", "tracing-core 0.1.22", ] @@ -7086,7 +5840,7 @@ checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log 0.4.17", - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing-attributes 0.1.21", "tracing-core 0.1.27", ] @@ -7146,7 +5900,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", + "pin-project", "tracing 0.1.35", ] @@ -7155,7 +5909,7 @@ name = "tracing-futures" version = "0.2.5" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing 0.1.30", ] @@ -7205,7 +5959,7 @@ version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ "futures 0.3.21", - "pin-project-lite 0.2.9", + "pin-project-lite", "tower-layer", "tower-make", "tower-service", @@ -7219,49 +5973,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" -[[package]] -name = "trust-dns-proto" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" -dependencies = [ - "async-trait", - "cfg-if 1.0.0", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.2.3", - "ipnet", - "lazy_static 1.4.0", - "log 0.4.17", - "rand 0.8.5", - "smallvec 1.8.0", - "thiserror", - "tinyvec", - "url 2.2.2", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" -dependencies = [ - "cfg-if 1.0.0", - "futures-util", - "ipconfig", - "lazy_static 1.4.0", - "log 0.4.17", - "lru-cache", - "parking_lot 0.11.2", - "resolv-conf", - "smallvec 1.8.0", - "thiserror", - "trust-dns-proto", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -7287,25 +5998,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "tungstenite" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" -dependencies = [ - "base64 0.13.0", - "byteorder", - "bytes 1.1.0", - "http", - "httparse", - "log 0.4.17", - "rand 0.8.5", - "sha-1 0.9.8", - "thiserror", - "url 2.2.2", - "utf-8", -] - [[package]] name = "typeable" version = "0.1.2" @@ -7324,18 +6016,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" -[[package]] -name = "uint" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unicase" version = "1.4.2" @@ -7393,16 +6073,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" -[[package]] -name = "universal-hash" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" -dependencies = [ - "generic-array 0.14.5", - "subtle 2.4.1", -] - [[package]] name = "unreachable" version = "1.0.0" @@ -7412,24 +6082,6 @@ dependencies = [ "void", ] -[[package]] -name = "unsigned-varint" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35" - -[[package]] -name = "unsigned-varint" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" -dependencies = [ - "asynchronous-codec", - "bytes 1.1.0", - "futures-io", - "futures-util", -] - [[package]] name = "untrusted" version = "0.7.1" @@ -7675,21 +6327,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures 0.3.21", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "wasmer" version = "2.2.0" @@ -8057,12 +6694,6 @@ dependencies = [ "libc", ] -[[package]] -name = "widestring" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" - [[package]] name = "winapi" version = "0.2.8" @@ -8192,15 +6823,6 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "winreg" version = "0.10.1" @@ -8220,17 +6842,6 @@ dependencies = [ "winapi-build", ] -[[package]] -name = "x25519-dalek" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" -dependencies = [ - "curve25519-dalek", - "rand_core 0.5.1", - "zeroize", -] - [[package]] name = "xattr" version = "0.2.3" @@ -8249,20 +6860,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "yamux" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" -dependencies = [ - "futures 0.3.21", - "log 0.4.17", - "nohash-hasher", - "parking_lot 0.11.2", - "rand 0.8.5", - "static_assertions", -] - [[package]] name = "zeroize" version = "1.5.5" diff --git a/Cargo.toml b/Cargo.toml index 2bb0d8ad10..0acc6646da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,6 @@ exclude = [ ] [patch.crates-io] -# TODO backported patch in the noise protocl for , blocked on libp2p upgrade -libp2p = {git = "https://github.com/heliaxdev/rust-libp2p.git", rev = "1abe349c231eb307d3dbe03f3ffffc6cf5e9084d"} # TODO temp patch for , and more tba. borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} diff --git a/Makefile b/Makefile index 6bcb62c75f..0330a569e7 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ build-wasm-image-docker: build-wasm-scripts-docker: build-wasm-image-docker docker run --rm -v ${PWD}:/__w/namada/namada namada-wasm make build-wasm-scripts -# Build the validity predicate, transactions, matchmaker and matchmaker filter wasm +# Build the validity predicate and transactions wasm build-wasm-scripts: make -C $(wasms) make opt-wasm diff --git a/README.md b/README.md index 323bc5e9b8..b93113ea5d 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ interaction with the protocol. ## 📓 Docs -- user docs: built from [docs mdBook](./documentation/docs/) -- dev docs: built from [dev mdBook](./documentation/dev/) -- specifications: built from [specs mdBook](./documentation/specs/) +* user docs: built from [docs mdBook](./documentation/docs/) +* dev docs: built from [dev mdBook](./documentation/dev/) +* specifications: built from [specs mdBook](./documentation/specs/) ## Warning @@ -47,7 +47,7 @@ Guide. ## ⚙️ Development ```shell -# Build the provided validity predicate, transaction and matchmaker wasm modules +# Build the provided validity predicate and transaction wasm modules make build-wasm-scripts-docker # Development (debug) build Anoma, which includes a validator and some default @@ -69,11 +69,11 @@ make clippy To change the log level, set `ANOMA_LOG` environment variable to one of: -- `error` -- `warn` -- `info` -- `debug` -- `trace` +* `error` +* `warn` +* `info` +* `debug` +* `trace` The default is set to `info` for all the modules, expect for Tendermint ABCI, which has a lot of `debug` logging. diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 65d7d84eed..23a27a65fc 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -63,7 +63,6 @@ color-eyre = "0.5.10" config = "0.11.0" curl = "0.4.43" derivative = "2.2.0" -directories = "4.0.1" ed25519-consensus = "1.2.0" ferveo = {git = "https://github.com/anoma/ferveo"} ferveo-common = {git = "https://github.com/anoma/ferveo"} @@ -76,14 +75,11 @@ itertools = "0.10.1" jsonpath_lib = "0.3.0" libc = "0.2.97" libloading = "0.7.2" -libp2p = "0.38.0" -message-io = {version = "0.14.3", default-features = false, features = ["websocket"]} num-derive = "0.3.3" num-traits = "0.2.14" num_cpus = "1.13.0" once_cell = "1.8.0" orion = "0.16.0" -pathdiff = "0.2.1" prost = "0.9.0" prost-types = "0.9.0" rand = {version = "0.8", default-features = false} @@ -97,7 +93,6 @@ rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" serde_json = {version = "1.0.62", features = ["raw_value"]} -serde_regex = "1.1.0" sha2 = "0.9.3" signal-hook = "0.3.9" sparse-merkle-tree = {git = "https://github.com/heliaxdev/sparse-merkle-tree", branch = "yuji/prost-0.9", features = ["borsh"]} @@ -135,4 +130,3 @@ tokio-test = "0.4.2" [build-dependencies] git2 = "0.13.25" -tonic-build = "0.6.0" diff --git a/apps/build.rs b/apps/build.rs index ae49503e78..32f7c57e87 100644 --- a/apps/build.rs +++ b/apps/build.rs @@ -1,6 +1,5 @@ -use std::fs::{read_to_string, File}; +use std::fs::File; use std::io::Write; -use std::process::Command; use std::{env, str}; use git2::{DescribeFormatOptions, DescribeOptions, Repository}; @@ -8,9 +7,6 @@ use git2::{DescribeFormatOptions, DescribeOptions, Repository}; /// Path to the .proto source files, relative to `apps` directory const PROTO_SRC: &str = "./proto"; -/// The version should match the one we use in the `Makefile` -const RUSTFMT_TOOLCHAIN_SRC: &str = "../rust-nightly-version"; - fn main() { // Discover the repository version, if it exists println!("cargo:rerun-if-changed=../.git"); @@ -66,43 +62,4 @@ fn main() { println!("cargo:rustc-cfg=feature=\"dev\""); } } - - let mut use_rustfmt = false; - - // The version should match the one we use in the `Makefile` - if let Ok(rustfmt_toolchain) = read_to_string(RUSTFMT_TOOLCHAIN_SRC) { - // Try to find the path to rustfmt. - if let Ok(output) = Command::new("rustup") - .args(&[ - "which", - "rustfmt", - "--toolchain", - rustfmt_toolchain.trim(), - ]) - .output() - { - if let Ok(rustfmt) = str::from_utf8(&output.stdout) { - // Set the command to be used by tonic_build below to format the - // generated files - let rustfmt = rustfmt.trim(); - if !rustfmt.is_empty() { - println!("using rustfmt from path \"{}\"", rustfmt); - env::set_var("RUSTFMT", rustfmt); - use_rustfmt = true - } - } - } - } - - tonic_build::configure() - .out_dir("src/lib/proto/generated") - .format(use_rustfmt) - .extern_path(".types", "::namada::proto::generated::types") - // This warning appears in tonic generated code - .server_mod_attribute(".", "#[allow(clippy::unit_arg)]") - // TODO try to add json encoding to simplify use for user - // .type_attribute("types.Intent", "#[derive(serde::Serialize, - // serde::Deserialize)]") - .compile(&[format!("{}/services.proto", PROTO_SRC)], &[PROTO_SRC]) - .unwrap(); } diff --git a/apps/src/bin/anoma-client/cli.rs b/apps/src/bin/anoma-client/cli.rs index d04eeff9ab..1da669e8ae 100644 --- a/apps/src/bin/anoma-client/cli.rs +++ b/apps/src/bin/anoma-client/cli.rs @@ -3,7 +3,7 @@ use color_eyre::eyre::Result; use namada_apps::cli; use namada_apps::cli::cmds::*; -use namada_apps::client::{gossip, rpc, tx, utils}; +use namada_apps::client::{rpc, tx, utils}; pub async fn main() -> Result<()> { match cli::anoma_client_cli() { @@ -80,13 +80,6 @@ pub async fn main() -> Result<()> { Sub::QueryProtocolParameters(QueryProtocolParameters(args)) => { rpc::query_protocol_parameters(ctx, args).await; } - // Gossip cmds - Sub::Intent(Intent(args)) => { - gossip::gossip_intent(ctx, args).await; - } - Sub::SubscribeTopic(SubscribeTopic(args)) => { - gossip::subscribe_topic(ctx, args).await; - } } } cli::AnomaClient::WithoutContext(cmd, global_args) => match cmd { diff --git a/apps/src/bin/anoma-node/cli.rs b/apps/src/bin/anoma-node/cli.rs index 407a6b7378..65ed7d59bf 100644 --- a/apps/src/bin/anoma-node/cli.rs +++ b/apps/src/bin/anoma-node/cli.rs @@ -1,8 +1,8 @@ //! Anoma node CLI. use eyre::{Context, Result}; -use namada_apps::cli::{self, args, cmds}; -use namada_apps::node::{gossip, ledger, matchmaker}; +use namada_apps::cli::{self, cmds}; +use namada_apps::node::ledger; pub fn main() -> Result<()> { let (cmd, mut ctx) = cli::anoma_node_cli(); @@ -20,55 +20,6 @@ pub fn main() -> Result<()> { .wrap_err("Failed to reset Anoma node")?; } }, - cmds::AnomaNode::Gossip(sub) => match sub { - cmds::Gossip::Run(cmds::GossipRun(args::GossipRun { - addr, - rpc, - })) => { - let config = ctx.config; - let mut gossip_cfg = config.intent_gossiper; - gossip_cfg.update(addr, rpc); - gossip::run( - gossip_cfg, - &config - .ledger - .shell - .base_dir - .join(ctx.global_config.default_chain_id.as_str()), - ) - .wrap_err("Failed to run gossip service")?; - } - }, - cmds::AnomaNode::Matchmaker(cmds::Matchmaker(args::Matchmaker { - intent_gossiper_addr, - matchmaker_path, - tx_code_path, - ledger_addr, - tx_signing_key, - tx_source_address, - })) => { - let tx_signing_key = ctx.get_cached(&tx_signing_key); - let tx_source_address = ctx.get(&tx_source_address); - - let wasm_dir = ctx.wasm_dir(); - let config = ctx.config; - let mut mm_config = config.matchmaker; - if matchmaker_path.is_some() { - mm_config.matchmaker_path = matchmaker_path; - } - if tx_code_path.is_some() { - mm_config.tx_code_path = tx_code_path; - } - - matchmaker::run( - mm_config, - intent_gossiper_addr, - ledger_addr, - tx_signing_key, - tx_source_address, - wasm_dir, - ); - } cmds::AnomaNode::Config(sub) => match sub { cmds::Config::Gen(cmds::ConfigGen) => { // If the config doesn't exit, it gets generated in the context. diff --git a/apps/src/bin/anoma/cli.rs b/apps/src/bin/anoma/cli.rs index b0ed783276..ccde0c3618 100644 --- a/apps/src/bin/anoma/cli.rs +++ b/apps/src/bin/anoma/cli.rs @@ -39,10 +39,7 @@ fn handle_command(cmd: cli::cmds::Anoma, raw_sub_cmd: String) -> Result<()> { } match cmd { - cli::cmds::Anoma::Node(_) - | cli::cmds::Anoma::Ledger(_) - | cli::cmds::Anoma::Gossip(_) - | cli::cmds::Anoma::Matchmaker(_) => { + cli::cmds::Anoma::Node(_) | cli::cmds::Anoma::Ledger(_) => { handle_subcommand("namadan", sub_args) } cli::cmds::Anoma::Client(_) @@ -52,8 +49,9 @@ fn handle_command(cmd: cli::cmds::Anoma, raw_sub_cmd: String) -> Result<()> { | cli::cmds::Anoma::TxInitNft(_) | cli::cmds::Anoma::TxMintNft(_) | cli::cmds::Anoma::TxInitProposal(_) - | cli::cmds::Anoma::TxVoteProposal(_) - | cli::cmds::Anoma::Intent(_) => handle_subcommand("namadac", sub_args), + | cli::cmds::Anoma::TxVoteProposal(_) => { + handle_subcommand("namadac", sub_args) + } cli::cmds::Anoma::Wallet(_) => handle_subcommand("namadaw", sub_args), } } diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index f546860cfd..1b014cc21b 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -41,8 +41,6 @@ pub mod cmds { // Inlined commands from the node. Ledger(Ledger), - Gossip(Gossip), - Matchmaker(Matchmaker), // Inlined commands from the client. TxCustom(TxCustom), @@ -52,7 +50,6 @@ pub mod cmds { TxMintNft(TxMintNft), TxInitProposal(TxInitProposal), TxVoteProposal(TxVoteProposal), - Intent(Intent), } impl Cmd for Anoma { @@ -61,8 +58,6 @@ pub mod cmds { .subcommand(AnomaClient::def()) .subcommand(AnomaWallet::def()) .subcommand(Ledger::def()) - .subcommand(Gossip::def()) - .subcommand(Matchmaker::def()) .subcommand(TxCustom::def()) .subcommand(TxTransfer::def()) .subcommand(TxUpdateVp::def()) @@ -70,7 +65,6 @@ pub mod cmds { .subcommand(TxMintNft::def()) .subcommand(TxInitProposal::def()) .subcommand(TxVoteProposal::def()) - .subcommand(Intent::def()) } fn parse(matches: &ArgMatches) -> Option { @@ -78,8 +72,6 @@ pub mod cmds { let client = SubCmd::parse(matches).map(Self::Client); let wallet = SubCmd::parse(matches).map(Self::Wallet); let ledger = SubCmd::parse(matches).map(Self::Ledger); - let gossip = SubCmd::parse(matches).map(Self::Gossip); - let matchmaker = SubCmd::parse(matches).map(Self::Matchmaker); let tx_custom = SubCmd::parse(matches).map(Self::TxCustom); let tx_transfer = SubCmd::parse(matches).map(Self::TxTransfer); let tx_update_vp = SubCmd::parse(matches).map(Self::TxUpdateVp); @@ -89,12 +81,9 @@ pub mod cmds { SubCmd::parse(matches).map(Self::TxInitProposal); let tx_vote_proposal = SubCmd::parse(matches).map(Self::TxVoteProposal); - let intent = SubCmd::parse(matches).map(Self::Intent); node.or(client) .or(wallet) .or(ledger) - .or(gossip) - .or(matchmaker) .or(tx_custom) .or(tx_transfer) .or(tx_update_vp) @@ -102,7 +91,6 @@ pub mod cmds { .or(tx_nft_mint) .or(tx_init_proposal) .or(tx_vote_proposal) - .or(intent) } } @@ -112,25 +100,18 @@ pub mod cmds { #[allow(clippy::large_enum_variant)] pub enum AnomaNode { Ledger(Ledger), - Gossip(Gossip), - Matchmaker(Matchmaker), Config(Config), } impl Cmd for AnomaNode { fn add_sub(app: App) -> App { - app.subcommand(Ledger::def()) - .subcommand(Gossip::def()) - .subcommand(Matchmaker::def()) - .subcommand(Config::def()) + app.subcommand(Ledger::def()).subcommand(Config::def()) } fn parse(matches: &ArgMatches) -> Option { let ledger = SubCmd::parse(matches).map(Self::Ledger); - let gossip = SubCmd::parse(matches).map(Self::Gossip); - let matchmaker = SubCmd::parse(matches).map(Self::Matchmaker); let config = SubCmd::parse(matches).map(Self::Config); - ledger.or(gossip).or(matchmaker).or(config) + ledger.or(config) } } impl SubCmd for AnomaNode { @@ -194,9 +175,6 @@ pub mod cmds { .subcommand(QueryProposal::def().display_order(3)) .subcommand(QueryProposalResult::def().display_order(3)) .subcommand(QueryProtocolParameters::def().display_order(3)) - // Intents - .subcommand(Intent::def().display_order(4)) - .subcommand(SubscribeTopic::def().display_order(4)) // Utils .subcommand(Utils::def().display_order(5)) } @@ -231,8 +209,6 @@ pub mod cmds { Self::parse_with_ctx(matches, QueryProposalResult); let query_protocol_parameters = Self::parse_with_ctx(matches, QueryProtocolParameters); - let intent = Self::parse_with_ctx(matches, Intent); - let subscribe_topic = Self::parse_with_ctx(matches, SubscribeTopic); let utils = SubCmd::parse(matches).map(Self::WithoutContext); tx_custom .or(tx_transfer) @@ -256,8 +232,6 @@ pub mod cmds { .or(query_proposal) .or(query_proposal_result) .or(query_protocol_parameters) - .or(intent) - .or(subscribe_topic) .or(utils) } } @@ -316,9 +290,6 @@ pub mod cmds { QueryProposal(QueryProposal), QueryProposalResult(QueryProposalResult), QueryProtocolParameters(QueryProtocolParameters), - // Gossip cmds - Intent(Intent), - SubscribeTopic(SubscribeTopic), } #[derive(Clone, Debug)] @@ -657,76 +628,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - pub enum Gossip { - Run(GossipRun), - } - - impl SubCmd for Gossip { - const CMD: &'static str = "gossip"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).and_then(|matches| { - let run = SubCmd::parse(matches).map(Gossip::Run); - run - // The `run` command is the default if no sub-command given - .or_else(|| { - Some(Gossip::Run(GossipRun(args::GossipRun::parse( - matches, - )))) - }) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Gossip node sub-commands. If no sub-command specified, \ - defaults to run the node.", - ) - .subcommand(GossipRun::def()) - .add_args::() - } - } - - #[derive(Clone, Debug)] - pub struct Matchmaker(pub args::Matchmaker); - - impl SubCmd for Matchmaker { - const CMD: &'static str = "matchmaker"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Matchmaker(args::Matchmaker::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Run a matchmaker.") - .add_args::() - } - } - - #[derive(Clone, Debug)] - pub struct GossipRun(pub args::GossipRun); - - impl SubCmd for GossipRun { - const CMD: &'static str = "run"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| GossipRun(args::GossipRun::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Run a gossip node.") - .add_args::() - } - } - #[derive(Clone, Debug)] pub enum Config { Gen(ConfigGen), @@ -1218,47 +1119,6 @@ pub mod cmds { } } - #[derive(Clone, Debug)] - pub struct Intent(pub args::Intent); - - impl SubCmd for Intent { - const CMD: &'static str = "intent"; - - fn parse(matches: &ArgMatches) -> Option { - matches - .subcommand_matches(Self::CMD) - .map(|matches| Intent(args::Intent::parse(matches))) - } - - fn def() -> App { - App::new(Self::CMD) - .about("Send an intent.") - .add_args::() - } - } - - #[derive(Clone, Debug)] - pub struct SubscribeTopic(pub args::SubscribeTopic); - - impl SubCmd for SubscribeTopic { - const CMD: &'static str = "subscribe-topic"; - - fn parse(matches: &ArgMatches) -> Option { - matches.subcommand_matches(Self::CMD).map(|matches| { - SubscribeTopic(args::SubscribeTopic::parse(matches)) - }) - } - - fn def() -> App { - App::new(Self::CMD) - .about( - "Subscribe intent gossip node with a matchmaker to a \ - topic.", - ) - .add_args::() - } - } - #[derive(Clone, Debug)] pub enum Utils { JoinNetwork(JoinNetwork), @@ -1355,23 +1215,18 @@ pub mod cmds { pub mod args { - use std::convert::TryFrom; use std::env; - use std::fs::File; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; - use libp2p::Multiaddr; use namada::types::address::Address; use namada::types::chain::{ChainId, ChainIdPrefix}; use namada::types::governance::ProposalVote; - use namada::types::intent::{DecimalWrapper, Exchange}; use namada::types::key::*; use namada::types::storage::{self, Epoch}; use namada::types::token; use namada::types::transaction::GasLimit; - use serde::Deserialize; use tendermint::Timeout; use tendermint_config::net::Address as TendermintAddress; @@ -1419,13 +1274,6 @@ pub mod args { arg_default("gas-limit", DefaultFn(|| token::Amount::from(0))); const GENESIS_PATH: Arg = arg("genesis-path"); const GENESIS_VALIDATOR: ArgOpt = arg("genesis-validator").opt(); - const INTENT_GOSSIPER_ADDR: ArgDefault = arg_default( - "intent-gossiper", - DefaultFn(|| { - let raw = "127.0.0.1:26661"; - SocketAddr::from_str(raw).unwrap() - }), - ); const LEDGER_ADDRESS_ABOUT: &str = "Address of a ledger node as \"{scheme}://{host}:{port}\". If the \ scheme is not supplied, it is assumed to be TCP."; @@ -1437,12 +1285,8 @@ pub mod args { const LEDGER_ADDRESS: Arg = arg("ledger-address"); const LOCALHOST: ArgFlag = flag("localhost"); - const MATCHMAKER_PATH: ArgOpt = arg_opt("matchmaker-path"); const MODE: ArgOpt = arg_opt("mode"); - const MULTIADDR_OPT: ArgOpt = arg_opt("address"); const NET_ADDRESS: Arg = arg("net-address"); - const NODE_OPT: ArgOpt = arg_opt("node"); - const NODE: Arg = arg("node"); const NFT_ADDRESS: Arg
= arg("nft-address"); const OWNER: ArgOpt = arg_opt("owner"); const PROPOSAL_OFFLINE: ArgFlag = flag("offline"); @@ -1456,7 +1300,6 @@ pub mod args { const RAW_PUBLIC_KEY_OPT: ArgOpt = arg_opt("public-key"); const REWARDS_CODE_PATH: ArgOpt = arg_opt("rewards-code-path"); const REWARDS_KEY: ArgOpt = arg_opt("rewards-key"); - const RPC_SOCKET_ADDR: ArgOpt = arg_opt("rpc"); const SCHEME: ArgDefault = arg_default("scheme", DefaultFn(|| SchemeType::Ed25519)); const SIGNER: ArgOpt = arg_opt("signer"); @@ -1466,12 +1309,8 @@ pub mod args { const SOURCE_OPT: ArgOpt = SOURCE.opt(); const STORAGE_KEY: Arg = arg("storage-key"); const TARGET: Arg = arg("target"); - const TO_STDOUT: ArgFlag = flag("stdout"); const TOKEN_OPT: ArgOpt = TOKEN.opt(); const TOKEN: Arg = arg("token"); - const TOPIC_OPT: ArgOpt = arg_opt("topic"); - const TOPIC: Arg = arg("topic"); - const TX_CODE_PATH: ArgOpt = arg_opt("tx-code-path"); const TX_HASH: Arg = arg("tx-hash"); const UNSAFE_DONT_ENCRYPT: ArgFlag = flag("unsafe-dont-encrypt"); const UNSAFE_SHOW_SECRET: ArgFlag = flag("unsafe-show-secret"); @@ -1523,11 +1362,11 @@ pub mod args { )) .arg(WASM_DIR.def().about( "Directory with built WASM validity predicates, \ - transactions and matchmaker files. This must not be an \ - absolute path as the directory is nested inside the \ - chain directory. This value can also be set via \ - `ANOMA_WASM_DIR` environment variable, but the argument \ - takes precedence, if specified.", + transactions. This must not be an absolute path as the \ + directory is nested inside the chain directory. This \ + value can also be set via `ANOMA_WASM_DIR` environment \ + variable, but the argument takes precedence, if \ + specified.", )) .arg(MODE.def().about( "The mode in which to run Anoma. Options are \n\t * \ @@ -2214,67 +2053,6 @@ pub mod args { } } - /// Helper struct for generating intents - #[derive(Debug, Clone, Deserialize)] - pub struct ExchangeDefinition { - /// The source address - pub addr: String, - /// The token to be sold - pub token_sell: String, - /// The minimum rate - pub rate_min: String, - /// The maximum amount of token to be sold - pub max_sell: String, - /// The token to be bought - pub token_buy: String, - /// The amount of token to be bought - pub min_buy: String, - /// The path to the wasm vp code - pub vp_path: Option, - } - - impl TryFrom for Exchange { - type Error = &'static str; - - fn try_from( - value: ExchangeDefinition, - ) -> Result { - let vp = if let Some(path) = value.vp_path { - if let Ok(wasm) = std::fs::read(path.clone()) { - Some(wasm) - } else { - eprintln!("File {} was not found.", path); - None - } - } else { - None - }; - - let addr = Address::decode(value.addr) - .expect("Addr should be a valid address"); - let token_buy = Address::decode(value.token_buy) - .expect("Token_buy should be a valid address"); - let token_sell = Address::decode(value.token_sell) - .expect("Token_sell should be a valid address"); - let min_buy = token::Amount::from_str(&value.min_buy) - .expect("Min_buy must be convertible to number"); - let max_sell = token::Amount::from_str(&value.max_sell) - .expect("Max_sell must be convertible to number"); - let rate_min = DecimalWrapper::from_str(&value.rate_min) - .expect("Max_sell must be convertible to decimal."); - - Ok(Exchange { - addr, - token_sell, - rate_min, - max_sell, - token_buy, - min_buy, - vp, - }) - } - } - /// Query PoS bond(s) #[derive(Clone, Debug)] pub struct QueryBonds { @@ -2393,218 +2171,6 @@ pub mod args { .arg(STORAGE_KEY.def().about("Storage key")) } } - /// Intent arguments - #[derive(Clone, Debug)] - pub struct Intent { - /// Gossip node address - pub node_addr: Option, - /// Intent topic - pub topic: Option, - /// Source address - pub source: Option, - /// Signing key - pub signing_key: Option, - /// Exchanges description - pub exchanges: Vec, - /// The address of the ledger node as host:port - pub ledger_address: TendermintAddress, - /// Print output to stdout - pub to_stdout: bool, - } - - impl Args for Intent { - fn parse(matches: &ArgMatches) -> Self { - let node_addr = NODE_OPT.parse(matches); - let data_path = DATA_PATH.parse(matches); - let source = SOURCE_OPT.parse(matches); - let signing_key = SIGNING_KEY_OPT.parse(matches); - let to_stdout = TO_STDOUT.parse(matches); - let topic = TOPIC_OPT.parse(matches); - - let file = File::open(&data_path).expect("File must exist."); - let exchange_definitions: Vec = - serde_json::from_reader(file) - .expect("JSON was not well-formatted"); - - let exchanges: Vec = exchange_definitions - .iter() - .map(|item| { - Exchange::try_from(item.clone()).expect( - "Conversion from ExchangeDefinition to Exchange \ - should not fail.", - ) - }) - .collect(); - let ledger_address = LEDGER_ADDRESS_DEFAULT.parse(matches); - - Self { - node_addr, - topic, - source, - signing_key, - exchanges, - ledger_address, - to_stdout, - } - } - - fn def(app: App) -> App { - app.arg( - NODE_OPT - .def() - .about("The gossip node address.") - .conflicts_with(TO_STDOUT.name), - ) - .arg(DATA_PATH.def().about( - "The data of the intent, that contains all value necessary \ - for the matchmaker.", - )) - .arg( - SOURCE_OPT - .def() - .about( - "Sign the intent with the key of a given address or \ - address alias from your wallet.", - ) - .conflicts_with(SIGNING_KEY_OPT.name), - ) - .arg( - SIGNING_KEY_OPT - .def() - .about( - "Sign the intent with the key for the given public \ - key, public key hash or alias from your wallet.", - ) - .conflicts_with(SOURCE_OPT.name), - ) - .arg(LEDGER_ADDRESS_DEFAULT.def().about(LEDGER_ADDRESS_ABOUT)) - .arg( - TOPIC_OPT - .def() - .about("The subnetwork where the intent should be sent to.") - .conflicts_with(TO_STDOUT.name), - ) - .arg( - TO_STDOUT - .def() - .about( - "Echo the serialized intent to stdout. Note that with \ - this option, the intent won't be submitted to the \ - intent gossiper RPC.", - ) - .conflicts_with_all(&[NODE_OPT.name, TOPIC.name]), - ) - } - } - - /// Subscribe intent topic arguments - #[derive(Clone, Debug)] - pub struct SubscribeTopic { - /// Gossip node address - pub node_addr: String, - /// Intent topic - pub topic: String, - } - - impl Args for SubscribeTopic { - fn parse(matches: &ArgMatches) -> Self { - let node_addr = NODE.parse(matches); - let topic = TOPIC.parse(matches); - Self { node_addr, topic } - } - - fn def(app: App) -> App { - app.arg(NODE.def().about("The gossip node address.")).arg( - TOPIC - .def() - .about("The new topic of interest for that node."), - ) - } - } - - #[derive(Clone, Debug)] - pub struct GossipRun { - pub addr: Option, - pub rpc: Option, - } - - impl Args for GossipRun { - fn parse(matches: &ArgMatches) -> Self { - let addr = MULTIADDR_OPT.parse(matches); - let rpc = RPC_SOCKET_ADDR.parse(matches); - Self { addr, rpc } - } - - fn def(app: App) -> App { - app.arg( - MULTIADDR_OPT - .def() - .about("Gossip service address as host:port."), - ) - .arg(RPC_SOCKET_ADDR.def().about("Enable RPC service.")) - } - } - - #[derive(Clone, Debug)] - pub struct Matchmaker { - pub matchmaker_path: Option, - pub tx_code_path: Option, - pub intent_gossiper_addr: SocketAddr, - pub ledger_addr: TendermintAddress, - pub tx_signing_key: WalletKeypair, - pub tx_source_address: WalletAddress, - } - - impl Args for Matchmaker { - fn parse(matches: &ArgMatches) -> Self { - let intent_gossiper_addr = INTENT_GOSSIPER_ADDR.parse(matches); - let matchmaker_path = MATCHMAKER_PATH.parse(matches); - let tx_code_path = TX_CODE_PATH.parse(matches); - let ledger_addr = LEDGER_ADDRESS_DEFAULT.parse(matches); - let tx_signing_key = SIGNING_KEY.parse(matches); - let tx_source_address = SOURCE.parse(matches); - Self { - intent_gossiper_addr, - matchmaker_path, - tx_code_path, - ledger_addr, - tx_signing_key, - tx_source_address, - } - } - - fn def(app: App) -> App { - app.arg(INTENT_GOSSIPER_ADDR.def().about( - "Intent Gossiper endpoint for matchmaker connections as \ - \"{host}:{port}\".", - )) - .arg(MATCHMAKER_PATH.def().about( - "The file name of the matchmaker compiled to a dynamic \ - library (the filename extension is optional).", - )) - .arg( - TX_CODE_PATH - .def() - .about("The transaction code to use with the matchmaker."), - ) - .arg(LEDGER_ADDRESS_DEFAULT.def().about( - "The address of the ledger as \"{scheme}://{host}:{port}\" \ - that the matchmaker must send transactions to. If the scheme \ - is not supplied, it is assumed to be TCP.", - )) - .arg(SIGNING_KEY.def().about( - "Sign the transactions created by the matchmaker with the key \ - for the given public key, public key hash or alias from your \ - wallet.", - )) - .arg(SOURCE.def().about( - "Source address or alias of an address of the transactions \ - created by the matchmaker. This must be matching the signing \ - key.", - )) - } - } - /// Common transaction arguments #[derive(Clone, Debug)] pub struct Tx { @@ -3004,7 +2570,7 @@ pub mod args { )) .arg(LOCALHOST.def().about( "Use localhost address for P2P and RPC connections for the \ - validators ledger and intent gossip nodes", + validators ledger", )) .arg(ALLOW_DUPLICATE_IP.def().about( "Toggle to disable guard against peers connecting from the \ diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 8189b633bf..ca5daff8fe 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -44,7 +44,7 @@ pub struct Context { pub wallet: Wallet, /// The global configuration pub global_config: GlobalConfig, - /// The ledger & intent gossip configuration for a specific chain ID + /// The ledger configuration for a specific chain ID pub config: Config, } diff --git a/apps/src/lib/client/gossip.rs b/apps/src/lib/client/gossip.rs deleted file mode 100644 index 2225898ff4..0000000000 --- a/apps/src/lib/client/gossip.rs +++ /dev/null @@ -1,116 +0,0 @@ -use std::collections::HashSet; -use std::io::Write; - -use borsh::BorshSerialize; -use namada::proto::Signed; -use namada::types::intent::{Exchange, FungibleTokenIntent}; -use tendermint_config::net::Address as TendermintAddress; - -use super::signing; -use crate::cli::{self, args, Context}; -use crate::proto::services::rpc_service_client::RpcServiceClient; -use crate::proto::{services, RpcMessage}; -use crate::wallet::Wallet; - -/// Create an intent, sign it and submit it to the gossip node (unless -/// `to_stdout` is `true`). -pub async fn gossip_intent( - mut ctx: Context, - args::Intent { - node_addr, - topic, - source, - signing_key, - exchanges, - ledger_address, - to_stdout, - }: args::Intent, -) { - let mut signed_exchanges: HashSet> = - HashSet::with_capacity(exchanges.len()); - for exchange in exchanges { - let signed = - sign_exchange(&mut ctx.wallet, exchange, ledger_address.clone()) - .await; - signed_exchanges.insert(signed); - } - - let source_keypair = match ctx.get_opt_cached(&signing_key) { - Some(key) => key, - None => { - let source = ctx.get_opt(&source).unwrap_or_else(|| { - eprintln!("A source or a signing key is required."); - cli::safe_exit(1) - }); - signing::find_keypair( - &mut ctx.wallet, - &source, - ledger_address.clone(), - ) - .await - } - }; - let signed_ft: Signed = Signed::new( - &*source_keypair, - FungibleTokenIntent { - exchange: signed_exchanges, - }, - ); - let data_bytes = signed_ft.try_to_vec().unwrap(); - - if to_stdout { - let mut out = std::io::stdout(); - out.write_all(&data_bytes).unwrap(); - out.flush().unwrap(); - } else { - let node_addr = node_addr.expect( - "Gossip node address must be defined to submit the intent to it.", - ); - let topic = topic.expect( - "The topic must be defined to submit the intent to a gossip node.", - ); - - match RpcServiceClient::connect(node_addr.clone()).await { - Ok(mut client) => { - let intent = namada::proto::Intent::new(data_bytes); - let message: services::RpcMessage = - RpcMessage::new_intent(intent, topic).into(); - let response = client.send_message(message).await.expect( - "Failed to send message and/or receive rpc response", - ); - println!("{:#?}", response); - } - Err(e) => { - eprintln!( - "Error connecting RPC client to {}: {}", - node_addr, e - ); - } - }; - } -} - -/// Request an intent gossip node with a matchmaker to subscribe to a given -/// topic. -pub async fn subscribe_topic( - _ctx: Context, - args::SubscribeTopic { node_addr, topic }: args::SubscribeTopic, -) { - let mut client = RpcServiceClient::connect(node_addr).await.unwrap(); - let message: services::RpcMessage = RpcMessage::new_topic(topic).into(); - let response = client - .send_message(message) - .await - .expect("failed to send message and/or receive rpc response"); - println!("{:#?}", response); -} - -async fn sign_exchange( - wallet: &mut Wallet, - exchange: Exchange, - ledger_address: TendermintAddress, -) -> Signed { - let source_keypair = - signing::find_keypair(wallet, &exchange.addr, ledger_address).await; - Signed::new(&*source_keypair, exchange.clone()) -} diff --git a/apps/src/lib/client/mod.rs b/apps/src/lib/client/mod.rs index a3d2ddece9..e27e575ce0 100644 --- a/apps/src/lib/client/mod.rs +++ b/apps/src/lib/client/mod.rs @@ -1,4 +1,3 @@ -pub mod gossip; pub mod rpc; pub mod signing; pub mod tendermint_rpc_types; diff --git a/apps/src/lib/client/utils.rs b/apps/src/lib/client/utils.rs index 9043a14db7..d2d49341fe 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, HashSet}; +use std::collections::HashMap; use std::env; use std::fs::{self, File, OpenOptions}; use std::io::Write; @@ -27,10 +27,7 @@ use crate::config::genesis::genesis_config::{ self, HexString, ValidatorPreGenesisConfig, }; use crate::config::global::GlobalConfig; -use crate::config::{ - self, Config, IntentGossiper, PeerAddress, TendermintMode, -}; -use crate::node::gossip; +use crate::config::{self, Config, TendermintMode}; use crate::node::ledger::tendermint_node; use crate::wallet::{pre_genesis, Wallet}; use crate::wasm_loader; @@ -422,25 +419,10 @@ pub fn init_network( let mut rng: ThreadRng = thread_rng(); + // Accumulator of validators' Tendermint P2P addresses let mut persistent_peers: Vec = Vec::with_capacity(config.validator.len()); - // Intent gossiper config bootstrap peers where we'll add the address for - // each validator's node - let mut seed_peers: HashSet = - HashSet::with_capacity(config.validator.len()); - let mut gossiper_configs: HashMap = - HashMap::with_capacity(config.validator.len()); - let mut matchmaker_configs: HashMap = - HashMap::with_capacity(config.validator.len()); - // Other accounts owned by one of the validators - let mut validator_owned_accounts: HashMap< - String, - genesis_config::EstablishedAccountConfig, - > = HashMap::default(); - - // We need a temporary copy to be able to use this inside the validator - // loop, which has mutable borrow on the config. - let established_accounts = config.established.clone(); + // Iterate over each validator, generating keys and addresses config.validator.iter_mut().for_each(|(name, config)| { let validator_dir = accounts_dir.join(name); @@ -477,36 +459,6 @@ pub fn init_network( )) .expect("Validator address must be valid"); persistent_peers.push(peer); - // Add a Intent gossiper bootstrap peer from the validator's IP - let mut gossiper_config = IntentGossiper::default(); - // Generate P2P identity - let p2p_identity = gossip::p2p::Identity::gen(&chain_dir); - let peer_id = p2p_identity.peer_id(); - let ledger_addr = - SocketAddr::from_str(config.net_address.as_ref().unwrap()).unwrap(); - let ip = ledger_addr.ip().to_string(); - let first_port = ledger_addr.port(); - let intent_peer_address = libp2p::Multiaddr::from_str( - format!("/ip4/{}/tcp/{}", ip, first_port + 3).as_str(), - ) - .unwrap(); - - gossiper_config.address = if localhost { - intent_peer_address.clone() - } else { - libp2p::Multiaddr::from_str( - format!("/ip4/0.0.0.0/tcp/{}", first_port + 3).as_str(), - ) - .unwrap() - }; - if let Some(discover) = gossiper_config.discover_peer.as_mut() { - // Disable mDNS local network peer discovery on the validator nodes - discover.mdns = false; - } - let intent_peer = PeerAddress { - address: intent_peer_address, - peer_id, - }; // Generate account and reward addresses let address = address::gen_established_address("validator account"); @@ -634,93 +586,20 @@ pub fn init_network( wallet.add_address(name.clone(), address); wallet.add_address(format!("{}-reward", &name), reward_address); - // Check if there's a matchmaker configured for this validator node - match ( - &config.matchmaker_account, - &config.matchmaker_code, - &config.matchmaker_tx, - ) { - (Some(account), Some(mm_code), Some(tx_code)) => { - if config.intent_gossip_seed.unwrap_or_default() { - eprintln!("A bootstrap node cannot run matchmakers"); - cli::safe_exit(1) - } - match established_accounts.as_ref().and_then(|e| e.get(account)) - { - Some(matchmaker) => { - let mut matchmaker = matchmaker.clone(); - - init_established_account( - account, - &mut wallet, - &mut matchmaker, - unsafe_dont_encrypt, - ); - validator_owned_accounts - .insert(account.clone(), matchmaker); - - let matchmaker_config = config::Matchmaker { - matchmaker_path: Some(mm_code.clone().into()), - tx_code_path: Some(tx_code.clone().into()), - }; - matchmaker_configs - .insert(name.clone(), matchmaker_config); - } - None => { - eprintln!( - "Misconfigured validator's matchmaker. No \ - established account with alias {} found", - account - ); - cli::safe_exit(1) - } - } - } - (None, None, None) => {} - _ => { - eprintln!( - "Misconfigured validator's matchmaker. \ - `matchmaker_account`, `matchmaker_code` and \ - `matchmaker_tx` must be all or none present." - ); - cli::safe_exit(1) - } - } - - // Store the gossip config - gossiper_configs.insert(name.clone(), gossiper_config); - if config.intent_gossip_seed.unwrap_or_default() { - seed_peers.insert(intent_peer); - } - wallet.save().unwrap(); }); - if seed_peers.is_empty() && config.validator.len() > 1 { - tracing::warn!( - "At least 1 validator with `intent_gossip_seed = true` is needed \ - to established connection between the intent gossiper nodes" - ); - } - // Create a wallet for all accounts other than validators let mut wallet = Wallet::load_or_new(&accounts_dir.join(NET_OTHER_ACCOUNTS_DIR)); if let Some(established) = &mut config.established { established.iter_mut().for_each(|(name, config)| { - match validator_owned_accounts.get(name) { - Some(validator_owned) => { - *config = validator_owned.clone(); - } - None => { - init_established_account( - name, - &mut wallet, - config, - unsafe_dont_encrypt, - ); - } - } + init_established_account( + name, + &mut wallet, + config, + unsafe_dont_encrypt, + ); }) } @@ -857,7 +736,7 @@ pub fn init_network( wallet.save().unwrap(); }); - // Generate the validators' ledger and intent gossip config + // Generate the validators' ledger config config.validator.iter_mut().enumerate().for_each( |(ix, (name, validator_config))| { let accounts_dir = chain_dir.join(NET_ACCOUNTS_DIR); @@ -920,26 +799,6 @@ pub fn init_network( // Validator node should turned off peer exchange reactor config.ledger.tendermint.p2p_pex = false; - // Configure the intent gossiper, matchmaker (if any) and RPC - config.intent_gossiper = gossiper_configs.remove(name).unwrap(); - config.intent_gossiper.seed_peers = seed_peers.clone(); - config.matchmaker = - matchmaker_configs.remove(name).unwrap_or_default(); - config.intent_gossiper.rpc = Some(config::RpcServer { - address: SocketAddr::new( - IpAddr::V4(if localhost { - Ipv4Addr::new(127, 0, 0, 1) - } else { - Ipv4Addr::new(0, 0, 0, 0) - }), - first_port + 4, - ), - }); - config - .intent_gossiper - .matchmakers_server_addr - .set_port(first_port + 5); - config.write(&validator_dir, &chain_id, true).unwrap(); }, ); @@ -960,7 +819,6 @@ pub fn init_network( } config.ledger.tendermint.p2p_addr_book_strict = !localhost; config.ledger.genesis_time = genesis.genesis_time.into(); - config.intent_gossiper.seed_peers = seed_peers; config .write(&global_args.base_dir, &chain_id, true) .unwrap(); diff --git a/apps/src/lib/config/genesis.rs b/apps/src/lib/config/genesis.rs index 5bd3dc803f..4462c62960 100644 --- a/apps/src/lib/config/genesis.rs +++ b/apps/src/lib/config/genesis.rs @@ -189,16 +189,6 @@ pub mod genesis_config { pub staking_reward_vp: Option, // IP:port of the validator. (used in generation only) pub net_address: Option, - /// Matchmaker account's alias, if any - pub matchmaker_account: Option, - /// Path to a matchmaker WASM program, if any - pub matchmaker_code: Option, - /// Path to a transaction WASM code used by the matchmaker, if any - pub matchmaker_tx: Option, - /// Is this validator running a seed intent gossip node? A seed node is - /// not part of the gossipsub where intents are being propagated and - /// hence cannot run matchmakers - pub intent_gossip_seed: Option, /// Tendermint node key is used to derive Tendermint node ID for node /// authentication pub tendermint_node_key: Option, @@ -803,13 +793,6 @@ pub fn genesis() -> Genesis { public_key: Some(wallet::defaults::christel_keypair().ref_to()), storage: HashMap::default(), }; - let matchmaker = EstablishedAccount { - address: wallet::defaults::matchmaker_address(), - vp_code_path: vp_user_path.into(), - vp_sha256: Default::default(), - public_key: Some(wallet::defaults::matchmaker_keypair().ref_to()), - storage: HashMap::default(), - }; let implicit_accounts = vec![ImplicitAccount { public_key: wallet::defaults::daewon_keypair().ref_to(), }]; @@ -836,10 +819,6 @@ pub fn genesis() -> Genesis { default_key_tokens, ), ((&validator.account_key).into(), default_key_tokens), - ( - matchmaker.public_key.as_ref().unwrap().into(), - default_key_tokens, - ), ]); let token_accounts = address::tokens() .into_iter() @@ -853,7 +832,7 @@ pub fn genesis() -> Genesis { Genesis { genesis_time: DateTimeUtc::now(), validators: vec![validator], - established_accounts: vec![albert, bertha, christel, matchmaker], + established_accounts: vec![albert, bertha, christel], implicit_accounts, token_accounts, parameters, diff --git a/apps/src/lib/config/mod.rs b/apps/src/lib/config/mod.rs index 987077d5cd..17b9529684 100644 --- a/apps/src/lib/config/mod.rs +++ b/apps/src/lib/config/mod.rs @@ -4,21 +4,15 @@ pub mod genesis; pub mod global; pub mod utils; -use std::collections::HashSet; -use std::fmt::Display; use std::fs::{create_dir_all, File}; use std::io::Write; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::{Path, PathBuf}; use std::str::FromStr; -use libp2p::multiaddr::{Multiaddr, Protocol}; -use libp2p::multihash::Multihash; -use libp2p::PeerId; use namada::types::chain::ChainId; use namada::types::time::Rfc3339String; -use regex::Regex; -use serde::{de, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; use tendermint::Timeout; use tendermint_config::net::Address as TendermintAddress; use thiserror::Error; @@ -43,9 +37,6 @@ pub const DB_DIR: &str = "db"; pub struct Config { pub wasm_dir: PathBuf, pub ledger: Ledger, - pub intent_gossiper: IntentGossiper, - // TODO allow to configure multiple matchmakers - pub matchmaker: Matchmaker, } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -126,32 +117,6 @@ pub struct Tendermint { pub instrumentation_namespace: String, } -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct IntentGossiper { - // Simple values - pub address: Multiaddr, - pub topics: HashSet, - /// The server address to which matchmakers can connect to receive intents - pub matchmakers_server_addr: SocketAddr, - - // Nested structures ⚠️ no simple values below any of these ⚠️ - pub subscription_filter: SubscriptionFilter, - pub seed_peers: HashSet, - pub rpc: Option, - pub discover_peer: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct RpcServer { - pub address: SocketAddr, -} - -#[derive(Default, Debug, Serialize, Deserialize, Clone)] -pub struct Matchmaker { - pub matchmaker_path: Option, - pub tx_code_path: Option, -} - impl Ledger { pub fn new( base_dir: impl AsRef, @@ -228,38 +193,6 @@ impl Shell { } } -// TODO maybe add also maxCount for a maximum number of subscription for a -// filter. - -// TODO toml failed to serialize without "untagged" because does not support -// enum with nested data, unless with the untagged flag. This might be a source -// of confusion in the future... Another approach would be to have multiple -// field for each filter possibility but it's less nice. -#[derive(Debug, Serialize, Deserialize, Clone)] -#[serde(untagged)] -pub enum SubscriptionFilter { - RegexFilter(#[serde(with = "serde_regex")] Regex), - WhitelistFilter(Vec), -} - -// TODO peer_id can be part of Multiaddr, mayby this splitting is not useful ? -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)] -pub struct PeerAddress { - pub address: Multiaddr, - pub peer_id: PeerId, -} - -// TODO add reserved_peers: explicit peers for gossipsub network, to not be -// added to kademlia -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct DiscoverPeer { - pub max_discovery_peers: u64, - /// Toggle Kademlia remote peer discovery, on by default - pub kademlia: bool, - /// Toggle local network mDNS peer discovery, off by default - pub mdns: bool, -} - #[derive(Error, Debug)] pub enum Error { #[error("Error while reading config: {0}")] @@ -302,8 +235,6 @@ impl Config { Self { wasm_dir: DEFAULT_WASM_DIR.into(), ledger: Ledger::new(base_dir, chain_id, mode), - intent_gossiper: IntentGossiper::default(), - matchmaker: Matchmaker::default(), } } @@ -412,97 +343,6 @@ impl Config { } } -impl Default for IntentGossiper { - fn default() -> Self { - Self { - address: Multiaddr::from_str("/ip4/0.0.0.0/tcp/26659").unwrap(), - topics: vec!["asset_v0"].into_iter().map(String::from).collect(), - matchmakers_server_addr: SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 26661, - ), - subscription_filter: SubscriptionFilter::RegexFilter( - Regex::new("asset_v\\d{1,2}").unwrap(), - ), - seed_peers: HashSet::default(), - rpc: None, - discover_peer: Some(DiscoverPeer::default()), - } - } -} - -impl IntentGossiper { - pub fn update(&mut self, addr: Option, rpc: Option) { - if let Some(addr) = addr { - self.address = addr; - } - if let Some(address) = rpc { - self.rpc = Some(RpcServer { address }); - } - } -} - -impl Default for RpcServer { - fn default() -> Self { - Self { - address: SocketAddr::new( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - 26660, - ), - } - } -} - -impl Serialize for PeerAddress { - fn serialize( - &self, - serializer: S, - ) -> std::result::Result - where - S: serde::Serializer, - { - let mut address = self.address.clone(); - address.push(Protocol::P2p(Multihash::from(self.peer_id))); - address.serialize(serializer) - } -} - -impl de::Error for SerdeError { - fn custom(msg: T) -> Self { - SerdeError::Message(msg.to_string()) - } -} - -impl<'de> Deserialize<'de> for PeerAddress { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - use serde::de::Error; - - let mut address = Multiaddr::deserialize(deserializer) - .map_err(|err| SerdeError::BadBootstrapPeerFormat(err.to_string())) - .map_err(D::Error::custom)?; - if let Some(Protocol::P2p(mh)) = address.pop() { - let peer_id = PeerId::from_multihash(mh).unwrap(); - Ok(Self { address, peer_id }) - } else { - Err(SerdeError::BadBootstrapPeerFormat(address.to_string())) - .map_err(D::Error::custom) - } - } -} - -impl Default for DiscoverPeer { - fn default() -> Self { - Self { - max_discovery_peers: 16, - kademlia: true, - mdns: false, - } - } -} - pub const VALUE_AFTER_TABLE_ERROR_MSG: &str = r#" Error while serializing to toml. It means that some nested structure is followed by simple fields. diff --git a/apps/src/lib/mod.rs b/apps/src/lib/mod.rs index eca89896eb..b0455934bd 100644 --- a/apps/src/lib/mod.rs +++ b/apps/src/lib/mod.rs @@ -10,7 +10,6 @@ pub mod client; pub mod config; pub mod logging; pub mod node; -pub mod proto; pub mod wallet; pub mod wasm_loader; diff --git a/apps/src/lib/node/gossip/intent_gossiper.rs b/apps/src/lib/node/gossip/intent_gossiper.rs deleted file mode 100644 index 2c7816a5bf..0000000000 --- a/apps/src/lib/node/gossip/intent_gossiper.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::net::ToSocketAddrs; -use std::sync::{Arc, RwLock}; - -use namada::proto::{Intent, IntentId}; - -use super::mempool::IntentMempool; -use super::rpc::matchmakers::{ - MsgFromClient, MsgFromServer, ServerDialer, ServerListener, -}; - -/// A server for connected matchmakers that can receive intents from the intent -/// gossiper node and send back the results from their filter, if any, or from -/// trying to match them. -#[derive(Debug, Default)] -pub struct MatchmakersServer { - /// A node listener and its abort receiver. These are consumed once the - /// listener is started with [`MatchmakersServer::listen`]. - listener: Option, - /// Known intents mempool, shared with [`IntentGossiper`]. - mempool: Arc>, -} - -/// Intent gossiper handle can be cloned and is thread safe. -#[derive(Clone, Debug)] -pub struct IntentGossiper { - /// Known intents mempool, shared with [`MatchmakersServer`]. - mempool: Arc>, - /// A dialer can send messages to the connected matchmaker - dialer: ServerDialer, -} - -impl MatchmakersServer { - /// Create a new gossip intent app with a matchmaker, if enabled. - pub fn new_pair( - matchmakers_server_addr: impl ToSocketAddrs, - ) -> (Self, IntentGossiper) { - // Prepare a server for matchmakers connections - let (listener, dialer) = - ServerListener::new_pair(matchmakers_server_addr); - - let mempool = Arc::new(RwLock::new(IntentMempool::default())); - let intent_gossiper = IntentGossiper { - mempool: mempool.clone(), - dialer, - }; - ( - Self { - listener: Some(listener), - mempool, - }, - intent_gossiper, - ) - } - - pub async fn listen(mut self) { - self.listener - .take() - .unwrap() - .listen(|msg| match msg { - MsgFromClient::InvalidIntent { id } => { - let id = IntentId(id); - // Remove matched intents from mempool - tracing::info!("Removing matched intent ID {}", id); - let mut w_mempool = self.mempool.write().unwrap(); - w_mempool.remove(&id); - } - MsgFromClient::IntentConstraintsTooComplex { id } => { - let id = IntentId(id); - tracing::info!( - "Intent ID {} has constraints that are too complex \ - for a connected matchmaker", - id - ); - } - MsgFromClient::IgnoredIntent { id } => { - let id = IntentId(id); - tracing::info!( - "Intent ID {} ignored by a connected matchmaker", - id - ); - } - MsgFromClient::Matched { intent_ids } => { - // Remove matched intents from mempool - let mut w_mempool = self.mempool.write().unwrap(); - for id in intent_ids { - let id = IntentId(id); - tracing::info!("Removing matched intent ID {}", id); - w_mempool.remove(&id); - } - } - MsgFromClient::Unmatched { id } => { - let id = IntentId(id); - tracing::info!("No match found for intent ID {}", id); - } - }) - .await - } -} - -impl IntentGossiper { - // Apply the logic to a new intent. It only tries to apply the matchmaker if - // this one exists. If no matchmaker then returns true. - pub async fn add_intent(&mut self, intent: Intent) { - let id = intent.id(); - - let r_mempool = self.mempool.read().unwrap(); - let is_known = r_mempool.contains(&id); - drop(r_mempool); - if !is_known { - let mut w_mempool = self.mempool.write().unwrap(); - w_mempool.insert(intent.clone()); - } - - tracing::info!( - "Sending intent ID {} to connected matchmakers, if any", - id - ); - self.dialer.send(MsgFromServer::AddIntent { - id: id.0, - data: intent.data, - }) - } -} diff --git a/apps/src/lib/node/gossip/mempool.rs b/apps/src/lib/node/gossip/mempool.rs deleted file mode 100644 index fce66447c4..0000000000 --- a/apps/src/lib/node/gossip/mempool.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::collections::HashMap; - -use namada::proto::{Intent, IntentId}; - -/// In-memory intent mempool -#[derive(Clone, Debug, Default)] -pub struct IntentMempool(HashMap); - -impl IntentMempool { - /// Insert a new intent. If the mempool didn't have this intent present, - /// returns `true`. - pub fn insert(&mut self, intent: Intent) -> bool { - self.0.insert(intent.id(), intent).is_none() - } - - /// Remove an intent from mempool. If the mempool didn't have this intent - /// present, returns `true`. in the mempool. - pub fn remove(&mut self, intent_id: &IntentId) -> bool { - self.0.remove(intent_id).is_some() - } - - /// Returns `true` if the map contains intent with specified ID. - pub fn contains(&self, intent_id: &IntentId) -> bool { - self.0.contains_key(intent_id) - } -} diff --git a/apps/src/lib/node/gossip/mod.rs b/apps/src/lib/node/gossip/mod.rs deleted file mode 100644 index 03b14f14ef..0000000000 --- a/apps/src/lib/node/gossip/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -pub mod intent_gossiper; -mod mempool; -pub mod p2p; -pub mod rpc; - -use std::path::Path; - -use namada::proto::Intent; -use thiserror::Error; -use tokio::sync::mpsc; - -use self::intent_gossiper::IntentGossiper; -use self::p2p::P2P; -use crate::config; -use crate::proto::services::{rpc_message, RpcResponse}; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Error initializing p2p: {0}")] - P2pInit(p2p::Error), -} - -type Result = std::result::Result; - -/// RPC async receiver end of the channel -pub type RpcReceiver = tokio::sync::mpsc::Receiver<( - rpc_message::Message, - tokio::sync::oneshot::Sender, -)>; - -#[tokio::main] -pub async fn run( - config: config::IntentGossiper, - base_dir: impl AsRef, -) -> Result<()> { - // Prepare matchmakers server and dialer - let (matchmakers_server, intent_gossiper) = - intent_gossiper::MatchmakersServer::new_pair( - &config.matchmakers_server_addr, - ); - - // Async channel for intents received from peer - let (peer_intent_send, peer_intent_recv) = tokio::sync::mpsc::channel(100); - - // Create the P2P gossip network, which can send messages directly to the - // matchmaker, if any - let p2p = p2p::P2P::new(&config, base_dir, peer_intent_send) - .await - .map_err(Error::P2pInit)?; - - // Run the matchmakers server - let mms_join_handle = tokio::task::spawn(async move { - matchmakers_server.listen().await; - }); - - // Start the RPC server, if enabled in the config - let rpc_receiver = config.rpc.map(|rpc_config| { - let (rpc_sender, rpc_receiver) = mpsc::channel(100); - tokio::spawn(async move { - rpc::client::start_rpc_server(&rpc_config, rpc_sender).await - }); - rpc_receiver - }); - - dispatcher( - p2p, - rpc_receiver, - peer_intent_recv, - intent_gossiper, - mms_join_handle, - ) - .await -} - -// loop over all possible event. The event can be from the rpc, a matchmaker -// program or the gossip network. The gossip network event are a special case -// that does not need to be handle as it's taking care of by the libp2p internal -// logic. -pub async fn dispatcher( - mut p2p: P2P, - mut rpc_receiver: Option, - mut peer_intent_recv: tokio::sync::mpsc::Receiver, - mut intent_gossiper: IntentGossiper, - _mms_join_handle: tokio::task::JoinHandle<()>, -) -> Result<()> { - loop { - tokio::select! { - Some((event, inject_response)) = recv_rpc_option(rpc_receiver.as_mut()), if rpc_receiver.is_some() => - { - let gossip_sub = &mut p2p.0.behaviour_mut().intent_gossip_behaviour; - let (response, maybe_intent) = rpc::client::handle_rpc_event(event, gossip_sub).await; - inject_response.send(response).expect("failed to send response to rpc server"); - - if let Some(intent) = maybe_intent { - intent_gossiper.add_intent(intent).await; - } - }, - Some(intent) = peer_intent_recv.recv() => { - intent_gossiper.add_intent(intent).await; - } - swarm_event = p2p.0.next() => { - // Never occurs, but call for the event must exists. - tracing::info!("event, {:?}", swarm_event); - }, - }; - } -} - -async fn recv_rpc_option( - x: Option<&mut RpcReceiver>, -) -> Option<( - rpc_message::Message, - tokio::sync::oneshot::Sender, -)> { - x?.recv().await -} diff --git a/apps/src/lib/node/gossip/p2p/behaviour/discovery.rs b/apps/src/lib/node/gossip/p2p/behaviour/discovery.rs deleted file mode 100644 index 57d99c7bc0..0000000000 --- a/apps/src/lib/node/gossip/p2p/behaviour/discovery.rs +++ /dev/null @@ -1,517 +0,0 @@ -// This file is almost identical to this -// https://github.com/webb-tools/anonima/blob/main/network/src/discovery.rs -// appropriate affiliation needs to be added here original header : -// -// Copyright 2020 ChainSafe Systems SPDX-License-Identifier: Apache-2.0, MIT - -use std::collections::{HashSet, VecDeque}; -use std::fmt::Display; -use std::task::{Context, Poll}; -use std::time::Duration; -use std::{cmp, io}; - -use async_std::stream::{self, Interval}; -use futures::StreamExt; -use libp2p::core::connection::{ConnectionId, ListenerId}; -use libp2p::core::ConnectedPoint; -use libp2p::kad::handler::KademliaHandlerProto; -use libp2p::kad::store::MemoryStore; -use libp2p::kad::{Kademlia, KademliaConfig, KademliaEvent, QueryId}; -use libp2p::mdns::{Mdns, MdnsConfig, MdnsEvent}; -use libp2p::swarm::toggle::{Toggle, ToggleIntoProtoHandler}; -use libp2p::swarm::{ - IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, ProtocolsHandler, -}; -use libp2p::{Multiaddr, PeerId}; -use thiserror::Error; - -use crate::config::PeerAddress; - -#[derive(Error, Debug)] -pub enum Error { - // TODO, it seems that NoKnownPeer is not exposed, could not find it - #[error("Failed to bootstrap kademlia {0}")] - FailedBootstrap(String), - #[error("Failed to initialize mdns {0}")] - FailedMdns(std::io::Error), -} - -pub type Result = std::result::Result; - -/// Event generated by the `DiscoveryBehaviour`. -#[derive(Debug)] -pub enum DiscoveryEvent { - /// Event that notifies that we connected to the node with the given peer - /// id. - Connected(PeerId), - - /// Event that notifies that we disconnected with the node with the given - /// peer id. - Disconnected(PeerId), - - /// This case is only use to clean the code in the poll fct - KademliaEvent(KademliaEvent), -} - -/// `DiscoveryBehaviour` configuration. -#[derive(Clone)] -pub struct DiscoveryConfig { - /// user defined peer that are given to kad in order to connect to the - /// network - user_defined: Vec, - /// maximum number of peer to connect to - discovery_max: u64, - /// enable kademlia to find new peer - enable_kademlia: bool, - /// look for new peer over local network. - // TODO: it seems that kademlia must activated where it should not be - // mandatory - enable_mdns: bool, - // TODO: should this be optional? if not explain why - /// use the option from kademlia. Prevent some type of attacks against - /// kademlia. - kademlia_disjoint_query_paths: bool, -} - -impl Default for DiscoveryConfig { - fn default() -> Self { - Self { - user_defined: Vec::new(), - discovery_max: u64::MAX, - enable_kademlia: true, - enable_mdns: true, - kademlia_disjoint_query_paths: true, - } - } -} - -#[derive(Default)] -pub struct DiscoveryConfigBuilder { - config: DiscoveryConfig, -} - -impl DiscoveryConfigBuilder { - /// Set the number of active connections at which we pause discovery. - pub fn discovery_limit(&mut self, limit: u64) -> &mut Self { - self.config.discovery_max = limit; - self - } - - /// Set custom nodes which never expire, e.g. bootstrap or reserved nodes. - pub fn with_user_defined(&mut self, user_defined: I) -> &mut Self - where - I: IntoIterator, - { - self.config.user_defined.extend(user_defined); - self - } - - /// Configures if disjoint query path is enabled - pub fn use_kademlia_disjoint_query_paths( - &mut self, - value: bool, - ) -> &mut Self { - self.config.kademlia_disjoint_query_paths = value; - self - } - - /// Configures if mdns is enabled. - pub fn with_mdns(&mut self, value: bool) -> &mut Self { - self.config.enable_mdns = value; - self - } - - /// Configures if Kademlia is enabled. - pub fn with_kademlia(&mut self, value: bool) -> &mut Self { - self.config.enable_kademlia = value; - self - } - - /// Build the discovery config - pub fn build(&self) -> Result { - Ok(self.config.clone()) - } -} - -/// Implementation of `NetworkBehaviour` that discovers the nodes on the -/// network. -pub struct DiscoveryBehaviour { - /// User-defined list of nodes and their addresses. Typically includes - /// bootstrap nodes and reserved nodes. - user_defined: Vec, - /// Kademlia discovery. - pub kademlia: Toggle>, - /// Discovers nodes on the local network. - mdns: Toggle, - /// Stream that fires when we need to perform the next random Kademlia - /// query. - next_kad_random_query: Option, - /// After `next_kad_random_query` triggers, the next one triggers after - /// this duration. - duration_to_next_kad: Duration, - /// Events to return in priority when polled. - pending_events: VecDeque, - /// Number of nodes we're currently connected to. - num_connections: u64, - /// Keeps hash set of peers connected. - peers: HashSet, - /// Number of active connections to pause discovery on. - discovery_max: u64, -} - -impl Display for DiscoveryBehaviour { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str( - format!( - "{{ - user_defined {:?}, - kademlia: {:?}, - mdns: {:?}, - next_kad_random_query: {:?}, - duration_to_next_kad {:?}, - num_connection: {:?}, - peers: {:?}, - discovery_max: {:?} -}}", - self.user_defined, - self.kademlia.is_enabled(), - self.mdns.is_enabled(), - self.next_kad_random_query, - self.duration_to_next_kad, - self.num_connections, - self.peers, - self.discovery_max - ) - .as_str(), - ) - } -} - -impl DiscoveryBehaviour { - /// Create a `DiscoveryBehaviour` from a config. - pub async fn new( - local_peer_id: PeerId, - config: DiscoveryConfig, - ) -> Result { - let DiscoveryConfig { - user_defined, - discovery_max, - enable_kademlia, - enable_mdns, - kademlia_disjoint_query_paths, - } = config; - - let mut peers = HashSet::with_capacity(user_defined.len()); - - // Kademlia config - let kademlia_opt = if enable_kademlia { - let store = MemoryStore::new(local_peer_id.to_owned()); - let mut kad_config = KademliaConfig::default(); - kad_config.disjoint_query_paths(kademlia_disjoint_query_paths); - // TODO: choose a better protocol name - kad_config.set_protocol_name( - "/anoma/kad/anoma/kad/1.0.0".as_bytes().to_vec(), - ); - - let mut kademlia = - Kademlia::with_config(local_peer_id, store, kad_config); - - user_defined - .iter() - .for_each(|PeerAddress { address, peer_id }| { - kademlia.add_address(peer_id, address.clone()); - peers.insert(*peer_id); - }); - - // TODO: For production should node fail when kad failed to - // bootstrap? - if let Err(err) = kademlia.bootstrap() { - tracing::error!("failed to bootstrap kad : {:?}", err); - }; - Some(kademlia) - } else { - None - }; - - let mdns_opt = if enable_mdns { - Some( - Mdns::new(MdnsConfig::default()) - .await - .map_err(Error::FailedMdns)?, - ) - } else { - None - }; - - Ok(DiscoveryBehaviour { - user_defined, - kademlia: kademlia_opt.into(), - mdns: mdns_opt.into(), - next_kad_random_query: None, - duration_to_next_kad: Duration::from_secs(1), - pending_events: VecDeque::new(), - num_connections: 0, - peers, - discovery_max, - }) - } -} - -// Most function here are a wrapper around kad behaviour, -impl NetworkBehaviour for DiscoveryBehaviour { - type OutEvent = DiscoveryEvent; - type ProtocolsHandler = - ToggleIntoProtoHandler>; - - fn new_handler(&mut self) -> Self::ProtocolsHandler { - self.kademlia.new_handler() - } - - /// Look for the address of a peer first in the user defined list then in - /// kademlia then lastly in the local network. Sum all possible address and - /// returns. - fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { - let mut list = self - .user_defined - .iter() - .filter_map(|peer_address| { - if &peer_address.peer_id == peer_id { - Some(peer_address.address.clone()) - } else { - None - } - }) - .collect::>(); - - list.extend(self.kademlia.addresses_of_peer(peer_id)); - - list.extend(self.mdns.addresses_of_peer(peer_id)); - - list - } - - fn inject_connected(&mut self, peer_id: &PeerId) { - tracing::debug!("Injecting connected peer {}", peer_id); - self.peers.insert(*peer_id); - self.pending_events - .push_back(DiscoveryEvent::Connected(*peer_id)); - - self.kademlia.inject_connected(peer_id) - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - tracing::debug!("Injecting disconnected peer {}", peer_id); - self.peers.remove(peer_id); - self.pending_events - .push_back(DiscoveryEvent::Disconnected(*peer_id)); - - self.kademlia.inject_disconnected(peer_id) - } - - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - ) { - tracing::debug!( - "Injecting connection established for peer ID {} with endpoint \ - {:#?}", - peer_id, - endpoint - ); - self.num_connections += 1; - - self.kademlia - .inject_connection_established(peer_id, conn, endpoint) - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - conn: &ConnectionId, - endpoint: &ConnectedPoint, - ) { - tracing::debug!("Injecting connection closed for peer ID {}", peer_id); - self.num_connections -= 1; - - self.kademlia - .inject_connection_closed(peer_id, conn, endpoint) - } - - fn inject_address_change( - &mut self, - peer: &PeerId, - id: &ConnectionId, - old: &ConnectedPoint, - new: &ConnectedPoint, - ) { - self.kademlia.inject_address_change(peer, id, old, new) - } - - fn inject_event( - &mut self, - peer_id: PeerId, - connection: ConnectionId, - event: <::Handler as ProtocolsHandler>::OutEvent, - ) { - self.kademlia.inject_event(peer_id, connection, event) - } - - fn inject_addr_reach_failure( - &mut self, - peer_id: Option<&PeerId>, - addr: &Multiaddr, - error: &dyn std::error::Error, - ) { - self.kademlia - .inject_addr_reach_failure(peer_id, addr, error) - } - - fn inject_dial_failure(&mut self, peer_id: &PeerId) { - self.kademlia.inject_dial_failure(peer_id) - } - - fn inject_new_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.kademlia.inject_new_listen_addr(id, addr) - } - - fn inject_expired_listen_addr(&mut self, id: ListenerId, addr: &Multiaddr) { - self.kademlia.inject_expired_listen_addr(id, addr); - } - - fn inject_listener_error( - &mut self, - id: ListenerId, - err: &(dyn std::error::Error + 'static), - ) { - self.kademlia.inject_listener_error(id, err) - } - - fn inject_listener_closed( - &mut self, - id: ListenerId, - reason: std::result::Result<(), &io::Error>, - ) { - self.kademlia.inject_listener_closed(id, reason) - } - - fn inject_new_external_addr(&mut self, addr: &Multiaddr) { - self.kademlia.inject_new_external_addr(addr) - } - - // This poll function is called by libp2p to fetch/generate new event. First - // in the local queue then in kademlia and lastly in Mdns. - #[allow(clippy::type_complexity)] - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll< - NetworkBehaviourAction< - <::Handler as ProtocolsHandler>::InEvent, - Self::OutEvent, - >, - >{ - // Immediately process the content of `discovered`. - if let Some(ev) = self.pending_events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)); - } - - // Poll Kademlia return every other event except kad event - while let Poll::Ready(ev) = self.kademlia.poll(cx, params) { - tracing::debug!("Kademlia event {:#?}", ev); - if let NetworkBehaviourAction::GenerateEvent(_kad_ev) = ev { - } else { - return Poll::Ready(ev.map_out(DiscoveryEvent::KademliaEvent)); - } - } - - // Poll the stream that fires when we need to start a random Kademlia - // query. When the stream provides a new value then it tries to look for - // a node and connect to it. - // TODO: explain a bit more the logic happening here - if let Some(next_kad_random_query) = self.next_kad_random_query.as_mut() - { - tracing::debug!( - "Kademlia random query {:#?}", - next_kad_random_query - ); - while next_kad_random_query.poll_next_unpin(cx).is_ready() { - if self.num_connections < self.discovery_max { - let random_peer_id = PeerId::random(); - tracing::debug!( - "Libp2p <= Starting random Kademlia request for {:?}", - random_peer_id - ); - if let Some(k) = self.kademlia.as_mut() { - k.get_closest_peers(random_peer_id); - } - } - - *next_kad_random_query = - stream::interval(self.duration_to_next_kad); - self.duration_to_next_kad = cmp::min( - self.duration_to_next_kad * 2, - Duration::from_secs(60), - ); - } - } - - // Poll mdns. If mdns generated new Discovered event then connect to it - // TODO: refactor this function, it can't be done as the kad done - while let Poll::Ready(ev) = self.mdns.poll(cx, params) { - match ev { - NetworkBehaviourAction::GenerateEvent(event) => match event { - MdnsEvent::Discovered(list) => { - if self.num_connections < self.discovery_max { - // Add any discovered peers to Kademlia - for (peer_id, multiaddr) in list { - if let Some(kad) = self.kademlia.as_mut() { - kad.add_address(&peer_id, multiaddr); - } - } - } else { - tracing::info!( - "max reached {:?}, {:?}", - self.num_connections, - self.discovery_max - ); - // Already over discovery max, don't add discovered - // peers. We could potentially buffer these - // addresses to be added later, but mdns is not an - // important use case and may be removed in future. - } - } - MdnsEvent::Expired(_) => {} - }, - NetworkBehaviourAction::DialAddress { address } => { - return Poll::Ready(NetworkBehaviourAction::DialAddress { - address, - }); - } - NetworkBehaviourAction::DialPeer { peer_id, condition } => { - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - }); - } - // Nothing to notify handler - NetworkBehaviourAction::NotifyHandler { .. } => {} - NetworkBehaviourAction::ReportObservedAddr { - address, - score, - } => { - return Poll::Ready( - NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }, - ); - } - } - } - Poll::Pending - } -} diff --git a/apps/src/lib/node/gossip/p2p/behaviour/mod.rs b/apps/src/lib/node/gossip/p2p/behaviour/mod.rs deleted file mode 100644 index f70a300b9e..0000000000 --- a/apps/src/lib/node/gossip/p2p/behaviour/mod.rs +++ /dev/null @@ -1,412 +0,0 @@ -mod discovery; -use std::collections::hash_map::DefaultHasher; -use std::convert::TryFrom; -use std::hash::{Hash, Hasher}; -use std::time::Duration; - -use libp2p::gossipsub::subscription_filter::regex::RegexSubscriptionFilter; -use libp2p::gossipsub::subscription_filter::{ - TopicSubscriptionFilter, WhitelistSubscriptionFilter, -}; -use libp2p::gossipsub::{ - self, GossipsubEvent, GossipsubMessage, IdentTopic, IdentityTransform, - MessageAcceptance, MessageAuthenticity, MessageId, TopicHash, - ValidationMode, -}; -use libp2p::identify::{Identify, IdentifyConfig, IdentifyEvent}; -use libp2p::identity::Keypair; -use libp2p::ping::{Ping, PingEvent, PingFailure, PingSuccess}; -use libp2p::swarm::NetworkBehaviourEventProcess; -use libp2p::{NetworkBehaviour, PeerId}; -use namada::proto::{self, Intent, IntentGossipMessage}; -use thiserror::Error; -use tokio::sync::mpsc::Sender; - -use self::discovery::DiscoveryEvent; -use crate::config; -use crate::node::gossip::p2p::behaviour::discovery::{ - DiscoveryBehaviour, DiscoveryConfigBuilder, -}; - -/// Behaviour is composed of a `DiscoveryBehaviour` and an GossipsubBehaviour`. -/// It automatically connect to newly discovered peer, except specified -/// otherwise, and propagates intents to other peers. -#[derive(NetworkBehaviour)] -pub struct Behaviour { - pub intent_gossip_behaviour: Gossipsub, - pub discover_behaviour: DiscoveryBehaviour, - /// The identify protocol allows establishing P2P connections via Kademlia - identify: Identify, - /// Responds to inbound pings and periodically sends outbound pings on - /// every established connection - ping: Ping, - #[behaviour(ignore)] - pub peer_intent_send: Sender, -} - -#[derive(Error, Debug)] -pub enum Error { - #[error("Failed to subscribe")] - FailedSubscription(libp2p::gossipsub::error::SubscriptionError), - #[error("Failed initializing the topic filter: {0}")] - Filter(String), - #[error("Failed initializing the gossip behaviour: {0}")] - GossipConfig(String), - #[error("Failed on the the discovery behaviour config: {0}")] - DiscoveryConfig(String), - #[error("Failed initializing the discovery behaviour: {0}")] - Discovery(discovery::Error), - #[error("Failed initializing mdns: {0}")] - Mdns(std::io::Error), -} - -pub type Gossipsub = libp2p::gossipsub::Gossipsub< - IdentityTransform, - IntentGossipSubscriptionFilter, ->; - -// TODO merge type of config and this one ? Maybe not a good idea -// TODO extends with MaxSubscribionFilter -/// IntentGossipSubscriptionfilter is a wrapper of TopicSubscriptionFilter to -/// allows combination of any sort of filter. -pub enum IntentGossipSubscriptionFilter { - RegexFilter(RegexSubscriptionFilter), - WhitelistFilter(WhitelistSubscriptionFilter), -} - -/// IntentGossipEvent describe events received/sent in the gossipsub network. -/// All information are extracted from the GossipsubEvent type. This type is -/// used as a wrapper of GossipsubEvent in order to have only information of -/// interest and possibly enforce some invariant. -#[derive(Debug)] -pub struct IntentGossipEvent { - /// The PeerId that initially created this message - pub propagation_source: PeerId, - /// The MessageId of this message. This MessageId allows to discriminate - /// already received message - pub message_id: MessageId, - // TODO maybe remove the Option of this field to make mandatory to have an - // id. - /// The peer that transmitted this message to us. It can be anonymous - pub source: Option, - /// The content of the data - pub data: Vec, - /// The topic from which we received the message - pub topic: TopicHash, -} - -impl From for IntentGossipEvent { - /// Transforme a GossipsubEvent into an IntentGossipEvent. This function - /// fails if the gossipsubEvent does not contain a GossipsubMessage. - fn from(event: GossipsubEvent) -> Self { - if let GossipsubEvent::Message { - propagation_source, - message_id, - message: - GossipsubMessage { - source, - data, - topic, - sequence_number: _, - }, - } = event - { - Self { - propagation_source, - message_id, - source, - data, - topic, - } - } else { - panic!("Expected a GossipsubEvent::Message got {:?}", event) - } - } -} - -impl TopicSubscriptionFilter for IntentGossipSubscriptionFilter { - /// tcheck that the proposed topic can be subscribed - fn can_subscribe(&mut self, topic_hash: &TopicHash) -> bool { - match self { - IntentGossipSubscriptionFilter::RegexFilter(filter) => { - filter.can_subscribe(topic_hash) - } - IntentGossipSubscriptionFilter::WhitelistFilter(filter) => { - filter.can_subscribe(topic_hash) - } - } - } -} - -/// [message_id] use the hash of the message data as an id -pub fn message_id(message: &GossipsubMessage) -> MessageId { - let mut hasher = DefaultHasher::new(); - message.data.hash(&mut hasher); - MessageId::from(hasher.finish().to_string()) -} - -impl Behaviour { - /// Create a new behaviour based on the config given - pub async fn new( - key: Keypair, - config: &config::IntentGossiper, - peer_intent_send: Sender, - ) -> Self { - let public_key = key.public(); - let peer_id = PeerId::from_public_key(public_key.clone()); - - // TODO remove hardcoded value and add them to the config Except - // validation_mode, protocol_id_prefix, message_id_fn and - // validate_messages - // Set a custom gossipsub for our use case - let gossipsub_config = gossipsub::GossipsubConfigBuilder::default() - .protocol_id_prefix("intent_gossip") - .heartbeat_interval(Duration::from_secs(1)) - .validation_mode(ValidationMode::Strict) - .message_id_fn(message_id) - .max_transmit_size(16 * 1024 * 1024) - .validate_messages() - .mesh_outbound_min(1) - // TODO bootstrap peers should not be part of the mesh, so all the - // `.mesh` args should be set to 0 https://github.com/libp2p/specs/blob/70d7fda47dda88d828b4db72775c1602de57e91b/pubsub/gossipsub/gossipsub-v1.1.md#recommendations-for-network-operators - .mesh_n_low(2) - .mesh_n(3) - .mesh_n_high(6) - .build() - .unwrap(); - - let filter = match &config.subscription_filter { - crate::config::SubscriptionFilter::RegexFilter(regex) => { - IntentGossipSubscriptionFilter::RegexFilter( - RegexSubscriptionFilter(regex.clone()), - ) - } - crate::config::SubscriptionFilter::WhitelistFilter(topics) => { - IntentGossipSubscriptionFilter::WhitelistFilter( - WhitelistSubscriptionFilter( - topics - .iter() - .map(IdentTopic::new) - .map(TopicHash::from) - .collect(), - ), - ) - } - }; - - let mut intent_gossip_behaviour: Gossipsub = - Gossipsub::new_with_subscription_filter( - MessageAuthenticity::Signed(key), - gossipsub_config, - filter, - ) - .unwrap(); - - // subscribe to all topic listed in the config. - config - .topics - .iter() - .try_for_each(|topic| { - intent_gossip_behaviour - .subscribe(&IdentTopic::new(topic)) - .map_err(Error::FailedSubscription) - // it returns bool signifying if it was already subscribed. - // discard because it can't be false as the config.topics is - // a hash set - .map(|_| ()) - }) - .expect("failed to subscribe to topic"); - - let discover_behaviour = { - // TODO: check that bootstrap_peers are in multiaddr (otherwise it - // fails silently) - let discover_config = - if let Some(discover_config) = &config.discover_peer { - DiscoveryConfigBuilder::default() - .with_user_defined(config.seed_peers.clone()) - .discovery_limit(discover_config.max_discovery_peers) - .with_kademlia(discover_config.kademlia) - .with_mdns(discover_config.mdns) - .use_kademlia_disjoint_query_paths(true) - .build() - .unwrap() - } else { - DiscoveryConfigBuilder::default().build().unwrap() - }; - DiscoveryBehaviour::new(peer_id, discover_config) - .await - .unwrap() - }; - Self { - intent_gossip_behaviour, - discover_behaviour, - identify: Identify::new(IdentifyConfig::new( - "anoma/id/anoma/id/1.0.0".into(), - public_key, - )), - ping: Ping::default(), - peer_intent_send, - } - } - - /// tries to apply a new intent. Fails if the logic fails or if the intent - /// is rejected. If the matchmaker fails the message is only ignore - fn handle_intent(&mut self, intent: Intent) -> MessageAcceptance { - if let Err(err) = self.peer_intent_send.try_send(intent) { - tracing::error!("Error sending intent to the matchmaker: {}", err); - // The buffer is full or the channel is closed - return MessageAcceptance::Ignore; - } - MessageAcceptance::Accept - } - - /// Tries to decoded the arbitrary data in an intent then call - /// [Self::handle_intent]. fails if the data does not contains an intent - fn handle_raw_intent( - &mut self, - data: impl AsRef<[u8]>, - ) -> MessageAcceptance { - match IntentGossipMessage::try_from(data.as_ref()) { - Ok(message) => self.handle_intent(message.intent), - Err(proto::Error::NoIntentError) => { - tracing::info!("Empty message, rejecting it"); - MessageAcceptance::Reject - } - Err(proto::Error::IntentDecodingError(err)) => { - tracing::info!("error while decoding the intent: {:?}", err); - MessageAcceptance::Reject - } - _ => unreachable!(), - } - } -} - -impl NetworkBehaviourEventProcess for Behaviour { - /// When a new event is generated by the intent gossip behaviour - fn inject_event(&mut self, event: GossipsubEvent) { - tracing::info!("received a new message : {:?}", event); - match event { - GossipsubEvent::Message { - message, - propagation_source, - message_id, - } => { - // validity is the type of response return to the network - // (valid|reject|ignore) - let validity = self.handle_raw_intent(message.data); - self.intent_gossip_behaviour - .report_message_validation_result( - &message_id, - &propagation_source, - validity, - ) - .expect("Failed to validate the message"); - } - // When a peer subscribe to a new topic, this node also tries to - // connect to it using the filter defined in the config - GossipsubEvent::Subscribed { peer_id: _, topic } => { - // try to subscribe to the new topic - self.intent_gossip_behaviour - .subscribe(&IdentTopic::new(topic.into_string())) - .map_err(Error::FailedSubscription) - .unwrap_or_else(|e| { - tracing::error!("failed to subscribe: {:?}", e); - false - }); - } - // Nothing to do when you are informed that a peer unsubscribed to a - // topic. - // TODO: It could be interesting to unsubscribe to a topic when the - // node is not connected to anyone else. - GossipsubEvent::Unsubscribed { - peer_id: _, - topic: _, - } => {} - } - } -} - -impl NetworkBehaviourEventProcess for Behaviour { - // The logic is part of the DiscoveryBehaviour, nothing to do here. - fn inject_event(&mut self, event: DiscoveryEvent) { - match event { - DiscoveryEvent::Connected(peer) => { - tracing::info!("Connect to a new peer: {:?}", peer) - } - DiscoveryEvent::Disconnected(peer) => { - tracing::info!("Peer disconnected: {:?}", peer) - } - _ => {} - } - } -} - -impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, event: IdentifyEvent) { - match event { - IdentifyEvent::Received { peer_id, info } => { - tracing::info!("Identified Peer {}", peer_id); - tracing::debug!("protocol_version {}", info.protocol_version); - tracing::debug!("agent_version {}", info.agent_version); - tracing::debug!("listening_addresses {:?}", info.listen_addrs); - tracing::debug!("observed_address {}", info.observed_addr); - tracing::debug!("protocols {:?}", info.protocols); - if let Some(kad) = self.discover_behaviour.kademlia.as_mut() { - // Only the first address is the public IP, the others - // seem to be private - if let Some(addr) = info.listen_addrs.first() { - tracing::info!( - "Routing updated peer ID: {}, address: {}", - peer_id, - addr - ); - let _update = kad.add_address(&peer_id, addr.clone()); - } - } - } - IdentifyEvent::Sent { .. } => (), - IdentifyEvent::Pushed { .. } => (), - IdentifyEvent::Error { peer_id, error } => { - tracing::error!( - "Error while attempting to identify the remote peer {}: \ - {},", - peer_id, - error - ); - } - } - } -} - -impl NetworkBehaviourEventProcess for Behaviour { - fn inject_event(&mut self, event: PingEvent) { - match event.result { - Ok(PingSuccess::Ping { rtt }) => { - tracing::debug!( - "PingSuccess::Ping rtt to {} is {} ms", - event.peer.to_base58(), - rtt.as_millis() - ); - } - Ok(PingSuccess::Pong) => { - tracing::debug!( - "PingSuccess::Pong from {}", - event.peer.to_base58() - ); - } - Err(PingFailure::Timeout) => { - tracing::warn!( - "PingFailure::Timeout {}", - event.peer.to_base58() - ); - } - Err(PingFailure::Other { error }) => { - tracing::warn!( - "PingFailure::Other {}: {}", - event.peer.to_base58(), - error - ); - } - } - } -} diff --git a/apps/src/lib/node/gossip/p2p/identity.rs b/apps/src/lib/node/gossip/p2p/identity.rs deleted file mode 100644 index 42442054c8..0000000000 --- a/apps/src/lib/node/gossip/p2p/identity.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::fs::OpenOptions; -use std::path::{Path, PathBuf}; - -use libp2p::identity::ed25519::Keypair; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; - -use crate::cli; - -const P2P_KEY_PATH: &str = "gossiper-p2p-private-key.json"; - -/// ed255519 keypair + hash of public key. The keypair used to encrypted the -/// data send in the libp2p network. -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Identity { - pub address: String, - #[serde(with = "keypair_serde")] - pub key: Keypair, -} - -// TODO this is needed because libp2p does not export ed255519 serde -// feature maybe a MR for libp2p to export theses functions ? -mod keypair_serde { - use libp2p::identity::ed25519::Keypair; - use serde::de::Error; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - pub fn serialize( - value: &Keypair, - serializer: S, - ) -> Result - where - S: Serializer, - { - let bytes = value.encode(); - let string = hex::encode(&bytes[..]); - string.serialize(serializer) - } - pub fn deserialize<'d, D>(deserializer: D) -> Result - where - D: Deserializer<'d>, - { - let string = String::deserialize(deserializer)?; - let mut bytes = hex::decode(&string).map_err(Error::custom)?; - Keypair::decode(bytes.as_mut()).map_err(Error::custom) - } -} - -impl Identity { - /// Generates a new gossiper keypair and hash. - pub fn new() -> Self { - let key = Keypair::generate(); - let mut hasher = Sha256::new(); - hasher.update(key.public().encode()); - let address = format!("{:.40X}", hasher.finalize()); - Identity { address, key } - } - - /// Load identity from file or generate a new one if none found. - pub fn load_or_gen(base_dir: impl AsRef) -> Identity { - let file_path = Self::file_path(&base_dir); - match OpenOptions::new().read(true).open(&file_path) { - Ok(file) => { - let gossiper: Identity = serde_json::from_reader(file) - .expect("unexpected key encoding"); - gossiper - } - Err(err) => { - if let std::io::ErrorKind::NotFound = err.kind() { - tracing::info!( - "No P2P key found, generating a new one. This will be \ - written into {}", - file_path.to_string_lossy() - ); - Self::gen(base_dir) - } else { - eprintln!( - "Cannot read {}: {}", - file_path.to_string_lossy(), - err - ); - cli::safe_exit(1); - } - } - } - } - - /// Generate a new identity. - pub fn gen(base_dir: impl AsRef) -> Identity { - let file_path = Self::file_path(base_dir); - std::fs::create_dir_all(&file_path.parent().unwrap()).unwrap(); - let file = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(&file_path) - .expect("Couldn't open P2P key file"); - let gossiper = Identity::new(); - serde_json::to_writer_pretty(file, &gossiper) - .expect("Couldn't write private validator key file"); - gossiper - } - - pub fn file_path(base_dir: impl AsRef) -> PathBuf { - base_dir.as_ref().join(P2P_KEY_PATH) - } - - pub fn peer_id(&self) -> libp2p::PeerId { - let pk = self.key.public(); - let pk = libp2p::identity::PublicKey::Ed25519(pk); - libp2p::PeerId::from(pk) - } - - pub fn key(&self) -> libp2p::identity::Keypair { - libp2p::identity::Keypair::Ed25519(self.key.clone()) - } -} - -impl Default for Identity { - fn default() -> Self { - Self::new() - } -} diff --git a/apps/src/lib/node/gossip/p2p/mod.rs b/apps/src/lib/node/gossip/p2p/mod.rs deleted file mode 100644 index 2c135bc995..0000000000 --- a/apps/src/lib/node/gossip/p2p/mod.rs +++ /dev/null @@ -1,139 +0,0 @@ -pub mod behaviour; -mod identity; - -use std::path::Path; -use std::time::Duration; - -use behaviour::Behaviour; -use libp2p::core::connection::ConnectionLimits; -use libp2p::core::muxing::StreamMuxerBox; -use libp2p::core::transport::Boxed; -use libp2p::dns::DnsConfig; -use libp2p::identity::Keypair; -use libp2p::swarm::SwarmBuilder; -use libp2p::tcp::TcpConfig; -use libp2p::websocket::WsConfig; -use libp2p::{core, mplex, noise, PeerId, Transport, TransportError}; -use namada::proto::Intent; -use thiserror::Error; -use tokio::sync::mpsc::Sender; - -pub use self::identity::Identity; -use crate::config; - -pub type Swarm = libp2p::Swarm; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Failed initializing the transport: {0}")] - Transport(std::io::Error), - #[error("Error with the network behavior: {0}")] - Behavior(crate::node::gossip::p2p::behaviour::Error), - #[error("Error while dialing: {0}")] - Dialing(libp2p::swarm::DialError), - #[error("Error while starting to listing: {0}")] - Listening(TransportError), - #[error("Error decoding peer identity")] - BadPeerIdentity(TransportError), -} -type Result = std::result::Result; - -pub struct P2P(pub Swarm); - -impl P2P { - /// Create a new peer based on the configuration given. Used transport is - /// tcp. A peer participate in the intent gossip system and helps the - /// propagation of intents. - pub async fn new( - config: &config::IntentGossiper, - base_dir: impl AsRef, - peer_intent_send: Sender, - ) -> Result { - let identity = Identity::load_or_gen(base_dir); - let peer_key = identity.key(); - // Id of the node on the libp2p network derived from the public key - let peer_id = identity.peer_id(); - - tracing::info!("Peer id: {:?}", peer_id.clone()); - - let transport = build_transport(&peer_key).await; - - // create intent gossip specific behaviour - let intent_gossip_behaviour = - Behaviour::new(peer_key, config, peer_intent_send).await; - - let connection_limits = build_p2p_connections_limit(); - - // Swarm is - let mut swarm = - SwarmBuilder::new(transport, intent_gossip_behaviour, peer_id) - .connection_limits(connection_limits) - .notify_handler_buffer_size( - std::num::NonZeroUsize::new(20).expect("Not zero"), - ) - .connection_event_buffer_size(64) - .build(); - - swarm - .listen_on(config.address.clone()) - .map_err(Error::Listening)?; - - Ok(Self(swarm)) - } -} - -// TODO explain a bit the choice made here -/// Create transport used by libp2p. See -/// for more information on libp2p -/// transport -pub async fn build_transport( - peer_key: &Keypair, -) -> Boxed<(PeerId, StreamMuxerBox)> { - let transport = { - let tcp_transport = TcpConfig::new().nodelay(true); - let dns_tcp_transport = DnsConfig::system(tcp_transport).await.unwrap(); - let ws_dns_tcp_transport = WsConfig::new(dns_tcp_transport.clone()); - dns_tcp_transport.or_transport(ws_dns_tcp_transport) - }; - - let auth_config = { - let dh_keys = noise::Keypair::::new() - .into_authentic(peer_key) - .expect("Noise key generation failed. Should never happen."); - - noise::NoiseConfig::xx(dh_keys).into_authenticated() - }; - - let mplex_config = { - let mut mplex_config = mplex::MplexConfig::new(); - mplex_config.set_max_buffer_behaviour(mplex::MaxBufferBehaviour::Block); - mplex_config.set_max_buffer_size(usize::MAX); - - let mut yamux_config = libp2p::yamux::YamuxConfig::default(); - yamux_config - .set_window_update_mode(libp2p::yamux::WindowUpdateMode::on_read()); - // TODO: check if its enought - yamux_config.set_max_buffer_size(16 * 1024 * 1024); - yamux_config.set_receive_window_size(16 * 1024 * 1024); - - core::upgrade::SelectUpgrade::new(yamux_config, mplex_config) - }; - - transport - .upgrade(core::upgrade::Version::V1) - .authenticate(auth_config) - .multiplex(mplex_config) - .timeout(Duration::from_secs(20)) - .boxed() -} - -// TODO document choice made here -// TODO inject it in the configuration instead of hard-coding it ? -pub fn build_p2p_connections_limit() -> ConnectionLimits { - ConnectionLimits::default() - .with_max_pending_incoming(Some(10)) - .with_max_pending_outgoing(Some(30)) - .with_max_established_incoming(Some(25)) - .with_max_established_outgoing(Some(25)) - .with_max_established_per_peer(Some(5)) -} diff --git a/apps/src/lib/node/gossip/rpc/client.rs b/apps/src/lib/node/gossip/rpc/client.rs deleted file mode 100644 index f33d6add34..0000000000 --- a/apps/src/lib/node/gossip/rpc/client.rs +++ /dev/null @@ -1,166 +0,0 @@ -use std::convert::TryFrom; -use std::net::SocketAddr; - -use libp2p::gossipsub::IdentTopic; -use namada::proto::{Intent, IntentGossipMessage}; -use tokio::sync::mpsc::{self, Sender}; -use tokio::sync::oneshot; -use tonic::transport::Server; -use tonic::{Request as TonicRequest, Response as TonicResponse, Status}; - -use crate::config::RpcServer; -use crate::node::gossip::p2p::behaviour::Gossipsub; -use crate::proto::services::rpc_service_server::{ - RpcService, RpcServiceServer, -}; -use crate::proto::services::{rpc_message, RpcMessage, RpcResponse}; -use crate::proto::{IntentMessage, SubscribeTopicMessage}; - -#[derive(Debug)] -struct Rpc { - inject_message: - mpsc::Sender<(rpc_message::Message, oneshot::Sender)>, -} - -#[tonic::async_trait] -impl RpcService for Rpc { - async fn send_message( - &self, - request: TonicRequest, - ) -> Result, Status> { - if let RpcMessage { message: Some(msg) } = request.into_inner() { - let (sender, receiver) = oneshot::channel(); - self.inject_message - .send((msg, sender)) - .await - .map_err(|err| - Status::cancelled(format!{"failed to send message to gossip app: {:?}",err}) - )? - ; - let response = receiver.await.map_err(|err| - Status::data_loss(format!{"failed to receive response from gossip app: {:?}", err}))?; - Ok(TonicResponse::new(response)) - } else { - tracing::error!("Received empty rpc message, nothing can be done"); - Ok(TonicResponse::new(RpcResponse::default())) - } - } -} - -pub async fn rpc_server( - addr: SocketAddr, - inject_message: Sender<( - rpc_message::Message, - oneshot::Sender, - )>, -) -> Result<(), tonic::transport::Error> { - let rpc = Rpc { inject_message }; - let svc = RpcServiceServer::new(rpc); - Server::builder().add_service(svc).serve(addr).await -} - -/// Start a rpc server in it's own thread. The used address to listen is in the -/// `config` argument. All received event by the rpc are send to the channel -/// return by this function. -pub async fn start_rpc_server( - config: &RpcServer, - rpc_sender: mpsc::Sender<( - rpc_message::Message, - tokio::sync::oneshot::Sender, - )>, -) { - let addr = config.address; - tracing::info!("RPC started at {}", config.address); - rpc_server(addr, rpc_sender).await.unwrap(); -} - -pub async fn handle_rpc_event( - event: rpc_message::Message, - gossip_sub: &mut Gossipsub, -) -> (RpcResponse, Option) { - match event { - rpc_message::Message::Intent(message) => { - match IntentMessage::try_from(message) { - Ok(message) => { - // Send the intent to gossip - let gossip_message = - IntentGossipMessage::new(message.intent.clone()); - let intent_bytes = gossip_message.to_bytes(); - - let gossip_result = match gossip_sub - .publish(IdentTopic::new(message.topic), intent_bytes) - { - Ok(message_id) => { - format!( - "Intent published in intent gossiper with \ - message ID: {}", - message_id - ) - } - Err(err) => { - format!( - "Failed to publish intent in gossiper: {:?}", - err - ) - } - }; - ( - RpcResponse { - result: format!( - "Intent received. {}.", - gossip_result, - ), - }, - Some(message.intent), - ) - } - Err(err) => ( - RpcResponse { - result: format!("Error decoding intent: {:?}", err), - }, - None, - ), - } - } - rpc_message::Message::Dkg(dkg_msg) => { - tracing::debug!("dkg not yet implemented {:?}", dkg_msg); - ( - RpcResponse { - result: String::from( - "DKG application not yet - implemented", - ), - }, - None, - ) - } - rpc_message::Message::Topic(topic_message) => { - let topic = SubscribeTopicMessage::from(topic_message); - let topic = IdentTopic::new(&topic.topic); - ( - match gossip_sub.subscribe(&topic) { - Ok(true) => { - let result = format!("Node subscribed to {}", topic); - tracing::info!("{}", result); - RpcResponse { result } - } - Ok(false) => { - let result = - format!("Node already subscribed to {}", topic); - tracing::info!("{}", result); - RpcResponse { result } - } - Err(err) => { - let result = format!( - "failed to subscribe to {}: {:?}", - topic, err - ); - tracing::error!("{}", result); - RpcResponse { result } - } - }, - None, - ) - } - } -} diff --git a/apps/src/lib/node/gossip/rpc/matchmakers.rs b/apps/src/lib/node/gossip/rpc/matchmakers.rs deleted file mode 100644 index c4912c8b96..0000000000 --- a/apps/src/lib/node/gossip/rpc/matchmakers.rs +++ /dev/null @@ -1,847 +0,0 @@ -//! This module provides connection between an intent gossiper node (the server) -//! and matchmakers (clients) over WebSocket. -//! -//! Both the server and the client can asynchronously listen for new messages -//! and send messages to the other side. - -use std::collections::HashSet; -use std::fmt::Debug; -use std::net::{SocketAddr, ToSocketAddrs}; -use std::sync::atomic::{self, AtomicBool}; -use std::sync::{Arc, RwLock}; - -use borsh::{BorshDeserialize, BorshSerialize}; -use derivative::Derivative; -use message_io::network::{Endpoint, ResourceId, ToRemoteAddr, Transport}; -use message_io::node::{self, NodeHandler, NodeListener}; - -use crate::cli; - -/// Message from intent gossiper to a matchmaker -#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub enum MsgFromServer { - /// Try to match an intent - AddIntent { id: Vec, data: Vec }, -} - -/// Message from a matchmaker to intent gossiper -#[derive(Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub enum MsgFromClient { - /// The intent is invalid and hence it shouldn't be gossiped - InvalidIntent { id: Vec }, - /// The intent constraints are too complex for this matchmaker, gossip it - IntentConstraintsTooComplex { id: Vec }, - /// The matchmaker doesn't care about this intent, gossip it - IgnoredIntent { id: Vec }, - /// Intents were matched into a tx. Remove the matched intents from mempool - /// if the tx gets applied. - Matched { intent_ids: HashSet> }, - /// An intent was accepted and added, but no match found yet. Gossip it - Unmatched { id: Vec }, -} - -/// Intent gossiper server listener handles connections from [`ClientDialer`]s. -#[derive(Derivative)] -#[derivative(Debug)] -pub struct ServerListener { - /// The address on which the server is listening - pub address: SocketAddr, - /// The accepted client connections, shared with the [`ServerDialer`] - clients: Arc>>, - /// A node listener and its abort receiver. These are consumed once the - /// listener is started with [`ServerListener::listen`]. - #[derivative(Debug = "ignore")] - listener: Option<(NodeListener<()>, tokio::sync::mpsc::Receiver<()>)>, -} - -/// Intent gossiper server dialer can send messages to the connected -/// [`ClientListener`]s. -#[derive(Clone, Derivative)] -#[derivative(Debug)] -pub struct ServerDialer { - /// The connection handler - #[derivative(Debug = "ignore")] - handler: NodeHandler<()>, - /// Connection resource ID - resource_id: ResourceId, - /// The accepted client connections, shared with the [`ServerListener`] - clients: Arc>>, - /// A message to abort the server must be sent to stop the - /// [`ServerListener`]. This message will be sent on [`ServerDialer`]'s - /// `drop` call. - abort_send: tokio::sync::mpsc::Sender<()>, -} - -/// Server events are used internally by the async [`ServerListener`]. -#[derive(Clone, Debug)] -enum ServerEvent { - /// New endpoint has been accepted by a listener and considered ready to - /// use. The event contains the resource id of the listener that - /// accepted this connection. - Accepted(Endpoint, ResourceId), - /// Input message received by the network. - Message(Endpoint, MsgFromClient), - /// This event is only dispatched when a connection is lost. - Disconnected(Endpoint), -} - -/// Matchmaker client listener handles a connection from [`ServerDialer`]. -#[derive(Derivative)] -#[derivative(Debug)] -pub struct ClientListener { - /// The connection handler - #[derivative(Debug = "ignore")] - handler: NodeHandler<()>, - /// The server connection endpoint - server: Endpoint, - /// The address on which the client is listening - local_addr: SocketAddr, - /// The client listener. This is consumed once the listener is started with - /// [`ClientListener::listen`]. - #[derivative(Debug = "ignore")] - listener: Option>, - /// Server connection status - is_connected: Arc, -} - -/// Matchmaker client dialer can send messages to the connected -/// [`ServerListener`]. -#[derive(Clone, Derivative)] -#[derivative(Debug)] -pub struct ClientDialer { - /// The address on which the client is listening - pub local_addr: SocketAddr, - /// The server address - server: Endpoint, - /// The connection handler - #[derivative(Debug = "ignore")] - handler: NodeHandler<()>, - /// Server connection status - is_connected: Arc, -} - -impl ServerListener { - /// Create a new intent gossiper node server. Returns a listener and - /// a dialer that can be used to send messages to clients and to shut down - /// the server. - pub fn new_pair(address: impl ToSocketAddrs) -> (Self, ServerDialer) { - let clients: Arc>> = Default::default(); - let (handler, listener) = node::split::<()>(); - - let (resource_id, address) = match handler - .network() - .listen(Transport::Ws, &address) - { - Ok((resource_id, real_addr)) => { - tracing::info!("Matchmakers server running at {}", real_addr); - (resource_id, real_addr) - } - Err(err) => { - eprintln!( - "The matchmakers server cannot listen at {:?}: {}", - address.to_socket_addrs().unwrap().collect::>(), - err - ); - cli::safe_exit(1); - } - }; - - let (abort_send, abort_recv) = tokio::sync::mpsc::channel::<()>(1); - - ( - Self { - address, - clients: clients.clone(), - listener: Some((listener, abort_recv)), - }, - ServerDialer { - handler, - clients, - resource_id, - abort_send, - }, - ) - } - - /// Start the server listener and call `on_msg` on every received message. - /// The listener can be stopped early by [`ServerDialer::shutdown`]. - pub async fn listen(mut self, mut on_msg: impl FnMut(MsgFromClient)) { - // Open a channel for events received from the async listener - let (send, mut recv) = tokio::sync::mpsc::unbounded_channel(); - - // This is safe because `listen` consumes `self` created by - // [`ServerListener::new_pair`] - let (listener, mut abort_recv) = self.listener.take().unwrap(); - - tracing::debug!("Starting intent gossiper matchmakers server..."); - - // Start the async listener that will send server events over the - // channel - let _task = listener.for_each_async(move |event| { - match event.network() { - message_io::network::NetEvent::Message( - endpoint, - mut msg_bytes, - ) => match MsgFromClient::deserialize(&mut msg_bytes) { - Ok(msg) => { - let _ = send.send(ServerEvent::Message(endpoint, msg)); - } - Err(err) => { - tracing::error!( - "Couldn't decode a msg from matchmaker {}: {}", - endpoint, - err - ); - } - }, - message_io::network::NetEvent::Accepted(endpoint, id) => { - tracing::info!( - "Accepted connection from matchmaker {}", - endpoint - ); - let _ = send.send(ServerEvent::Accepted(endpoint, id)); - } - message_io::network::NetEvent::Disconnected(endpoint) => { - tracing::info!("Matchmaker disconnected: {}", endpoint); - let _ = send.send(ServerEvent::Disconnected(endpoint)); - } - message_io::network::NetEvent::Connected(endpoint, status) => { - // Server only gets `NetEvent::Accepted` from connected - // clients - tracing::error!( - "Unexpected server `NetEvent::Connected` with \ - endpoint {}, status {}", - endpoint, - status - ); - } - } - }); - - tracing::debug!("Intent gossiper matchmakers server is ready."); - - // Process the server events - loop { - tokio::select! { - _ = abort_recv.recv() => { - tracing::debug!("Shutting down intent gossiper matchmakers server."); - return; - }, - event = recv.recv() => if let Some(event) = event { - match event { - ServerEvent::Message(endpoint, msg) => { - tracing::debug!( - "Received msg from matchmaker {}: {:?}", - endpoint, - msg - ); - on_msg(msg); - } - ServerEvent::Accepted(endpoint, _id) => { - let mut clients = self.clients.write().unwrap(); - if !clients.insert(endpoint) { - tracing::warn!( - "Accepted matchmaker already known {}", - endpoint - ) - } - } - ServerEvent::Disconnected(endpoint) => { - let mut clients = self.clients.write().unwrap(); - if !clients.remove(&endpoint) { - tracing::warn!( - "Disconnected matchmaker unknown endpoint {}", - endpoint - ) - } - } - } - } - } - } - } -} - -impl ServerDialer { - /// Broadcast a message to all connected matchmaker clients - pub fn send(&mut self, msg: MsgFromServer) { - let net = self.handler.network(); - for client in self.clients.read().unwrap().iter() { - let msg_bytes = msg.try_to_vec().unwrap(); - let status = net.send(*client, &msg_bytes); - tracing::info!( - "Sent msg {:?} to {} with status {:?}", - msg, - client, - status - ); - } - } - - /// Is the server listener ready to start handling incoming connections? - pub fn is_ready(&self) -> bool { - self.handler - .network() - .is_ready(self.resource_id) - .unwrap_or_default() - } - - /// Force shut-down the [`ServerListener`] associated with this dialer. - pub fn shutdown(&mut self) { - self.handler.stop(); - // Send a message to abort and ignore the result - let _ = self.abort_send.blocking_send(()); - } -} - -impl ClientListener { - /// Create a new matchmaker client. Returns a listener and a dialer that - /// can be used to send messages to the server and to shut down the client. - pub fn new_pair(server_addr: impl ToRemoteAddr) -> (Self, ClientDialer) { - let server_addr = server_addr.to_remote_addr().unwrap(); - // Not using message-io signals - let (handler, listener) = node::split::<()>(); - - let (server, local_addr) = match handler - .network() - .connect(Transport::Ws, server_addr.clone()) - { - Ok(res) => res, - Err(err) => { - eprintln!( - "Cannot listen at {} for matchmakers server: {}", - server_addr, err, - ); - cli::safe_exit(1); - } - }; - tracing::info!("Matchmaker client running at {}", local_addr); - - let is_connected = Arc::new(AtomicBool::new(false)); - - ( - Self { - server, - local_addr, - listener: Some(listener), - is_connected: is_connected.clone(), - handler: handler.clone(), - }, - ClientDialer { - server, - local_addr, - handler, - is_connected, - }, - ) - } - - /// Start the client listener and call `on_msg` on every received message. - /// The listener can be stopped early by [`ClientDialer::shutdown`]. - pub fn listen(mut self, mut on_msg: impl FnMut(MsgFromServer)) { - // This is safe because `listen` consumes `self` - let listener = self.listener.take().unwrap(); - - // Start the blocking listener that will call `on_msg` on every message - let server_addr = self.server.addr(); - let local_addr_port = self.local_addr.port(); - - tracing::debug!("Matchmakers client is ready."); - - listener.for_each(move |event| { - tracing::debug!("Client event {:#?}", event); - match event { - node::NodeEvent::Network(net_event) => match net_event { - message_io::network::NetEvent::Message( - endpoint, - mut msg_bytes, - ) => match MsgFromServer::deserialize(&mut msg_bytes) { - Ok(msg) => { - on_msg(msg); - } - Err(err) => { - tracing::error!( - "Couldn't decode a msg from intent gossiper \ - {}: {}", - endpoint, - err - ); - } - }, - message_io::network::NetEvent::Connected( - _endpoint, - established, - ) => { - if established { - tracing::info!( - "Connected to the server at {}. The client is \ - identified by local port: {}", - server_addr, - local_addr_port - ); - } else { - tracing::error!( - "Cannot connect to the server at {}", - server_addr - ) - } - self.is_connected - .store(established, atomic::Ordering::SeqCst); - } - message_io::network::NetEvent::Disconnected(endpoint) => { - tracing::info!("Disconnected from {}", endpoint); - self.is_connected - .store(false, atomic::Ordering::SeqCst); - // Exit on disconnect, a user of this client can - // implement retry logic - self.handler.stop(); - } - message_io::network::NetEvent::Accepted(endpoint, _) => { - // Client only gets `NetEvent::Connected` from connected - // clients - tracing::error!( - "Unexpected client `NetEvent::Accepted` with \ - endpoint {}", - endpoint - ); - } - }, - node::NodeEvent::Signal(()) => { - // unused - } - } - }); - - tracing::debug!("Matchmakers client is shutting down."); - } -} - -impl ClientDialer { - /// Send a message to the intent gossiper server - pub fn send(&mut self, msg: MsgFromClient) { - let net = self.handler.network(); - let msg_bytes = msg.try_to_vec().unwrap(); - let status = net.send(self.server, &msg_bytes); - tracing::info!( - "Sent msg {:?} to {} with status {:?}", - msg, - self.server, - status - ); - } - - /// Is the client connected? - pub fn is_connected(&self) -> bool { - self.is_connected.load(atomic::Ordering::SeqCst) - } - - /// Force shut-down the [`ClientListener`] associated with this dialer. - pub fn shutdown(&mut self) { - self.handler.stop(); - } -} - -impl Drop for ServerDialer { - fn drop(&mut self) { - self.shutdown(); - } -} - -impl Drop for ClientDialer { - fn drop(&mut self) { - self.shutdown(); - } -} - -#[cfg(test)] -mod test { - use std::collections::HashMap; - use std::sync::atomic; - - use itertools::Itertools; - use proptest::prelude::*; - use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; - use proptest::test_runner::Config; - use test_log::test; - - use super::*; - - prop_state_machine! { - #![proptest_config(Config { - // Instead of the default 256, we only run 10 because otherwise it - // takes too long - cases: 10, - // 10 second timeout - timeout: 10_000, - .. Config::default() - })] - #[test] - /// A `StateMachineTest` implemented on `AbstractState` - fn connections_state_machine_test(sequential 1..20 => AbstractState); - } - - /// Abstract representation of a state of a server and client(s) - #[derive(Clone, Debug)] - struct AbstractState { - // true == running - server: bool, - clients: HashSet, - } - - /// State of a concrete server and client(s) implementation - #[derive(Default)] - struct ConcreteState { - server: Option, - clients: HashMap, - } - - /// State machine transitions - #[derive(Clone, Debug)] - enum Transition { - StartServer, - StopServer, - StartClient(ClientId), - StopClient(ClientId), - ServerMsg(MsgFromServer), - ClientMsg(ClientId, MsgFromClient), - } - - type ClientId = usize; - - struct TestServer { - /// The address of the server (assigned dynamically) - address: SocketAddr, - /// Runtime for the async listener - rt: tokio::runtime::Runtime, - #[allow(dead_code)] - /// Task that runs the async server listener - listener_handle: tokio::task::JoinHandle<()>, - /// A server dialer can send messages to clients - dialer: ServerDialer, - /// Messages received by the `listener` from clients are forwarded - /// to this receiver, to be checked by the test. - msgs_recv: std::sync::mpsc::Receiver, - } - - struct TestClient { - /// A client dialer can send messages to the server - dialer: ClientDialer, - /// A thread that runs the client listener - listener_handle: std::thread::JoinHandle<()>, - /// Messages received by the `listener` from the server are forwarded - /// to this receiver, to be checked by the test. - msgs_recv: std::sync::mpsc::Receiver, - } - - impl StateMachineTest for AbstractState { - type Abstract = Self; - type ConcreteState = ConcreteState; - - fn init_test( - _initial_state: ::State, - ) -> Self::ConcreteState { - ConcreteState::default() - } - - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { - match transition { - Transition::StartServer => { - // Assign port dynamically - let (listener, dialer) = - ServerListener::new_pair("127.0.0.1:0"); - let address = listener.address; - let (msgs_send, msgs_recv) = std::sync::mpsc::channel(); - // Run the listener, we need an async runtime - let rt = tokio::runtime::Runtime::new().unwrap(); - let listener_handle = rt.spawn(async move { - listener - .listen(move |msg| { - msgs_send.send(msg).unwrap(); - }) - .await; - }); - - // Wait for the server to be ready - while !dialer.is_ready() { - println!("Waiting for the server to be ready"); - } - - state.server = Some(TestServer { - address, - rt, - dialer, - listener_handle, - msgs_recv, - }) - } - Transition::StopServer => { - // For the server, we have to send abort signal and drop - // the dialer - let mut server = state.server.take().unwrap(); - server.dialer.shutdown(); - server - .rt - .shutdown_timeout(std::time::Duration::from_secs(2)); - drop(server.dialer); - - if !state.clients.is_empty() { - println!( - "The server is waiting for all the clients to \ - stop..." - ); - while state.clients.values().any(|client| { - client - .dialer - .is_connected - .load(atomic::Ordering::SeqCst) - }) {} - // Stop the clients - for (id, client) in - std::mem::take(&mut state.clients).into_iter() - { - // Ask the client to stop - client.dialer.handler.stop(); - println!("Asking client {} listener to stop", id); - // Wait for it to actually stop - client.listener_handle.join().unwrap(); - println!("Client {} listener stopped", id); - } - println!("Clients stopped"); - } - } - Transition::StartClient(id) => { - let server_addr = state.server.as_ref().unwrap().address; - let (listener, dialer) = - ClientListener::new_pair(server_addr); - let (msgs_send, msgs_recv) = std::sync::mpsc::channel(); - let listener_handle = std::thread::spawn(move || { - listener.listen(|msg| { - msgs_send.send(msg).unwrap(); - }) - }); - - // If there is a server running ... - if let Some(server) = state.server.as_ref() { - // ... wait for the client to connect ... - while !dialer.is_connected() {} - // ... and for the server to accept it - while !server.dialer.clients.read().unwrap().iter().any( - |client| { - // Client's address is added once it's accepted - client.addr() == dialer.local_addr - }, - ) {} - } - - state.clients.insert( - id, - TestClient { - dialer, - listener_handle, - msgs_recv, - }, - ); - } - Transition::StopClient(id) => { - // Remove the client - let client = state.clients.remove(&id).unwrap(); - // Ask the client to stop - client.dialer.handler.stop(); - // Wait for it to actually stop - client.listener_handle.join().unwrap(); - } - Transition::ServerMsg(msg) => { - state.server.as_mut().unwrap().dialer.send(msg.clone()); - - // Post-condition: every client must receive the msg - for client in state.clients.values() { - let recv_msg = client.msgs_recv.recv().unwrap(); - assert_eq!(msg, recv_msg); - } - } - Transition::ClientMsg(id, msg) => { - let client = state.clients.get_mut(&id).unwrap(); - client.dialer.send(msg.clone()); - - // Post-condition: - // If there is a server running ... - if let Some(server) = state.server.as_mut() { - // ... it must receive the msg - let recv_msg = server.msgs_recv.recv().unwrap(); - assert_eq!(msg, recv_msg); - } - } - } - state - } - - fn test_sequential( - initial_state: ::State, - transitions: Vec< - ::Transition, - >, - ) { - let mut state = Self::init_test(initial_state); - for transition in transitions { - state = Self::apply_concrete(state, transition); - Self::invariants(&state); - } - - // Shutdown the server gracefully - if let Some(mut server) = state.server { - server.dialer.shutdown(); - server - .rt - .shutdown_timeout(std::time::Duration::from_secs(4)); - } - // Shutdown any clients too - if !state.clients.is_empty() { - println!( - "The server is waiting for all the clients to stop..." - ); - while state.clients.values().any(|client| { - client.dialer.is_connected.load(atomic::Ordering::SeqCst) - }) {} - println!("Clients stopped"); - } - } - } - - impl AbstractStateMachine for AbstractState { - type State = Self; - type Transition = Transition; - - fn init_state() -> BoxedStrategy { - Just(Self { - server: false, - clients: HashSet::default(), - }) - .boxed() - } - - fn transitions(state: &Self::State) -> BoxedStrategy { - use Transition::*; - if state.clients.is_empty() { - prop_oneof![ - Just(StartServer), - Just(StopServer), - (0..4_usize).prop_map(StartClient), - arb_msg_from_server().prop_map(ServerMsg), - ] - .boxed() - } else { - let ids: Vec<_> = - state.clients.iter().sorted().cloned().collect(); - let arb_id = proptest::sample::select(ids); - prop_oneof![ - Just(StartServer), - Just(StopServer), - (0..4_usize).prop_map(StartClient), - arb_msg_from_server().prop_map(ServerMsg), - arb_id.clone().prop_map(StopClient), - arb_id.prop_flat_map(|id| arb_msg_from_client() - .prop_map(move |msg| { ClientMsg(id, msg) })), - ] - .boxed() - } - } - - fn preconditions( - state: &Self::State, - transition: &Self::Transition, - ) -> bool { - match transition { - Transition::StartServer => !state.server, - Transition::StopServer => state.server, - Transition::StartClient(id) => { - // only start clients if the server is running and this - // client ID is not running - state.server && !state.clients.contains(id) - } - Transition::StopClient(id) => { - // stop only if this client is running - state.clients.contains(id) - } - Transition::ServerMsg(_) => { - // can send only if the server is running - state.server - } - Transition::ClientMsg(id, _) => { - // can send only if the server and this client is running - state.server && state.clients.contains(id) - } - } - } - - fn apply_abstract( - mut state: Self::State, - transition: &Self::Transition, - ) -> Self::State { - match transition { - Transition::StartServer => { - state.server = true; - } - Transition::StopServer => { - state.server = false; - // Clients should disconnect and stop - state.clients = Default::default(); - } - Transition::StartClient(id) => { - state.clients.insert(*id); - } - Transition::StopClient(id) => { - state.clients.remove(id); - } - Transition::ServerMsg(_msg) => { - // no change - } - Transition::ClientMsg(_id, _msg) => { - // no change - } - } - state - } - } - - prop_compose! { - /// Generate an arbitrary MsgFromServer - fn arb_msg_from_server() - (id in proptest::collection::vec(any::(), 1..100), - data in proptest::collection::vec(any::(), 1..100)) - -> MsgFromServer { - MsgFromServer::AddIntent { id, data } - } - } - - /// Generate an arbitrary MsgFromClient - fn arb_msg_from_client() -> impl Strategy { - let arb_intent_id = proptest::collection::vec(any::(), 1..100); - let invalid_intent = arb_intent_id - .clone() - .prop_map(|id| MsgFromClient::InvalidIntent { id }); - let intent_too_complex = arb_intent_id - .clone() - .prop_map(|id| MsgFromClient::IntentConstraintsTooComplex { id }); - let ignored_intent = arb_intent_id - .clone() - .prop_map(|id| MsgFromClient::IgnoredIntent { id }); - let unmatched_intent = arb_intent_id - .clone() - .prop_map(|id| MsgFromClient::Unmatched { id }); - let matched_intent = - proptest::collection::hash_set(arb_intent_id, 1..10).prop_map( - move |intent_ids| MsgFromClient::Matched { intent_ids }, - ); - prop_oneof![ - invalid_intent, - intent_too_complex, - ignored_intent, - matched_intent, - unmatched_intent, - ] - } -} diff --git a/apps/src/lib/node/gossip/rpc/mod.rs b/apps/src/lib/node/gossip/rpc/mod.rs deleted file mode 100644 index 541f5b1eb7..0000000000 --- a/apps/src/lib/node/gossip/rpc/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod client; -pub mod matchmakers; diff --git a/apps/src/lib/node/ledger/shell/init_chain.rs b/apps/src/lib/node/ledger/shell/init_chain.rs index a989338751..42a0ff1e9d 100644 --- a/apps/src/lib/node/ledger/shell/init_chain.rs +++ b/apps/src/lib/node/ledger/shell/init_chain.rs @@ -20,7 +20,6 @@ where /// Create a new genesis for the chain with specified id. This includes /// 1. A set of initial users and tokens /// 2. Setting up the validity predicates for both users and tokens - /// 3. A matchmaker pub fn init_chain( &mut self, init: request::InitChain, diff --git a/apps/src/lib/node/matchmaker.rs b/apps/src/lib/node/matchmaker.rs deleted file mode 100644 index 0a01528b00..0000000000 --- a/apps/src/lib/node/matchmaker.rs +++ /dev/null @@ -1,364 +0,0 @@ -use std::env; -use std::net::SocketAddr; -use std::path::{Path, PathBuf}; -use std::rc::Rc; -use std::sync::Arc; - -use borsh::{BorshDeserialize, BorshSerialize}; -use libc::c_void; -use libloading::Library; -use namada::proto::Tx; -use namada::types::address::{self, Address}; -use namada::types::dylib; -use namada::types::intent::{IntentTransfers, MatchedExchanges}; -use namada::types::key::*; -use namada::types::matchmaker::AddIntentResult; -use namada::types::transaction::{hash_tx, Fee, WrapperTx}; -use tendermint_config::net; -use tendermint_config::net::Address as TendermintAddress; - -use super::gossip::rpc::matchmakers::{ - ClientDialer, ClientListener, MsgFromClient, MsgFromServer, -}; -use crate::cli::args; -use crate::client::rpc; -use crate::client::tendermint_rpc_types::TxBroadcastData; -use crate::client::tx::broadcast_tx; -use crate::{cli, config, wasm_loader}; - -/// Run a matchmaker -#[tokio::main] -pub async fn run( - config::Matchmaker { - matchmaker_path, - tx_code_path, - }: config::Matchmaker, - intent_gossiper_addr: SocketAddr, - ledger_addr: TendermintAddress, - tx_signing_key: Rc, - tx_source_address: Address, - wasm_dir: impl AsRef, -) { - let matchmaker_path = matchmaker_path.unwrap_or_else(|| { - eprintln!("Please configure or specify the matchmaker path"); - cli::safe_exit(1); - }); - let tx_code_path = tx_code_path.unwrap_or_else(|| { - eprintln!("Please configure or specify the transaction code path"); - cli::safe_exit(1); - }); - - let (runner, result_handler) = Runner::new_pair( - intent_gossiper_addr, - matchmaker_path, - tx_code_path, - ledger_addr, - tx_signing_key, - tx_source_address, - wasm_dir, - ); - - // Instantiate and run the matchmaker implementation in a dedicated thread - let runner_join_handle = std::thread::spawn(move || { - runner.listen(); - }); - - // Process results async - result_handler.run().await; - - if let Err(error) = runner_join_handle.join() { - eprintln!("Matchmaker runner failed with: {:?}", error); - cli::safe_exit(1) - } -} - -/// A matchmaker receive intents and tries to find a match with previously -/// received intent. -#[derive(Debug)] -pub struct Runner { - matchmaker_path: PathBuf, - /// The client listener. This is consumed once the listener is started with - /// [`Runner::listen`]. - listener: Option, - /// Sender of results of matched intents to the [`ResultHandler`]. - result_send: tokio::sync::mpsc::UnboundedSender, -} - -/// Result handler processes the results sent from the matchmaker [`Runner`]. -#[derive(Debug)] -pub struct ResultHandler { - /// A dialer can send messages to the connected intent gossip node - dialer: ClientDialer, - /// A receiver of matched intents results from the [`Runner`]. - result_recv: tokio::sync::mpsc::UnboundedReceiver, - /// The ledger address to send any crafted transaction to - ledger_address: net::Address, - /// The code of the transaction that is going to be send to a ledger. - tx_code: Vec, - /// A source address for transactions created from intents. - tx_source_address: Address, - /// A keypair that will be used to sign transactions. - tx_signing_key: Rc, -} - -/// The loaded implementation's dylib and its state -#[derive(Debug)] -struct MatchmakerImpl { - /// The matchmaker's state as a raw mutable pointer to allow custom user - /// implementation in a dylib. - /// NOTE: The `state` field MUST be above the `library` field to ensure - /// that its destructor is ran before the implementation code is dropped. - state: MatchmakerState, - /// Matchmaker's implementation loaded from dylib - library: Library, -} - -/// The matchmaker's state as a raw mutable pointer to allow custom user -/// implementation in a dylib -#[derive(Debug)] -struct MatchmakerState(Arc<*mut c_void>); - -impl Runner { - /// Create a new matchmaker and a dialer that can be used to send messages - /// to the intent gossiper node. - pub fn new_pair( - intent_gossiper_addr: SocketAddr, - matchmaker_path: PathBuf, - tx_code_path: PathBuf, - ledger_address: TendermintAddress, - tx_signing_key: Rc, - tx_source_address: Address, - wasm_dir: impl AsRef, - ) -> (Self, ResultHandler) { - // Setup a channel for sending matchmaker results from `Self` to the - // `ResultHandler` - let (result_send, result_recv) = tokio::sync::mpsc::unbounded_channel(); - - // Prepare a client for intent gossiper node connection - let (listener, dialer) = ClientListener::new_pair(intent_gossiper_addr); - - let tx_code = wasm_loader::read_wasm(&wasm_dir, tx_code_path); - - ( - Self { - matchmaker_path, - listener: Some(listener), - result_send, - }, - ResultHandler { - dialer, - result_recv, - ledger_address, - tx_code, - tx_source_address, - tx_signing_key, - }, - ) - } - - pub fn listen(mut self) { - // Load the implementation's dylib and instantiate it. We have to do - // that here instead of `Self::new_pair`, because we cannot send - // it across threads and the listener is launched in a dedicated thread. - - // Check or add a filename extension to matchmaker path - let matchmaker_filename = - if let Some(ext) = self.matchmaker_path.extension() { - if ext != dylib::FILE_EXT { - tracing::warn!( - "Unexpected matchmaker file extension. Expected {}, \ - got {}.", - dylib::FILE_EXT, - ext.to_string_lossy(), - ); - } - self.matchmaker_path.clone() - } else { - let mut filename = self.matchmaker_path.clone(); - filename.set_extension(dylib::FILE_EXT); - filename - }; - - let matchmaker_dylib = if matchmaker_filename.is_absolute() { - // If the path is absolute, use it as is - matchmaker_filename - } else { - // The dylib should be built in the same directory as where Anoma - // binaries are, even when ran via `cargo run`. Anoma's pre-built - // binaries are distributed with the dylib(s) in the same directory. - let dylib_dir_with_bins = || { - let anoma_path = env::current_exe().unwrap(); - anoma_path - .parent() - .map(|path| path.to_owned()) - .unwrap() - .join(&matchmaker_filename) - }; - // Anoma built from source (`make install`) will install the - // dylib(s) to `~/.cargo/lib`. - let dylib_dir_installed = || { - directories::BaseDirs::new() - .expect("Couldn't determine the $HOME directory") - .home_dir() - .join(".cargo") - .join("lib") - .join(&matchmaker_filename) - }; - // Argument with file path relative to the current dir. - let dylib_dir_in_cwd = || { - let anoma_path = env::current_dir().unwrap(); - anoma_path.join(&matchmaker_filename) - }; - - // Try to find the matchmaker lib in either directory (computed - // lazily) - let matchmaker_dylib: Option = - check_file_exists(dylib_dir_with_bins) - .or_else(|| check_file_exists(dylib_dir_installed)) - .or_else(|| check_file_exists(dylib_dir_in_cwd)); - matchmaker_dylib.unwrap_or_else(|| { - panic!( - "The matchmaker library couldn't not be found. Did you \ - build it? Attempted to find it in directories \"{}\", \ - \"{}\" and \"{}\".", - dylib_dir_with_bins().to_string_lossy(), - dylib_dir_installed().to_string_lossy(), - dylib_dir_in_cwd().to_string_lossy(), - ); - }) - }; - tracing::info!( - "Running matchmaker from {}", - matchmaker_dylib.to_string_lossy() - ); - - let matchmaker_code = - unsafe { Library::new(matchmaker_dylib).unwrap() }; - - // Instantiate the matchmaker - let new_matchmaker: libloading::Symbol< - unsafe extern "C" fn() -> *mut c_void, - > = unsafe { matchmaker_code.get(b"_new_matchmaker").unwrap() }; - - let state = MatchmakerState(Arc::new(unsafe { new_matchmaker() })); - - let r#impl = MatchmakerImpl { - state, - library: matchmaker_code, - }; - - // Run the listener for messages from the connected intent gossiper node - self.listener.take().unwrap().listen(|msg| match msg { - MsgFromServer::AddIntent { id, data } => { - self.try_match_intent(&r#impl, id, data); - } - }) - } - - /// add the intent to the matchmaker mempool and tries to find a match for - /// that intent - fn try_match_intent( - &self, - r#impl: &MatchmakerImpl, - intent_id: Vec, - intent_data: Vec, - ) { - let add_intent: libloading::Symbol< - unsafe extern "C" fn( - *mut c_void, - &Vec, - &Vec, - ) -> AddIntentResult, - > = unsafe { r#impl.library.get(b"_add_intent").unwrap() }; - - let result = - unsafe { add_intent(*r#impl.state.0, &intent_id, &intent_data) }; - - self.result_send.send(result).unwrap(); - } -} - -impl Drop for MatchmakerImpl { - fn drop(&mut self) { - let drop_matchmaker: libloading::Symbol< - unsafe extern "C" fn(*mut c_void), - > = unsafe { self.library.get(b"_drop_matchmaker").unwrap() }; - - unsafe { drop_matchmaker(*self.state.0) }; - } -} - -impl ResultHandler { - async fn run(mut self) { - while let Some(result) = self.result_recv.recv().await { - if let Some(tx) = result.tx { - self.submit_tx(tx).await - } - if let Some(intent_ids) = result.matched_intents { - self.dialer.send(MsgFromClient::Matched { intent_ids }) - } - } - } - - async fn submit_tx(&self, tx_data: Vec) { - let tx_code = self.tx_code.clone(); - let matches = MatchedExchanges::try_from_slice(&tx_data[..]).unwrap(); - let intent_transfers = IntentTransfers { - matches, - source: self.tx_source_address.clone(), - }; - let tx_data = intent_transfers.try_to_vec().unwrap(); - let to_broadcast = { - let epoch = rpc::query_epoch(args::Query { - ledger_address: self.ledger_address.clone(), - }) - .await; - let tx = WrapperTx::new( - Fee { - amount: 0.into(), - token: address::xan(), - }, - &self.tx_signing_key, - epoch, - 0.into(), - Tx::new(tx_code, Some(tx_data)).sign(&self.tx_signing_key), - // TODO: Actually use the fetched encryption key - Default::default(), - ); - let wrapper_hash = hash_tx(&tx.try_to_vec().unwrap()).to_string(); - - let decrypted_hash = tx.tx_hash.to_string(); - TxBroadcastData::Wrapper { - tx: tx - .sign(&self.tx_signing_key) - .expect("Wrapper tx signing keypair should be correct"), - wrapper_hash, - decrypted_hash, - } - }; - - let response = - broadcast_tx(self.ledger_address.clone(), &to_broadcast).await; - match response { - Ok(tx_response) => { - tracing::info!( - "Injected transaction from matchmaker with result: {:#?}", - tx_response - ); - } - Err(err) => { - tracing::error!( - "Matchmaker error in submitting a transaction to the \ - ledger: {}", - err - ); - } - } - } -} - -/// Return the path of the file returned by `lazy_path` argument, if it exists. -fn check_file_exists(lazy_path: impl Fn() -> PathBuf) -> Option { - let path = lazy_path(); - if path.exists() { Some(path) } else { None } -} diff --git a/apps/src/lib/node/mod.rs b/apps/src/lib/node/mod.rs index 2265547868..370e1150a2 100644 --- a/apps/src/lib/node/mod.rs +++ b/apps/src/lib/node/mod.rs @@ -1,3 +1 @@ -pub mod gossip; pub mod ledger; -pub mod matchmaker; diff --git a/apps/src/lib/proto/README.md b/apps/src/lib/proto/README.md deleted file mode 100644 index 33a3b4673d..0000000000 --- a/apps/src/lib/proto/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Protobuf Compiled Definitions - -**The source files in the `generated` directory are generated by build.rs, do not edit them by hand** diff --git a/apps/src/lib/proto/generated.rs b/apps/src/lib/proto/generated.rs deleted file mode 100644 index 4e379ae78b..0000000000 --- a/apps/src/lib/proto/generated.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod services; diff --git a/apps/src/lib/proto/generated/.gitignore b/apps/src/lib/proto/generated/.gitignore deleted file mode 100644 index 6f5f3d11d3..0000000000 --- a/apps/src/lib/proto/generated/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.rs diff --git a/apps/src/lib/proto/mod.rs b/apps/src/lib/proto/mod.rs deleted file mode 100644 index 363f4e5455..0000000000 --- a/apps/src/lib/proto/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod generated; -mod types; - -pub use generated::services; -pub use types::{IntentMessage, RpcMessage, SubscribeTopicMessage}; diff --git a/apps/src/lib/proto/types.rs b/apps/src/lib/proto/types.rs deleted file mode 100644 index c8ef8af7e3..0000000000 --- a/apps/src/lib/proto/types.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::convert::{TryFrom, TryInto}; - -use namada::proto::{Dkg, Error, Intent}; - -use super::generated::services; - -pub type Result = std::result::Result; - -pub enum RpcMessage { - IntentMessage(IntentMessage), - SubscribeTopicMessage(SubscribeTopicMessage), - Dkg(Dkg), -} - -impl From for services::RpcMessage { - fn from(message: RpcMessage) -> Self { - let message = match message { - RpcMessage::IntentMessage(m) => { - services::rpc_message::Message::Intent(m.into()) - } - RpcMessage::SubscribeTopicMessage(m) => { - services::rpc_message::Message::Topic(m.into()) - } - RpcMessage::Dkg(d) => services::rpc_message::Message::Dkg(d.into()), - }; - services::RpcMessage { - message: Some(message), - } - } -} - -impl RpcMessage { - pub fn new_intent(intent: Intent, topic: String) -> Self { - RpcMessage::IntentMessage(IntentMessage::new(intent, topic)) - } - - pub fn new_topic(topic: String) -> Self { - RpcMessage::SubscribeTopicMessage(SubscribeTopicMessage::new(topic)) - } - - pub fn new_dkg(dkg: Dkg) -> Self { - RpcMessage::Dkg(dkg) - } -} - -#[derive(Debug, PartialEq)] -pub struct IntentMessage { - pub intent: Intent, - pub topic: String, -} - -impl TryFrom for IntentMessage { - type Error = Error; - - fn try_from(message: services::IntentMessage) -> Result { - match message.intent { - Some(intent) => Ok(IntentMessage { - intent: intent.try_into()?, - topic: message.topic, - }), - None => Err(Error::NoIntentError), - } - } -} - -impl From for services::IntentMessage { - fn from(message: IntentMessage) -> Self { - services::IntentMessage { - intent: Some(message.intent.into()), - topic: message.topic, - } - } -} - -impl IntentMessage { - pub fn new(intent: Intent, topic: String) -> Self { - IntentMessage { intent, topic } - } -} - -#[derive(Debug, PartialEq)] -pub struct SubscribeTopicMessage { - pub topic: String, -} - -impl From for SubscribeTopicMessage { - fn from(message: services::SubscribeTopicMessage) -> Self { - SubscribeTopicMessage { - topic: message.topic, - } - } -} - -impl From for services::SubscribeTopicMessage { - fn from(message: SubscribeTopicMessage) -> Self { - services::SubscribeTopicMessage { - topic: message.topic, - } - } -} - -impl SubscribeTopicMessage { - pub fn new(topic: String) -> Self { - SubscribeTopicMessage { topic } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_intent_message() { - let data = "arbitrary data".as_bytes().to_owned(); - let intent = Intent::new(data); - let topic = "arbitrary string".to_owned(); - let intent_message = IntentMessage::new(intent.clone(), topic.clone()); - - let intent_rpc_message = RpcMessage::new_intent(intent, topic); - let services_rpc_message: services::RpcMessage = - intent_rpc_message.into(); - match services_rpc_message.message { - Some(services::rpc_message::Message::Intent(i)) => { - let message_from_types = - IntentMessage::try_from(i).expect("no intent"); - assert_eq!(intent_message, message_from_types); - } - _ => panic!("no intent message"), - } - } - - #[test] - fn test_topic_message() { - let topic = "arbitrary string".to_owned(); - let topic_message = SubscribeTopicMessage::new(topic.clone()); - - let topic_rpc_message = RpcMessage::new_topic(topic); - let services_rpc_message: services::RpcMessage = - topic_rpc_message.into(); - match services_rpc_message.message { - Some(services::rpc_message::Message::Topic(t)) => { - let message_from_types = SubscribeTopicMessage::from(t); - assert_eq!(topic_message, message_from_types); - } - _ => panic!("no intent message"), - } - } -} diff --git a/apps/src/lib/wallet/defaults.rs b/apps/src/lib/wallet/defaults.rs index 6a4f9dacc3..ad519d64de 100644 --- a/apps/src/lib/wallet/defaults.rs +++ b/apps/src/lib/wallet/defaults.rs @@ -4,8 +4,7 @@ pub use dev::{ addresses, albert_address, albert_keypair, bertha_address, bertha_keypair, christel_address, christel_keypair, daewon_address, daewon_keypair, keys, - matchmaker_address, matchmaker_keypair, validator_address, - validator_keypair, validator_keys, + validator_address, validator_keypair, validator_keys, }; use namada::ledger::{eth_bridge, governance, pos}; use namada::types::address::Address; @@ -107,7 +106,6 @@ mod dev { ("bertha".into(), bertha_keypair()), ("christel".into(), christel_keypair()), ("daewon".into(), daewon_keypair()), - ("matchmaker".into(), matchmaker_keypair()), ("validator".into(), validator_keypair()), ] } @@ -118,7 +116,6 @@ mod dev { ("pos".into(), pos::ADDRESS), ("pos_slash_pool".into(), pos::SLASH_POOL_ADDRESS), ("governance".into(), governance::vp::ADDRESS), - ("matchmaker".into(), matchmaker_address()), ("validator".into(), validator_address()), ("albert".into(), albert_address()), ("bertha".into(), bertha_address()), @@ -158,11 +155,6 @@ mod dev { Address::decode("atest1v4ehgw36ggcnsdee8qerswph8y6ry3p5xgunvve3xaqngd3kxc6nqwz9gseyydzzg5unys3ht2n48q").expect("The token address decoding shouldn't fail") } - /// An established matchmaker address for testing & development - pub fn matchmaker_address() -> Address { - Address::decode("atest1v4ehgw36x5mnswphx565gv2yxdprzvf5gdp523jpxy6rvv6zxaznzsejxeznzseh8pp5ywz93xwala").expect("The address decoding shouldn't fail") - } - pub fn albert_keypair() -> common::SecretKey { // generated from // [`namada::types::key::ed25519::gen_keypair`] @@ -222,16 +214,4 @@ mod dev { let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); ed_sk.try_to_sk().unwrap() } - - pub fn matchmaker_keypair() -> common::SecretKey { - // generated from - // [`namada::types::key::ed25519::gen_keypair`] - let bytes = [ - 91, 67, 244, 37, 241, 33, 157, 218, 37, 172, 191, 122, 75, 2, 44, - 219, 28, 123, 44, 34, 9, 240, 244, 49, 112, 192, 180, 98, 142, 160, - 182, 14, - ]; - let ed_sk = ed25519::SecretKey::try_from_slice(&bytes).unwrap(); - ed_sk.try_to_sk().unwrap() - } } diff --git a/documentation/docs/src/testnets/internal-testnet-1.md b/documentation/docs/src/testnets/internal-testnet-1.md index d4ad45adb6..ef376341d8 100644 --- a/documentation/docs/src/testnets/internal-testnet-1.md +++ b/documentation/docs/src/testnets/internal-testnet-1.md @@ -165,7 +165,7 @@ specified. However, any transparent account can sign these transactions. ### Build from Source -Build the provided validity predicate, transaction and matchmaker wasm modules +Build the provided validity predicate and transaction wasm modules ```shell make build-wasm-scripts-docker diff --git a/documentation/docs/src/user-guide/getting-started.md b/documentation/docs/src/user-guide/getting-started.md index 6f2ce00e30..03e77110ba 100644 --- a/documentation/docs/src/user-guide/getting-started.md +++ b/documentation/docs/src/user-guide/getting-started.md @@ -3,7 +3,7 @@ This guide assumes that the Namada binaries are [installed](./install.md) and available on path. These are: - `namada`: The main binary that can be used to interact with all the components of Namada -- `namadan`: The ledger and intent gossiper node +- `namadan`: The ledger node - `namadac`: The client - `namadaw`: The wallet diff --git a/documentation/docs/src/user-guide/intent-gossiper-and-matchmaker.md b/documentation/docs/src/user-guide/intent-gossiper-and-matchmaker.md deleted file mode 100644 index abacd929d6..0000000000 --- a/documentation/docs/src/user-guide/intent-gossiper-and-matchmaker.md +++ /dev/null @@ -1,124 +0,0 @@ -# The Intent gossiper and Matchmaker - -To run an intent gossiper node with an RPC server through which new intents can be submitted: - -```shell -anoma node gossip --rpc "127.0.0.1:26660" -``` - -To run a token exchange matchmaker: - -```shell -anoma node matchmaker --matchmaker-path libmm_token_exch --tx-code-path wasm/tx_from_intent.wasm --ledger-address "127.0.0.1:26657" --source matchmaker --signing-key matchmaker -``` - -Mind that `matchmaker` must be an established account known on the ledger with a key in your wallet that will be used to sign transactions submitted from the matchmaker to the ledger. - -This pre-built matchmaker implementation is [the fungible token exchange `mm_token_exch`](https://github.com/anoma/anoma/blob/5051b3abbc645aed2e40e1ff8db2d682e9a115e9/matchmaker/mm_token_exch/src/lib.rs), that is being used together with [the pre-built `tx_from_intent` transaction WASM](https://github.com/anoma/anoma/blob/5051b3abbc645aed2e40e1ff8db2d682e9a115e9/wasm/wasm_source/src/lib.rs#L140) to submit transaction from matched intents to the ledger. - -## ✋ Example intents - -1) Lets create some accounts: - - ```shell - anoma wallet key gen --alias alberto --unsafe-dont-encrypt - anoma client init-account --alias alberto-account --public-key alberto --source alberto - - anoma wallet key gen --alias christel --unsafe-dont-encrypt - anoma client init-account --alias christel-account --public-key christel --source christel - - anoma wallet key gen --alias bertha --unsafe-dont-encrypt - anoma client init-account --alias bertha-account --public-key bertha --source bertha - - anoma wallet key gen --alias my-matchmaker --unsafe-dont-encrypt - anoma client init-account --alias my-matchmaker-account --public-key my-matchmaker --source my-matchmaker - ``` - -1) We then need some tokens: - - ```shell - anoma client transfer --source faucet --target alberto-account --signer alberto-account --token BTC --amount 1000 - anoma client transfer --source faucet --target bertha-account --signer bertha-account --token ETH --amount 1000 - anoma client transfer --source faucet --target christel-account --signer christel-account --token NAM --amount 1000 - ``` - -1) Lets export some variables: - - ```shell - export ALBERTO=$(anoma wallet address find --alias alberto-account | cut -c 28- | tr -d '\n') - export CHRISTEL=$(anoma wallet address find --alias christel-account | cut -c 28- | tr -d '\n') - export BERTHA=$(anoma wallet address find --alias bertha-account | cut -c 28- | tr -d '\n') - export NAM=$(anoma wallet address find --alias NAM | cut -c 28- | tr -d '\n') - export BTC=$(anoma wallet address find --alias BTC | cut -c 28- | tr -d '\n') - export ETH=$(anoma wallet address find --alias ETH | cut -c 28- | tr -d '\n') - ``` - -1) Create files with the intents description: - - ```shell - echo '[{"addr":"'$ALBERTO'","key":"'$ALBERTO'","max_sell":"70","min_buy":"100","rate_min":"2","token_buy":"'$NAM'","token_sell":"'$BTC'","vp_path": "wasm_for_tests/vp_always_true.wasm"}]' > intent.A.data - - echo '[{"addr":"'$BERTHA'","key":"'$BERTHA'","max_sell":"300","min_buy":"50","rate_min":"0.7","token_buy":"'$BTC'","token_sell":"'$ETH'"}]' > intent.B.data - - echo '[{"addr":"'$CHRISTEL'","key":"'$CHRISTEL'","max_sell":"200","min_buy":"20","rate_min":"0.5","token_buy":"'$ETH'","token_sell":"'$NAM'"}]' > intent.C.data - ``` - -1) Start the ledger, intent gossiper and the matchmaker. Instruct the intent gossiper to subscribe to a topic "asset_v1": - - ```shell - anoma node ledger run - - anoma node gossip --rpc "127.0.0.1:26660" - - anoma node matchmaker --matchmaker-path wasm/mm_token_exch.wasm --tx-code-path wasm/tx_from_intent.wasm --ledger-address "127.0.0.1:26657" --source mm-1 --signing-key mm-1 - - anoma client subscribe-topic --node "http://127.0.0.1:26660" --topic "asset_v1" - ``` - -1) Submit the intents (the target gossiper node must be running an RPC server): - - ```shell - anoma client intent --data-path intent.A.data --topic "asset_v1" --signing-key alberto --node "http://127.0.0.1:26660" - anoma client intent --data-path intent.B.data --topic "asset_v1" --signing-key bertha --node "http://127.0.0.1:26660" - anoma client intent --data-path intent.C.data --topic "asset_v1" --signing-key christel --node "http://127.0.0.1:26660" - ``` - - The matchmaker should find a match from these intents and submit a transaction to the ledger that performs the n-party transfers of tokens. - -1) You can check the balances with: - - ```shell - anoma client balance --owner alberto-account - anoma client balance --owner bertha-account - anoma client balance --owner christel-account - ``` - -## 🤝 Custom matchmaker - -A custom matchmaker code can be built from [`matchmaker/mm_template`](https://github.com/anoma/anoma/tree/master/matchmaker/mm_template). - -The `anoma_macros::Matchmaker` macro can be used to derive the binding code for the matchmaker runner on any custom implementation, e.g.: - -```rust -#[derive(Default, Matchmaker)] -struct MyMatchmaker; -``` - -This macro requires that there is a `Default` implementation (derived or custom) for the matchmaker, which can be used by the runner to instantiate the matchmaker. - -The matchmaker must also implement `AddIntent`, e.g.: - -```rust -impl AddIntent for MyMatchmaker { - // This function will be called when a new intent is received - fn add_intent( - &mut self, - _intent_id: &Vec, - _intent_data: &Vec, - ) -> AddIntentResult { - AddIntentResult::default() - } -} -``` - -To submit a transaction from the matchmaker, add it to the `AddIntentResult` along with a hash set of the intent IDs that were matched into the transaction. diff --git a/genesis/dev.toml b/genesis/dev.toml index 4c48bcc279..35b8140ea9 100644 --- a/genesis/dev.toml +++ b/genesis/dev.toml @@ -114,11 +114,6 @@ address = "atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwf public_key = "d06f8d4f897f329a50fd23ba5d2503bbe22fab2f14d5f625e07a65f617eb2778" vp = "vp_user" -[established.matchmaker] -address = "atest1v4ehgw36x5mnswphx565gv2yxdprzvf5gdp523jpxy6rvv6zxaznzsejxeznzseh8pp5ywz93xwala" -public_key = "f4fe03b0d3130f077e4d51cc7748baac998750476bef994a0a73ac4e7d183168" -vp = "vp_user" - # An implicit account present at genesis. # Daewon (a1qyqzsqqqqqcyvvf5xcu5vd6rg4z5233hg9pn23pjgdryzdjy8pz52wzxxscnvvjxx3rryvzz8y5p6mtz) diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 96fd787ca2..54961a99fe 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -1,6 +1,5 @@ # Genesis configuration source for E2E tests with: -# - 1 genesis validator and intent # gossip nodes -# - a matchmaker configured on the first validator node +# - 1 genesis validator # - User accounts same as the ones in "dev" build (Albert, Bertha, Christel) genesis_time = "2021-09-30T10:00:00Z" @@ -18,13 +17,6 @@ staking_reward_vp = "vp_user" # We set the port to be the default+1000, so that if a local node was running at # the same time as the E2E tests, it wouldn't affect them. net_address = "127.0.0.1:27656" -# This has to be an alias of one of the established accounts -matchmaker_account = "matchmaker" -# A matchmaker dylib program's name (the platform specific extension -# `(dll|dylib|so)` is added by Anoma) -matchmaker_code = "libmm_token_exch" -# A transaction WASM code used by the matchmaker -matchmaker_tx = "wasm/tx_from_intent.wasm" # Some tokens present at genesis. @@ -41,8 +33,6 @@ Christel = 1000000 Daewon = 1000000 faucet = 9223372036854 "faucet.public_key" = 100 -matchmaker = 1000000 -"matchmaker.public_key" = 1000 "validator-0.public_key" = 100 [token.BTC] @@ -110,9 +100,6 @@ faucet = 9223372036854 [established.faucet] vp = "vp_testnet_faucet" -[established.matchmaker] -vp = "vp_user" - [established.Albert] vp = "vp_user" diff --git a/macros/src/lib.rs b/macros/src/lib.rs index afa49c66ab..2d6e06d66d 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -1,5 +1,5 @@ -//! Anoma macros for generating WASM binding code for transactions, validity -//! predicates and matchmaker. +//! Anoma macros for generating WASM binding code for transactions and validity +//! predicates. #![doc(html_favicon_url = "https://dev.anoma.net/master/favicon.png")] #![doc(html_logo_url = "https://dev.anoma.net/master/rustdoc-logo.png")] @@ -8,7 +8,7 @@ use proc_macro::TokenStream; use quote::quote; -use syn::{parse_macro_input, DeriveInput, ItemFn}; +use syn::{parse_macro_input, ItemFn}; /// Generate WASM binding for a transaction main entrypoint function. /// @@ -123,114 +123,3 @@ pub fn validity_predicate( }; TokenStream::from(gen) } - -/// Derive dynamic library binding for a matchmaker implementation. -/// -/// This macro requires that the data structure implements -/// [`std::default::Default`] that is used to instantiate the matchmaker and -/// `namada::types::matchmaker::AddIntent` to implement a custom matchmaker -/// algorithm. -/// -/// # Examples -/// -/// ```compiler_fail -/// use namada::types::matchmaker::AddIntent; -/// use namada_macros::Matchmaker; -/// -/// #[derive(Default, Matchmaker)] -/// struct Matchmaker; -/// -/// impl AddIntent for Matchmaker { -/// } -/// ``` -#[proc_macro_derive(Matchmaker)] -pub fn matchmaker(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let ident = &ast.ident; - // Print out the original AST and add add_intent implementation and binding - let gen = quote! { - - /// Add the marker trait - #[automatically_derived] - impl namada::types::matchmaker::Matchmaker for #ident {} - - /// Instantiate a new matchmaker and return a pointer to it. The caller is - /// responsible for making sure that the memory of the pointer will be dropped, - /// which can be done by calling the `_drop_matchmaker` function. - #[no_mangle] - #[automatically_derived] - fn _new_matchmaker() -> *mut std::ffi::c_void { - let state = Box::new(#ident::default()); - let state_ptr = Box::into_raw(state) as *mut std::ffi::c_void; - state_ptr - } - - /// Drop the matchmaker's state to reclaim its memory - #[no_mangle] - #[automatically_derived] - fn _drop_matchmaker(state_ptr: *mut std::ffi::c_void) { - // The state will be dropped on going out of scope - let _state = unsafe { Box::from_raw(state_ptr as *mut #ident) }; - } - - /// Ask the matchmaker to process a new intent - #[allow(clippy::ptr_arg)] - #[no_mangle] - #[automatically_derived] - fn _add_intent( - state_ptr: *mut std::ffi::c_void, - intent_id: &Vec, - intent_data: &Vec, - ) -> namada::types::matchmaker::AddIntentResult { - let state_ptr = state_ptr as *mut #ident; - let mut state: #ident = unsafe { std::ptr::read(state_ptr) }; - let result = state.add_intent(intent_id, intent_data); - unsafe { std::ptr::write(state_ptr, state) }; - result - } - }; - TokenStream::from(gen) -} - -/// Generate WASM binding for matchmaker filter main entrypoint function. -/// -/// This macro expects a function with signature: -/// -/// ```compiler_fail -/// fn validate_intent(intent: Vec) -> bool -/// ``` -#[proc_macro_attribute] -pub fn filter(_attr: TokenStream, input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as ItemFn); - let ident = &ast.sig.ident; - let gen = quote! { - // Use `wee_alloc` as the global allocator. - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - - #ast - - /// The module interface callable by wasm runtime - #[no_mangle] - extern "C" fn _validate_intent( - intent_data_ptr: u64, - intent_data_len: u64, - ) -> u64 { - let get_data = |ptr, len| { - let slice = unsafe { - core::slice::from_raw_parts(ptr as *const u8, len as _) - }; - slice.to_vec() - }; - - if #ident( - get_data(intent_data_ptr, intent_data_len), - ) { - 0 - } else { - 1 - } - } - }; - TokenStream::from(gen) -} diff --git a/proto/services.proto b/proto/services.proto deleted file mode 100644 index 9f16dddb91..0000000000 --- a/proto/services.proto +++ /dev/null @@ -1,30 +0,0 @@ -syntax = "proto3"; - -package services; - -import "types.proto"; - -service RPCService { - rpc SendMessage(RpcMessage) returns (RpcResponse); -} - -message IntentMessage{ - types.Intent intent = 1; - string topic = 2; -} - -message SubscribeTopicMessage{ - string topic = 2; -} - -message RpcMessage { - oneof message { - IntentMessage intent = 1; - SubscribeTopicMessage topic = 2; - types.Dkg dkg = 3; - } -} - -message RpcResponse { - string result = 1; -} diff --git a/proto/types.proto b/proto/types.proto index b4a0162b50..58494ec824 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -11,24 +11,8 @@ message Tx { google.protobuf.Timestamp timestamp = 3; } -message Intent { - bytes data = 1; - google.protobuf.Timestamp timestamp = 2; -} - -message IntentGossipMessage{ - // TODO remove oneof because it's not used so far - oneof msg { - Intent intent = 1; - } -} - -message Dkg { - string data = 1; -} +message Dkg { string data = 1; } -message DkgGossipMessage{ - oneof dkg_message { - Dkg dkg = 1; - } +message DkgGossipMessage { + oneof dkg_message { Dkg dkg = 1; } } diff --git a/shared/build.rs b/shared/build.rs index 3c5ffb41f1..74dd72b753 100644 --- a/shared/build.rs +++ b/shared/build.rs @@ -58,9 +58,6 @@ fn main() { tonic_build::configure() .out_dir("src/proto/generated") .format(use_rustfmt) - // TODO try to add json encoding to simplify use for user - // .type_attribute("types.Intent", "#[derive(serde::Serialize, - // serde::Deserialize)]") .protoc_arg("--experimental_allow_proto3_optional") .compile(&[format!("{}/types.proto", PROTO_SRC)], &[PROTO_SRC]) .unwrap(); diff --git a/shared/src/proto/mod.rs b/shared/src/proto/mod.rs index aa971f0b96..564a26e941 100644 --- a/shared/src/proto/mod.rs +++ b/shared/src/proto/mod.rs @@ -3,9 +3,7 @@ pub mod generated; mod types; -pub use types::{ - Dkg, Error, Intent, IntentGossipMessage, IntentId, Signed, SignedTxData, Tx, -}; +pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; #[cfg(test)] mod tests { diff --git a/shared/src/proto/types.rs b/shared/src/proto/types.rs index 7a936dd58f..6f7efd4b70 100644 --- a/shared/src/proto/types.rs +++ b/shared/src/proto/types.rs @@ -1,6 +1,4 @@ -use std::collections::hash_map::DefaultHasher; use std::convert::{TryFrom, TryInto}; -use std::fmt::Display; use std::hash::{Hash, Hasher}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; @@ -17,12 +15,8 @@ use crate::types::transaction::hash_tx; pub enum Error { #[error("Error decoding a transaction from bytes: {0}")] TxDecodingError(prost::DecodeError), - #[error("Error decoding an IntentGossipMessage from bytes: {0}")] - IntentDecodingError(prost::DecodeError), #[error("Error decoding an DkgGossipMessage from bytes: {0}")] DkgDecodingError(prost::DecodeError), - #[error("Intent is empty")] - NoIntentError, #[error("Dkg is empty")] NoDkgError, #[error("Timestamp is empty")] @@ -221,53 +215,6 @@ impl Tx { } } -#[derive(Clone, Debug, PartialEq)] -pub struct IntentGossipMessage { - pub intent: Intent, -} - -impl TryFrom<&[u8]> for IntentGossipMessage { - type Error = Error; - - fn try_from(intent_bytes: &[u8]) -> Result { - let intent = types::IntentGossipMessage::decode(intent_bytes) - .map_err(Error::IntentDecodingError)?; - match &intent.msg { - Some(types::intent_gossip_message::Msg::Intent(intent)) => { - Ok(IntentGossipMessage { - intent: intent.clone().try_into()?, - }) - } - None => Err(Error::NoIntentError), - } - } -} - -impl From for types::IntentGossipMessage { - fn from(message: IntentGossipMessage) -> Self { - types::IntentGossipMessage { - msg: Some(types::intent_gossip_message::Msg::Intent( - message.intent.into(), - )), - } - } -} - -impl IntentGossipMessage { - pub fn new(intent: Intent) -> Self { - IntentGossipMessage { intent } - } - - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - let message: types::IntentGossipMessage = self.clone().into(); - message - .encode(&mut bytes) - .expect("encoding an intent gossip message failed"); - bytes - } -} - #[allow(dead_code)] #[derive(Clone, Debug, PartialEq)] pub struct DkgGossipMessage { @@ -317,67 +264,6 @@ impl DkgGossipMessage { } } -#[derive(Clone, Debug, PartialEq, Hash, Eq)] -pub struct Intent { - pub data: Vec, - pub timestamp: DateTimeUtc, -} - -impl TryFrom for Intent { - type Error = Error; - - fn try_from(intent: types::Intent) -> Result { - let timestamp = match intent.timestamp { - Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, - None => return Err(Error::NoTimestampError), - }; - Ok(Intent { - data: intent.data, - timestamp, - }) - } -} - -impl From for types::Intent { - fn from(intent: Intent) -> Self { - let timestamp = Some(intent.timestamp.into()); - types::Intent { - data: intent.data, - timestamp, - } - } -} - -impl Intent { - pub fn new(data: Vec) -> Self { - Intent { - data, - timestamp: DateTimeUtc::now(), - } - } - - pub fn id(&self) -> IntentId { - let mut hasher = DefaultHasher::new(); - self.hash(&mut hasher); - IntentId::from(hasher.finish().to_string()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct IntentId(pub Vec); - -impl>> From for IntentId { - fn from(value: T) -> Self { - Self(value.into()) - } -} - -impl Display for IntentId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", hex::encode(&self.0)) - } -} - #[allow(dead_code)] #[derive(Clone, Debug, PartialEq)] pub struct Dkg { @@ -431,18 +317,6 @@ mod tests { } } - #[test] - fn test_intent_gossip_message() { - let data = "arbitrary data".as_bytes().to_owned(); - let intent = Intent::new(data); - let message = IntentGossipMessage::new(intent); - - let bytes = message.to_bytes(); - let message_from_bytes = IntentGossipMessage::try_from(bytes.as_ref()) - .expect("decoding failed"); - assert_eq!(message_from_bytes, message); - } - #[test] fn test_dkg_gossip_message() { let data = "arbitrary string".to_owned(); @@ -455,26 +329,6 @@ mod tests { assert_eq!(message_from_bytes, message); } - #[test] - fn test_intent() { - let data = "arbitrary data".as_bytes().to_owned(); - let intent = Intent::new(data.clone()); - - let types_intent: types::Intent = intent.clone().into(); - let intent_from_types = - Intent::try_from(types_intent).expect("no timestamp"); - assert_eq!(intent_from_types, intent); - - let types_intent = types::Intent { - data, - timestamp: None, - }; - match Intent::try_from(types_intent) { - Err(Error::NoTimestampError) => {} - _ => panic!("unexpected result"), - } - } - #[test] fn test_dkg() { let data = "arbitrary string".to_owned(); diff --git a/shared/src/types/intent.rs b/shared/src/types/intent.rs deleted file mode 100644 index c3effbb4fc..0000000000 --- a/shared/src/types/intent.rs +++ /dev/null @@ -1,475 +0,0 @@ -//! Intent data definitions and transaction and validity-predicate helpers. - -use std::collections::{HashMap, HashSet}; -use std::convert::TryFrom; -use std::io::ErrorKind; - -use borsh::{BorshDeserialize, BorshSerialize}; -use derivative::Derivative; -use rust_decimal::prelude::*; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::proto::Signed; -use crate::types::address::Address; -use crate::types::storage::{DbKeySeg, Key, KeySeg}; -use crate::types::token; - -/// A simple intent for fungible token trade -#[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - Eq, -)] -pub struct FungibleTokenIntent { - /// List of exchange definitions - pub exchange: HashSet>, -} - -#[derive( - Debug, - Clone, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - Eq, - PartialEq, - Hash, - PartialOrd, - Derivative, -)] -/// The definition of an intent exchange -pub struct Exchange { - /// The source address - pub addr: Address, - /// The token to be sold - pub token_sell: Address, - /// The minimum rate - pub rate_min: DecimalWrapper, - /// The maximum amount of token to be sold - pub max_sell: token::Amount, - /// The token to be bought - pub token_buy: Address, - /// The amount of token to be bought - pub min_buy: token::Amount, - /// The vp code - #[derivative(Debug = "ignore")] - pub vp: Option>, -} - -/// These are transfers crafted from matched [`Exchange`]s created by a -/// matchmaker program. -#[derive( - Debug, - Clone, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - PartialEq, -)] -pub struct MatchedExchanges { - /// Transfers crafted from the matched intents - pub transfers: HashSet, - // TODO benchmark between an map or a set, see which is less costly - /// The exchanges that were matched - pub exchanges: HashMap>, - /// The intents - // TODO: refactor this without duplicating stuff. The exchanges in the - // `exchanges` hashmap are already contained in the FungibleTokenIntents - // belows - pub intents: HashMap>, -} - -/// These are transfers crafted from matched [`Exchange`]s with a source address -/// that is expected to sign this data. -#[derive( - Debug, - Clone, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, - PartialEq, -)] -pub struct IntentTransfers { - /// Matched exchanges - pub matches: MatchedExchanges, - /// Source address that should sign this data - pub source: Address, -} - -/// Struct holding a safe rapresentation of a float -#[derive( - Debug, - Clone, - Eq, - PartialEq, - Hash, - PartialOrd, - Serialize, - Deserialize, - Default, -)] -pub struct DecimalWrapper(pub Decimal); - -impl From for DecimalWrapper { - fn from(decimal: Decimal) -> Self { - DecimalWrapper(decimal) - } -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Error parsing as decimal: {0}.")] - DecimalParseError(String), -} - -impl TryFrom for DecimalWrapper { - type Error = Error; - - fn try_from(amount: token::Amount) -> Result { - let decimal = Decimal::from_i128(amount.change()); - - match decimal { - Some(d) => Ok(DecimalWrapper::from(d)), - None => Err(Error::DecimalParseError(amount.change().to_string())), - } - } -} - -impl FromStr for DecimalWrapper { - type Err = Error; - - fn from_str(s: &str) -> Result { - let decimal = Decimal::from_str(s) - .map_err(|e| Self::Err::DecimalParseError(e.to_string())); - - match decimal { - Ok(d) => Ok(DecimalWrapper::from(d)), - Err(e) => Err(e), - } - } -} - -impl BorshSerialize for DecimalWrapper { - fn serialize( - &self, - writer: &mut W, - ) -> std::io::Result<()> { - let vec = self.0.to_string().as_bytes().to_vec(); - let bytes = vec - .try_to_vec() - .expect("DecimalWrapper bytes encoding shouldn't fail"); - writer.write_all(&bytes) - } -} - -impl BorshDeserialize for DecimalWrapper { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - // deserialize the bytes first - let bytes: Vec = - BorshDeserialize::deserialize(buf).map_err(|e| { - std::io::Error::new( - ErrorKind::InvalidInput, - format!("Error decoding DecimalWrapper: {}", e), - ) - })?; - let decimal_str: &str = - std::str::from_utf8(bytes.as_slice()).map_err(|e| { - std::io::Error::new( - ErrorKind::InvalidInput, - format!("Error decoding decimal: {}", e), - ) - })?; - let decimal = Decimal::from_str(decimal_str).map_err(|e| { - std::io::Error::new( - ErrorKind::InvalidInput, - format!("Error decoding decimal: {}", e), - ) - })?; - Ok(DecimalWrapper(decimal)) - } -} - -impl MatchedExchanges { - /// Create an empty [`MatchedExchanges`]. - pub fn empty() -> Self { - Self { - transfers: HashSet::new(), - exchanges: HashMap::new(), - intents: HashMap::new(), - } - } -} - -const INVALID_INTENT_STORAGE_KEY: &str = "invalid_intent"; - -/// Obtain a storage key for user's invalid intent set. -pub fn invalid_intent_key(owner: &Address) -> Key { - Key::from(owner.to_db_key()) - .push(&INVALID_INTENT_STORAGE_KEY.to_owned()) - .expect("Cannot obtain a storage key") -} - -/// Check if the given storage key is a key for a set of intent sig. If it is, -/// returns the owner. -pub fn is_invalid_intent_key(key: &Key) -> Option<&Address> { - match &key.segments[..] { - [DbKeySeg::AddressSeg(owner), DbKeySeg::StringSeg(key)] - if key == INVALID_INTENT_STORAGE_KEY => - { - Some(owner) - } - _ => None, - } -} - -#[cfg(test)] -mod tests { - use std::env; - use std::iter::FromIterator; - - use constants::*; - - use super::*; - use crate::ledger::storage::types::{decode, encode}; - use crate::types::key; - - #[test] - fn test_encode_decode_intent_transfer_without_vp() { - let bertha_addr = Address::from_str(BERTHA).unwrap(); - let albert_addr = Address::from_str(ALBERT).unwrap(); - - let bertha_keypair = key::testing::keypair_1(); - let albert_keypair = key::testing::keypair_2(); - - let exchange_one = Exchange { - addr: Address::from_str(BERTHA).unwrap(), - token_buy: Address::from_str(XAN).unwrap(), - token_sell: Address::from_str(BTC).unwrap(), - max_sell: token::Amount::from(100), - min_buy: token::Amount::from(1), - rate_min: DecimalWrapper::from_str("0.1").unwrap(), - vp: None, - }; - let exchange_two = Exchange { - addr: Address::from_str(ALBERT).unwrap(), - token_buy: Address::from_str(BTC).unwrap(), - token_sell: Address::from_str(XAN).unwrap(), - max_sell: token::Amount::from(1), - min_buy: token::Amount::from(100), - rate_min: DecimalWrapper::from_str("10").unwrap(), - vp: None, - }; - - let signed_exchange_one = Signed::new(&bertha_keypair, exchange_one); - let signed_exchange_two = Signed::new(&bertha_keypair, exchange_two); - - let mut it = MatchedExchanges::empty(); - it.exchanges = HashMap::<_, _>::from_iter( - vec![ - (bertha_addr.clone(), signed_exchange_one.clone()), - (albert_addr.clone(), signed_exchange_two.clone()), - ] - .into_iter(), - ); - - it.intents = HashMap::<_, _>::from_iter( - vec![ - ( - bertha_addr.clone(), - Signed::new( - &bertha_keypair, - FungibleTokenIntent { - exchange: HashSet::from_iter(vec![ - signed_exchange_one, - ]), - }, - ), - ), - ( - albert_addr.clone(), - Signed::new( - &albert_keypair, - FungibleTokenIntent { - exchange: HashSet::from_iter(vec![ - signed_exchange_two, - ]), - }, - ), - ), - ] - .into_iter(), - ); - - it.transfers = HashSet::<_>::from_iter( - vec![ - token::Transfer { - source: bertha_addr.clone(), - target: albert_addr.clone(), - token: Address::from_str(BTC).unwrap(), - amount: token::Amount::from(100), - }, - token::Transfer { - source: albert_addr, - target: bertha_addr, - token: Address::from_str(XAN).unwrap(), - amount: token::Amount::from(1), - }, - ] - .into_iter(), - ); - - let encoded_intent_transfer = encode(&it); - let decoded_intent_transfer: MatchedExchanges = - decode(encoded_intent_transfer).unwrap(); - - assert!(decoded_intent_transfer == it); - } - - #[test] - fn test_encode_decode_intent_transfer_with_vp() { - let bertha_addr = Address::from_str(BERTHA).unwrap(); - let albert_addr = Address::from_str(ALBERT).unwrap(); - - let bertha_keypair = key::testing::keypair_1(); - let albert_keypair = key::testing::keypair_2(); - - let working_dir = env::current_dir().unwrap(); - - let exchange_one = Exchange { - addr: Address::from_str(BERTHA).unwrap(), - token_buy: Address::from_str(XAN).unwrap(), - token_sell: Address::from_str(BTC).unwrap(), - max_sell: token::Amount::from(100), - min_buy: token::Amount::from(1), - rate_min: DecimalWrapper::from_str("0.1").unwrap(), - vp: Some( - std::fs::read(format!( - "{}/../{}", - working_dir.to_string_lossy(), - VP_ALWAYS_FALSE_WASM - )) - .unwrap(), - ), - }; - let exchange_two = Exchange { - addr: Address::from_str(ALBERT).unwrap(), - token_buy: Address::from_str(BTC).unwrap(), - token_sell: Address::from_str(XAN).unwrap(), - max_sell: token::Amount::from(1), - min_buy: token::Amount::from(100), - rate_min: DecimalWrapper::from_str("10").unwrap(), - vp: Some( - std::fs::read(format!( - "{}/../{}", - working_dir.to_string_lossy(), - VP_ALWAYS_TRUE_WASM - )) - .unwrap(), - ), - }; - - let signed_exchange_one = Signed::new(&bertha_keypair, exchange_one); - let signed_exchange_two = Signed::new(&bertha_keypair, exchange_two); - - let mut it = MatchedExchanges::empty(); - it.exchanges = HashMap::<_, _>::from_iter( - vec![ - (bertha_addr.clone(), signed_exchange_one.clone()), - (albert_addr.clone(), signed_exchange_two.clone()), - ] - .into_iter(), - ); - - it.intents = HashMap::<_, _>::from_iter( - vec![ - ( - bertha_addr.clone(), - Signed::new( - &bertha_keypair, - FungibleTokenIntent { - exchange: HashSet::from_iter(vec![ - signed_exchange_one, - ]), - }, - ), - ), - ( - albert_addr.clone(), - Signed::new( - &albert_keypair, - FungibleTokenIntent { - exchange: HashSet::from_iter(vec![ - signed_exchange_two, - ]), - }, - ), - ), - ] - .into_iter(), - ); - - it.transfers = HashSet::<_>::from_iter( - vec![ - token::Transfer { - source: bertha_addr.clone(), - target: albert_addr.clone(), - token: Address::from_str(BTC).unwrap(), - amount: token::Amount::from(100), - }, - token::Transfer { - source: albert_addr, - target: bertha_addr, - token: Address::from_str(XAN).unwrap(), - amount: token::Amount::from(1), - }, - ] - .into_iter(), - ); - - let encoded_intent_transfer = encode(&it); - let decoded_intent_transfer: MatchedExchanges = - decode(encoded_intent_transfer).unwrap(); - - assert!(decoded_intent_transfer == it); - } - - #[cfg(test)] - #[allow(dead_code)] - mod constants { - - // User addresses - pub const ALBERT: &str = "atest1v4ehgw368ycryv2z8qcnxv3cxgmrgvjpxs6yg333gym5vv2zxepnj334g4rryvj9xucrgve4x3xvr4"; - pub const BERTHA: &str = "atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw"; - pub const CHRISTEL: &str = "atest1v4ehgw36x3qng3jzggu5yvpsxgcngv2xgguy2dpkgvu5x33kx3pr2w2zgep5xwfkxscrxs2pj8075p"; - - // Fungible token addresses - pub const XAN: &str = "atest1v4ehgw36x3prswzxggunzv6pxqmnvdj9xvcyzvpsggeyvs3cg9qnywf589qnwvfsg5erg3fkl09rg5"; - pub const BTC: &str = "atest1v4ehgw36xdzryve5gsc52veeg5cnsv2yx5eygvp38qcrvd29xy6rys6p8yc5xvp4xfpy2v694wgwcp"; - pub const ETH: &str = "atest1v4ehgw36xqmr2d3nx3ryvd2xxgmrq33j8qcns33sxezrgv6zxdzrydjrxveygd2yxumrsdpsf9jc2p"; - pub const DOT: &str = "atest1v4ehgw36gg6nvs2zgfpyxsfjgc65yv6pxy6nwwfsxgungdzrggeyzv35gveyxsjyxymyz335hur2jn"; - - // Bite-sized tokens - pub const SCHNITZEL: &str = "atest1v4ehgw36xue5xvf5xvuyzvpjx5un2v3k8qeyvd3cxdqns32p89rrxd6xx9zngvpegccnzs699rdnnt"; - pub const APFEL: &str = "atest1v4ehgw36gfryydj9g3p5zv3kg9znyd358ycnzsfcggc5gvecgc6ygs2rxv6ry3zpg4zrwdfeumqcz9"; - pub const KARTOFFEL: &str = "atest1v4ehgw36gep5ysecxq6nyv3jg3zygv3e89qn2vp48pryxsf4xpznvve5gvmy23fs89pryvf5a6ht90"; - - // Paths to the WASMs used for tests - pub const TX_TRANSFER_WASM: &str = "wasm/tx_transfer.wasm"; - pub const VP_USER_WASM: &str = "wasm/vp_user.wasm"; - pub const TX_NO_OP_WASM: &str = "wasm_for_tests/tx_no_op.wasm"; - pub const VP_ALWAYS_TRUE_WASM: &str = - "wasm_for_tests/vp_always_true.wasm"; - pub const VP_ALWAYS_FALSE_WASM: &str = - "wasm_for_tests/vp_always_false.wasm"; - } -} diff --git a/shared/src/types/matchmaker.rs b/shared/src/types/matchmaker.rs deleted file mode 100644 index 5ee67a83ed..0000000000 --- a/shared/src/types/matchmaker.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Matchmaker types - -use std::collections::HashSet; - -/// A matchmaker marker trait. This should not be implemented manually. Instead, -/// it is added by the derive `Matchmaker` macro, which also adds necessary -/// binding code for matchmaker dylib runner. -pub trait Matchmaker: AddIntent {} - -/// A matchmaker must implement this trait -pub trait AddIntent: Default { - // TODO: For some reason, using `&[u8]` causes the `decode_intent_data` to - // fail decoding - /// Add a new intent to matchmaker's state - #[allow(clippy::ptr_arg)] - fn add_intent( - &mut self, - intent_id: &Vec, - intent_data: &Vec, - ) -> AddIntentResult; -} - -/// The result of calling matchmaker's `add_intent` function -#[derive(Clone, Debug, Default)] -pub struct AddIntentResult { - /// A transaction matched from the intent, if any - pub tx: Option>, - /// The intent IDs that were matched into the tx, if any - pub matched_intents: Option>>, -} diff --git a/shared/src/types/mod.rs b/shared/src/types/mod.rs index 4b8d7288ca..240fbdb70c 100644 --- a/shared/src/types/mod.rs +++ b/shared/src/types/mod.rs @@ -6,10 +6,8 @@ pub mod dylib; pub mod governance; pub mod hash; pub mod ibc; -pub mod intent; pub mod internal; pub mod key; -pub mod matchmaker; pub mod nft; pub mod storage; pub mod time; diff --git a/shared/src/vm/mod.rs b/shared/src/vm/mod.rs index 88c8803836..2e14666f81 100644 --- a/shared/src/vm/mod.rs +++ b/shared/src/vm/mod.rs @@ -1,5 +1,4 @@ -//! Virtual machine modules for running transactions, validity predicates, -//! matchmaker and matchmaker's filter. +//! Virtual machine modules for running transactions and validity predicates. use std::ffi::c_void; use std::marker::PhantomData; diff --git a/shared/src/vm/types.rs b/shared/src/vm/types.rs index c2a9ef11cb..480190ad08 100644 --- a/shared/src/vm/types.rs +++ b/shared/src/vm/types.rs @@ -29,9 +29,6 @@ pub struct VpInput<'a> { pub verifiers: &'a BTreeSet
, } -/// Input for matchmaker wasm module call -pub type MatchmakerInput = Vec; - /// Key-value pair represents data from account's subspace #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct KeyVal { diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 4ae17e8fff..cc07e3704f 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -38,7 +38,6 @@ file-serve = "0.2.0" fs_extra = "1.2.0" hex = "0.4.3" itertools = "0.10.0" -libp2p = "0.38.0" pretty_assertions = "0.7.2" # A fork with state machine testing proptest = {git = "https://github.com/heliaxdev/proptest", branch = "tomas/sm"} diff --git a/tests/src/e2e.rs b/tests/src/e2e.rs index a9eb2b2cf5..0afc343098 100644 --- a/tests/src/e2e.rs +++ b/tests/src/e2e.rs @@ -12,7 +12,6 @@ //! `ANOMA_E2E_KEEP_TEMP=true`. pub mod eth_bridge_tests; -pub mod gossip_tests; pub mod helpers; pub mod ledger_tests; pub mod setup; diff --git a/tests/src/e2e/gossip_tests.rs b/tests/src/e2e/gossip_tests.rs deleted file mode 100644 index 5da19c0758..0000000000 --- a/tests/src/e2e/gossip_tests.rs +++ /dev/null @@ -1,348 +0,0 @@ -//! By default, these tests will run in release mode. This can be disabled -//! by setting environment variable `ANOMA_E2E_DEBUG=true`. For debugging, -//! you'll typically also want to set `RUST_BACKTRACE=1`, e.g.: -//! -//! ```ignore,shell -//! ANOMA_E2E_DEBUG=true RUST_BACKTRACE=1 cargo test e2e::gossip_tests -- --test-threads=1 --nocapture -//! ``` -//! -//! To keep the temporary files created by a test, use env var -//! `ANOMA_E2E_KEEP_TEMP=true`. - -use std::env; -use std::fs::OpenOptions; -use std::path::PathBuf; - -use color_eyre::eyre::Result; -use escargot::CargoBuild; -use serde_json::json; -use setup::constants::*; - -use super::setup::ENV_VAR_DEBUG; -use crate::e2e::helpers::{ - find_address, get_actor_rpc, get_gossiper_mm_server, -}; -use crate::e2e::setup::{self, Bin, Who}; -use crate::{run, run_as}; - -/// Test that when we "run-gossip" a peer with no seeds should fail -/// bootstrapping kademlia. A peer with a seed should be able to -/// bootstrap kademia and connect to the other peer. -/// In this test we: -/// 1. Check that a gossip node can start and stop cleanly -/// 2. Check that two peers connected to the same seed node discover each other -#[test] -#[ignore] // this is not currently being developed, run with `cargo test -- --ignored` -fn run_gossip() -> Result<()> { - let test = - setup::network(|genesis| setup::add_validators(2, genesis), None)?; - - // 1. Start the first gossip node and then stop it - let mut node_0 = - run_as!(test, Who::Validator(0), Bin::Node, &["gossip"], Some(40))?; - node_0.send_control('c')?; - node_0.exp_eof()?; - drop(node_0); - - // 2. Check that two peers connected to the same seed node discover each - // other. Start the first gossip node again (the seed node). - let mut node_0 = - run_as!(test, Who::Validator(0), Bin::Node, &["gossip"], Some(40))?; - let (_unread, matched) = node_0.exp_regex(r"Peer id: PeerId\(.*\)")?; - let node_0_peer_id = matched - .trim() - .rsplit_once('\"') - .unwrap() - .0 - .rsplit_once('\"') - .unwrap() - .1; - let _bg_node_0 = node_0.background(); - - // Start the second gossip node (a peer node) - let mut node_1 = - run_as!(test, Who::Validator(1), Bin::Node, &["gossip"], Some(40))?; - - let (_unread, matched) = node_1.exp_regex(r"Peer id: PeerId\(.*\)")?; - let node_1_peer_id = matched - .trim() - .rsplit_once('\"') - .unwrap() - .0 - .rsplit_once('\"') - .unwrap() - .1; - node_1.exp_string(&format!( - "Connect to a new peer: PeerId(\"{}\")", - node_0_peer_id - ))?; - let _bg_node_1 = node_1.background(); - - // Start the third gossip node (another peer node) - let mut node_2 = - run_as!(test, Who::Validator(2), Bin::Node, &["gossip"], Some(20))?; - // The third node should connect to node 1 via Identify and Kademlia peer - // discovery protocol - node_2.exp_string(&format!( - "Connect to a new peer: PeerId(\"{}\")", - node_1_peer_id - ))?; - node_2.exp_string(&format!("Identified Peer {}", node_1_peer_id))?; - node_2 - .exp_string(&format!("Routing updated peer ID: {}", node_1_peer_id))?; - - Ok(()) -} - -/// This test runs a ledger node and 2 gossip nodes. It then crafts 3 intents -/// and sends them to the matchmaker. The matchmaker should be able to match -/// them into a transfer transaction and submit it to the ledger. -#[test] -#[ignore] // this is not currently being developed, run with `cargo test -- --ignored` -fn match_intents() -> Result<()> { - let test = setup::single_node_net()?; - - // Make sure that the default matchmaker is built - println!("Building the matchmaker \"mm_token_exch\" implementation..."); - let run_debug = match env::var(ENV_VAR_DEBUG) { - Ok(val) => val.to_ascii_lowercase() != "false", - _ => false, - }; - let manifest_path = test - .working_dir - .join("matchmaker") - .join("mm_token_exch") - .join("Cargo.toml"); - let cmd = CargoBuild::new().manifest_path(manifest_path); - let cmd = if run_debug { cmd } else { cmd.release() }; - let msgs = cmd.exec().unwrap(); - for msg in msgs { - msg.unwrap(); - } - println!("Done building the matchmaker."); - - let mut ledger = - run_as!(test, Who::Validator(0), Bin::Node, &["ledger"], Some(40))?; - ledger.exp_string("Anoma ledger node started")?; - ledger.exp_string("No state could be found")?; - // Wait to commit a block - ledger.exp_regex(r"Committed block hash.*, height: [0-9]+")?; - let bg_ledger = ledger.background(); - - let intent_a_path_input = test.test_dir.path().join("intent.A.data"); - let intent_b_path_input = test.test_dir.path().join("intent.B.data"); - let intent_c_path_input = test.test_dir.path().join("intent.C.data"); - - let albert = find_address(&test, ALBERT)?; - let bertha = find_address(&test, BERTHA)?; - let christel = find_address(&test, CHRISTEL)?; - let xan = find_address(&test, XAN)?; - let btc = find_address(&test, BTC)?; - let eth = find_address(&test, ETH)?; - let intent_a_json = json!([ - { - "key": bertha, - "addr": bertha, - "min_buy": "100.0", - "max_sell": "70", - "token_buy": xan, - "token_sell": btc, - "rate_min": "2", - "vp_path": test.working_dir.join(VP_ALWAYS_TRUE_WASM).to_string_lossy().into_owned(), - } - ]); - - let intent_b_json = json!([ - { - "key": albert, - "addr": albert, - "min_buy": "50", - "max_sell": "300", - "token_buy": btc, - "token_sell": eth, - "rate_min": "0.7" - } - ]); - let intent_c_json = json!([ - { - "key": christel, - "addr": christel, - "min_buy": "20", - "max_sell": "200", - "token_buy": eth, - "token_sell": xan, - "rate_min": "0.5" - } - ]); - generate_intent_json(intent_a_path_input.clone(), intent_a_json); - generate_intent_json(intent_b_path_input.clone(), intent_b_json); - generate_intent_json(intent_c_path_input.clone(), intent_c_json); - - let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); - let validator_one_gossiper = - get_gossiper_mm_server(&test, &Who::Validator(0)); - - // The RPC port starts at 27660 (see `setup::network`) - let rpc_port = 27660; - let rpc_address = format!("127.0.0.1:{}", rpc_port); - - // Start intent gossiper node - let mut gossiper = run_as!( - test, - Who::Validator(0), - Bin::Node, - &["gossip", "--rpc", &rpc_address], - Some(20) - )?; - - // Wait gossip to start - gossiper.exp_string(&format!("RPC started at {}", rpc_address))?; - let _bg_gossiper = gossiper.background(); - - // Start matchmaker - let mut matchmaker = run_as!( - test, - Who::Validator(0), - Bin::Node, - &[ - "matchmaker", - "--source", - "matchmaker", - "--signing-key", - "matchmaker-key", - "--ledger-address", - &validator_one_rpc, - "--intent-gossiper", - &validator_one_gossiper, - ], - Some(40) - )?; - - // Wait for the matchmaker to start - matchmaker.exp_string("Connected to the server")?; - let bg_matchmaker = matchmaker.background(); - - let rpc_address = format!("http://{}", rpc_address); - // Send intent A - let mut session_send_intent_a = run!( - test, - Bin::Client, - &[ - "intent", - "--node", - &rpc_address, - "--data-path", - intent_a_path_input.to_str().unwrap(), - "--topic", - "asset_v1", - "--signing-key", - BERTHA_KEY, - "--ledger-address", - &validator_one_rpc - ], - Some(40), - )?; - - // means it sent it correctly but not able to gossip it (which is - // correct since there is only 1 node) - session_send_intent_a.exp_string( - "Failed to publish intent in gossiper: InsufficientPeers", - )?; - drop(session_send_intent_a); - - let mut matchmaker = bg_matchmaker.foreground(); - matchmaker.exp_string("trying to match new intent")?; - let bg_matchmaker = matchmaker.background(); - - // Send intent B - let mut session_send_intent_b = run!( - test, - Bin::Client, - &[ - "intent", - "--node", - &rpc_address, - "--data-path", - intent_b_path_input.to_str().unwrap(), - "--topic", - "asset_v1", - "--signing-key", - ALBERT_KEY, - "--ledger-address", - &validator_one_rpc - ], - Some(40), - )?; - - // means it sent it correctly but not able to gossip it (which is - // correct since there is only 1 node) - session_send_intent_b.exp_string( - "Failed to publish intent in gossiper: InsufficientPeers", - )?; - drop(session_send_intent_b); - - let mut matchmaker = bg_matchmaker.foreground(); - matchmaker.exp_string("trying to match new intent")?; - let bg_matchmaker = matchmaker.background(); - - // Send intent C - let mut session_send_intent_c = run!( - test, - Bin::Client, - &[ - "intent", - "--node", - &rpc_address, - "--data-path", - intent_c_path_input.to_str().unwrap(), - "--topic", - "asset_v1", - "--signing-key", - CHRISTEL_KEY, - "--ledger-address", - &validator_one_rpc - ], - Some(40), - )?; - - // means it sent it correctly but not able to gossip it (which is - // correct since there is only 1 node) - session_send_intent_c.exp_string( - "Failed to publish intent in gossiper: InsufficientPeers", - )?; - drop(session_send_intent_c); - - // check that the transfers transactions are correct - let mut matchmaker = bg_matchmaker.foreground(); - matchmaker.exp_string(&format!( - "crafting transfer: {}, {}, 70", - bertha, albert - ))?; - matchmaker.exp_string(&format!( - "crafting transfer: {}, {}, 200", - christel, bertha - ))?; - matchmaker.exp_string(&format!( - "crafting transfer: {}, {}, 100", - albert, christel - ))?; - - // check that the all VPs accept the transaction - let mut ledger = bg_ledger.foreground(); - ledger.exp_string("all VPs accepted transaction")?; - - Ok(()) -} - -fn generate_intent_json( - intent_path: PathBuf, - exchange_json: serde_json::Value, -) { - let intent_writer = OpenOptions::new() - .create(true) - .write(true) - .truncate(true) - .open(intent_path) - .unwrap(); - serde_json::to_writer(intent_writer, &exchange_json).unwrap(); -} diff --git a/tests/src/e2e/helpers.rs b/tests/src/e2e/helpers.rs index cc0c45cf8c..705c822760 100644 --- a/tests/src/e2e/helpers.rs +++ b/tests/src/e2e/helpers.rs @@ -42,7 +42,7 @@ pub fn find_address(test: &Test, alias: impl AsRef) -> Result
{ Ok(address) } -/// Find the address of the intent gossiper node's RPC endpoint. +/// Find the address of the node's RPC endpoint. pub fn get_actor_rpc(test: &Test, who: &Who) -> String { let base_dir = test.get_base_dir(who); let tendermint_mode = match who { @@ -54,18 +54,6 @@ pub fn get_actor_rpc(test: &Test, who: &Who) -> String { config.ledger.tendermint.rpc_address.to_string() } -/// Find the address of the intent gossiper node's matchmakers server. -pub fn get_gossiper_mm_server(test: &Test, who: &Who) -> String { - let base_dir = test.get_base_dir(who); - let tendermint_mode = match who { - Who::NonValidator => TendermintMode::Full, - Who::Validator(_) => TendermintMode::Validator, - }; - let config = - Config::load(&base_dir, &test.net.chain_id, Some(tendermint_mode)); - config.intent_gossiper.matchmakers_server_addr.to_string() -} - /// Find the address of an account by its alias from the wallet #[allow(dead_code)] pub fn find_keypair( diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6499bf9806..d1df560108 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -65,21 +65,12 @@ pub fn add_validators(num: u8, mut genesis: GenesisConfig) -> GenesisConfig { let validator_0 = genesis.validator.get_mut("validator-0").unwrap(); // Clone the first validator before modifying it let other_validators = validator_0.clone(); - // Set the first validator to be a bootstrap node to enable P2P connectivity - validator_0.intent_gossip_seed = Some(true); - // A bootstrap node doesn't participate in the gossipsub protocol for - // gossiping intents, so we remove its matchmaker - validator_0.matchmaker_account = None; - validator_0.matchmaker_code = None; - validator_0.matchmaker_tx = None; let net_address_0 = SocketAddr::from_str(validator_0.net_address.as_ref().unwrap()) .unwrap(); let net_address_port_0 = net_address_0.port(); for ix in 0..num { let mut validator = other_validators.clone(); - // Only the first validator is bootstrap - validator.intent_gossip_seed = None; let mut net_address = net_address_0; // 6 ports for each validator let first_port = net_address_port_0 + 6 * (ix as u16 + 1); @@ -769,7 +760,6 @@ pub mod constants { pub const CHRISTEL: &str = "Christel"; pub const CHRISTEL_KEY: &str = "Christel-key"; pub const DAEWON: &str = "Daewon"; - pub const MATCHMAKER_KEY: &str = "matchmaker-key"; // Native VP aliases pub const GOVERNANCE_ADDRESS: &str = "governance"; diff --git a/vm_env/src/intent.rs b/vm_env/src/intent.rs deleted file mode 100644 index 226cb708db..0000000000 --- a/vm_env/src/intent.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::collections::HashSet; - -use namada::proto::Signed; -use namada::types::intent; -use namada::types::key::*; - -/// Tx imports and functions. -pub mod tx { - pub use namada::types::intent::*; - - use super::*; - pub fn invalidate_exchange(intent: &Signed) { - use crate::imports::tx; - let key = intent::invalid_intent_key(&intent.data.addr); - let mut invalid_intent: HashSet = - tx::read(&key.to_string()).unwrap_or_default(); - invalid_intent.insert(intent.sig.clone()); - tx::write(&key.to_string(), &invalid_intent) - } -} - -/// Vp imports and functions. -pub mod vp { - pub use namada::types::intent::*; - - use super::*; - - pub fn vp_exchange(intent: &Signed) -> bool { - use crate::imports::vp; - let key = intent::invalid_intent_key(&intent.data.addr); - - let invalid_intent_pre: HashSet = - vp::read_pre(&key.to_string()).unwrap_or_default(); - let invalid_intent_post: HashSet = - vp::read_post(&key.to_string()).unwrap_or_default(); - !invalid_intent_pre.contains(&intent.sig) - && invalid_intent_post.contains(&intent.sig) - } -} diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 578079d695..0da9ae9804 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -9,7 +9,6 @@ pub mod governance; pub mod ibc; pub mod imports; -pub mod intent; pub mod key; pub mod nft; pub mod proof_of_stake; @@ -29,7 +28,6 @@ pub mod tx_prelude { pub use crate::governance::tx as governance; pub use crate::ibc::{Ibc, IbcActions}; pub use crate::imports::tx::*; - pub use crate::intent::tx as intent; pub use crate::nft::tx as nft; pub use crate::proof_of_stake::{self, PoS, PosRead, PosWrite}; pub use crate::token::tx as token; @@ -48,7 +46,6 @@ pub mod vp_prelude { pub use namada_macros::validity_predicate; pub use crate::imports::vp::*; - pub use crate::intent::vp as intent; pub use crate::key::vp as key; pub use crate::nft::vp as nft; pub use crate::token::vp as token; diff --git a/wasm/wasm_source/src/lib.rs b/wasm/wasm_source/src/lib.rs index 3a674ad97b..8929992754 100644 --- a/wasm/wasm_source/src/lib.rs +++ b/wasm/wasm_source/src/lib.rs @@ -1,7 +1,5 @@ #[cfg(feature = "tx_bond")] pub mod tx_bond; -#[cfg(feature = "tx_from_intent")] -pub mod tx_from_intent; #[cfg(feature = "tx_ibc")] pub mod tx_ibc; #[cfg(feature = "tx_init_account")] diff --git a/wasm/wasm_source/src/tx_from_intent.rs b/wasm/wasm_source/src/tx_from_intent.rs deleted file mode 100644 index e39963fae7..0000000000 --- a/wasm/wasm_source/src/tx_from_intent.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! A tx for a token transfer crafted by matchmaker from intents. -//! This tx uses `intent::IntentTransfers` wrapped inside -//! `SignedTxData` as its input as declared in `shared` crate. - -use namada_tx_prelude::*; - -#[transaction] -fn apply_tx(tx_data: Vec) { - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - - let tx_data = - intent::IntentTransfers::try_from_slice(&signed.data.unwrap()[..]); - - let tx_data = tx_data.unwrap(); - - // make sure that the matchmaker has to validate this tx - insert_verifier(&tx_data.source); - - for token::Transfer { - source, - target, - token, - amount, - } in tx_data.matches.transfers - { - token::transfer(&source, &target, &token, amount); - } - - tx_data - .matches - .exchanges - .values() - .into_iter() - .for_each(intent::invalidate_exchange); -} diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index a222a344ef..fbd606b182 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -6,24 +6,15 @@ //! It allows to bond, unbond and withdraw tokens to and from PoS system with a //! valid signature. //! -//! It allows to fulfil intents that were signed by this account's key if they -//! haven't already been fulfilled (fulfilled intents are added to the owner's -//! invalid intent set). -//! //! Any other storage key changes are allowed only with a valid signature. -use namada_vp_prelude::intent::{ - Exchange, FungibleTokenIntent, IntentTransfers, -}; use namada_vp_prelude::storage::KeySeg; use namada_vp_prelude::*; use once_cell::unsync::Lazy; -use rust_decimal::prelude::*; enum KeyType<'a> { Token(&'a Address), PoS, - InvalidIntentSet(&'a Address), Nft(&'a Address), Vp(&'a Address), GovernanceVote(&'a Address), @@ -36,8 +27,6 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { Self::Token(address) } else if proof_of_stake::is_pos_key(key) { Self::PoS - } else if let Some(address) = intent::is_invalid_intent_key(key) { - Self::InvalidIntentSet(address) } else if let Some(address) = nft::is_nft_key(key) { Self::Nft(address) } else if gov_storage::is_vote_key(key) { @@ -83,11 +72,6 @@ fn validate_tx( _ => false, }); - let valid_intent = Lazy::new(|| match &*signed_tx_data { - Ok(signed_tx_data) => check_intent_transfers(&addr, signed_tx_data), - _ => false, - }); - if !is_tx_whitelisted() { return false; } @@ -103,14 +87,13 @@ fn validate_tx( read_post(&key).unwrap_or_default(); let change = post.change() - pre.change(); // debit has to signed, credit doesn't - let valid = change >= 0 || *valid_sig || *valid_intent; + let valid = change >= 0 || *valid_sig; debug_log!( - "token key: {}, change: {}, valid_sig: {}, \ - valid_intent: {}, valid modification: {}", + "token key: {}, change: {}, valid_sig: {}, valid \ + modification: {}", key, change, *valid_sig, - *valid_intent, valid ); valid @@ -148,27 +131,6 @@ fn validate_tx( ); valid } - KeyType::InvalidIntentSet(owner) => { - if owner == &addr { - let key = key.to_string(); - let pre: HashSet = - read_pre(&key).unwrap_or_default(); - let post: HashSet = - read_post(&key).unwrap_or_default(); - // A new invalid intent must have been added - pre.len() + 1 == post.len() - } else { - debug_log!( - "This address ({}) is not of owner ({}) of \ - InvalidIntentSet key: {}", - addr, - owner, - key - ); - // If this is not the owner, allow any change - true - } - } KeyType::Nft(owner) => { if owner == &addr { *valid_sig @@ -218,148 +180,6 @@ fn validate_tx( true } -fn check_intent_transfers( - addr: &Address, - signed_tx_data: &SignedTxData, -) -> bool { - if let Some((raw_intent_transfers, exchange, intent)) = - try_decode_intent(addr, signed_tx_data) - { - log_string("check intent"); - return check_intent(addr, exchange, intent, raw_intent_transfers); - } - false -} - -fn try_decode_intent( - addr: &Address, - signed_tx_data: &SignedTxData, -) -> Option<( - Vec, - namada_vp_prelude::Signed, - namada_vp_prelude::Signed, -)> { - let raw_intent_transfers = signed_tx_data.data.as_ref().cloned()?; - let mut tx_data = - IntentTransfers::try_from_slice(&raw_intent_transfers[..]).ok()?; - debug_log!( - "tx_data.matches.exchanges: {:?}, {}", - tx_data.matches.exchanges, - &addr - ); - if let (Some(exchange), Some(intent)) = ( - tx_data.matches.exchanges.remove(addr), - tx_data.matches.intents.remove(addr), - ) { - return Some((raw_intent_transfers, exchange, intent)); - } else { - log_string("no intent with a matching address"); - } - None -} - -fn check_intent( - addr: &Address, - exchange: namada_vp_prelude::Signed, - intent: namada_vp_prelude::Signed, - raw_intent_transfers: Vec, -) -> bool { - // verify signature - let pk = key::get(addr); - if let Some(pk) = pk { - if intent.verify(&pk).is_err() { - log_string("invalid sig"); - return false; - } - } else { - return false; - } - - // verify the intent have not been already used - if !intent::vp_exchange(&exchange) { - return false; - } - - // verify the intent is fulfilled - let Exchange { - addr, - token_sell, - rate_min, - token_buy, - min_buy, - max_sell, - vp, - } = &exchange.data; - - debug_log!("vp is: {}", vp.is_some()); - - if let Some(code) = vp { - let eval_result = eval(code.to_vec(), raw_intent_transfers); - debug_log!("eval result: {}", eval_result); - if !eval_result { - return false; - } - } - - debug_log!( - "exchange description: {}, {}, {}, {}, {}", - token_sell, - token_buy, - max_sell.change(), - min_buy.change(), - rate_min.0 - ); - - let token_sell_key = token::balance_key(token_sell, addr).to_string(); - let mut sell_difference: token::Amount = - read_pre(&token_sell_key).unwrap_or_default(); - let sell_post: token::Amount = - read_post(token_sell_key).unwrap_or_default(); - - sell_difference.spend(&sell_post); - - let token_buy_key = token::balance_key(token_buy, addr).to_string(); - let buy_pre: token::Amount = read_pre(&token_buy_key).unwrap_or_default(); - let mut buy_difference: token::Amount = - read_post(token_buy_key).unwrap_or_default(); - - buy_difference.spend(&buy_pre); - - let sell_diff: Decimal = sell_difference.change().into(); // -> how many token I sold - let buy_diff: Decimal = buy_difference.change().into(); // -> how many token I got - - debug_log!( - "buy_diff > 0: {}, rate check: {}, max_sell > sell_diff: {}, buy_diff \ - > min_buy: {}", - buy_difference.change() > 0, - buy_diff / sell_diff >= rate_min.0, - max_sell.change() >= sell_difference.change(), - buy_diff >= min_buy.change().into() - ); - - if !(buy_difference.change() > 0 - && (buy_diff / sell_diff >= rate_min.0) - && max_sell.change() >= sell_difference.change() - && buy_diff >= min_buy.change().into()) - { - debug_log!( - "invalid exchange, {} / {}, sell diff: {}, buy diff: {}, \ - max_sell: {}, rate_min: {}, min_buy: {}, buy_diff / sell_diff: {}", - token_sell, - token_buy, - sell_difference.change(), - buy_difference.change(), - max_sell.change(), - rate_min.0, - min_buy.change(), - buy_diff / sell_diff - ); - false - } else { - true - } -} - #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..876455fcaa 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1619,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From 40836504fc64fadaeaf1245812791817c18f5a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 11:09:26 +0200 Subject: [PATCH 348/373] changelog: add #500 --- .../unreleased/miscellaneous/493-remove-intent-gossiper.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md diff --git a/.changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md b/.changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md new file mode 100644 index 0000000000..543edeb6aa --- /dev/null +++ b/.changelog/unreleased/miscellaneous/493-remove-intent-gossiper.md @@ -0,0 +1,2 @@ +- Removed intent gossiper and matchmaker code + ([#493](https://github.com/anoma/namada/issues/493)) \ No newline at end of file From 4c0e7e895e5f637d4821f2d14e88b419bf4dde85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 11:36:23 +0200 Subject: [PATCH 349/373] update wasm checksums --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 898f6e9763..e222bd6aef 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.16097490afa7378c79e6216751b20796cde3a9026c34255c3f1e5ec5a4c9482e.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d1937b17a3abaf7ea595526c870b3d57ddef8e0c1bc96f8e0a448864b186c7.wasm", - "tx_ibc.wasm": "tx_ibc.378b10551c0b22c2c892d24e2676ee5160d654e2e53a50e7925e0f2c6321497b.wasm", - "tx_init_account.wasm": "tx_init_account.adab66c2b4d635e9c42133936aafb143363f91dddff2a60f94df504ffec951a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.d1065ebd80ba6ea97f29bc2268becf9ba3ba2952641992464f3e9e868df17447.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.184131576a579f9ece96460d1eb20e5970fcd149b0527c8e56b711e5c535aa5f.wasm", - "tx_init_validator.wasm": "tx_init_validator.2990747d24d467b56e19724c5d13df826a3aab83f7e1bf26558dbdf44e260f8a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.33db14dea4a03ff7508ca44f3ae956d83c0abceb3dae5be844668e54ac22b273.wasm", - "tx_transfer.wasm": "tx_transfer.a601d62296f56f6b4dabb0a2ad082478d195e667c7469f363bdfd5fe41349bd8.wasm", - "tx_unbond.wasm": "tx_unbond.014cbf5b0aa3ac592c0a6940dd502ec8569a3af4d12782e3a5931c15dc13042f.wasm", - "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bcb5280be9dfeed0a7650ba5e4a3cebc2c19b76780fd74dcb345be3da766b64a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8fc0a3439ee9ae66047c520519877bc1f540e0cb02abfa31afa8cce8cd069b6f.wasm", - "vp_nft.wasm": "vp_nft.2c820c728d241b82bf0ed3c552ee9e7c046bceaa4f7b6f12d3236a1a3d7c1589.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e762f3fda8aa7a252e2b29a4a312db91ded062d6c18b8b489883733c89dc227.wasm", - "vp_token.wasm": "vp_token.c45cc3848f12fc47713702dc206d1312ad740a6bbee7f141556714f6f89d4985.wasm", - "vp_user.wasm": "vp_user.d6cd2f4b5bc26f96df6aa300fddf4d25e1656920d59896209bd54ae8d407ecde.wasm" + "tx_bond.wasm": "tx_bond.3955960def0f45b2c7eda810354716b29cf7063891cf23bf001f067220b348a5.wasm", + "tx_from_intent.wasm": "tx_from_intent.e21563260c03cfdab1f195878f49bf93722027ad26fcd097cfebbc5c4d279082.wasm", + "tx_ibc.wasm": "tx_ibc.1b6ee957fb5a8ad75bdbdf5435014dcd1b9bc4dc0d749d9401a9814ddaf4c55a.wasm", + "tx_init_account.wasm": "tx_init_account.273fadc04419989eaecdbcce8efac0ee40223f1850c55a29228afd6fa5e0abce.wasm", + "tx_init_nft.wasm": "tx_init_nft.2ef0c7cb42266494a37c5de05cf912eca777c76f8cedbe1164f13cbaaf490cd9.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c6426e5f9bc06c01f331f8399f69c131287c707a33e78d8c8c57dfce435d9150.wasm", + "tx_init_validator.wasm": "tx_init_validator.dde9e2b6f7bea6e56326667becc97a3ab863a15a44528550536922bb4550cba2.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.db1c11ed264dcce8b0a3218cecabc4af8832a22891d9c67fa6f92be78fa861fb.wasm", + "tx_transfer.wasm": "tx_transfer.6deab964149869ec638777a344c5fb1ad486c4f44c731d89793d5940fc3d8714.wasm", + "tx_unbond.wasm": "tx_unbond.57286cab933765181af77672fad7f69c373708e459a01d48631f549c693399b9.wasm", + "tx_update_vp.wasm": "tx_update_vp.dd4b87f25b3d866ef6a7c30ea3cba743e2c011fa5c44ece7a53cff6ad682d975.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.a18464e5966177552fc237c07111bbdd0d70c2940e5e761b2f80c217f4fa32db.wasm", + "tx_withdraw.wasm": "tx_withdraw.fe25543edec0ef74eadcd1de2f354e32002f8a252e4232ed2be4e0340675646f.wasm", + "vp_nft.wasm": "vp_nft.70446e909b233f60beda248085d7de9ba7822d81e580e220bda28764975ace54.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.af177f0935c903c8b5c49305b759e48131cbf8032b6d409216ee74a12a6903b2.wasm", + "vp_token.wasm": "vp_token.3511bed65a7c98fea300cebafed28623ba776da488d06e285f0bf168d4bf1fdf.wasm", + "vp_user.wasm": "vp_user.5748f41419ac1b2eab918ffbe4e7f68125e1cdfe9537ffbcbcc2785c3030d0dd.wasm" } \ No newline at end of file From dad616b883c76a9ede8ea0275fdae8cfdbe1089f Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 14 Sep 2022 20:07:58 +0200 Subject: [PATCH 350/373] WIP: StateMachine tests for lazy_vec validation update Cargo.lock --- .../storage_api/collections/lazy_vec.rs | 322 +-------- shared/src/types/storage.rs | 11 +- .../storage_api/collections/lazy_vec.txt | 7 + tests/src/lib.rs | 2 + tests/src/storage_api/collections/lazy_vec.rs | 631 ++++++++++++++++++ tests/src/storage_api/collections/mod.rs | 1 + tests/src/storage_api/mod.rs | 1 + tests/src/vm_host_env/tx.rs | 25 + 8 files changed, 684 insertions(+), 316 deletions(-) create mode 100644 tests/proptest-regressions/storage_api/collections/lazy_vec.txt create mode 100644 tests/src/storage_api/collections/lazy_vec.rs create mode 100644 tests/src/storage_api/collections/mod.rs create mode 100644 tests/src/storage_api/mod.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index 79967cb9c3..d86f33ec08 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -55,7 +55,7 @@ pub enum SubKeyWithData { /// Possible actions that can modify a [`LazyVec`]. This roughly corresponds to /// the methods that have `StorageWrite` access. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Action { /// Push a value `T` into a [`LazyVec`] Push(T), @@ -86,10 +86,10 @@ pub enum ValidationError { #[error("Pop at a wrong index. Got {got}, expected {expected}.")] UnexpectedPopIndex { got: Index, expected: Index }, #[error( - "Update (combination of pop and push) at a wrong index. Got {got}, \ - expected {expected}." + "Update (or a combination of pop and push) at a wrong index. Got \ + {got}, expected maximum {max}." )] - UnexpectedUpdateIndex { got: Index, expected: Index }, + UnexpectedUpdateIndex { got: Index, max: Index }, #[error("An index has overflown its representation: {0}")] IndexOverflow(>::Error), #[error("Unexpected underflow in `{0} - {0}`")] @@ -323,8 +323,8 @@ where let builder = builder.get_or_insert(ValidationBuilder::default()); builder.changes.push(change); - return Ok(true); } + return Ok(true); } Ok(false) } @@ -474,34 +474,14 @@ impl ValidationBuilder { } } - // And finally iterate updates in increasing order of indices - let mut last_updated = Option::None; + // And finally iterate updates for index in updated { - if let Some(last_updated) = last_updated { - // Following additions should be at monotonically increasing - // indices - let expected = last_updated + 1; - if expected != index { - return Err(ValidationError::UnexpectedUpdateIndex { - got: index, - expected, - }); - } - } - last_updated = Some(index); - } - if let Some(index) = last_updated { - let expected = len_pre.checked_sub(deleted_len).ok_or( - ValidationError::UnexpectedUnderflow(len_pre, deleted_len), - )?; - if index != expected { - // The last update must be at the pre length value minus - // deleted_len. - // If something is added and then deleted in a - // single tx, it will never be visible here. + // Update index has to be within the length bounds + let max = len_pre + len_diff; + if index >= max { return Err(ValidationError::UnexpectedUpdateIndex { got: index, - expected: len_pre, + max, }); } } @@ -512,12 +492,6 @@ impl ValidationBuilder { #[cfg(test)] mod test { - use proptest::prelude::*; - use proptest::prop_state_machine; - use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; - use proptest::test_runner::Config; - use test_log::test; - use super::*; use crate::ledger::storage::testing::TestStorage; @@ -556,280 +530,4 @@ mod test { Ok(()) } - - prop_state_machine! { - #![proptest_config(Config { - // Instead of the default 256, we only run 5 because otherwise it - // takes too long and it's preferable to crank up the number of - // transitions instead, to allow each case to run for more epochs as - // some issues only manifest once the model progresses further. - // Additionally, more cases will be explored every time this test is - // executed in the CI. - cases: 5, - .. Config::default() - })] - #[test] - /// A `StateMachineTest` implemented on `LazyVec` that manipulates - /// it with `Transition`s and checks its state against an in-memory - /// `std::collections::Vec`. - fn lazy_vec_api_state_machine_test(sequential 1..100 => ConcreteLazyVecState); - - } - - /// Some borsh-serializable type with arbitrary fields to be used inside - /// LazyVec state machine test - #[derive( - Clone, - Debug, - BorshSerialize, - BorshDeserialize, - PartialEq, - Eq, - PartialOrd, - Ord, - )] - struct TestVecItem { - x: u64, - y: bool, - } - - #[derive(Debug)] - struct ConcreteLazyVecState { - // The eager vec in `AbstractLazyVecState` is not visible in `impl - // StateMachineTest for ConcreteLazyVecState`, it's only used to drive - // transition generation, so we duplicate it here and apply the - // transitions on it the same way (with - // `fn apply_transition_on_eager_vec`) - eager_vec: Vec, - lazy_vec: LazyVec, - storage: TestStorage, - } - - #[derive(Clone, Debug)] - struct AbstractLazyVecState(Vec); - - /// Possible transitions that can modify a [`LazyVec`]. This roughly - /// corresponds to the methods that have `StorageWrite` access and is very - /// similar to [`Action`] - #[derive(Clone, Debug)] - pub enum Transition { - /// Push a value `T` into a [`LazyVec`] - Push(T), - /// Pop a value from a [`LazyVec`] - Pop, - /// Update a value `T` at index from pre to post state in a - /// [`LazyVec`] - Update { - /// index at which the value is updated - index: Index, - /// value to update the element to - value: T, - }, - } - - impl AbstractStateMachine for AbstractLazyVecState { - type State = Self; - type Transition = Transition; - - fn init_state() -> BoxedStrategy { - Just(Self(vec![])).boxed() - } - - // Apply a random transition to the state - fn transitions(state: &Self::State) -> BoxedStrategy { - if state.0.is_empty() { - prop_oneof![arb_test_vec_item().prop_map(Transition::Push)] - .boxed() - } else { - let indices: Vec = - (0_usize..state.0.len()).map(|ix| ix as Index).collect(); - let arb_index = proptest::sample::select(indices); - prop_oneof![ - Just(Transition::Pop), - arb_test_vec_item().prop_map(Transition::Push), - (arb_index, arb_test_vec_item()).prop_map( - |(index, value)| Transition::Update { index, value } - ) - ] - .boxed() - } - } - - fn apply_abstract( - mut state: Self::State, - transition: &Self::Transition, - ) -> Self::State { - apply_transition_on_eager_vec(&mut state.0, transition); - state - } - - fn preconditions( - state: &Self::State, - transition: &Self::Transition, - ) -> bool { - if state.0.is_empty() { - // Ensure that the pop or update transitions are not applied to - // an empty state - !matches!( - transition, - Transition::Pop | Transition::Update { .. } - ) - } else if let Transition::Update { index, .. } = transition { - // Ensure that the update index is a valid one - *index < (state.0.len() - 1) as Index - } else { - true - } - } - } - - impl StateMachineTest for ConcreteLazyVecState { - type Abstract = AbstractLazyVecState; - type ConcreteState = Self; - - fn init_test( - _initial_state: ::State, - ) -> Self::ConcreteState { - Self { - eager_vec: vec![], - lazy_vec: LazyVec::open( - storage::Key::parse("key_path/arbitrary").unwrap(), - ), - storage: TestStorage::default(), - } - } - - fn apply_concrete( - mut state: Self::ConcreteState, - transition: ::Transition, - ) -> Self::ConcreteState { - // Transition application on lazy vec and post-conditions: - match dbg!(&transition) { - Transition::Push(value) => { - let old_len = state.lazy_vec.len(&state.storage).unwrap(); - - state - .lazy_vec - .push(&mut state.storage, value.clone()) - .unwrap(); - - // Post-conditions: - let new_len = state.lazy_vec.len(&state.storage).unwrap(); - let stored_value = state - .lazy_vec - .get(&state.storage, new_len - 1) - .unwrap() - .unwrap(); - assert_eq!( - &stored_value, value, - "the new item must be added to the back" - ); - assert_eq!(old_len + 1, new_len, "length must increment"); - } - Transition::Pop => { - let old_len = state.lazy_vec.len(&state.storage).unwrap(); - - let popped = state - .lazy_vec - .pop(&mut state.storage) - .unwrap() - .unwrap(); - - // Post-conditions: - let new_len = state.lazy_vec.len(&state.storage).unwrap(); - assert_eq!(old_len, new_len + 1, "length must decrement"); - assert_eq!( - &popped, - state.eager_vec.last().unwrap(), - "popped element matches the last element in eager vec \ - before it's updated" - ); - } - Transition::Update { index, value } => { - let old_len = state.lazy_vec.len(&state.storage).unwrap(); - let old_val = state - .lazy_vec - .get(&state.storage, *index) - .unwrap() - .unwrap(); - - state - .lazy_vec - .update(&mut state.storage, *index, value.clone()) - .unwrap(); - - // Post-conditions: - let new_len = state.lazy_vec.len(&state.storage).unwrap(); - let new_val = state - .lazy_vec - .get(&state.storage, *index) - .unwrap() - .unwrap(); - assert_eq!(old_len, new_len, "length must not change"); - assert_eq!( - &old_val, - state.eager_vec.get(*index as usize).unwrap(), - "old value must match the value at the same index in \ - the eager vec before it's updated" - ); - assert_eq!( - &new_val, value, - "new value must match that which was passed into the \ - Transition::Update" - ); - } - } - - // Apply transition in the eager vec for comparison - apply_transition_on_eager_vec(&mut state.eager_vec, &transition); - - // Global post-conditions: - - // All items in eager vec must be present in lazy vec - for (ix, expected_item) in state.eager_vec.iter().enumerate() { - let got = state - .lazy_vec - .get(&state.storage, ix as Index) - .unwrap() - .expect("The expected item must be present in lazy vec"); - assert_eq!(expected_item, &got, "at index {ix}"); - } - - // All items in lazy vec must be present in eager vec - for (ix, expected_item) in - state.lazy_vec.iter(&state.storage).unwrap().enumerate() - { - let expected_item = expected_item.unwrap(); - let got = state - .eager_vec - .get(ix) - .expect("The expected item must be present in eager vec"); - assert_eq!(&expected_item, got, "at index {ix}"); - } - - state - } - } - - /// Generate an arbitrary `TestVecItem` - fn arb_test_vec_item() -> impl Strategy { - (any::(), any::()).prop_map(|(x, y)| TestVecItem { x, y }) - } - - /// Apply `Transition` on an eager `Vec`. - fn apply_transition_on_eager_vec( - vec: &mut Vec, - transition: &Transition, - ) { - match transition { - Transition::Push(value) => vec.push(value.clone()), - Transition::Pop => { - let _popped = vec.pop(); - } - Transition::Update { index, value } => { - let entry = vec.get_mut(*index as usize).unwrap(); - *entry = value.clone(); - } - } - } } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index b5d72b7b02..1c3f6b1313 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -378,15 +378,18 @@ impl Key { /// - `Some(None)` if the prefix is matched, but it has no suffix, or /// - `None` if it doesn't match pub fn split_prefix(&self, prefix: &Self) -> Option> { - if self.segments.len() < prefix.len() { + if self.segments.len() < prefix.segments.len() { return None; } else if self == prefix { return Some(None); } - let mut self_prefix = self.segments.clone(); - let rest = self_prefix.split_off(prefix.len()); + // This is safe, because we check that the length of segments in self >= + // in prefix above + let (self_prefix, rest) = self.segments.split_at(prefix.segments.len()); if self_prefix == prefix.segments { - Some(Some(Key { segments: rest })) + Some(Some(Key { + segments: rest.to_vec(), + })) } else { None } diff --git a/tests/proptest-regressions/storage_api/collections/lazy_vec.txt b/tests/proptest-regressions/storage_api/collections/lazy_vec.txt new file mode 100644 index 0000000000..97a16dcbeb --- /dev/null +++ b/tests/proptest-regressions/storage_api/collections/lazy_vec.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 4330a283e32b5ff3f38d0af2298e1e98c30b1901c1027b572070a1af3356688e # shrinks to (initial_state, transitions) = (AbstractLazyVecState { valid_transitions: [], committed_transitions: [] }, [Push(TestVecItem { x: 15352583996758053781, y: true }), Pop, CommitTx, Push(TestVecItem { x: 6904067244182623445, y: false }), CommitTx, Pop, Push(TestVecItem { x: 759762287021483883, y: true }), Push(TestVecItem { x: 7885704082671389345, y: true }), Pop, Pop, Push(TestVecItem { x: 2762344561419437403, y: false }), Push(TestVecItem { x: 11448034977049028254, y: false }), Update { index: 0, value: TestVecItem { x: 7097339541298715775, y: false } }, Pop, Pop, Push(TestVecItem { x: 457884036257686887, y: true }), CommitTx, Push(TestVecItem { x: 17719281119971095810, y: true }), CommitTx, Push(TestVecItem { x: 4612681906563857058, y: false }), CommitTx, CommitTx, Pop, CommitTx, Pop, Push(TestVecItem { x: 4269537158299505726, y: false }), CommitTx, Pop, Pop, CommitTx, CommitTx, CommitTx, CommitTx, Push(TestVecItem { x: 9020889554694833528, y: true }), Push(TestVecItem { x: 4022797489860699620, y: false }), Update { index: 0, value: TestVecItem { x: 6485081152860611495, y: true } }, Pop, CommitTx, Push(TestVecItem { x: 14470031031894733310, y: false }), Push(TestVecItem { x: 1113274973965556867, y: true }), Push(TestVecItem { x: 4122902042678339346, y: false }), Push(TestVecItem { x: 9672639635189564637, y: true }), Pop, Pop, Pop, CommitTx, Update { index: 0, value: TestVecItem { x: 6372193991838429158, y: false } }, Push(TestVecItem { x: 15140852824102579010, y: false }), Pop, Pop, Pop, Push(TestVecItem { x: 4012218522073776592, y: false }), Push(TestVecItem { x: 10637893847792386454, y: true }), Push(TestVecItem { x: 3357788278949652885, y: false }), CommitTx, CommitTx, Pop, Pop, CommitTx, Pop, Push(TestVecItem { x: 11768518086398350214, y: true }), Push(TestVecItem { x: 4361685178396183644, y: true }), Pop, CommitTx, Push(TestVecItem { x: 2450907664540456425, y: false }), Push(TestVecItem { x: 18184919885943118586, y: true }), Update { index: 1, value: TestVecItem { x: 10611906658537706503, y: false } }, Push(TestVecItem { x: 4887827541279511396, y: false }), Update { index: 0, value: TestVecItem { x: 13021774003761931172, y: false } }, Push(TestVecItem { x: 3644118228573898014, y: false }), CommitTx, Update { index: 0, value: TestVecItem { x: 1276840798381751183, y: false } }, Pop, Pop]) diff --git a/tests/src/lib.rs b/tests/src/lib.rs index 1b75f83bdc..78ebf2473c 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -12,6 +12,8 @@ mod e2e; #[cfg(test)] mod native_vp; pub mod storage; +#[cfg(test)] +mod storage_api; /// Using this import requires `tracing` and `tracing-subscriber` dependencies. /// Set env var `RUST_LOG=info` to see the logs from a test run (and diff --git a/tests/src/storage_api/collections/lazy_vec.rs b/tests/src/storage_api/collections/lazy_vec.rs new file mode 100644 index 0000000000..20ee80592d --- /dev/null +++ b/tests/src/storage_api/collections/lazy_vec.rs @@ -0,0 +1,631 @@ +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::types::address::{self, Address}; + use namada::types::storage; + use namada_tx_prelude::storage::KeySeg; + use namada_tx_prelude::storage_api::collections::{ + lazy_vec, LazyCollection, LazyVec, + }; + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + + use crate::tx::tx_host_env; + use crate::vp::vp_host_env; + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + fn lazy_vec_api_state_machine_test(sequential 1..100 => ConcreteLazyVecState); + } + + /// Some borsh-serializable type with arbitrary fields to be used inside + /// LazyVec state machine test + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + )] + struct TestVecItem { + x: u64, + y: bool, + } + + /// A `StateMachineTest` implemented on this struct manipulates it with + /// `Transition`s, which are also being accumulated into + /// `current_transitions`. It then: + /// + /// - checks its state against an in-memory `std::collections::Vec` + /// - runs validation and checks that the `LazyVec::Action`s reported from + /// validation match with transitions that were applied + /// + /// Additionally, one of the transitions is to commit a block and/or + /// transaction, during which the currently accumulated state changes are + /// persisted, or promoted from transaction write log to block's write log. + #[derive(Debug)] + struct ConcreteLazyVecState { + /// Address is used to prefix the storage key of the `lazy_vec` in + /// order to simulate a transaction and a validity predicate + /// check from changes on the `lazy_vec` + address: Address, + /// In the test, we apply the same transitions on the `lazy_vec` as on + /// `eager_vec` to check that `lazy_vec`'s state is consistent with + /// `eager_vec`. + eager_vec: Vec, + /// Handle to a lazy vec + lazy_vec: LazyVec, + /// Valid LazyVec changes in the current transaction + current_transitions: Vec>, + } + + #[derive(Clone, Debug)] + struct AbstractLazyVecState { + /// Valid LazyVec changes in the current transaction + valid_transitions: Vec>, + /// Valid LazyVec changes committed to storage + committed_transitions: Vec>, + } + + /// Possible transitions that can modify a [`LazyVec`]. This roughly + /// corresponds to the methods that have `StorageWrite` access and is very + /// similar to [`Action`] + #[derive(Clone, Debug)] + pub enum Transition { + /// Commit all valid transitions in the current transaction + CommitTx, + /// Commit all valid transitions in the current transaction and also + /// commit the current block + CommitTxAndBlock, + /// Push a value `T` into a [`LazyVec`] + Push(T), + /// Pop a value from a [`LazyVec`] + Pop, + /// Update a value `T` at index from pre to post state in a + /// [`LazyVec`] + Update { + /// index at which the value is updated + index: lazy_vec::Index, + /// value to update the element to + value: T, + }, + } + + impl AbstractStateMachine for AbstractLazyVecState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self { + valid_transitions: vec![], + committed_transitions: vec![], + }) + .boxed() + } + + // Apply a random transition to the state + fn transitions(state: &Self::State) -> BoxedStrategy { + let length = state.len(); + if length == 0 { + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => arb_test_vec_item().prop_map(Transition::Push) + ] + .boxed() + } else { + let arb_index = || { + let indices: Vec = (0..length).collect(); + proptest::sample::select(indices) + }; + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_index(), arb_test_vec_item()).prop_map( + |(index, value)| Transition::Update { index, value } + ), + 3 => Just(Transition::Pop), + 5 => arb_test_vec_item().prop_map(Transition::Push), + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + match transition { + Transition::CommitTx => { + let valid_actions_to_commit = + std::mem::take(&mut state.valid_transitions); + state + .committed_transitions + .extend(valid_actions_to_commit.into_iter()); + } + _ => state.valid_transitions.push(transition.clone()), + } + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + let length = state.len(); + if length == 0 { + // Ensure that the pop or update transitions are not applied to + // an empty state + !matches!( + transition, + Transition::Pop | Transition::Update { .. } + ) + } else if let Transition::Update { index, .. } = transition { + // Ensure that the update index is a valid one + *index < (length - 1) + } else { + true + } + } + } + + impl StateMachineTest for ConcreteLazyVecState { + type Abstract = AbstractLazyVecState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + // Init transaction env in which we'll be applying the transitions + tx_host_env::init(); + + // The lazy_vec's path must be prefixed by the address to be able + // to trigger a validity predicate on it + let address = address::testing::established_address_1(); + tx_host_env::with(|env| env.spawn_accounts([&address])); + let lazy_vec_prefix: storage::Key = address.to_db_key().into(); + + Self { + address, + eager_vec: vec![], + lazy_vec: LazyVec::open( + lazy_vec_prefix.push(&"arbitrary".to_string()).unwrap(), + ), + current_transitions: vec![], + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Apply transitions in transaction env + let ctx = tx_host_env::ctx(); + + // Persist the transitions in the current tx, or clear previous ones + // if we're committing a tx + match &transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + state.current_transitions = vec![]; + } + _ => { + state.current_transitions.push(transition.clone()); + } + } + + // Transition application on lazy vec and post-conditions: + match &transition { + Transition::CommitTx => { + // commit the tx without committing the block + tx_host_env::with(|env| env.write_log.commit_tx()); + } + Transition::CommitTxAndBlock => { + // commit the tx and the block + tx_host_env::commit_tx_and_block(); + } + Transition::Push(value) => { + let old_len = state.lazy_vec.len(ctx).unwrap(); + + state.lazy_vec.push(ctx, value.clone()).unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(ctx).unwrap(); + let stored_value = + state.lazy_vec.get(ctx, new_len - 1).unwrap().unwrap(); + assert_eq!( + &stored_value, value, + "the new item must be added to the back" + ); + assert_eq!(old_len + 1, new_len, "length must increment"); + + state.assert_validation_accepted(new_len); + } + Transition::Pop => { + let old_len = state.lazy_vec.len(ctx).unwrap(); + + let popped = state.lazy_vec.pop(ctx).unwrap().unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(ctx).unwrap(); + assert_eq!(old_len, new_len + 1, "length must decrement"); + assert_eq!( + &popped, + state.eager_vec.last().unwrap(), + "popped element matches the last element in eager vec \ + before it's updated" + ); + + state.assert_validation_accepted(new_len); + } + Transition::Update { index, value } => { + let old_len = state.lazy_vec.len(ctx).unwrap(); + let old_val = + state.lazy_vec.get(ctx, *index).unwrap().unwrap(); + + state.lazy_vec.update(ctx, *index, value.clone()).unwrap(); + + // Post-conditions: + let new_len = state.lazy_vec.len(ctx).unwrap(); + let new_val = + state.lazy_vec.get(ctx, *index).unwrap().unwrap(); + assert_eq!(old_len, new_len, "length must not change"); + assert_eq!( + &old_val, + state.eager_vec.get(*index as usize).unwrap(), + "old value must match the value at the same index in \ + the eager vec before it's updated" + ); + assert_eq!( + &new_val, value, + "new value must match that which was passed into the \ + Transition::Update" + ); + + state.assert_validation_accepted(new_len); + } + } + + // Apply transition in the eager vec for comparison + apply_transition_on_eager_vec(&mut state.eager_vec, &transition); + + // Global post-conditions: + + // All items in eager vec must be present in lazy vec + for (ix, expected_item) in state.eager_vec.iter().enumerate() { + let got = state + .lazy_vec + .get(ctx, ix as lazy_vec::Index) + .unwrap() + .expect("The expected item must be present in lazy vec"); + assert_eq!(expected_item, &got, "at index {ix}"); + } + + // All items in lazy vec must be present in eager vec + for (ix, expected_item) in + state.lazy_vec.iter(ctx).unwrap().enumerate() + { + let expected_item = expected_item.unwrap(); + let got = state + .eager_vec + .get(ix) + .expect("The expected item must be present in eager vec"); + assert_eq!(&expected_item, got, "at index {ix}"); + } + + state + } + } + + impl AbstractLazyVecState { + /// Find the length of the vector from the applied transitions + fn len(&self) -> u64 { + (vec_len_diff_from_transitions(self.committed_transitions.iter()) + + vec_len_diff_from_transitions(self.valid_transitions.iter())) + .try_into() + .expect( + "It shouldn't be possible to underflow length from all \ + transactions applied in abstract state", + ) + } + } + + /// Find the difference in length of the vector from the applied transitions + fn vec_len_diff_from_transitions<'a>( + all_transitions: impl Iterator>, + ) -> i64 { + let mut push_count: i64 = 0; + let mut pop_count: i64 = 0; + + for trans in all_transitions { + match trans { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Update { .. } => {} + Transition::Push(_) => push_count += 1, + Transition::Pop => pop_count += 1, + } + } + push_count - pop_count + } + + impl ConcreteLazyVecState { + fn assert_validation_accepted(&self, new_vec_len: u64) { + // Init the VP env from tx env in which we applied the vec + // transitions + let tx_env = tx_host_env::take(); + vp_host_env::init_from_tx(self.address.clone(), tx_env, |_| {}); + + // Simulate a validity predicate run using the lazy vec's validation + // helpers + let changed_keys = + vp_host_env::with(|env| env.all_touched_storage_keys()); + + let mut validation_builder = None; + + // Push followed by pop is a no-op, in which case we'd still see the + // changed keys for these actions, but they wouldn't affect the + // validation result and they never get persisted, but we'd still + // them as changed key here. To guard against this case, + // we check that `vec_len_from_transitions` is not empty. + let vec_len_diff = + vec_len_diff_from_transitions(self.current_transitions.iter()); + + // To help debug validation issues... + dbg!( + &self.current_transitions, + &changed_keys + .iter() + .map(storage::Key::to_string) + .collect::>() + ); + + for key in &changed_keys { + let is_sub_key = self + .lazy_vec + .accumulate( + vp_host_env::ctx(), + &mut validation_builder, + key, + ) + .unwrap(); + + assert!( + is_sub_key, + "We're only modifying the lazy_vec's keys here. Key: \ + \"{key}\", vec length diff {vec_len_diff}" + ); + } + if !changed_keys.is_empty() && vec_len_diff != 0 { + assert!( + validation_builder.is_some(), + "If some keys were changed, the builder must get filled in" + ); + let actions = validation_builder.unwrap().build().expect( + "With valid transitions only, validation should always \ + pass", + ); + let mut actions_to_check = actions.clone(); + + // Check that every transition has a corresponding action from + // validation. We drop the found actions to check that all + // actions are matched too. + let current_transitions = normalize_transitions( + &self.current_transitions, + new_vec_len, + ); + for transition in ¤t_transitions { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + } + Transition::Push(expected_val) => { + let mut ix = 0; + while ix < actions_to_check.len() { + if let lazy_vec::Action::Push(val) = + &actions_to_check[ix] + { + if expected_val == val { + actions_to_check.remove(ix); + break; + } + } + ix += 1; + } + } + Transition::Pop => { + let mut ix = 0; + while ix < actions_to_check.len() { + if let lazy_vec::Action::Pop(_val) = + &actions_to_check[ix] + { + actions_to_check.remove(ix); + break; + } + ix += 1; + } + } + Transition::Update { + index: expected_index, + value, + } => { + let mut ix = 0; + while ix < actions_to_check.len() { + if let lazy_vec::Action::Update { + index, + pre: _, + post, + } = &actions_to_check[ix] + { + if expected_index == index && post == value + { + actions_to_check.remove(ix); + break; + } + } + ix += 1; + } + } + } + } + + assert!( + actions_to_check.is_empty(), + "All the actions reported from validation {actions:#?} \ + should have been matched with SM transitions \ + {current_transitions:#?}, but these actions didn't \ + match: {actions_to_check:#?}", + ) + } + + // Put the tx_env back before checking the result + tx_host_env::set_from_vp_env(vp_host_env::take()); + } + } + + /// Generate an arbitrary `TestVecItem` + fn arb_test_vec_item() -> impl Strategy { + (any::(), any::()).prop_map(|(x, y)| TestVecItem { x, y }) + } + + /// Apply `Transition` on an eager `Vec`. + fn apply_transition_on_eager_vec( + vec: &mut Vec, + transition: &Transition, + ) { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => {} + Transition::Push(value) => vec.push(value.clone()), + Transition::Pop => { + let _popped = vec.pop(); + } + Transition::Update { index, value } => { + let entry = vec.get_mut(*index as usize).unwrap(); + *entry = value.clone(); + } + } + } + + /// Normalize transitions: + /// - pop at ix + push(val) at ix -> update(ix, val) + /// - push(val) at ix + update(ix, new_val) -> push(new_val) at ix + /// - update(ix, val) + update(ix, new_val) -> update(ix, new_val) + /// + /// Note that the normalizable transitions pairs do not have to be directly + /// next to each other, but their order does matter. + fn normalize_transitions( + transitions: &[Transition], + new_vec_len: u64, + ) -> Vec> { + let stack_start_pos = ((new_vec_len as i64) + - vec_len_diff_from_transitions(transitions.iter())) + as u64; + let mut stack_pos = stack_start_pos; + let mut collapsed = vec![]; + 'outer: for transition in transitions { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + collapsed.push(transition.clone()) + } + Transition::Push(value) => { + // If there are some pops, the last one can be collapsed + // with this push + if stack_pos < stack_start_pos { + // Find the pop from the back + let mut found_ix = None; + for (ix, transition) in + collapsed.iter().enumerate().rev() + { + if let Transition::Pop = transition { + found_ix = Some(ix); + break; + } + } + let ix = found_ix.expect("Pop must be found"); + // pop at ix + push(val) at ix -> update(ix, val) + + // Replace the Pop with an Update and don't insert the + // Push + *collapsed.get_mut(ix).unwrap() = Transition::Update { + index: stack_pos, + value: value.clone(), + }; + } else { + collapsed.push(transition.clone()); + } + stack_pos += 1; + } + Transition::Pop => { + collapsed.push(transition.clone()); + stack_pos -= 1; + } + Transition::Update { index, value } => { + // If there are some pushes, check if one of them is at the + // same index as this update + if stack_pos > stack_start_pos { + let mut current_pos = stack_start_pos; + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + match collapsed_transition { + Transition::CommitTx + | Transition::CommitTxAndBlock => {} + Transition::Push(_) => { + if ¤t_pos == index { + // push(val) at `ix` + update(ix, + // new_val) -> + // push(new_val) at `ix` + + // Replace the Push with the new Push of + // Update's + // value and don't insert the Update + *collapsed.get_mut(ix).unwrap() = + Transition::Push(value.clone()); + continue 'outer; + } + current_pos += 1; + } + Transition::Pop => { + current_pos -= 1; + } + Transition::Update { + index: prev_update_index, + value: _, + } => { + if index == prev_update_index { + // update(ix, val) + update(ix, new_val) + // -> update(ix, new_val) + + // Replace the Update with the new + // Update instead of inserting it + *collapsed.get_mut(ix).unwrap() = + transition.clone(); + continue 'outer; + } + } + } + } + } + collapsed.push(transition.clone()) + } + } + } + collapsed + } +} diff --git a/tests/src/storage_api/collections/mod.rs b/tests/src/storage_api/collections/mod.rs new file mode 100644 index 0000000000..d874b88e22 --- /dev/null +++ b/tests/src/storage_api/collections/mod.rs @@ -0,0 +1 @@ +mod lazy_vec; diff --git a/tests/src/storage_api/mod.rs b/tests/src/storage_api/mod.rs new file mode 100644 index 0000000000..bc487bd59e --- /dev/null +++ b/tests/src/storage_api/mod.rs @@ -0,0 +1 @@ +mod collections; diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index 728c488bca..6e23ced06b 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -18,6 +18,8 @@ use namada::vm::{self, WasmCacheRwAccess}; use namada_tx_prelude::{BorshSerialize, Ctx}; use tempfile::TempDir; +use crate::vp::TestVpEnv; + /// Tx execution context provides access to host env functions static mut CTX: Ctx = unsafe { Ctx::new() }; @@ -235,6 +237,29 @@ mod native_tx_host_env { with(|env| env.commit_tx_and_block()) } + /// Set the [`TestTxEnv`] back from a [`TestVpEnv`]. This is useful when + /// testing validation with multiple transactions that accumulate some state + /// changes. + pub fn set_from_vp_env(vp_env: TestVpEnv) { + let TestVpEnv { + storage, + write_log, + tx, + vp_wasm_cache, + vp_cache_dir, + .. + } = vp_env; + let tx_env = TestTxEnv { + storage, + write_log, + vp_wasm_cache, + vp_cache_dir, + tx, + ..Default::default() + }; + set(tx_env); + } + /// A helper macro to create implementations of the host environment /// functions exported to wasm, which uses the environment from the /// `ENV` variable. From 4b60051a5f72f137dc3321145a0ff878132df0fe Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 16 Sep 2022 17:15:14 +0200 Subject: [PATCH 351/373] WIP: validation for lazy_map --- .../storage_api/collections/lazy_map.rs | 148 ++++- .../storage_api/collections/lazy_map.txt | 7 + tests/src/storage_api/collections/lazy_map.rs | 608 ++++++++++++++++++ tests/src/storage_api/collections/mod.rs | 1 + 4 files changed, 761 insertions(+), 3 deletions(-) create mode 100644 tests/proptest-regressions/storage_api/collections/lazy_map.txt create mode 100644 tests/src/storage_api/collections/lazy_map.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index a47ff0734a..3801848b8f 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -3,12 +3,15 @@ use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; +use derivative::Derivative; +use thiserror::Error; use super::super::Result; use super::{LazyCollection, ReadError}; -use crate::ledger::storage_api::validation::Data; +use crate::ledger::storage_api::validation::{self, Data}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage::{self, KeySeg}; +use crate::ledger::vp_env::VpEnv; +use crate::types::storage::{self, DbKeySeg, KeySeg}; /// Subkey corresponding to the data elements of the LazyMap pub const DATA_SUBKEY: &str = "data"; @@ -52,12 +55,21 @@ pub enum SubKeyWithData { /// the methods that have `StorageWrite` access. /// TODO: In a nested collection, `V` may be an action inside the nested /// collection. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum Action { /// Insert or update a value `V` at key `K` in a [`LazyMap`]. Insert(K, V), /// Remove a value `V` at key `K` from a [`LazyMap`]. Remove(K, V), + /// Update a value `V` at key `K` in a [`LazyMap`]. + Update { + /// key at which the value is updated + key: K, + /// value before the update + pre: V, + /// value after the update + post: V, + }, } /// TODO: In a nested collection, `V` may be an action inside the nested @@ -70,6 +82,46 @@ pub enum Nested { Remove(K, V), } +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ValidationError { + #[error("Storage error in reading key {0}")] + StorageError(storage::Key), + // #[error("Incorrect difference in LazyVec's length")] + // InvalidLenDiff, + // #[error("An empty LazyVec must be deleted from storage")] + // EmptyVecShouldBeDeleted, + // #[error("Push at a wrong index. Got {got}, expected {expected}.")] + // UnexpectedPushIndex { got: Index, expected: Index }, + // #[error("Pop at a wrong index. Got {got}, expected {expected}.")] + // UnexpectedPopIndex { got: Index, expected: Index }, + // #[error( + // "Update (combination of pop and push) at a wrong index. Got {got}, + // \ expected {expected}." + // )] + // UnexpectedUpdateIndex { got: Index, expected: Index }, + // #[error("An index has overflown its representation: {0}")] + // IndexOverflow(>::Error), + // #[error("Unexpected underflow in `{0} - {0}`")] + // UnexpectedUnderflow(Index, Index), + #[error("Invalid storage key {0}")] + InvalidSubKey(storage::Key), +} + +/// [`LazyMap`] validation result +pub type ValidationResult = std::result::Result; + +/// [`LazyMap`] validation builder from storage changes. The changes can be +/// accumulated with `LazyMap::validate()` and then turned into a list +/// of valid actions on the map with `ValidationBuilder::build()`. +#[derive(Debug, Derivative)] +// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound +#[derivative(Default(bound = ""))] +pub struct ValidationBuilder { + /// The accumulator of found changes under the vector + pub changes: Vec>, +} + impl LazyCollection for LazyMap where K: storage::KeySeg, @@ -247,6 +299,96 @@ where ) -> Result<()> { storage.write(storage_key, val) } + + /// Check if the given storage key is a valid LazyMap sub-key and if so + /// return which one + pub fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result>> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { + Ok(Some(SubKey::Data(key_in_kv))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + /// Accumulate storage changes inside a [`ValidationBuilder`]. This is + /// typically done by the validity predicate while looping through the + /// changed keys. If the resulting `builder` is not `None`, one must + /// call `fn build()` on it to get the validation result. + /// This function will return `Ok(true)` if the storage key is a valid + /// sub-key of this collection, `Ok(false)` if the storage key doesn't match + /// the prefix of this collection, or fail with + /// [`ValidationError::InvalidSubKey`] if the prefix matches this + /// collection, but the key itself is not recognized. + pub fn accumulate( + &self, + env: &ENV, + builder: &mut Option>, + key_changed: &storage::Key, + ) -> storage_api::Result + where + ENV: for<'a> VpEnv<'a>, + { + if let Some(sub) = self.is_valid_sub_key(key_changed)? { + let SubKey::Data(key) = sub; + let data = validation::read_data(env, key_changed)?; + let change = data.map(|data| SubKeyWithData::Data(key, data)); + if let Some(change) = change { + let builder = + builder.get_or_insert(ValidationBuilder::default()); + builder.changes.push(change); + } + return Ok(true); + } + Ok(false) + } +} + +impl ValidationBuilder +where + K: storage::KeySeg + Ord + Clone, +{ + /// Build a list of actions from storage changes. + pub fn build(self) -> Vec> { + self.changes + .into_iter() + .map(|change| { + let SubKeyWithData::Data(key, data) = change; + match data { + Data::Add { post } => Action::Insert(key, post), + Data::Update { pre, post } => { + Action::Update { key, pre, post } + } + Data::Delete { pre } => Action::Remove(key, pre), + } + }) + .collect() + } } #[cfg(test)] diff --git a/tests/proptest-regressions/storage_api/collections/lazy_map.txt b/tests/proptest-regressions/storage_api/collections/lazy_map.txt new file mode 100644 index 0000000000..2de7510923 --- /dev/null +++ b/tests/proptest-regressions/storage_api/collections/lazy_map.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 59b8eaaf5d8e03e58b346ef229a2487f68fea488197420f150682f7275ce2b83 # shrinks to (initial_state, transitions) = (AbstractLazyMapState { valid_transitions: [], committed_transitions: [] }, [Insert(11178241982156558453, TestVal { x: 9618691367534591266, y: true }), CommitTx, Update(11178241982156558453, TestVal { x: 2635377083098935189, y: false }), Update(11178241982156558453, TestVal { x: 11485387163946255361, y: false }), Insert(4380901092919801530, TestVal { x: 17235291421018840542, y: false }), Update(11178241982156558453, TestVal { x: 1936190700145956620, y: false }), Update(11178241982156558453, TestVal { x: 6934621224353358508, y: false }), Update(11178241982156558453, TestVal { x: 16175036327810390362, y: true }), Remove(5606457884982633480), Insert(7124206407862523505, TestVal { x: 5513772825695605555, y: true }), CommitTxAndBlock, CommitTx, Insert(13347045100814804679, TestVal { x: 5157295776286367034, y: false }), Update(7124206407862523505, TestVal { x: 1989909525753197955, y: false }), Update(4380901092919801530, TestVal { x: 13085578877588425331, y: false }), Update(7124206407862523505, TestVal { x: 1620781139263176467, y: true }), Insert(5806457332157050619, TestVal { x: 14632354209749334932, y: true }), Remove(1613213961397167063), Update(7124206407862523505, TestVal { x: 3848976302483310370, y: true }), Update(4380901092919801530, TestVal { x: 15281186775251770467, y: false }), Remove(5303306623647571548), Insert(5905425607805327902, TestVal { x: 1274794101048822414, y: false }), Insert(2305446651611241243, TestVal { x: 7872403441503057017, y: true }), Insert(2843165193114615911, TestVal { x: 13698490566286768452, y: false }), Insert(3364298091459048760, TestVal { x: 8891279000465212397, y: true }), CommitTx, Insert(17278527568142155478, TestVal { x: 8166151895050476136, y: false }), Remove(9206713523174765253), Remove(1148985045479283759), Insert(13346103305566843535, TestVal { x: 13148026974798633058, y: true }), Remove(17185699086139524651), CommitTx, Update(7124206407862523505, TestVal { x: 3047872255943216792, y: false }), CommitTxAndBlock, CommitTxAndBlock, Remove(4672009405538026945), Update(5905425607805327902, TestVal { x: 6635343936644805461, y: false }), Insert(14100441716981493843, TestVal { x: 8068697312326956479, y: true }), Insert(8370580326875672309, TestVal { x: 18416630552728813406, y: false }), Update(2305446651611241243, TestVal { x: 3777718192999015176, y: false }), Remove(1532142753559370584), Remove(10097030807802775125), Insert(10080356901530935857, TestVal { x: 17171047520093964037, y: false }), Update(3364298091459048760, TestVal { x: 702372485798608773, y: true }), Insert(5504969092734638033, TestVal { x: 314752460808087203, y: true }), Remove(5486040497128339175), Insert(7884678026881625058, TestVal { x: 4313610278903495077, y: true }), CommitTx, Insert(11228024342874184864, TestVal { x: 428512502841968552, y: false }), Insert(4684666745142518471, TestVal { x: 13122515680485564107, y: true }), Remove(14243063045921130600), Remove(4530767959521683042), Insert(10236349778753659715, TestVal { x: 3138294567956031715, y: true }), Update(2305446651611241243, TestVal { x: 8133236604817109805, y: false }), Update(2843165193114615911, TestVal { x: 12001998927296899868, y: false }), CommitTxAndBlock, CommitTx, CommitTxAndBlock]) diff --git a/tests/src/storage_api/collections/lazy_map.rs b/tests/src/storage_api/collections/lazy_map.rs new file mode 100644 index 0000000000..c7a309ab4d --- /dev/null +++ b/tests/src/storage_api/collections/lazy_map.rs @@ -0,0 +1,608 @@ +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + use std::convert::TryInto; + + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::types::address::{self, Address}; + use namada::types::storage; + use namada_tx_prelude::storage::KeySeg; + use namada_tx_prelude::storage_api::collections::{ + lazy_map, LazyCollection, LazyMap, + }; + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + + use crate::tx::tx_host_env; + use crate::vp::vp_host_env; + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + fn lazy_map_api_state_machine_test(sequential 1..100 => ConcreteLazyMapState); + } + + /// Type of key used in the map + type TestKey = u64; + + /// Some borsh-serializable type with arbitrary fields to be used inside + /// LazyMap state machine test + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + )] + struct TestVal { + x: u64, + y: bool, + } + + /// A `StateMachineTest` implemented on this struct manipulates it with + /// `Transition`s, which are also being accumulated into + /// `current_transitions`. It then: + /// + /// - checks its state against an in-memory `std::collections::HashMap` + /// - runs validation and checks that the `LazyMap::Action`s reported from + /// validation match with transitions that were applied + /// + /// Additionally, one of the transitions is to commit a block and/or + /// transaction, during which the currently accumulated state changes are + /// persisted, or promoted from transaction write log to block's write log. + #[derive(Debug)] + struct ConcreteLazyMapState { + /// Address is used to prefix the storage key of the `lazy_map` in + /// order to simulate a transaction and a validity predicate + /// check from changes on the `lazy_map` + address: Address, + /// In the test, we apply the same transitions on the `lazy_map` as on + /// `eager_map` to check that `lazy_map`'s state is consistent with + /// `eager_map`. + eager_map: BTreeMap, + /// Handle to a lazy map + lazy_map: LazyMap, + /// Valid LazyMap changes in the current transaction + current_transitions: Vec, + } + + #[derive(Clone, Debug, Default)] + struct AbstractLazyMapState { + /// Valid LazyMap changes in the current transaction + valid_transitions: Vec, + /// Valid LazyMap changes committed to storage + committed_transitions: Vec, + } + + /// Possible transitions that can modify a [`LazyMap`]. + /// This roughly corresponds to the methods that have `StorageWrite` + /// access and is very similar to [`Action`] + #[derive(Clone, Debug)] + enum Transition { + /// Commit all valid transitions in the current transaction + CommitTx, + /// Commit all valid transitions in the current transaction and also + /// commit the current block + CommitTxAndBlock, + /// Insert a key-val into a [`LazyMap`] + Insert(TestKey, TestVal), + /// Remove a key-val from a [`LazyMap`] + Remove(TestKey), + /// Update a value at key from pre to post state in a + /// [`LazyMap`] + Update(TestKey, TestVal), + } + + impl AbstractStateMachine for AbstractLazyMapState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self::default()).boxed() + } + + // Apply a random transition to the state + fn transitions(state: &Self::State) -> BoxedStrategy { + let length = state.len(); + if length == 0 { + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_map_key(), arb_map_val()).prop_map(|(key, val)| Transition::Insert(key, val)) + ] + .boxed() + } else { + let keys = state.find_existing_keys(); + let arb_existing_map_key = + || proptest::sample::select(keys.clone()); + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_existing_map_key(), arb_map_val()).prop_map(|(key, val)| + Transition::Update(key, val) + ), + 3 => arb_existing_map_key().prop_map(Transition::Remove), + 5 => (arb_map_key().prop_filter("insert on non-existing keys only", move |key| !keys.contains(&key)), arb_map_val()).prop_map(|(key, val)| Transition::Insert(key, val)) + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + let valid_actions_to_commit = + std::mem::take(&mut state.valid_transitions); + state + .committed_transitions + .extend(valid_actions_to_commit.into_iter()); + } + _ => state.valid_transitions.push(transition.clone()), + } + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + let length = state.len(); + // Ensure that the remove or update transitions are not applied + // to an empty state + if length == 0 + && matches!( + transition, + Transition::Remove(_) | Transition::Update(_, _) + ) + { + return false; + } + match transition { + Transition::Update(key, _) | Transition::Remove(key) => { + let keys = state.find_existing_keys(); + // Ensure that the update/remove key is an existing one + keys.contains(key) + } + Transition::Insert(key, _) => { + let keys = state.find_existing_keys(); + // Ensure that the insert key is not an existing one + !keys.contains(key) + } + _ => true, + } + } + } + + impl StateMachineTest for ConcreteLazyMapState { + type Abstract = AbstractLazyMapState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + // Init transaction env in which we'll be applying the transitions + tx_host_env::init(); + + // The lazy_map's path must be prefixed by the address to be able + // to trigger a validity predicate on it + let address = address::testing::established_address_1(); + tx_host_env::with(|env| env.spawn_accounts([&address])); + let lazy_map_prefix: storage::Key = address.to_db_key().into(); + + Self { + address, + eager_map: BTreeMap::new(), + lazy_map: LazyMap::open( + lazy_map_prefix.push(&"arbitrary".to_string()).unwrap(), + ), + current_transitions: vec![], + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Apply transitions in transaction env + let ctx = tx_host_env::ctx(); + + // Persist the transitions in the current tx, or clear previous ones + // if we're committing a tx + match &transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + state.current_transitions = vec![]; + } + _ => { + state.current_transitions.push(transition.clone()); + } + } + + // Transition application on lazy map and post-conditions: + match &transition { + Transition::CommitTx => { + // commit the tx without committing the block + tx_host_env::with(|env| env.write_log.commit_tx()); + } + Transition::CommitTxAndBlock => { + // commit the tx and the block + tx_host_env::commit_tx_and_block(); + } + Transition::Insert(key, value) => { + state.lazy_map.insert(ctx, *key, value.clone()).unwrap(); + + // Post-conditions: + let stored_value = + state.lazy_map.get(ctx, key).unwrap().unwrap(); + assert_eq!( + &stored_value, value, + "the new item must be added to the back" + ); + + state.assert_validation_accepted(); + } + Transition::Remove(key) => { + let removed = + state.lazy_map.remove(ctx, key).unwrap().unwrap(); + + // Post-conditions: + assert_eq!( + &removed, + state.eager_map.get(key).unwrap(), + "removed element matches the value in eager map \ + before it's updated" + ); + + state.assert_validation_accepted(); + } + Transition::Update(key, value) => { + let old_val = + state.lazy_map.get(ctx, key).unwrap().unwrap(); + + state.lazy_map.insert(ctx, *key, value.clone()).unwrap(); + + // Post-conditions: + let new_val = + state.lazy_map.get(ctx, key).unwrap().unwrap(); + assert_eq!( + &old_val, + state.eager_map.get(key).unwrap(), + "old value must match the value at the same key in \ + the eager map before it's updated" + ); + assert_eq!( + &new_val, value, + "new value must match that which was passed into the \ + Transition::Update" + ); + + state.assert_validation_accepted(); + } + } + + // Apply transition in the eager map for comparison + apply_transition_on_eager_map(&mut state.eager_map, &transition); + + // Global post-conditions: + + // All items in eager map must be present in lazy map + for (key, expected_item) in state.eager_map.iter() { + let got = + state.lazy_map.get(ctx, key).unwrap().expect( + "The expected item must be present in lazy map", + ); + assert_eq!(expected_item, &got, "at key {key}"); + } + + // All items in lazy map must be present in eager map + for key_val in state.lazy_map.iter(ctx).unwrap() { + let (key, expected_val) = key_val.unwrap(); + let got = state + .eager_map + .get(&key) + .expect("The expected item must be present in eager map"); + assert_eq!(&expected_val, got, "at key {key}"); + } + + state + } + } + + impl AbstractLazyMapState { + /// Find the length of the map from the applied transitions + fn len(&self) -> u64 { + (map_len_diff_from_transitions(self.committed_transitions.iter()) + + map_len_diff_from_transitions(self.valid_transitions.iter())) + .try_into() + .expect( + "It shouldn't be possible to underflow length from all \ + transactions applied in abstract state", + ) + } + + /// Build an eager map from the committed and current transitions + fn eager_map(&self) -> BTreeMap { + let mut eager_map = BTreeMap::new(); + for transition in &self.committed_transitions { + apply_transition_on_eager_map(&mut eager_map, transition); + } + for transition in &self.valid_transitions { + apply_transition_on_eager_map(&mut eager_map, transition); + } + eager_map + } + + /// Find the keys currently present in the map + fn find_existing_keys(&self) -> Vec { + self.eager_map().keys().cloned().collect() + } + } + + /// Find the difference in length of the map from the applied transitions + fn map_len_diff_from_transitions<'a>( + transitions: impl Iterator, + ) -> i64 { + let mut insert_count: i64 = 0; + let mut remove_count: i64 = 0; + + for trans in transitions { + match trans { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Update(_, _) => {} + Transition::Insert(_, _) => insert_count += 1, + Transition::Remove(_) => remove_count += 1, + } + } + insert_count - remove_count + } + + impl ConcreteLazyMapState { + fn assert_validation_accepted(&self) { + // Init the VP env from tx env in which we applied the map + // transitions + let tx_env = tx_host_env::take(); + vp_host_env::init_from_tx(self.address.clone(), tx_env, |_| {}); + + // Simulate a validity predicate run using the lazy map's validation + // helpers + let changed_keys = + vp_host_env::with(|env| env.all_touched_storage_keys()); + + let mut validation_builder = None; + + // Push followed by pop is a no-op, in which case we'd still see the + // changed keys for these actions, but they wouldn't affect the + // validation result and they never get persisted, but we'd still + // them as changed key here. To guard against this case, + // we check that `map_len_from_transitions` is not empty. + let map_len_diff = + map_len_diff_from_transitions(self.current_transitions.iter()); + + // To help debug validation issues... + dbg!( + &self.current_transitions, + &changed_keys + .iter() + .map(storage::Key::to_string) + .collect::>() + ); + + for key in &changed_keys { + let is_sub_key = self + .lazy_map + .accumulate( + vp_host_env::ctx(), + &mut validation_builder, + key, + ) + .unwrap(); + + assert!( + is_sub_key, + "We're only modifying the lazy_map's keys here. Key: \ + \"{key}\", map length diff {map_len_diff}" + ); + } + if !changed_keys.is_empty() && map_len_diff != 0 { + assert!( + validation_builder.is_some(), + "If some keys were changed, the builder must get filled in" + ); + let actions = validation_builder.unwrap().build(); + let mut actions_to_check = actions.clone(); + + // Check that every transition has a corresponding action from + // validation. We drop the found actions to check that all + // actions are matched too. + let current_transitions = + normalize_transitions(&self.current_transitions); + for transition in ¤t_transitions { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + } + Transition::Insert(expected_key, expected_val) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_map::Action::Insert(key, val) = + action + { + if expected_key == key + && expected_val == val + { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Remove(expected_key) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_map::Action::Remove(key, _val) = + action + { + if expected_key == key { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Update(expected_key, value) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let lazy_map::Action::Update { + key, + pre: _, + post, + } = action + { + if expected_key == key && post == value { + actions_to_check.remove(ix); + break; + } + } + } + } + } + } + + assert!( + actions_to_check.is_empty(), + "All the actions reported from validation {actions:#?} \ + should have been matched with SM transitions \ + {current_transitions:#?}, but these actions didn't \ + match: {actions_to_check:#?}", + ) + } + + // Put the tx_env back before checking the result + tx_host_env::set_from_vp_env(vp_host_env::take()); + } + } + + /// Generate an arbitrary `TestKey` + fn arb_map_key() -> impl Strategy { + any::() + } + + /// Generate an arbitrary `TestVal` + fn arb_map_val() -> impl Strategy { + (any::(), any::()).prop_map(|(x, y)| TestVal { x, y }) + } + + /// Apply `Transition` on an eager `Map`. + fn apply_transition_on_eager_map( + map: &mut BTreeMap, + transition: &Transition, + ) { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => {} + Transition::Insert(key, value) => { + map.insert(*key, value.clone()); + } + Transition::Remove(key) => { + let _popped = map.remove(key); + } + Transition::Update(key, value) => { + let entry = map.get_mut(key).unwrap(); + *entry = value.clone(); + } + } + } + + /// Normalize transitions: + /// - remove(key) + insert(key, val) -> update(key, val) + /// - insert(key, val) + update(key, new_val) -> insert(key, new_val) + /// - update(key, val) + update(key, new_val) -> update(key, new_val) + /// + /// Note that the normalizable transitions pairs do not have to be directly + /// next to each other, but their order does matter. + fn normalize_transitions(transitions: &[Transition]) -> Vec { + let mut collapsed = vec![]; + 'outer: for transition in transitions { + match transition { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Remove(_) => collapsed.push(transition.clone()), + Transition::Insert(key, val) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Remove(remove_key) = + collapsed_transition + { + if key == remove_key { + // remove(key) + insert(key, val) -> update(key, + // val) + + // Replace the Remove with an Update instead of + // inserting the Insert + *collapsed.get_mut(ix).unwrap() = + Transition::Update(*key, val.clone()); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + Transition::Update(key, value) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Insert(insert_key, _) = + collapsed_transition + { + if key == insert_key { + // insert(key, val) + update(key, new_val) -> + // insert(key, new_val) + + // Replace the insert with the new update's + // value instead of inserting it + *collapsed.get_mut(ix).unwrap() = + Transition::Insert(*key, value.clone()); + continue 'outer; + } + } else if let Transition::Update(update_key, _) = + collapsed_transition + { + if key == update_key { + // update(key, val) + update(key, new_val) -> + // update(key, new_val) + + // Replace the insert with the new update's + // value instead of inserting it + *collapsed.get_mut(ix).unwrap() = + Transition::Update(*key, value.clone()); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + } + } + collapsed + } +} diff --git a/tests/src/storage_api/collections/mod.rs b/tests/src/storage_api/collections/mod.rs index d874b88e22..fc7c5832ce 100644 --- a/tests/src/storage_api/collections/mod.rs +++ b/tests/src/storage_api/collections/mod.rs @@ -1 +1,2 @@ +mod lazy_map; mod lazy_vec; From 8d2ad09fdcf8bce99dd846824f8fe75bbac0a4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 19 Sep 2022 16:43:20 +0200 Subject: [PATCH 352/373] WIP: Nested LazyMap validation and testing --- .../storage_api/collections/lazy_map.rs | 401 ++++++---- .../storage_api/collections/lazy_vec.rs | 353 ++++----- .../src/ledger/storage_api/collections/mod.rs | 118 ++- shared/src/types/storage.rs | 5 + .../collections/nested_lazy_map.txt | 7 + tests/src/storage_api/collections/lazy_map.rs | 9 +- tests/src/storage_api/collections/lazy_vec.rs | 5 +- tests/src/storage_api/collections/mod.rs | 1 + .../collections/nested_lazy_map.rs | 723 ++++++++++++++++++ 9 files changed, 1290 insertions(+), 332 deletions(-) create mode 100644 tests/proptest-regressions/storage_api/collections/nested_lazy_map.txt create mode 100644 tests/src/storage_api/collections/nested_lazy_map.rs diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 3801848b8f..686f9d336e 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -1,9 +1,11 @@ //! Lazy map. +use std::collections::HashMap; +use std::fmt::Debug; +use std::hash::Hash; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; -use derivative::Derivative; use thiserror::Error; use super::super::Result; @@ -30,14 +32,18 @@ pub const DATA_SUBKEY: &str = "data"; /// This is different from [`super::LazyHashMap`], which hashes borsh encoded /// key. #[derive(Debug)] -pub struct LazyMap { +pub struct LazyMap { key: storage::Key, phantom_k: PhantomData, phantom_v: PhantomData, + phantom_son: PhantomData, } +/// A `LazyMap` with another `LazyCollection` inside it's value `V` +pub type NestedMap = LazyMap; + /// Possible sub-keys of a [`LazyMap`] -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum SubKey { /// Data sub-key, further sub-keyed by its literal map key Data(K), @@ -45,16 +51,14 @@ pub enum SubKey { /// Possible sub-keys of a [`LazyMap`], together with their [`validation::Data`] /// that contains prior and posterior state. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum SubKeyWithData { /// Data sub-key, further sub-keyed by its literal map key Data(K, Data), } -/// Possible actions that can modify a [`LazyMap`]. This roughly corresponds to -/// the methods that have `StorageWrite` access. -/// TODO: In a nested collection, `V` may be an action inside the nested -/// collection. +/// Possible actions that can modify a simple (not nested) [`LazyMap`]. This +/// roughly corresponds to the methods that have `StorageWrite` access. #[derive(Clone, Debug)] pub enum Action { /// Insert or update a value `V` at key `K` in a [`LazyMap`]. @@ -72,14 +76,23 @@ pub enum Action { }, } -/// TODO: In a nested collection, `V` may be an action inside the nested -/// collection. -#[derive(Debug)] -pub enum Nested { - /// Insert or update a value `V` at key `K` in a [`LazyMap`]. - Insert(K, V), - /// Remove a value `V` at key `K` from a [`LazyMap`]. - Remove(K, V), +/// Possible actions that can modify a nested [`LazyMap`]. +#[derive(Clone, Debug)] +pub enum NestedAction { + /// Nested collection action `A` at key `K` + At(K, A), +} + +/// Possible sub-keys of a nested [`LazyMap`] +#[derive(Clone, Debug)] +pub enum NestedSubKey { + /// Data sub-key + Data { + /// Literal map key + key: K, + /// Sub-key in the nested collection + nested_sub_key: S, + }, } #[allow(missing_docs)] @@ -87,57 +100,228 @@ pub enum Nested { pub enum ValidationError { #[error("Storage error in reading key {0}")] StorageError(storage::Key), - // #[error("Incorrect difference in LazyVec's length")] - // InvalidLenDiff, - // #[error("An empty LazyVec must be deleted from storage")] - // EmptyVecShouldBeDeleted, - // #[error("Push at a wrong index. Got {got}, expected {expected}.")] - // UnexpectedPushIndex { got: Index, expected: Index }, - // #[error("Pop at a wrong index. Got {got}, expected {expected}.")] - // UnexpectedPopIndex { got: Index, expected: Index }, - // #[error( - // "Update (combination of pop and push) at a wrong index. Got {got}, - // \ expected {expected}." - // )] - // UnexpectedUpdateIndex { got: Index, expected: Index }, - // #[error("An index has overflown its representation: {0}")] - // IndexOverflow(>::Error), - // #[error("Unexpected underflow in `{0} - {0}`")] - // UnexpectedUnderflow(Index, Index), #[error("Invalid storage key {0}")] InvalidSubKey(storage::Key), + #[error("Invalid nested storage key {0}")] + InvalidNestedSubKey(storage::Key), } /// [`LazyMap`] validation result pub type ValidationResult = std::result::Result; -/// [`LazyMap`] validation builder from storage changes. The changes can be -/// accumulated with `LazyMap::validate()` and then turned into a list -/// of valid actions on the map with `ValidationBuilder::build()`. -#[derive(Debug, Derivative)] -// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound -#[derivative(Default(bound = ""))] -pub struct ValidationBuilder { - /// The accumulator of found changes under the vector - pub changes: Vec>, +impl LazyCollection for LazyMap +where + K: storage::KeySeg + Clone + Hash + Eq + Debug, + V: LazyCollection + Debug, +{ + type Action = NestedAction::Action>; + type SubKey = NestedSubKey::SubKey>; + type SubKeyWithData = + NestedSubKey::SubKeyWithData>; + type Value = ::Value; + + fn open(key: storage::Key) -> Self { + Self { + key, + phantom_k: PhantomData, + phantom_v: PhantomData, + phantom_son: PhantomData, + } + } + + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..2] { + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { + let nested = self.at(&key_in_kv).is_valid_sub_key(key)?; + match nested { + Some(nested_sub_key) => Ok(Some(NestedSubKey::Data { + key: key_in_kv, + nested_sub_key, + })), + None => Err(ValidationError::InvalidNestedSubKey( + key.clone(), + )) + .into_storage_result(), + } + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> VpEnv<'a>, + { + let NestedSubKey::Data { + key, + // In here, we just have a nested sub-key without data + nested_sub_key, + } = sub_key; + // Try to read data from the nested collection + let nested_data = ::read_sub_key_data( + env, + storage_key, + nested_sub_key, + )?; + // If found, transform it back into a `NestedSubKey`, but with + // `nested_sub_key` replaced with the one we read + Ok(nested_data.map(|nested_sub_key| NestedSubKey::Data { + key, + nested_sub_key, + })) + } + + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { + // We have to group the nested sub-keys by the key from this map + let mut grouped_by_key: HashMap< + K, + Vec<::SubKeyWithData>, + > = HashMap::new(); + for NestedSubKey::Data { + key, + nested_sub_key, + } in keys + { + grouped_by_key + .entry(key) + .or_insert_with(Vec::new) + .push(nested_sub_key); + } + + // Recurse for each sub-keys group + let mut actions = vec![]; + for (key, sub_keys) in grouped_by_key { + let nested_actions = + ::validate_changed_sub_keys(sub_keys)?; + actions.extend( + nested_actions + .into_iter() + .map(|action| NestedAction::At(key.clone(), action)), + ); + } + Ok(actions) + } } -impl LazyCollection for LazyMap +impl LazyCollection for LazyMap where - K: storage::KeySeg, + K: storage::KeySeg + Debug, + V: BorshDeserialize + BorshSerialize + 'static + Debug, { + type Action = Action; + type SubKey = SubKey; + type SubKeyWithData = SubKeyWithData; + type Value = V; + /// Create or use an existing map with the given storage `key`. fn open(key: storage::Key) -> Self { Self { key, phantom_k: PhantomData, phantom_v: PhantomData, + phantom_son: PhantomData, } } + + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { + Ok(Some(SubKey::Data(key_in_kv))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> VpEnv<'a>, + { + let SubKey::Data(key) = sub_key; + let data = validation::read_data(env, storage_key)?; + Ok(data.map(|data| SubKeyWithData::Data(key, data))) + } + + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { + Ok(keys + .into_iter() + .map(|change| { + let SubKeyWithData::Data(key, data) = change; + match data { + Data::Add { post } => Action::Insert(key, post), + Data::Update { pre, post } => { + Action::Update { key, pre, post } + } + Data::Delete { pre } => Action::Remove(key, pre), + } + }) + .collect()) + } } // Generic `LazyMap` methods that require no bounds on values `V` -impl LazyMap +impl LazyMap where K: storage::KeySeg, { @@ -162,10 +346,10 @@ where } // `LazyMap` methods with nested `LazyCollection`s `V` -impl LazyMap +impl LazyMap where - K: storage::KeySeg, - V: LazyCollection, + K: storage::KeySeg + Clone + Hash + Eq + Debug, + V: LazyCollection + Debug, { /// Get a nested collection at given key `key`. If there is no nested /// collection at the given key, a new empty one will be provided. The @@ -173,10 +357,39 @@ where pub fn at(&self, key: &K) -> V { V::open(self.get_data_key(key)) } + + /// An iterator visiting all key-value elements, where the values are from + /// the inner-most collection. The iterator element type is `Result<_>`, + /// because iterator's call to `next` may fail with e.g. out of gas or + /// data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded maps to avoid gas usage increasing with the length of the + /// map. + pub fn iter<'iter>( + &'iter self, + storage: &'iter impl StorageRead<'iter>, + ) -> Result< + impl Iterator< + Item = Result<( + ::SubKey, + ::Value, + )>, + > + 'iter, + > { + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (key, val) = key_val_res?; + let sub_key = LazyCollection::is_valid_sub_key(self, &key)? + .ok_or(ReadError::UnexpectedlyEmptyStorageKey) + .into_storage_result()?; + Ok((sub_key, val)) + })) + } } // `LazyMap` methods with borsh encoded values `V` -impl LazyMap +impl LazyMap where K: storage::KeySeg, V: BorshDeserialize + BorshSerialize + 'static, @@ -299,96 +512,6 @@ where ) -> Result<()> { storage.write(storage_key, val) } - - /// Check if the given storage key is a valid LazyMap sub-key and if so - /// return which one - pub fn is_valid_sub_key( - &self, - key: &storage::Key, - ) -> storage_api::Result>> { - let suffix = match key.split_prefix(&self.key) { - None => { - // not matching prefix, irrelevant - return Ok(None); - } - Some(None) => { - // no suffix, invalid - return Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result(); - } - Some(Some(suffix)) => suffix, - }; - - // Match the suffix against expected sub-keys - match &suffix.segments[..] { - [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] - if sub_a == DATA_SUBKEY => - { - if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { - Ok(Some(SubKey::Data(key_in_kv))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } - } - _ => Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result(), - } - } - - /// Accumulate storage changes inside a [`ValidationBuilder`]. This is - /// typically done by the validity predicate while looping through the - /// changed keys. If the resulting `builder` is not `None`, one must - /// call `fn build()` on it to get the validation result. - /// This function will return `Ok(true)` if the storage key is a valid - /// sub-key of this collection, `Ok(false)` if the storage key doesn't match - /// the prefix of this collection, or fail with - /// [`ValidationError::InvalidSubKey`] if the prefix matches this - /// collection, but the key itself is not recognized. - pub fn accumulate( - &self, - env: &ENV, - builder: &mut Option>, - key_changed: &storage::Key, - ) -> storage_api::Result - where - ENV: for<'a> VpEnv<'a>, - { - if let Some(sub) = self.is_valid_sub_key(key_changed)? { - let SubKey::Data(key) = sub; - let data = validation::read_data(env, key_changed)?; - let change = data.map(|data| SubKeyWithData::Data(key, data)); - if let Some(change) = change { - let builder = - builder.get_or_insert(ValidationBuilder::default()); - builder.changes.push(change); - } - return Ok(true); - } - Ok(false) - } -} - -impl ValidationBuilder -where - K: storage::KeySeg + Ord + Clone, -{ - /// Build a list of actions from storage changes. - pub fn build(self) -> Vec> { - self.changes - .into_iter() - .map(|change| { - let SubKeyWithData::Data(key, data) = change; - match data { - Data::Add { post } => Action::Insert(key, post), - Data::Update { pre, post } => { - Action::Update { key, pre, post } - } - Data::Delete { pre } => Action::Remove(key, pre), - } - }) - .collect() - } } #[cfg(test)] diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index d86f33ec08..fd61bef804 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -1,10 +1,10 @@ //! Lazy dynamically-sized vector. use std::collections::BTreeSet; +use std::fmt::Debug; use std::marker::PhantomData; use borsh::{BorshDeserialize, BorshSerialize}; -use derivative::Derivative; use thiserror::Error; use super::super::Result; @@ -110,18 +110,15 @@ pub enum UpdateError { /// [`LazyVec`] validation result pub type ValidationResult = std::result::Result; -/// [`LazyVec`] validation builder from storage changes. The changes can be -/// accumulated with `LazyVec::validate()` and then turned into a list -/// of valid actions on the vector with `ValidationBuilder::build()`. -#[derive(Debug, Derivative)] -// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound -#[derivative(Default(bound = ""))] -pub struct ValidationBuilder { - /// The accumulator of found changes under the vector - pub changes: Vec>, -} +impl LazyCollection for LazyVec +where + T: BorshSerialize + BorshDeserialize + 'static + Debug, +{ + type Action = Action; + type SubKey = SubKey; + type SubKeyWithData = SubKeyWithData; + type Value = T; -impl LazyCollection for LazyVec { /// Create or use an existing vector with the given storage `key`. fn open(key: storage::Key) -> Self { Self { @@ -129,131 +126,10 @@ impl LazyCollection for LazyVec { phantom: PhantomData, } } -} - -// Generic `LazyVec` methods that require no bounds on values `T` -impl LazyVec { - /// Reads the number of elements in the vector. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let len = storage.read(&self.get_len_key())?; - Ok(len.unwrap_or_default()) - } - - /// Returns `true` if the vector contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - Ok(self.len(storage)? == 0) - } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of vector's elements storage - fn get_data_key(&self, index: Index) -> storage::Key { - self.get_data_prefix().push(&index).unwrap() - } - - /// Get the sub-key of vector's length storage - fn get_len_key(&self) -> storage::Key { - self.key.push(&LEN_SUBKEY.to_owned()).unwrap() - } -} - -// `LazyVec` methods with borsh encoded values `T` -impl LazyVec -where - T: BorshSerialize + BorshDeserialize + 'static, -{ - /// Appends an element to the back of a collection. - pub fn push(&self, storage: &mut S, val: T) -> Result<()> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let len = self.len(storage)?; - let data_key = self.get_data_key(len); - storage.write(&data_key, val)?; - storage.write(&self.get_len_key(), len + 1) - } - - /// Removes the last element from a vector and returns it, or `Ok(None)` if - /// it is empty. - /// - /// Note that an empty vector is completely removed from storage. - pub fn pop(&self, storage: &mut S) -> Result> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let len = self.len(storage)?; - if len == 0 { - Ok(None) - } else { - let index = len - 1; - let data_key = self.get_data_key(index); - if len == 1 { - storage.delete(&self.get_len_key())?; - } else { - storage.write(&self.get_len_key(), index)?; - } - let popped_val = storage.read(&data_key)?; - storage.delete(&data_key)?; - Ok(popped_val) - } - } - - /// Update an element at the given index. - /// - /// The index must be smaller than the length of the vector, otherwise this - /// will fail with `UpdateError::InvalidIndex`. - pub fn update(&self, storage: &mut S, index: Index, val: T) -> Result<()> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let len = self.len(storage)?; - if index >= len { - return Err(UpdateError::InvalidIndex { index, len }) - .into_storage_result(); - } - let data_key = self.get_data_key(index); - storage.write(&data_key, val) - } - - /// Read an element at the index or `Ok(None)` if out of bounds. - pub fn get(&self, storage: &S, index: Index) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - storage.read(&self.get_data_key(index)) - } - - /// An iterator visiting all elements. The iterator element type is - /// `Result`, because iterator's call to `next` may fail with e.g. out of - /// gas or data decoding error. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - pub fn iter<'iter>( - &self, - storage: &'iter impl StorageRead<'iter>, - ) -> Result> + 'iter> { - let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; - Ok(iter.map(|key_val_res| { - let (_key, val) = key_val_res?; - Ok(val) - })) - } /// Check if the given storage key is a valid LazyVec sub-key and if so /// return which one - pub fn is_valid_sub_key( + fn is_valid_sub_key( &self, key: &storage::Key, ) -> storage_api::Result> { @@ -290,50 +166,27 @@ where } } - /// Accumulate storage changes inside a [`ValidationBuilder`]. This is - /// typically done by the validity predicate while looping through the - /// changed keys. If the resulting `builder` is not `None`, one must - /// call `fn build()` on it to get the validation result. - /// This function will return `Ok(true)` if the storage key is a valid - /// sub-key of this collection, `Ok(false)` if the storage key doesn't match - /// the prefix of this collection, or fail with - /// [`ValidationError::InvalidSubKey`] if the prefix matches this - /// collection, but the key itself is not recognized. - pub fn accumulate( - &self, + fn read_sub_key_data( env: &ENV, - builder: &mut Option>, - key_changed: &storage::Key, - ) -> storage_api::Result + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> where ENV: for<'a> VpEnv<'a>, { - if let Some(sub) = self.is_valid_sub_key(key_changed)? { - let change = match sub { - SubKey::Len => { - let data = validation::read_data(env, key_changed)?; - data.map(SubKeyWithData::Len) - } - SubKey::Data(index) => { - let data = validation::read_data(env, key_changed)?; - data.map(|data| SubKeyWithData::Data(index, data)) - } - }; - if let Some(change) = change { - let builder = - builder.get_or_insert(ValidationBuilder::default()); - builder.changes.push(change); + let change = match sub_key { + SubKey::Len => { + let data = validation::read_data(env, storage_key)?; + data.map(SubKeyWithData::Len) } - return Ok(true); - } - Ok(false) + SubKey::Data(index) => { + let data = validation::read_data(env, storage_key)?; + data.map(|data| SubKeyWithData::Data(index, data)) + } + }; + Ok(change) } -} -impl ValidationBuilder { - /// Validate storage changes and if valid, build from them a list of - /// actions. - /// /// The validation rules for a [`LazyVec`] are: /// - A difference in the vector's length must correspond to the /// difference in how many elements were pushed versus how many elements @@ -342,7 +195,9 @@ impl ValidationBuilder { /// - In addition, we check that indices of any changes are within an /// expected range (i.e. the vectors indices should always be /// monotonically increasing from zero) - pub fn build(self) -> ValidationResult>> { + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { let mut actions = vec![]; // We need to accumulate some values for what's changed @@ -353,14 +208,15 @@ impl ValidationBuilder { let mut updated = BTreeSet::::default(); let mut deleted = BTreeSet::::default(); - for change in self.changes { - match change { + for key in keys { + match key { SubKeyWithData::Len(data) => match data { Data::Add { post } => { if post == 0 { return Err( ValidationError::EmptyVecShouldBeDeleted, - ); + ) + .into_storage_result(); } post_gt_pre = true; len_diff = post; @@ -369,7 +225,8 @@ impl ValidationBuilder { if post == 0 { return Err( ValidationError::EmptyVecShouldBeDeleted, - ); + ) + .into_storage_result(); } if post > pre { post_gt_pre = true; @@ -403,11 +260,13 @@ impl ValidationBuilder { let added_len: u64 = added .len() .try_into() - .map_err(ValidationError::IndexOverflow)?; + .map_err(ValidationError::IndexOverflow) + .into_storage_result()?; let deleted_len: u64 = deleted .len() .try_into() - .map_err(ValidationError::IndexOverflow)?; + .map_err(ValidationError::IndexOverflow) + .into_storage_result()?; if len_diff != 0 && !(if post_gt_pre { @@ -416,7 +275,7 @@ impl ValidationBuilder { added_len + len_diff == deleted_len }) { - return Err(ValidationError::InvalidLenDiff); + return Err(ValidationError::InvalidLenDiff).into_storage_result(); } let mut last_added = Option::None; @@ -430,7 +289,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedPushIndex { got: index, expected, - }); + }) + .into_storage_result(); } } else if index != len_pre { // The first addition must be at the pre length value. @@ -440,7 +300,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedPushIndex { got: index, expected: len_pre, - }); + }) + .into_storage_result(); } last_added = Some(index); } @@ -456,7 +317,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedPopIndex { got: index, expected, - }); + }) + .into_storage_result(); } } last_deleted = Some(index); @@ -469,7 +331,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedPopIndex { got: index, expected: len_pre, - }); + }) + .into_storage_result(); } } } @@ -482,7 +345,8 @@ impl ValidationBuilder { return Err(ValidationError::UnexpectedUpdateIndex { got: index, max, - }); + }) + .into_storage_result(); } } @@ -490,6 +354,127 @@ impl ValidationBuilder { } } +// Generic `LazyVec` methods that require no bounds on values `T` +impl LazyVec { + /// Reads the number of elements in the vector. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + let len = storage.read(&self.get_len_key())?; + Ok(len.unwrap_or_default()) + } + + /// Returns `true` if the vector contains no elements. + pub fn is_empty(&self, storage: &S) -> Result + where + S: for<'iter> StorageRead<'iter>, + { + Ok(self.len(storage)? == 0) + } + + /// Get the prefix of set's elements storage + fn get_data_prefix(&self) -> storage::Key { + self.key.push(&DATA_SUBKEY.to_owned()).unwrap() + } + + /// Get the sub-key of vector's elements storage + fn get_data_key(&self, index: Index) -> storage::Key { + self.get_data_prefix().push(&index).unwrap() + } + + /// Get the sub-key of vector's length storage + fn get_len_key(&self) -> storage::Key { + self.key.push(&LEN_SUBKEY.to_owned()).unwrap() + } +} + +// `LazyVec` methods with borsh encoded values `T` +impl LazyVec +where + T: BorshSerialize + BorshDeserialize + 'static, +{ + /// Appends an element to the back of a collection. + pub fn push(&self, storage: &mut S, val: T) -> Result<()> + where + S: StorageWrite + for<'iter> StorageRead<'iter>, + { + let len = self.len(storage)?; + let data_key = self.get_data_key(len); + storage.write(&data_key, val)?; + storage.write(&self.get_len_key(), len + 1) + } + + /// Removes the last element from a vector and returns it, or `Ok(None)` if + /// it is empty. + /// + /// Note that an empty vector is completely removed from storage. + pub fn pop(&self, storage: &mut S) -> Result> + where + S: StorageWrite + for<'iter> StorageRead<'iter>, + { + let len = self.len(storage)?; + if len == 0 { + Ok(None) + } else { + let index = len - 1; + let data_key = self.get_data_key(index); + if len == 1 { + storage.delete(&self.get_len_key())?; + } else { + storage.write(&self.get_len_key(), index)?; + } + let popped_val = storage.read(&data_key)?; + storage.delete(&data_key)?; + Ok(popped_val) + } + } + + /// Update an element at the given index. + /// + /// The index must be smaller than the length of the vector, otherwise this + /// will fail with `UpdateError::InvalidIndex`. + pub fn update(&self, storage: &mut S, index: Index, val: T) -> Result<()> + where + S: StorageWrite + for<'iter> StorageRead<'iter>, + { + let len = self.len(storage)?; + if index >= len { + return Err(UpdateError::InvalidIndex { index, len }) + .into_storage_result(); + } + let data_key = self.get_data_key(index); + storage.write(&data_key, val) + } + + /// Read an element at the index or `Ok(None)` if out of bounds. + pub fn get(&self, storage: &S, index: Index) -> Result> + where + S: for<'iter> StorageRead<'iter>, + { + storage.read(&self.get_data_key(index)) + } + + /// An iterator visiting all elements. The iterator element type is + /// `Result`, because iterator's call to `next` may fail with e.g. out of + /// gas or data decoding error. + /// + /// Note that this function shouldn't be used in transactions and VPs code + /// on unbounded sets to avoid gas usage increasing with the length of the + /// set. + pub fn iter<'iter>( + &self, + storage: &'iter impl StorageRead<'iter>, + ) -> Result> + 'iter> { + let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; + Ok(iter.map(|key_val_res| { + let (_key, val) = key_val_res?; + Ok(val) + })) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index b3e1b4af0c..b77b207c7f 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -7,21 +7,27 @@ //! just receive the storage sub-keys that have experienced changes without //! having to check any of the unchanged elements. +use std::fmt::Debug; + +use borsh::BorshDeserialize; +use derivative::Derivative; use thiserror::Error; mod hasher; -pub mod lazy_hashmap; -pub mod lazy_hashset; +// pub mod lazy_hashmap; +// pub mod lazy_hashset; pub mod lazy_map; -pub mod lazy_set; +// pub mod lazy_set; pub mod lazy_vec; -pub use lazy_hashmap::LazyHashMap; -pub use lazy_hashset::LazyHashSet; +// pub use lazy_hashmap::LazyHashMap; +// pub use lazy_hashset::LazyHashSet; pub use lazy_map::LazyMap; -pub use lazy_set::LazySet; +// pub use lazy_set::LazySet; pub use lazy_vec::LazyVec; +use crate::ledger::storage_api; +use crate::ledger::vp_env::VpEnv; use crate::types::storage; #[allow(missing_docs)] @@ -31,6 +37,14 @@ pub enum ReadError { UnexpectedlyEmptyStorageKey, } +/// Simple lazy collection with borsh deserializable elements +#[derive(Debug)] +pub struct Simple; + +/// Lazy collection with a nested lazy collection +#[derive(Debug)] +pub struct Nested; + /// A lazy collection of storage values is a handler with some storage prefix /// that is given to its `fn new()`. The values are typically nested under this /// prefix and they can be changed individually (e.g. without reading in the @@ -40,6 +54,98 @@ pub enum ReadError { /// /// An empty collection must be deleted from storage. pub trait LazyCollection { + /// Actions on the collection determined from changed storage keys by + /// `Self::validate` + type Action; + + /// Possible sub-keys in the collection + type SubKey: Debug; + + /// Possible sub-keys together with the data read from storage + type SubKeyWithData: Debug; + + /// A type of a value in the inner-most collection + type Value: BorshDeserialize; + /// Create or use an existing vector with the given storage `key`. fn open(key: storage::Key) -> Self; + + /// Check if the given storage key is a valid LazyVec sub-key and if so + /// return which one. Returns: + /// - `Ok(Some(_))` if it's a valid sub-key + /// - `Ok(None)` if it's not a sub-key + /// - `Err(_)` if it's an invalid sub-key + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result>; + + /// Try to read and decode the data for each change storage key in prior and + /// posterior state. If there is no value in neither prior or posterior + /// state (which is a possible state when transaction e.g. writes and then + /// deletes one storage key, but it is treated as a no-op as it doesn't + /// affect result of validation), returns `Ok(None)`. + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> VpEnv<'a>; + + /// Validate changed sub-keys associated with their data and return back + /// a vector of `Self::Action`s, if the changes are valid + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result>; + + /// Accumulate storage changes inside a `ValidationBuilder`. This is + /// typically done by the validity predicate while looping through the + /// changed keys. If the resulting `builder` is not `None`, one must + /// call `fn build()` on it to get the validation result. + /// This function will return `Ok(true)` if the storage key is a valid + /// sub-key of this collection, `Ok(false)` if the storage key doesn't match + /// the prefix of this collection, or fail with + /// [`ValidationError::InvalidSubKey`] if the prefix matches this + /// collection, but the key itself is not recognized. + fn accumulate( + &self, + env: &ENV, + builder: &mut Option>, + key_changed: &storage::Key, + ) -> storage_api::Result + where + ENV: for<'a> VpEnv<'a>, + { + if let Some(sub) = self.is_valid_sub_key(key_changed)? { + let change = Self::read_sub_key_data(env, key_changed, sub)?; + if let Some(change) = change { + let builder = + builder.get_or_insert(ValidationBuilder::default()); + builder.changes.push(change); + } + return Ok(true); + } + Ok(false) + } + + /// Execute validation on the validation builder, to be called when + /// `accumulate` instantiates the builder to `Some(_)`, after all the + /// changes storage keys have been processed. + fn validate( + builder: ValidationBuilder, + ) -> storage_api::Result> { + Self::validate_changed_sub_keys(builder.changes) + } +} + +/// Validation builder from storage changes. The changes can +/// be accumulated with `LazyCollection::accumulate()` and then turned into a +/// list of valid actions on the collection with `LazyCollection::validate()`. +#[derive(Debug, Derivative)] +// https://mcarton.github.io/rust-derivative/latest/Default.html#custom-bound +#[derivative(Default(bound = ""))] +pub struct ValidationBuilder { + /// The accumulator of found changes under the vector + pub changes: Vec, } diff --git a/shared/src/types/storage.rs b/shared/src/types/storage.rs index 1c3f6b1313..c9f87908fe 100644 --- a/shared/src/types/storage.rs +++ b/shared/src/types/storage.rs @@ -285,6 +285,11 @@ impl Key { self.len() == 0 } + /// Returns the first segment of the key, or `None` if it is empty. + pub fn first(&self) -> Option<&DbKeySeg> { + self.segments.first() + } + /// Returns the last segment of the key, or `None` if it is empty. pub fn last(&self) -> Option<&DbKeySeg> { self.segments.last() diff --git a/tests/proptest-regressions/storage_api/collections/nested_lazy_map.txt b/tests/proptest-regressions/storage_api/collections/nested_lazy_map.txt new file mode 100644 index 0000000000..d587a9680e --- /dev/null +++ b/tests/proptest-regressions/storage_api/collections/nested_lazy_map.txt @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc b5ce7502439712f95a4b50de0d5455e0a6788cc95dbd535e749d5717da0ee8e1 # shrinks to (initial_state, transitions) = (AbstractLazyMapState { valid_transitions: [], committed_transitions: [] }, [Insert((22253647846329582, -2060910714, -85), TestVal { x: 16862967849328560500, y: true })]) diff --git a/tests/src/storage_api/collections/lazy_map.rs b/tests/src/storage_api/collections/lazy_map.rs index c7a309ab4d..afff09bbf1 100644 --- a/tests/src/storage_api/collections/lazy_map.rs +++ b/tests/src/storage_api/collections/lazy_map.rs @@ -137,7 +137,9 @@ mod tests { Transition::Update(key, val) ), 3 => arb_existing_map_key().prop_map(Transition::Remove), - 5 => (arb_map_key().prop_filter("insert on non-existing keys only", move |key| !keys.contains(&key)), arb_map_val()).prop_map(|(key, val)| Transition::Insert(key, val)) + 5 => (arb_map_key().prop_filter("insert on non-existing keys only", + move |key| !keys.contains(key)), arb_map_val()) + .prop_map(|(key, val)| Transition::Insert(key, val)) ] .boxed() } @@ -426,7 +428,10 @@ mod tests { validation_builder.is_some(), "If some keys were changed, the builder must get filled in" ); - let actions = validation_builder.unwrap().build(); + let actions = LazyMap::::validate( + validation_builder.unwrap(), + ) + .unwrap(); let mut actions_to_check = actions.clone(); // Check that every transition has a corresponding action from diff --git a/tests/src/storage_api/collections/lazy_vec.rs b/tests/src/storage_api/collections/lazy_vec.rs index 20ee80592d..65e08b4ca7 100644 --- a/tests/src/storage_api/collections/lazy_vec.rs +++ b/tests/src/storage_api/collections/lazy_vec.rs @@ -418,7 +418,10 @@ mod tests { validation_builder.is_some(), "If some keys were changed, the builder must get filled in" ); - let actions = validation_builder.unwrap().build().expect( + let actions = LazyVec::::validate( + validation_builder.unwrap(), + ) + .expect( "With valid transitions only, validation should always \ pass", ); diff --git a/tests/src/storage_api/collections/mod.rs b/tests/src/storage_api/collections/mod.rs index fc7c5832ce..f39b880c09 100644 --- a/tests/src/storage_api/collections/mod.rs +++ b/tests/src/storage_api/collections/mod.rs @@ -1,2 +1,3 @@ mod lazy_map; mod lazy_vec; +mod nested_lazy_map; diff --git a/tests/src/storage_api/collections/nested_lazy_map.rs b/tests/src/storage_api/collections/nested_lazy_map.rs new file mode 100644 index 0000000000..80b066c18f --- /dev/null +++ b/tests/src/storage_api/collections/nested_lazy_map.rs @@ -0,0 +1,723 @@ +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + use std::convert::TryInto; + + use borsh::{BorshDeserialize, BorshSerialize}; + use namada::types::address::{self, Address}; + use namada::types::storage; + use namada_tx_prelude::storage::KeySeg; + use namada_tx_prelude::storage_api::collections::lazy_map::{ + NestedMap, NestedSubKey, SubKey, + }; + use namada_tx_prelude::storage_api::collections::{ + lazy_map, LazyCollection, LazyMap, + }; + use proptest::prelude::*; + use proptest::prop_state_machine; + use proptest::state_machine::{AbstractStateMachine, StateMachineTest}; + use proptest::test_runner::Config; + use test_log::test; + + use crate::tx::tx_host_env; + use crate::vp::vp_host_env; + + prop_state_machine! { + #![proptest_config(Config { + // Instead of the default 256, we only run 5 because otherwise it + // takes too long and it's preferable to crank up the number of + // transitions instead, to allow each case to run for more epochs as + // some issues only manifest once the model progresses further. + // Additionally, more cases will be explored every time this test is + // executed in the CI. + cases: 5, + .. Config::default() + })] + #[test] + fn nested_lazy_map_api_state_machine_test(sequential 1..100 => ConcreteLazyMapState); + } + + /// Some borsh-serializable type with arbitrary fields to be used inside + /// LazyMap state machine test + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + PartialEq, + Eq, + PartialOrd, + Ord, + )] + struct TestVal { + x: u64, + y: bool, + } + + type KeyOuter = u64; + type KeyMiddle = i32; + type KeyInner = i8; + + type NestedTestMap = + NestedMap>>; + + type NestedEagerMap = + BTreeMap>>; + + /// A `StateMachineTest` implemented on this struct manipulates it with + /// `Transition`s, which are also being accumulated into + /// `current_transitions`. It then: + /// + /// - checks its state against an in-memory `std::collections::HashMap` + /// - runs validation and checks that the `LazyMap::Action`s reported from + /// validation match with transitions that were applied + /// + /// Additionally, one of the transitions is to commit a block and/or + /// transaction, during which the currently accumulated state changes are + /// persisted, or promoted from transaction write log to block's write log. + #[derive(Debug)] + struct ConcreteLazyMapState { + /// Address is used to prefix the storage key of the `lazy_map` in + /// order to simulate a transaction and a validity predicate + /// check from changes on the `lazy_map` + address: Address, + /// In the test, we apply the same transitions on the `lazy_map` as on + /// `eager_map` to check that `lazy_map`'s state is consistent with + /// `eager_map`. + eager_map: NestedEagerMap, + /// Handle to a lazy map with nested lazy collections + lazy_map: NestedTestMap, + /// Valid LazyMap changes in the current transaction + current_transitions: Vec, + } + + #[derive(Clone, Debug, Default)] + struct AbstractLazyMapState { + /// Valid LazyMap changes in the current transaction + valid_transitions: Vec, + /// Valid LazyMap changes committed to storage + committed_transitions: Vec, + } + + /// Possible transitions that can modify a [`NestedTestMap`]. + /// This roughly corresponds to the methods that have `StorageWrite` + /// access and is very similar to [`Action`] + #[derive(Clone, Debug)] + enum Transition { + /// Commit all valid transitions in the current transaction + CommitTx, + /// Commit all valid transitions in the current transaction and also + /// commit the current block + CommitTxAndBlock, + /// Insert a key-val into a [`LazyMap`] + Insert(Key, TestVal), + /// Remove a key-val from a [`LazyMap`] + Remove(Key), + /// Update a value at key from pre to post state in a + /// [`LazyMap`] + Update(Key, TestVal), + } + + /// A key for transition + type Key = (KeyOuter, KeyMiddle, KeyInner); + + impl AbstractStateMachine for AbstractLazyMapState { + type State = Self; + type Transition = Transition; + + fn init_state() -> BoxedStrategy { + Just(Self::default()).boxed() + } + + // Apply a random transition to the state + fn transitions(state: &Self::State) -> BoxedStrategy { + let length = state.len(); + if length == 0 { + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_map_key(), arb_map_val()).prop_map(|(key, val)| Transition::Insert(key, val)) + ] + .boxed() + } else { + let keys = state.find_existing_keys(); + let arb_existing_map_key = + || proptest::sample::select(keys.clone()); + prop_oneof![ + 1 => Just(Transition::CommitTx), + 1 => Just(Transition::CommitTxAndBlock), + 3 => (arb_existing_map_key(), arb_map_val()).prop_map(|(key, val)| + Transition::Update(key, val)), + 3 => arb_existing_map_key().prop_map(Transition::Remove), + 5 => (arb_map_key().prop_filter( + "insert on non-existing keys only", + move |key| !keys.contains(key)), arb_map_val()) + .prop_map(|(key, val)| Transition::Insert(key, val)) + ] + .boxed() + } + } + + fn apply_abstract( + mut state: Self::State, + transition: &Self::Transition, + ) -> Self::State { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + let valid_actions_to_commit = + std::mem::take(&mut state.valid_transitions); + state + .committed_transitions + .extend(valid_actions_to_commit.into_iter()); + } + _ => state.valid_transitions.push(transition.clone()), + } + state + } + + fn preconditions( + state: &Self::State, + transition: &Self::Transition, + ) -> bool { + let length = state.len(); + // Ensure that the remove or update transitions are not applied + // to an empty state + if length == 0 + && matches!( + transition, + Transition::Remove(_) | Transition::Update(_, _) + ) + { + return false; + } + match transition { + Transition::Update(key, _) | Transition::Remove(key) => { + let keys = state.find_existing_keys(); + // Ensure that the update/remove key is an existing one + keys.contains(key) + } + Transition::Insert(key, _) => { + let keys = state.find_existing_keys(); + // Ensure that the insert key is not an existing one + !keys.contains(key) + } + _ => true, + } + } + } + + impl StateMachineTest for ConcreteLazyMapState { + type Abstract = AbstractLazyMapState; + type ConcreteState = Self; + + fn init_test( + _initial_state: ::State, + ) -> Self::ConcreteState { + // Init transaction env in which we'll be applying the transitions + tx_host_env::init(); + + // The lazy_map's path must be prefixed by the address to be able + // to trigger a validity predicate on it + let address = address::testing::established_address_1(); + tx_host_env::with(|env| env.spawn_accounts([&address])); + let lazy_map_prefix: storage::Key = address.to_db_key().into(); + + Self { + address, + eager_map: BTreeMap::new(), + lazy_map: NestedTestMap::open( + lazy_map_prefix.push(&"arbitrary".to_string()).unwrap(), + ), + current_transitions: vec![], + } + } + + fn apply_concrete( + mut state: Self::ConcreteState, + transition: ::Transition, + ) -> Self::ConcreteState { + // Apply transitions in transaction env + let ctx = tx_host_env::ctx(); + + // Persist the transitions in the current tx, or clear previous ones + // if we're committing a tx + match &transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + state.current_transitions = vec![]; + } + _ => { + state.current_transitions.push(transition.clone()); + } + } + + // Transition application on lazy map and post-conditions: + match &transition { + Transition::CommitTx => { + // commit the tx without committing the block + tx_host_env::with(|env| env.write_log.commit_tx()); + } + Transition::CommitTxAndBlock => { + // commit the tx and the block + tx_host_env::commit_tx_and_block(); + } + Transition::Insert( + (key_outer, key_middle, key_inner), + value, + ) => { + let inner = state.lazy_map.at(key_outer).at(key_middle); + + inner.insert(ctx, *key_inner, value.clone()).unwrap(); + + // Post-conditions: + let stored_value = + inner.get(ctx, key_inner).unwrap().unwrap(); + assert_eq!( + &stored_value, value, + "the new item must be added to the back" + ); + + state.assert_validation_accepted(); + } + Transition::Remove((key_outer, key_middle, key_inner)) => { + let inner = state.lazy_map.at(key_outer).at(key_middle); + + let removed = + inner.remove(ctx, key_inner).unwrap().unwrap(); + + // Post-conditions: + assert_eq!( + &removed, + state + .eager_map + .get(key_outer) + .unwrap() + .get(key_middle) + .unwrap() + .get(key_inner) + .unwrap(), + "removed element matches the value in eager map \ + before it's updated" + ); + + state.assert_validation_accepted(); + } + Transition::Update( + (key_outer, key_middle, key_inner), + value, + ) => { + let inner = state.lazy_map.at(key_outer).at(key_middle); + + let old_val = inner.get(ctx, key_inner).unwrap().unwrap(); + + inner.insert(ctx, *key_inner, value.clone()).unwrap(); + + // Post-conditions: + let new_val = inner.get(ctx, key_inner).unwrap().unwrap(); + assert_eq!( + &old_val, + state + .eager_map + .get(key_outer) + .unwrap() + .get(key_middle) + .unwrap() + .get(key_inner) + .unwrap(), + "old value must match the value at the same key in \ + the eager map before it's updated" + ); + assert_eq!( + &new_val, value, + "new value must match that which was passed into the \ + Transition::Update" + ); + + state.assert_validation_accepted(); + } + } + + // Apply transition in the eager map for comparison + apply_transition_on_eager_map(&mut state.eager_map, &transition); + + // Global post-conditions: + + // All items in eager map must be present in lazy map + for (key_outer, middle) in state.eager_map.iter() { + for (key_middle, inner) in middle { + for (key_inner, expected_item) in inner { + let got = state + .lazy_map + .at(key_outer) + .at(key_middle) + .get(ctx, key_inner) + .unwrap() + .expect( + "The expected item must be present in lazy map", + ); + assert_eq!( + expected_item, &got, + "at key {key_outer}, {key_middle} {key_inner}" + ); + } + } + } + + // All items in lazy map must be present in eager map + for key_val in state.lazy_map.iter(ctx).unwrap() { + let ( + NestedSubKey::Data { + key: key_outer, + nested_sub_key: + NestedSubKey::Data { + key: key_middle, + nested_sub_key: SubKey::Data(key_inner), + }, + }, + expected_val, + ) = key_val.unwrap(); + let got = state + .eager_map + .get(&key_outer) + .unwrap() + .get(&key_middle) + .unwrap() + .get(&key_inner) + .expect("The expected item must be present in eager map"); + assert_eq!( + &expected_val, got, + "at key {key_outer}, {key_middle} {key_inner})" + ); + } + + state + } + } + + impl AbstractLazyMapState { + /// Find the length of the map from the applied transitions + fn len(&self) -> u64 { + (map_len_diff_from_transitions(self.committed_transitions.iter()) + + map_len_diff_from_transitions(self.valid_transitions.iter())) + .try_into() + .expect( + "It shouldn't be possible to underflow length from all \ + transactions applied in abstract state", + ) + } + + /// Build an eager map from the committed and current transitions + fn eager_map(&self) -> NestedEagerMap { + let mut eager_map = BTreeMap::new(); + for transition in &self.committed_transitions { + apply_transition_on_eager_map(&mut eager_map, transition); + } + for transition in &self.valid_transitions { + apply_transition_on_eager_map(&mut eager_map, transition); + } + eager_map + } + + /// Find the keys currently present in the map + fn find_existing_keys(&self) -> Vec { + let outer_map = self.eager_map(); + outer_map + .into_iter() + .fold(vec![], |acc, (outer, middle_map)| { + middle_map.into_iter().fold( + acc, + |mut acc, (middle, inner_map)| { + acc.extend( + inner_map + .into_iter() + .map(|(inner, _)| (outer, middle, inner)), + ); + acc + }, + ) + }) + } + } + + /// Find the difference in length of the map from the applied transitions + fn map_len_diff_from_transitions<'a>( + transitions: impl Iterator, + ) -> i64 { + let mut insert_count: i64 = 0; + let mut remove_count: i64 = 0; + + for trans in transitions { + match trans { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Update(_, _) => {} + Transition::Insert(_, _) => insert_count += 1, + Transition::Remove(_) => remove_count += 1, + } + } + insert_count - remove_count + } + + impl ConcreteLazyMapState { + fn assert_validation_accepted(&self) { + // Init the VP env from tx env in which we applied the map + // transitions + let tx_env = tx_host_env::take(); + vp_host_env::init_from_tx(self.address.clone(), tx_env, |_| {}); + + // Simulate a validity predicate run using the lazy map's validation + // helpers + let changed_keys = + vp_host_env::with(|env| env.all_touched_storage_keys()); + + let mut validation_builder = None; + + // Push followed by pop is a no-op, in which case we'd still see the + // changed keys for these actions, but they wouldn't affect the + // validation result and they never get persisted, but we'd still + // them as changed key here. To guard against this case, + // we check that `map_len_from_transitions` is not empty. + let map_len_diff = + map_len_diff_from_transitions(self.current_transitions.iter()); + + // To help debug validation issues... + dbg!( + &self.current_transitions, + &changed_keys + .iter() + .map(storage::Key::to_string) + .collect::>() + ); + + for key in &changed_keys { + let is_sub_key = self + .lazy_map + .accumulate( + vp_host_env::ctx(), + &mut validation_builder, + key, + ) + .unwrap(); + + assert!( + is_sub_key, + "We're only modifying the lazy_map's keys here. Key: \ + \"{key}\", map length diff {map_len_diff}" + ); + } + if !changed_keys.is_empty() && map_len_diff != 0 { + assert!( + validation_builder.is_some(), + "If some keys were changed, the builder must get filled in" + ); + let actions = + NestedTestMap::validate(validation_builder.unwrap()) + .unwrap(); + let mut actions_to_check = actions.clone(); + + // Check that every transition has a corresponding action from + // validation. We drop the found actions to check that all + // actions are matched too. + let current_transitions = + normalize_transitions(&self.current_transitions); + for transition in ¤t_transitions { + use lazy_map::Action; + use lazy_map::NestedAction::At; + + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => { + } + Transition::Insert(expected_key, expected_val) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let At( + key_outer, + At( + key_middle, + Action::Insert(key_inner, val), + ), + ) = action + { + let key = + (*key_outer, *key_middle, *key_inner); + if expected_key == &key + && expected_val == val + { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Remove(expected_key) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let At( + key_outer, + At( + key_middle, + Action::Remove(key_inner, _val), + ), + ) = action + { + let key = + (*key_outer, *key_middle, *key_inner); + if expected_key == &key { + actions_to_check.remove(ix); + break; + } + } + } + } + Transition::Update(expected_key, value) => { + for (ix, action) in + actions_to_check.iter().enumerate() + { + if let At( + key_outer, + At( + key_middle, + Action::Update { + key: key_inner, + pre: _, + post, + }, + ), + ) = action + { + let key = + (*key_outer, *key_middle, *key_inner); + if expected_key == &key && post == value { + actions_to_check.remove(ix); + break; + } + } + } + } + } + } + + assert!( + actions_to_check.is_empty(), + "All the actions reported from validation {actions:#?} \ + should have been matched with SM transitions \ + {current_transitions:#?}, but these actions didn't \ + match: {actions_to_check:#?}", + ) + } + + // Put the tx_env back before checking the result + tx_host_env::set_from_vp_env(vp_host_env::take()); + } + } + + /// Generate an arbitrary `TestKey` + fn arb_map_key() -> impl Strategy { + (any::(), any::(), any::()) + } + + /// Generate an arbitrary `TestVal` + fn arb_map_val() -> impl Strategy { + (any::(), any::()).prop_map(|(x, y)| TestVal { x, y }) + } + + /// Apply `Transition` on an eager `Map`. + fn apply_transition_on_eager_map( + map: &mut NestedEagerMap, + transition: &Transition, + ) { + match transition { + Transition::CommitTx | Transition::CommitTxAndBlock => {} + Transition::Insert((key_outer, key_middle, key_inner), value) + | Transition::Update((key_outer, key_middle, key_inner), value) => { + let middle = + map.entry(*key_outer).or_insert_with(Default::default); + let inner = + middle.entry(*key_middle).or_insert_with(Default::default); + inner.insert(*key_inner, value.clone()); + } + Transition::Remove((key_outer, key_middle, key_inner)) => { + let middle = + map.entry(*key_outer).or_insert(Default::default()); + let inner = + middle.entry(*key_middle).or_insert_with(Default::default); + let _popped = inner.remove(key_inner); + } + } + } + + /// Normalize transitions: + /// - remove(key) + insert(key, val) -> update(key, val) + /// - insert(key, val) + update(key, new_val) -> insert(key, new_val) + /// - update(key, val) + update(key, new_val) -> update(key, new_val) + /// + /// Note that the normalizable transitions pairs do not have to be directly + /// next to each other, but their order does matter. + fn normalize_transitions(transitions: &[Transition]) -> Vec { + let mut collapsed = vec![]; + 'outer: for transition in transitions { + match transition { + Transition::CommitTx + | Transition::CommitTxAndBlock + | Transition::Remove(_) => collapsed.push(transition.clone()), + Transition::Insert(key, val) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Remove(remove_key) = + collapsed_transition + { + if key == remove_key { + // remove(key) + insert(key, val) -> update(key, + // val) + + // Replace the Remove with an Update instead of + // inserting the Insert + *collapsed.get_mut(ix).unwrap() = + Transition::Update(*key, val.clone()); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + Transition::Update(key, value) => { + for (ix, collapsed_transition) in + collapsed.iter().enumerate() + { + if let Transition::Insert(insert_key, _) = + collapsed_transition + { + if key == insert_key { + // insert(key, val) + update(key, new_val) -> + // insert(key, new_val) + + // Replace the insert with the new update's + // value instead of inserting it + *collapsed.get_mut(ix).unwrap() = + Transition::Insert(*key, value.clone()); + continue 'outer; + } + } else if let Transition::Update(update_key, _) = + collapsed_transition + { + if key == update_key { + // update(key, val) + update(key, new_val) -> + // update(key, new_val) + + // Replace the insert with the new update's + // value instead of inserting it + *collapsed.get_mut(ix).unwrap() = + Transition::Update(*key, value.clone()); + continue 'outer; + } + } + } + collapsed.push(transition.clone()); + } + } + } + collapsed + } +} From 4adf30130baa0152a5738f8f84a89124f8bd65b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 18:01:30 +0200 Subject: [PATCH 353/373] ledger/storage/lazy: remove unused error cases --- shared/src/ledger/storage_api/collections/lazy_map.rs | 2 -- shared/src/ledger/storage_api/collections/lazy_vec.rs | 2 -- 2 files changed, 4 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 686f9d336e..6e32b5399b 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -98,8 +98,6 @@ pub enum NestedSubKey { #[allow(missing_docs)] #[derive(Error, Debug)] pub enum ValidationError { - #[error("Storage error in reading key {0}")] - StorageError(storage::Key), #[error("Invalid storage key {0}")] InvalidSubKey(storage::Key), #[error("Invalid nested storage key {0}")] diff --git a/shared/src/ledger/storage_api/collections/lazy_vec.rs b/shared/src/ledger/storage_api/collections/lazy_vec.rs index fd61bef804..59eaa225e5 100644 --- a/shared/src/ledger/storage_api/collections/lazy_vec.rs +++ b/shared/src/ledger/storage_api/collections/lazy_vec.rs @@ -75,8 +75,6 @@ pub enum Action { #[allow(missing_docs)] #[derive(Error, Debug)] pub enum ValidationError { - #[error("Storage error in reading key {0}")] - StorageError(storage::Key), #[error("Incorrect difference in LazyVec's length")] InvalidLenDiff, #[error("An empty LazyVec must be deleted from storage")] From 4b22589549c6ec09de5fed371ace1cd27a3cc801 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 22 Sep 2022 11:16:33 +0200 Subject: [PATCH 354/373] [misc] rebase --- apps/src/lib/client/tx.rs | 1 - apps/src/lib/node/ledger/shell/governance.rs | 20 ++++++++-------- tests/src/e2e/ledger_tests.rs | 24 ++++++++++---------- wasm_for_tests/wasm_source/Cargo.lock | 24 ++++++++++---------- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ab900fc732..bf7c99714e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -17,7 +17,6 @@ use namada::types::governance::{ use namada::types::key::*; use namada::types::nft::{self, Nft, NftToken}; use namada::types::storage::Epoch; -use namada::types::token::Amount; use namada::types::transaction::governance::{ InitProposalData, VoteProposalData, }; diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 180c0a9af4..50f7adf40b 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -1,15 +1,15 @@ -use anoma::ledger::governance::storage as gov_storage; -use anoma::ledger::governance::utils::{ +use namada::ledger::governance::storage as gov_storage; +use namada::ledger::governance::utils::{ compute_tally, get_proposal_votes, ProposalEvent, }; -use anoma::ledger::governance::vp::ADDRESS as gov_address; -use anoma::ledger::storage::types::encode; -use anoma::ledger::storage::{DBIter, StorageHasher, DB}; -use anoma::ledger::slash_fund::ADDRESS as slash_fund_address; -use anoma::types::address::{xan as m1t, Address}; -use anoma::types::governance::TallyResult; -use anoma::types::storage::Epoch; -use anoma::types::token; +use namada::ledger::governance::vp::ADDRESS as gov_address; +use namada::ledger::storage::types::encode; +use namada::ledger::storage::{DBIter, StorageHasher, DB}; +use namada::ledger::slash_fund::ADDRESS as slash_fund_address; +use namada::types::address::{xan as m1t, Address}; +use namada::types::governance::TallyResult; +use namada::types::storage::Epoch; +use namada::types::token; use super::*; use crate::node::ledger::events::EventType; diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 037bdb64d8..af54f4677a 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1046,7 +1046,7 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); + let test_dir = tempfile::tempdir_in(test.test_dir.path()).unwrap(); let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); let albert = find_address(&test, ALBERT)?; @@ -1064,9 +1064,9 @@ fn proposal_submission() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 12, - "voting_end_epoch": 24, - "grace_epoch": 30, + "voting_start_epoch": 12 as u64, + "voting_end_epoch": 24 as u64, + "grace_epoch": 30 as u64, "proposal_code_path": proposal_code.to_str().unwrap() } ); @@ -1164,9 +1164,9 @@ fn proposal_submission() -> Result<()> { eros.", "requires": "2" }, "author": albert, - "voting_start_epoch": 9999, - "voting_end_epoch": 10000, - "grace_epoch": 10009, + "voting_start_epoch": 9999 as u64, + "voting_end_epoch": 10000 as u64, + "grace_epoch": 10009 as u64, } ); let invalid_proposal_file = @@ -1393,8 +1393,8 @@ fn proposal_offline() -> Result<()> { client.exp_string("Transaction is valid.")?; client.assert_success(); - // 2. Create an offline proposal - let test_dir = tempfile::tempdir_in(test.base_dir.path()).unwrap(); + // 2. Create an offline + let test_dir = tempfile::tempdir_in(test.test_dir.path()).unwrap(); let albert = find_address(&test, ALBERT)?; let valid_proposal_json = json!( @@ -1411,9 +1411,9 @@ fn proposal_offline() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 3, - "voting_end_epoch": 9, - "grace_epoch": 18 + "voting_start_epoch": 3 as u64, + "voting_end_epoch": 9 as u64, + "grace_epoch": 18 as u64 } ); let proposal_file = diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..876455fcaa 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1619,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From 3863caa36f4a34b547fd74357cf466708bf34378 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 22 Sep 2022 12:15:11 +0200 Subject: [PATCH 355/373] fix proposal_submission e2e test --- apps/src/lib/client/rpc.rs | 6 +++--- apps/src/lib/client/tx.rs | 7 ++++--- apps/src/lib/node/ledger/protocol/mod.rs | 2 +- apps/src/lib/node/ledger/shell/governance.rs | 11 ++++------ shared/src/ledger/mod.rs | 2 +- shared/src/types/address.rs | 4 +++- tests/src/e2e/ledger_tests.rs | 21 ++++++++++--------- vm_env/src/lib.rs | 2 +- vp_prelude/src/lib.rs | 7 ++++--- wasm_for_tests/tx_memory_limit.wasm | Bin 135502 -> 135517 bytes wasm_for_tests/tx_mint_tokens.wasm | Bin 226185 -> 231855 bytes wasm_for_tests/tx_no_op.wasm | Bin 25027 -> 25030 bytes wasm_for_tests/tx_proposal_code.wasm | Bin 214371 -> 212604 bytes wasm_for_tests/tx_read_storage_key.wasm | Bin 152558 -> 152605 bytes wasm_for_tests/tx_write_storage_key.wasm | Bin 149083 -> 154634 bytes wasm_for_tests/vp_always_false.wasm | Bin 160359 -> 160865 bytes wasm_for_tests/vp_always_true.wasm | Bin 161066 -> 161197 bytes wasm_for_tests/vp_eval.wasm | Bin 161675 -> 162157 bytes wasm_for_tests/vp_memory_limit.wasm | Bin 163364 -> 163530 bytes wasm_for_tests/vp_read_storage_key.wasm | Bin 170912 -> 170224 bytes 20 files changed, 32 insertions(+), 30 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index ea86cfd314..d19ded990c 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -12,20 +12,20 @@ use async_std::path::PathBuf; use async_std::prelude::*; use borsh::BorshDeserialize; use itertools::Itertools; +use namada::ledger::governance::parameters::GovParams; use namada::ledger::governance::storage as gov_storage; use namada::ledger::governance::utils::Votes; -use namada::types::governance::VotePower; use namada::ledger::parameters::{storage as param_storage, EpochDuration}; use namada::ledger::pos::types::{ Epoch as PosEpoch, VotingPower, WeightedValidator, }; -use namada::ledger::governance::parameters::GovParams; use namada::ledger::pos::{ self, is_validator_slashes_key, BondId, Bonds, PosParams, Slash, Unbonds, }; use namada::types::address::Address; use namada::types::governance::{ - OfflineProposal, OfflineVote, ProposalVote, TallyResult, ProposalResult, + OfflineProposal, OfflineVote, ProposalResult, ProposalVote, TallyResult, + VotePower, }; use namada::types::key::*; use namada::types::storage::{Epoch, PrefixValue}; diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index bf7c99714e..2bac35a896 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -571,8 +571,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { { eprintln!( "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and at max {} and end epoch must \ - be a multiple of {}", + and end epoch must be at least {} and at max {} and end epoch \ + must be a multiple of {}", governance_parameters.min_proposal_period, governance_parameters.max_proposal_period, governance_parameters.min_proposal_period @@ -634,7 +634,8 @@ pub async fn submit_init_proposal(mut ctx: Context, args: args::InitProposal) { let balance = rpc::get_token_balance(&client, &m1t(), &proposal.author) .await .unwrap_or_default(); - if balance < token::Amount::from(governance_parameters.min_proposal_fund) + if balance + < token::Amount::from(governance_parameters.min_proposal_fund) { eprintln!( "Address {} doesn't have enough funds.", diff --git a/apps/src/lib/node/ledger/protocol/mod.rs b/apps/src/lib/node/ledger/protocol/mod.rs index c8069d5a9c..bd40f7f843 100644 --- a/apps/src/lib/node/ledger/protocol/mod.rs +++ b/apps/src/lib/node/ledger/protocol/mod.rs @@ -9,9 +9,9 @@ use namada::ledger::ibc::vp::{Ibc, IbcToken}; use namada::ledger::native_vp::{self, NativeVp}; use namada::ledger::parameters::{self, ParametersVp}; use namada::ledger::pos::{self, PosVP}; +use namada::ledger::slash_fund::SlashFundVp; use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{DBIter, Storage, StorageHasher, DB}; -use namada::ledger::slash_fund::SlashFundVp; use namada::proto::{self, Tx}; use namada::types::address::{Address, InternalAddress}; use namada::types::storage; diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index 50f7adf40b..a1d17110eb 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -3,9 +3,9 @@ use namada::ledger::governance::utils::{ compute_tally, get_proposal_votes, ProposalEvent, }; use namada::ledger::governance::vp::ADDRESS as gov_address; +use namada::ledger::slash_fund::ADDRESS as slash_fund_address; use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; -use namada::ledger::slash_fund::ADDRESS as slash_fund_address; use namada::types::address::{xan as m1t, Address}; use namada::types::governance::TallyResult; use namada::types::storage::Epoch; @@ -14,6 +14,7 @@ use namada::types::token; use super::*; use crate::node::ledger::events::EventType; +#[derive(Default)] pub struct ProposalsResult { passed: Vec, rejected: Vec, @@ -28,10 +29,7 @@ where D: DB + for<'iter> DBIter<'iter> + Sync + 'static, H: StorageHasher + Sync + 'static, { - let mut proposals_result = ProposalsResult { - passed: Vec::new(), - rejected: Vec::new(), - }; + let mut proposals_result = ProposalsResult::default(); if !new_epoch { return Ok(proposals_result); @@ -55,8 +53,7 @@ where ) })?; - let votes = - get_proposal_votes(&shell.storage, proposal_end_epoch, id); + let votes = get_proposal_votes(&shell.storage, proposal_end_epoch, id); let tally_result = compute_tally(&shell.storage, proposal_end_epoch, votes); diff --git a/shared/src/ledger/mod.rs b/shared/src/ledger/mod.rs index 8f0f049683..a317b509d8 100644 --- a/shared/src/ledger/mod.rs +++ b/shared/src/ledger/mod.rs @@ -7,6 +7,6 @@ pub mod ibc; pub mod native_vp; pub mod parameters; pub mod pos; -pub mod storage; pub mod slash_fund; +pub mod storage; pub mod vp_env; diff --git a/shared/src/types/address.rs b/shared/src/types/address.rs index a1ff9702ea..6f047730fe 100644 --- a/shared/src/types/address.rs +++ b/shared/src/types/address.rs @@ -185,7 +185,9 @@ impl Address { InternalAddress::Governance => { internal::GOVERNANCE.to_string() } - InternalAddress::SlashFund => internal::SLASH_FUND.to_string(), + InternalAddress::SlashFund => { + internal::SLASH_FUND.to_string() + } InternalAddress::IbcEscrow(hash) => { format!("{}::{}", PREFIX_INTERNAL, hash) } diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index af54f4677a..777090d4d4 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1064,9 +1064,9 @@ fn proposal_submission() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 12 as u64, - "voting_end_epoch": 24 as u64, - "grace_epoch": 30 as u64, + "voting_start_epoch": 12_u64, + "voting_end_epoch": 24_u64, + "grace_epoch": 30_u64, "proposal_code_path": proposal_code.to_str().unwrap() } ); @@ -1164,9 +1164,9 @@ fn proposal_submission() -> Result<()> { eros.", "requires": "2" }, "author": albert, - "voting_start_epoch": 9999 as u64, - "voting_end_epoch": 10000 as u64, - "grace_epoch": 10009 as u64, + "voting_start_epoch": 9999_u64, + "voting_end_epoch": 10000_u64, + "grace_epoch": 10009_u64, } ); let invalid_proposal_file = @@ -1186,7 +1186,8 @@ fn proposal_submission() -> Result<()> { let mut client = run!(test, Bin::Client, submit_proposal_args, Some(40))?; client.exp_string( "Invalid proposal end epoch: difference between proposal start and \ - end epoch must be at least 3 and end epoch must be a multiple of 3", + end epoch must be at least 3 and at max 27 and end epoch must be a \ + multiple of 3", )?; client.assert_failure(); @@ -1411,9 +1412,9 @@ fn proposal_offline() -> Result<()> { "requires": "2" }, "author": albert, - "voting_start_epoch": 3 as u64, - "voting_end_epoch": 9 as u64, - "grace_epoch": 18 as u64 + "voting_start_epoch": 3_u64, + "voting_end_epoch": 9_u64, + "grace_epoch": 18_u64 } ); let proposal_file = diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 2d3cab9e8c..8902a64ed1 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -18,8 +18,8 @@ pub mod token; pub mod tx_prelude { pub use namada::ledger::governance::storage; pub use namada::ledger::parameters::storage as parameters_storage; - pub use namada::ledger::storage::types::encode; pub use namada::ledger::slash_fund::storage as slash_fund_storage; + pub use namada::ledger::storage::types::encode; pub use namada::proto::{Signed, SignedTxData}; pub use namada::types::address::Address; pub use namada::types::storage::Key; diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index daf35b6d06..abf280090a 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -43,14 +43,15 @@ pub fn is_proposal_accepted(proposal_id: u64) -> bool { /// Checks whether a transaction is valid, which happens in two cases: /// - tx is whitelisted, or -/// - tx is executed by an approved governance proposal (no need to be whitelisted) +/// - tx is executed by an approved governance proposal (no need to be +/// whitelisted) pub fn is_valid_tx(tx_data: &[u8]) -> bool { if is_tx_whitelisted() { - return true + true } else { let proposal_id = u64::try_from_slice(tx_data).ok(); - proposal_id.map_or(false, |id| is_proposal_accepted(id)) + proposal_id.map_or(false, is_proposal_accepted) } } diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 88c8ef0ada51f31c8e0f25c2fa633b4ce20ad65b..2bda493fccaf7713bc2e507d9bb61f548ae799ca 100755 GIT binary patch delta 1262 zcmZuwYfKzf6u#%q%VyFIGd?g#cL*l==`vD*PQD&LNK3Jcs7-fhb@rIRxgEg3HPydanO z0)B~c0EsC8G8C%-tnhOQfa31Q6%`cl&iwR5!rU3IjYC_eMBkAry}P9>7-}x7uo4Z8 zO?S0-WLsPI?hywL?&}H_tvOG^Ek!$ZZi$I=1p0o=dyn7YZ+H)uFBYaO7GNefxlr?_ zY=XWIvuet-J%^KOtrXGz#3cwh=hZEMQ_gHr1wb0V(&B{c+Ps|Zk-PA+7Qs1x1Eif5 z|D&`Flf?#9JBN!mB=3EkD6%y!UHT4iZeuW*0S)g7 zzJ@=vR~bHL(lo8n{OrKy*4!_%tGDKJnoL1wC3qe}g$kLe2w&cm@?y6sjq6c76TS)t zj+8%0c(MEy!gS;fFq}(~-$2?b)^NQwj2EK8fS~j1xNb=TdJSBOKEmxBCJmbg=Ylcm zp)zW${ZQo`v#5qjT&TSSHTXm`i^HF$`qtE4WR3D@}}9 zAR^C+$n%yg*kFJ~aUj!O{GtJ%Atvp59Q%AXKThw5#BR+DslkePYjsaL0Y!7&UL<{UMzl0X?bbKG8uY3 ze%iGh7mJ^`cFYx#I#4HJ7APVVx=qiEYlT_oj9613iTU2{5<6!xgE$5~LdwXP?u+7D z@BNf!Q{QCx|JA}T`d$jy{d6`Qo}3IYreRrs@Af-&TbthPyD@v^1Q?+;xy;N(nm-y@=fdBOC^CZs011K0Gn_useJ= dcoF>0^w7^h!{`yOjzeRKshP2bnK8Kh`M={O-6K^Kpx#w{o z=R4=l?{zm=d$@$8OV?-CO-LIhV!qnc#v;Ju6fJ zW)G(Hglo-*6M97P8Q1VU`0UgAb$~g0Iy- zDGcKUK?vFd1cch_)dc_mj)A>^kjvSl^cb!*Yx9j=>-Vj5&!*!fBBy$919$VfPFRa2Pmrr z1{SrI;&OS3Ay|}~mLj2l9F^cM&W3O-I7Px<2~N1^ro+}2D7W9X?%_~|*CJ;?;7rXh z;mw)_%J;|Gr-8Gy-5Y&~msT-D{_`p^8f1phoOFmWfwQq{h~TA|pzUtP8facuzZ0tM z1NB!FsKH(F5aDF}XhjJv11aRiw`dPX(d4(9oX=?(OANbwVdhm0Tayg(&b)3p5ND0Wrm#J@lCJ>&WYKXc>qaKnUIIFoCvg?||03jS`*-!XY%L?I4>$1cDD{b!)Io0iZII(=V zV~p@>M+ko?sYFj_H~28qxsTF6+xd9hZ-kk+m1?1rz&a8CO*}+_WYkNB$|R;!Nng3K zfozl(IrOX}!EC@IsVE%8GpW}?wX`Ld(GC`(fRB&UbydOWU5@W2?xjRGrzcAPzs~q|`q=`Ord?rRVj`b0gW=NPGjIj>XC9~$ zT9m7nTOJs6QhAc)1$M~qj2uCe*p)@P3!;w diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index c9a102773e28ced047f374606582082da3d839d0..cb4bc94490309c7b58874752bd50018a050d5ba8 100755 GIT binary patch delta 23752 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 delta 17923 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 diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index bdab4054d9fe3a54897713c81028396ec6a24f0e..649a1b72f152044560b29408aec863baf09a4e4b 100755 GIT binary patch delta 286 zcmX?nnDN+Q#tF)*_4N!1Om%e(39R)Xz*wKaSkG9;%*(^d!pP3Z#KyqP#K1h!BZY&L zi<_05gKOf%Eos~v8V@iyo&XX8Y?D*M?2y(%4Yv^ALRg8G z!m8*q*d%tAK7ioFR5`_WIGpBttFWr@X0cHekh2gV$ACpH!zdVdIwd43WWY7!&}$tT zsdUXI$yM*+*bchS;O`^qh8lP>v2Sr>_Wl3LcnXd==FO>79_uyw!F4tMXo+1j*rF$P z5UZTwD+~wZGO$wsdtS44_1Jqm*kTk?KE}mT=`*tcm4p+?NBHH diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index 3c753377ed91ac3086bd2c55079414182be5aa0b..decd2575af60b98c5b27e1f5f6775753990bf8a1 100755 GIT binary patch delta 16853 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 delta 18654 zcmcJX3t$|@nV`G6d)_lr%X(Ny!tNOxkO5rIISGh?netlzb_0Ped&g z-4^BboWHhi<@pz!xAKgEb!S}EziObzNqZNsQ?BP^viy1~>3T8Oag?j#UQF>{uFj?8 zN-dzHI`)Lm_-bWO*Qmzk_5(V`cOEx+`izd5N6*s7 zFI}{F$-+e+Yy0$);-ZsQobie7)90UZ>ZeZo+&L#M`Rt;<`{eSI&phkw^Ul9u)j8+( z_Vus1aP5-xPoHzw{nF?w*PA8oRVKV4Z(iBbkP2O$Eru#sXBxb7Rl!{5O;K_4E$?K? zms~L=6L&I;T?Oev*F3T1K=WC@u@tJ%4c4`Ve&~i?ak}ehC-ZcWlwP&}#yNVE>MgkA z9k*-bU#PYflG-`J?+TMSrB&X`x|sklVN$CgQSj#GR3`L7XIq#I*5w7k&Y~Xt-A*S+ z>Q?OsiIVhj=;BWBI%l|)bWo(*%;E&kiRwwIPp;J6?k@}u4hA#a<$~~ZcbTrYpnMs| z*P9u8g5%6^q>?D=n@8$y=udZ7gl_k>46s+taL;DkQm2gmgd@MZoMHfqoo;6iqC|L1 zEq1yNQ~{Vf!99G2mt6KvB)`R#SV_fm(w*A_TNI%UQrPupb=sXLy}a_nFacvS6t9 zIN14{%Cm9m%y4%VvThuSUzzc3!Gn zLySrHdfpGMl8KtGV2=Bf?b3flxKY#*lA&Vdxq>0M``q?MI#ZE+t8g)9!K6i7^NA2$ zwc1#63jTCQ@EuIfa1Td`V$pw}!Yp52p@KgxzIeu`yzHJSy2D2H7@d#Sk;)Od(b_LkTfMr5L?O zuxc&ly)dp@dkgWLlgYT&3Y=Y5_U+xRmu-v6LS*o_BxsFL4gW)^mi`}b{RpUbd^oDf z2-RQ&)zW_sSw8}*S&OQ*zaJ!P5@AVMCn_|?^%loF^%?l<>WxZ-AI|{etYgxHwq0h--t5m834t0 z>s?ZLf**NwoKgprh%Cb1f-~L;9oOG?r3*F@1aAnAgVOotsR)2*vSX& zzy&qP{L~DJDik!PCxyoJ-Y440*-8Jrte@%Z(ISqAPc9F3af9!xQ7<<^3$u z@}42o$Pm1X3Ct5TAzR|sWq?vH^S%*M8SO@a4VI=$(DMboltVO~NT$-6Y{p`dk$x@! zwZ|+(T`SAITKHoA~H zH8+=IzF|U~s~@ItsPwZYIHBL=Kh>AdAzWUV$Y;H-Fh}HU8Q_IMv5;Y4vG*l#;k8U8 z79(PlDbrpF9toaDv?o3dRU|fXL4^n%RN`}UO_?x`Gh(IC7OL%Gy^SeXU%nw<4+=4^ zG+bbqh@*DYxQtp74(HPdfQV8&RESb_&_)?F{TBvJ3qR#mgsXyIA#S5%s!djMEF5`i zbs!%f!9H2Wz6AQoxw%oSFMw4>Ul?&^Sid1Z3MsPlERRsI1NsaYX;B=UE$%gV6B;yR zAVp=gXn;}q`Y@TdOP3VX57Qz}kfT%#>t#_Q078NyDIsI=sN@$#IGr#DWAZt4%z9G- zu--yeylKS#(ApPKa`-J$gPm|tlU&3o2#ZeS;7oROMuuX_Srk3Y(p;i_L^+{PPA4Tc ziD|E>|GqDb^%jKR(%hSmLvIDy!yIoivEUkd9|Lfj=+jOOtIVNsBv=(1I4X(#0z`c$Egf+~8LlV&#Tdr6FE!h}#B15(GiI z5MRu42@F-pf=MWYZk5(R%ln>r_$}95mzY?R35s$x3uE@KbnAd_#riMs(ZNzg zkmLFaGQ`-vf~(^L{S?t+gnE}^1=tpm=(s*`=OI0DT6Bu*z0qS=j~ZZcx7UGf6CF=i zdiH1ZxOq91?ZD1PL)v;S*AL?(PP)}`5itwfQpVyE zb=nUeN-*w61_lQob>`t@EGB~%Z(S0%G#JPfH9??M2K|NYnSu)`dm*=rxEGQOEu3T# zD-jF15m*z?)E}R736qgkVF?AVuaNG|qhaOlqBu4s)LCXmHhjtx+0$Jcp_OO{(6 z79ibQNHbC)(VKS}jGI`6Oc54%eT*lW37jakfkHyJ_5mg?{ZrcS6;zW9OnzGUAKsj@TxH*s{r zA+U(`3Kil-<`<8igd|cJ4PfQzd&ns~hbYAY^iRle8PH_Dy(D0mQyL13FtFIDOh2ui zB{^X_)5jLoPQPfU@6}FU047k0%-U$?_?>~u%;}Dm@Wo2F3m3`sLER4TgmJ4^n2(V; zzo)y!G|3Dtb3l_A1kQEdsg%yf4_m?_CfN$hxJ zK{LJhr)=JHy8!BG2a3R-1X>)%78cxgL>xX5)GW>=r7d*Z^J#I)Li{L`g#f}LD~A?G z!8`@t`p9{(S1`x=+DsX4Nmuj^gj93ofFzORTh? zJ_uE9pZI>dB+$;9IVDEApm2gO7P{PK?Rh#(6qL0I;+lklLMFeenjgRXGo_q9a zZ;k%K`VGn6K8WJ?%0rTLYe-nWnCmE6x~Jd_q?WkX+7+Q_!4u`Q2u0Q$uQ3n)u`C85YnwGPYS@EuP5Q98AL-=<%eEfcC zNv=gaL>EpOt{|Jo(~TS5n7E_4f>!ZFhdau4mu!OS)o~QJ8@1L?IlX#2mZXvROLV(g zJJva441z2fS z>y_wmMnsXw8A(sK(zC`Ri5FWfLQ!^(c1X{5bj^v$)=`CI*#)Jn{Q*7G8jL9gqG+)& z5E$HqIV^-SM9LNRU>&6@HZl4KMcpt%PpBoVu%dt`ux=gmh+_v0Cc$j2Hj1c-*%Y@A z8s9H1l8MXmNted<$*hx=-iW}5^scXVW?&T5N@=1`Tr3Nn@(-AZuO(M(W1&1YF{LvD z`8rta>2dia7pTJw`4`ri&os7`8f5-;VZ%IhfoL_tNgRcQK%Q7NZp2f_fM6Rv@pNHv z4nxlhINzXuj`^lska(#}9828Q@~-2M=P*1>bJxQwn~iWk-qUuqjiCS_K&ezLqGgZ#lMb*4p2H=K`&_}o`5)7k7fIW$s z>~=&X!0^NhaEd2{RakZr;F5T2BskS?yG9aBS1Z9O;T`Q>B*BubA-uC4BMDB9#CN=7 z<2(CKf0@@30V5E_3_>cQrd2ATS(Wdj-p}}m@8doK-_6$=#}xiLd(FQ#cK&Tx*Z>ng z$ii`@l$PX+RvF|hl5w0vnX9xvewE9EG)V^eNfro6*gg@7)sJ8Cfk#Rf2P`oW-k{FD zAhK@jYH$f{`KU;2T!(eL_t&?57v)UTQ`t#7jx-*G7 z@mrDiD)kG;{q8thR@vr^erm%GM|bLXxCMvH6YWNBmy*&FudBZHScqF(RqRRLDPs{- zY?XmztIF3ms*>Hh^yn7)6TD4q?JZzrM3ASFR@6Ua-*Fr?W_-q$l?x@ZT};UDWK5Aj zUvDLrfw*ix(708$T9KZp2$s^1H+XTQ(usKz2T$-eD)Ja^iPRHp3b-GJzEl~#1w7zHeP980Y zrlTLE7Ql9rgNag@16jnm%;=CW3a_BDmq6?rD zt3}=?+mug1N`kO_401~1LdVvkLqt18;`n%&n8&$+2&XU}K%Y)`0sXlxBL^Zv5e5+@ zq=~l*B{xk ziflQFov@<|W+DLZ9nOv>J4-TP7~2nKquvwQN{oC_(|7 z>tonL%xBF*2+k@;&;vVytJr|vMHbTQTSGAZt^4l!H;;gv_5;?kbVeU(ed(4*zU4-( zu5EaGc*E-|YVd8t|Nh;^-kRGcj>8!9vPbO*S z>n>SJNTh3(aAu)i_Rs6LNwyYw^4Z!ij3S>)E;05AY)+szy|fcy3NE4ovVG*RJu92x zC@b$_V1<2xx4^!)4YrQjM#_!!rX{W`<{`qal*Y6ouJ03LT3BTQT!Af1EPfWY7b3|< z&+}P2#Klk9E=h@ui1NpZXYyc|m|HRxnlxFABw!A+o=;#SnILj=omdQima|D^6D6!7 z9f?vCYs+M1iN@ALBz+%H3&an#c%Sf*;#?-KR?K^pF%#H;6)#16-#Z}g!g@4c^Dck| z7tCLA3^#0HE&Ue={V~EYAXUz-*MZLj40NGI(&}Pf9gXeREHohy9#uleRC$%2>diMo z9wo$)e-W81p;Bc+MS(ku9f|iENhgqE2pd@y_S-dp3hS2U9ONJ8PR0b}Smi`{;(k5I z3UnPTXSO!YSQq76z+!36Ex#ix4crs)aScs`(St6mL%%AxjocBWjbDjkT-N(_o}*j| zUWKCA23CWTvCU$lGrte-h zabn7G9NA_{>-wT~@9tt|?VKQK$x_$S{#;JDMw$>qFW!GLz{SnP^|hr%vQ>nChFXGD z6^bm4auUw7sE0i=6+t-`;b2$4F+CWMh$8E>xL(nxx0dGTQW3DNr4w=__z}~z9vi{6 z%kH$ktWTdmpZ*bof}h<8h+B|}NT)1D7G|;r<{|8k!&|vw_w*vLUfimaNm<~ zGsbRKY0xj@Nborb6B$5eMTWm}!k|f*Ek;CAgjupU6=JqQ z8aV_cecpIkzz(AFvahs`7LXKAOcayPiVTG8M>mly72VYnC4j@KMe#NPF{#;3Hz*Yv zWvxcB16Ia3ewA-R)}#D4vxA>UI?l#!b@{R=pMo(D6N|+&NX+JnF&E2jacs*<^ z7Sg;PHb~@(&tPXQ+W&O*t``YEBxq(7y|+@4ab;AoC!>q`*2>`d11x+5B0N9bj!-r$1EB{D^(iJEM9@&V?!co$AxFY-5QVL$CV=Y)-Uo%% z2A+`Ag`QFiY*JC*$|TB%oRsF+&JEQ`F-lm}*VNWqi~36Hr7CtoQUAG$ATL0pH)`uI zS1Tjf3-ZGte}DWVBhMPBypSM65~>P(Il*IrmhtgG*kWC+b6|}9sA9FwibXcx%XMFO zMrm$r&=L$-OvFv-WVhjCBTxf@S{;PF-M<-B8-!qjx;yjr;?4*~kOlUg>qH?*O(Ezb z@*^3c$DZ3+DhrTorV7d_eKT~{6Gg&B-d`4(wl$IWozp z$l3QKpns%VOHjL|+A|rFs<^)TQ1Uutm(rZeEQ>MQh|pL+P%AkI#$C1b+pCok!C)V_ z`$hy~(A%G2$f#whmXo;RHpCb<&vIDPg=#xH+uhy-6T$plt!O|pncge}`_(mTa~b_wyGMMIN>nnck}MDAf}6kTNj5k!4)-|)gNLKF^* z!h*^mb{Su&eOnS->;=)Ux+1Z|VVejA9Bsx1_q|oIpB^5jMs-dBCv}c*L?!OtNGB3OA0* z*ux9&J1Uby=6EXlFGr;WrPjQ?g_d-1;#?p)QPPY=s_gU;0ob^rd_bvu_)ttG52M3} zat?+&bn7=VV|xTa)FOPe1}bI^~19NfcN}lBM-D zGFRy@_Ct6X9ZA84`kZS5TIL_kiHn=fslMX;c{BA8GPuA8!3t(IkMV zoz|PSUb5%%Ev$EII{@pd>R7xDtZQrQz}j4`+jT`nMsxI*@T3;jk8A5p>!;OviQd5a z*J?uq>$%!Guzpvqi$ku?D@$*Qc4}eWT3ZL!H)`vE+Fn}^Is5FiqKI*ZKOu0w{mbp1(cg1P9;c}HLVEoapMKcW z-aA+c>T!Cn;KlBaet&&4G-VACz53dEDvt`4*?zcG*@L)UWHcl;jncRthV33)ywy9d<*y8 zMY9e|6gYe=d?YHBq_}M9Lw_5qkaScWqsAc%!4t!U#aF6~<-Kgo2XQmJtJ-I0n4!_S>MMK#;g|A>uFL7AdHgjHrRX86$4j`56{be_r$ zpY@sNe6x2^;^+_Kf2447Ry`7cf^RpYqlzgkKEnKL+6h9ogQhP~X86n#)ltK-v;IMW z@K4S@9Y{x;^N*eyhib3XWsr>)b6gL*yReLJPT1$@Gb?)j&UH>OJ9_x?<%D7V*;C`@ z_-So$#H^Y&p^+E)R7gcU<}E)s%-nVE<)A(I1L{n%HzZFTjFo4McT22;&*{hO@R zkQW(|nwb7UtRgkntoc;zK(U)<#Xy_c`;}RyYoHz0e0rdN{21CrA}b837OL#ZJZ)Yb zcqE1R5K;TVq2{|6=FM{#j$#ukbYkYs3y*6li`9!@X!nC%wp%hcnmIX*w>F-|ma%99Xd5aLGv+H<%20 z*efaOw-8K@5KJDv^P;>`lg&S_I}N4IU))N0*2TN|9sBvu&~oYLHwdr1OTGr1w_I|j z3(?hOXYt!}*`L*6=Iqkl^a=(~JgPxt)a5Hx5mBq#+#^=pDSIv%Z zex8=RX}|n)2ffZiE$O!oS-bGLNdxB|FzYC#0NEptb6?#!Gj2!8n~QE4G(8(m1oMYB zED#T(u4rKbtyi3>mX!_UeOH`DnGc73zT&lC7~_$TcdwLhjN9f$h7AbT26^%!lB`nf z2gfaD%T@J99hPYn86*WIE1B)!Dr<|BwAm8p-@bDDjwkNl`RxDtlaujjH8EFr(aGaf@2S8{o1`V~;ML`7oG|Z?ausQIS1LB)`Q|5YTgLn=3C=}w| zNh74X=oVvMxMtbtgzTHhnN5tp7=G&maJw4|n`(>yvM8TV$@k z_S=*2d_q^7&Pito3#gHo5q}{Sgvr`)sb}6*t_iMhG84_bLs0go@NEH~FoBG$KLk}-P+Cz#i-Yo)R7tFM}$uA9^G3;Zne zN><1bM#5J0I|rB9^WlEyIg`EqYIu6X_2OgxDo?}P=8ydgfEU+%NjjHHS@_4D4*nw$tud9G&f(-xIa`;ft%{ zuaORoUjA~_xaCW$;*U#XlZ?2VQji~B{AE08`qnuePk_jW$+t1LHZ)s9ZjboTw=7gv zq|L{-TrC3qfj@-MVvylX&ETg0w%N^*of74(Dsga(`N3@mH6W4JgpG|%n0d{-b=#r- zd71TT^W@QudUW*AWNp8VQcOuv?K4kqZFgVVXa42(w)k)M(Vn(1J-yEy`}MZ?ViJKNM2 zqv-kY>oPU>x;JD#>Pl&ge?j`j?EGIdJMU~Xo!>1l9TF!fA z>?51abGwjOM-s#CV)N2}Nwn-Kzhv9g;(pG)IT^>?_Fpej&zkAqJbjF8u=<>-;hT*( z2XSOuGcF5V&e)JS9RAifU*OM(Y@3_0@vQvtb?fQdE`Cq_*V|6^d?z$bcmKq7mY8Sn z{B7ms}d~EBB#i8dZxra_IF&bxiIknwIyR zxB7zqmHihF{q_LW8H+kS(WVYf@9qzrR_gEc5Bcq?aL5$OQ@J|0WH!^dCY^J^c|DW* zS6)1+zi0IYljN_(3|-NxCJ=u+PJ;B=?{+$wzVlX{f5zwfSDw{#2A$42$Ce|%Su{ZK`0kyligp8H?Ba~Yr;pjHOgZ{+TD+dO8 z`p>uYSsOS@dH!WMo#*b9>o-zwvh|^Rn^YST@}nj-E4jQ(;-Q9S)ixA1tN74i&Fbow zia@QsD6mt6@y^DJhJM_vzE`(kUBHwbr)SN8vwYpq4K3;hHDzexXmyC{9y)2X8mm4& zbfMJd4Ba|fO$p}GcTUe)7o6SG-E-DCQ;vG*&~}wnYSTkgI@Fikp{K{FQOX&*eT*7? R)t)hm%YA6i7;p2~{|nVN^vD1J diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index b4266e69403f1fadf79e23f7bb0a1bcfda421c68..74e1abc492bfc3d5cb06a4a4b434fdb73043ce06 100755 GIT binary patch delta 5246 zcmcIo3viUx6~6cGzwd1FZ#LOHHYtA=NHz}=j3FTpG#7~pB%n12STQ6bj4p&g62wGg z14-mj2MI@>wIm@z5|BJxM6DL%W27C?$_!H-iIr-c+KRR+ww)2%p8M}+^Fy7^(9xab z{{MH*x#ygF&bhDui(d0D`^>v?%Jn|+Bh_rvCQ=;$)K^V?bW=4U)f9=65I~wiJk>X2 z;7t&9n^X$+rkgr^3DG9qiZu_~YSz@%)+}p$Xj%P=n)`^oV<>r%iBhJclrUX=SX zC^b->Yzen9d}TyWsg6dNQ9}J=7Q_r+je;QQ4eA345>02$OpcR?Ag$S;^$ zcyn%YTKcUf{oMI^x;x_HU5aP=4cRy56yG%aw%cdsmo1oi%bb#VTKv=77gjF4 zr|Pb|%Pgbt7JY%W_+cB%r&s*1vRLZ4^2`WPSS}-0#mr^z6k;8PZtoVTleRMEg9~et zAidsBtzfI01f}{#v=k2McVKHP%r!1x7(E{Y`)W5lYOu#z6{;|KS-c`COwoB!D<{@p zr-sAkQYRFpn&B&ti(4wOMZ(`A5^X|TQ~@#JW26@@nw_xIBBt6(4cIA!Kbme}LA8%tEU?cqqmsvSihi;ZTg?Asx?hpw~3@@ZExGOx_=9&-*WYd2* zTu*=0f{mAzA65SeIXMDzW5JOHb7S{^Bo|OBs}Yxxu15Sb8UfQCmnnyT6~+5+j@nD} z&5B;k%l4;3^Hk4qUVayfF!H|%f#AF}mDtP>?o70q6$6hJ^U}cEj>Al62NtgR#O&Bj zy<$#SMG@gdmcp!X(K(r#L5j(%3ZrUq-)5uMiRu=`_(N2IRa8NyrnKA8_dsb&!P1sQ z8Do8qhspvAV<%%A)b&Xd2X;*y*nh{tDB>^_E8@TcabO}2@5e5UYU>*zJP#*D7xnV} zEeN*t_solG&YBgxa%Y0mq^fL<3vaKLI;HoXF!u4wB?+;R9>Y~-K8%dXmN z4Nk)01U4iO3loVDMW9l08LsLI;jEG4{#BKb5HqG~a1$FkgF+@RmJyZjf{ujgh5*lh zCgCMX)^XoF#l$1L{NHpPXXOA%N11P)ztH`mE>pAd&}%jxDiW*NcqqRBWyuxS+vU~d zSz?2rDap)N0jVjUvY~Epc;<>nm1l|ADxUXLBn0!z_)IVVjW)f+`Zy@5e_$g$yVIPk z>j(^HM8mYS=}3TONO@wM;(*SyS$BN=RA}cUVgR3MG0xzxwHPbnTP;?+y!D8t|5PvE zb_6oh<9w&IVyO1C7NgoXv>4UCt;MMJ_gai#50Bv2- zVzjmSDX7ax4kqh-4<>7-uR|+?Mh<8(8tK+zw9&7{Xk%E5(Z;C|4wE$Fi9ip>4qrW< zONg(E-DRx++Pb2}Xsa!x8?~XW?I9ed)7T0}?sM9%IINeo7>9Kxh(odaomK#Cy{pA& zYxFQgWXAb+XvwH|w-%$`gIbJ3IIP8}_Lvr<+QAUU)nua7!f*&5;6w{m7Ml75@l7ED z+WK58fVRHTVzl+07Ne~#hvAdV&cq^xd6+3lhl`ho5w{+X!bt=-KX#jhhvm+!`HU`r zFS1fBR;gG)hBrxqSF&RLxzk7KWVLg#8xK?-Hq+~t_79@DCEX+-M#IXTGm%QL>B>})?sd2V*; z75ijrJnIm ziI+crbcFCOJmIN((tfcGP3EJ=5R2_Agu$Hu0zC<@<=>30uHYVQYYLpE;CA=A-&b%a zHC6_%6&q_)b{C1ehrK9@e(Fyy+C#;i0fb6oJ79_UulL79VQ+o zyLFtFy5Ll46)yAOEc1!geqY&>7L?AZe30Tg?0!%UoraZQh$F6td1!jSb#EX;etu`unR3UKhvKyR1mq&L7hKM1f>#R>g|FVp9lkkJM z4Wcn~XZpX0ru~AGe&bUr7?XJE9uR@j8Q}Ni-q_&;ab4y>NIM}`vsjt%>_V(zWcJvXJ)>Rc{?R)R=mS+E!!-{y z;V}-cYGC&+!M8*DT&mvhuzc1^j|OjekwuDx8bx}A<5;eN>)yAk?Aj~d_leC}{R(8> zJBM{0g^sn++RTkhV4(YeNMdJ!7dpTySlV80=dZ0OsNk|e}}9;mj?V|O`gTnpis zVA6(GdKr2)6r_hd#no#pFb=$u3&S!R{!ok(=d##FulS7*EunNDR5!WU)@R_6rg--E zVMuy7p8g5mX>!r`L~D3s7`sHm<|Ze5R^_t(Vdy}iE+}pmA0#$n_x53+8{-Y%4xy|C zWn*Bd*<~0ThfeygAh0)w)SkZr?-Q+|U63Q8ra6WVh>nK84vZUM9}fs0 z*!clChm2PQ!yy!~Qv+fQ?EDG!(LVsg&GGDzN;Di0VtUoiV=(f_{bEX{ZAzrR$6iE+ zXt2WFxB`ez9;}$RAq7_vshDpI@Q{;4#r`8WoEG53RapvEJ77jg8RmzT*W+=Pl<<~d zH(v!(X?Z=8iIrIU@VgRzaG}1*coJIW)j# z`EYsEdzj=l@{Q}<2HYazkz!B`=qN5-F8|S%KT}B}3WaBV8{l;7W=6|^w=ETq&bF2G z3>exM(`=|}chMYwbNh0tFLTIb34HWuVerkpwGLUkx902E<`n;1JD#$bbK+zoemT&* z=SG;nKhILRx~AcN<<|9U?t_l~F4(x=1jGB=oa2J0?J{B50Y9vfckEDp;B@$|NSWx6 zw4$MrJQ)cw2i>eM5(*Edv9pnIVb5awoee8+60fYUS-!lcwq{LM=Drat%Ys#VV}g$D z*qgR~}5Kw9mi$7e8m9Ka|#5dg!^*L$vqV{{Y%~RVn}g delta 5207 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 TN+5e@V#~6f&n(+X+n@dyL@{59 diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm index 89b669f6af9c1826ece9718b94c1767b2b95d1f3..19ce5cff37807e416555878e60fe60182b205024 100755 GIT binary patch delta 12266 zcmcIq3w%|@nV*?+&b>Fe_a-L+NeB_ny#%;X9^sKh6U9t0@(A*(($*&kk#f-hA!zNk zoQPm)7u(dKyR6zaDoO;>(#>v-Ke5Iw-O~QLHLkd&Re!Q;^cT18Zrbhck8XYJ|C@8p zy*IoH?QY=QGv7Be-^};T_nMjW`tH0h_vLktb%pD)RfbEy9FK{TE^>F3*tU(WvIyY< z;x6B|ilJ2oiPqcj=XXMrkZwzj!(NGs#x~s@#P~L`+B8~P?-{kab>rQuZ*TwX?RQPs zxbF5X?QQE@*Re2F#WPiHEvwhE;;%hB(~TR;mYN1COAy~c~CncEjzthE=Nml7baO#+&~LE zEq`^WpQRY9VGM92E#C%Q$ylw1Uq7U}3dR?A5uq>$J85}b6H)^OkBh$jYD-~tNRz%& zeYdZ1TxpJF(cdXqd}B)fc1N}+#8D$+xt$@O9XqLkdZG9W#!K}! zxUHWNBLd=;?kogRy$Z&Y=0e2-ugUL9%@|7ygb03Q5v3CQT4BK}anxxpW?p8(qgD zc%9nU4?&n1%&ZYi8VNJTqmT~jX!xW(jj^Ov$A%2YmOPyRR`LxA^Wl}FUB@aAu%M`D za7<|~1rN5gye1Z!IChHhBp8xB5t1wsi3!b^$5O!`%~HTo*kh@HSaONnxmn7OI;Im@ zXf}{w|F6dPhj+}zM>^EXlF~mshX^fv)QRXhgqq@UniWYZ1ZhX?6=n2-05RL9IL9g& zB>lWP_O^h8A@(r1#3EQuUV$)7frXO$thD4dZm1w z3J>K{vti(3z)j6II|Wo6s5x0wM<@-%RcZ3~q>*xmMl@`|th9_AtDc`Abu=njtQHD} zB2ds&AWR)wj%kJb(9%>%uCDw6Q1MDb#|$B#2NcUdy@2vF&_O_X8R$ts;Q;a#nmtNm zf|MfJbcNDMtI-$5UI5_$=mZegfa?f)?+T%PzX53lP5J=j2f$$fF(05;w}Ixnz`kc( z#dvoc1RaLh3hd%GdzAc`Z-$kd@+GzbepSdmEO4CW*==}>(+xzS>+zsh>o5>Sjzv6a zxF2xXlZJ+m0k%AOXm|jy=}ClOqTLy)HOTTS!BGM}M(#zMm}6#WcIrOaG2C66s1~Fq z+5tH1Er^Cskx-nX41r;ii&GfDG*p*aYrr-jz+mxo&ptrGzy&#Mbq5Patz6j!P_L@) zv=@+Gae|2W5TvDj-Pg5%ziwt!M|J`e41nbTl;x`fP?o7vB6o~oK*1PpW{@Fro$sPb z$RAl`X`W7t8SIBp;z5Bpmlv~gydD|W#oK-m(|v#JMw{Y!P830=qod2HMXZM3&H7

yeP)4r(4GK^D_Vv5)B{Z@O)rj3NLX==v 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 TnRsSB@YKD}K6Nku-p~FQXdH6Q delta 6781 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 BP6z-1 diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index 57b061685cc46e16f94d95afaac8e36b3f141778..4e08217e64b38a7dc9d901b42a87ce7b283236c7 100755 GIT binary patch delta 17259 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 delta 2772 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 delta 5761 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 XowPSl#hq8~dE&h*FTZyMnqK}7^>)UO delta 3870 zcmai13v`sl6`q;@|2Mn&cQ=0$Ht&$xO+wxYK@5RxLMD(0d6UQ^A|||Ha}a|l56J^6 z6c32NEj=8SA}=`hq}0|_k4mvUt`sY$#};U9r9D1+=iiX1)niU_XYRc- zckX=m-noA+er`MRnQhmUNnLGfH~!G>HcgIlLM35cU8IeWHl@pySxlu=gqXA49oh4A zW!~L(rf5`Q#6l=#lq!x8rt)JX~cF=}BUl4p!El~QXcGi-{Y;uL@7 zkE+p7jqbj+yeTX$);TsQKWl1LsBX_Lo>6_rMB%+7A|WMpTv~cg-kk-L3M*?H8fVs2 z*Vi@QHEZ@95&Eyo?Cz;^!Tz(v znC?*=gfq^B)yD~^#%%bnGM)}XmYQn`;0p0Pw5qFzr~Y@<4=q|FLzLk0sx zagmOJ?(n%tFNJSF+8nW!j`3fPXkf~9rw%>&QBdX@qjQqZ438pcWd|&B-OETloO5;K z;GZ6~+Jg5xo#3U>{!bE`8FfKb(ixfvl+Quh!r!0)e}?-Z3{I8*pl3cyj3>Hy;y7M0 zOPt?{Si}&WYw|h>Ln6edCQ~;|P335{Ej1hGkEJ$I?hmAXptwB_i=F9Q6}KM8{pn(G z7Ymwp4Tsvov09CpVjYE^@LaB<<8H+a zJ;9uTqlG>?0gTB7^bb%uc{3dk7bkDUTyuZR+AUMo;@X2v2@qSLpKRs3rujG{{>^O`Oko9L+-EzW%aUQ9%al;T` zR277R6Q6n9H%jgb{Hf|u#A4ZuZQqGS5=^VEjQx)DjyqwEHy@69r%?^APp?FpGd0nU z1tG>vAsBoyu%{#yHWjv*!ZNyd08x;*QLe}ri#}Jzx0Wf7NDQe2zI|B9L*yGH)27N{!C}~{ ztlJQ-U~pva51JS$MkZ(JtO_Fp&rUrM-h0Rd8kL5%+(GSjm{(fl{HoNU!;#W-yx4C` z-8J%J0o?LcuY^lNfky2f1A%z0pu0%He9s zGF&+VDEv*tVpjGPM@dYR$_l9+=9Z;6?4xTC)4z*2I9xU*Yzk)?tQO77OF69Yaal#F zCJ*L{Jo^#1DyUnNNjZ#w@wFWY!#5n$bOQHJE1QU6`kKemK!9KxhUwxK#zjmDS`=py z4)NI$e9dg9rngZ#01L{iX(sfQzs3TGVNJyhIlNpkm!aFSm9=pry9<3{$I+3^1P^|x zx*^mn-&&#i3@=30lsk|W24;6qvb<)cDdAFr)!~IJH4ETibs{`oUy7&=)vv-@E^W9E zX@7$&RKAzNDh5&eZ(0I{b*V`2tviO)QeT0zuD%S08h(lAYCI%pb6;aEo}$Tfhb1=~ zH<}VWH$>Rwxf^B%*Nm4E)7!UZwx}u*1hSeQG#{ABYR?v$3W2677}uOZC;JBeM%aTkjZ#?Onnrav*qV@L7qs1N|Nb4HwC3WDtZ9|^^~ELv2A^uVId5K@b7lUhh#oR3>( zEndWs`T62(WIkHF0vF9(vMftB&oFf4sb_{eX&iSNvIjc3%aBJ%WuK{pKQ95?yLRb9 zq?ebLQI|h)*#M35a$7n(%iLkyCeAP)j||5Dqvg3l)lg71II7B8Q5{qTf~rfV%FKxE zD~5O72&%4`Dw!JoH&%S2xUmI`9h)%U)5QP|^3y_`mwcM|*yR0JSEf?@%nDsq9Q?Kl zK;5dA+ta>PO?1%z<*H7KA8VZ*mnbI0><6C5ObA&sgpgcX(@%?F+k<&X&pgu+a_(=T#s3HaPw^K`vVU#+ujN{b1~iRM0oL)iN(u&wkK zpvnHGp9ENVvIt0h2rj%9R8Bj1KE;Fm`-EKTRQ^}ka%p})G)u!;Wxc;@OkDeY%&*mS4VyO2RbwyU*{xMXqDEiUhN>V zzp2lzg?EmZ-!5tQiLTqzqbJ(n;b(s>-?@sudvNd{>9gU#CH2t%9J2M`IJsXc`%aq5 z(EeX2_^-=_Q@RsQoVA1VR19r|Rj)+Dgjc-u2+sVDyan@yLcY!vY{vW34+W-In diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index fa5e6bcb3ad1e0d030d9333b252ca90debdb2237..dd51d7be2b6bb2582dc20bf7e9317df2e19e33c9 100755 GIT binary patch delta 20437 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 I%s=e@zhjuQ5&!@I delta 20848 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 HkB|O8nV}VJ From 860e1752a94e14f53102e3a713ddb02c066a94e9 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 22 Sep 2022 17:40:29 +0200 Subject: [PATCH 356/373] fix display proposal result in cli --- shared/src/types/governance.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shared/src/types/governance.rs b/shared/src/types/governance.rs index 8a8577abdc..5f82335cb2 100644 --- a/shared/src/types/governance.rs +++ b/shared/src/types/governance.rs @@ -5,6 +5,7 @@ use std::fmt::{self, Display}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSerialize}; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use thiserror::Error; @@ -101,13 +102,19 @@ pub struct ProposalResult { impl Display for ProposalResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let percentage = Decimal::checked_div( + self.total_yay_power.into(), + self.total_voting_power.into(), + ) + .unwrap_or_default(); + write!( f, "{} with {} yay votes over {} ({:.2}%)", self.result, self.total_yay_power / SCALE as u128, self.total_voting_power / SCALE as u128, - (self.total_yay_power / self.total_voting_power) * 100 + percentage.checked_mul(100.into()).unwrap_or_default() ) } } From 8e8af7a9cf11a91221356d19a19d050e5cee1d15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Sep 2022 14:37:46 +0000 Subject: [PATCH 357/373] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 898f6e9763..3d1538fc9f 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.16097490afa7378c79e6216751b20796cde3a9026c34255c3f1e5ec5a4c9482e.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d1937b17a3abaf7ea595526c870b3d57ddef8e0c1bc96f8e0a448864b186c7.wasm", - "tx_ibc.wasm": "tx_ibc.378b10551c0b22c2c892d24e2676ee5160d654e2e53a50e7925e0f2c6321497b.wasm", - "tx_init_account.wasm": "tx_init_account.adab66c2b4d635e9c42133936aafb143363f91dddff2a60f94df504ffec951a6.wasm", - "tx_init_nft.wasm": "tx_init_nft.d1065ebd80ba6ea97f29bc2268becf9ba3ba2952641992464f3e9e868df17447.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.184131576a579f9ece96460d1eb20e5970fcd149b0527c8e56b711e5c535aa5f.wasm", - "tx_init_validator.wasm": "tx_init_validator.2990747d24d467b56e19724c5d13df826a3aab83f7e1bf26558dbdf44e260f8a.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.33db14dea4a03ff7508ca44f3ae956d83c0abceb3dae5be844668e54ac22b273.wasm", - "tx_transfer.wasm": "tx_transfer.a601d62296f56f6b4dabb0a2ad082478d195e667c7469f363bdfd5fe41349bd8.wasm", - "tx_unbond.wasm": "tx_unbond.014cbf5b0aa3ac592c0a6940dd502ec8569a3af4d12782e3a5931c15dc13042f.wasm", - "tx_update_vp.wasm": "tx_update_vp.83d4caeb5a9ca3009cd899810493a6b87b4c07fa9ed36f297db99dc881fb9a1c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.bcb5280be9dfeed0a7650ba5e4a3cebc2c19b76780fd74dcb345be3da766b64a.wasm", - "tx_withdraw.wasm": "tx_withdraw.8fc0a3439ee9ae66047c520519877bc1f540e0cb02abfa31afa8cce8cd069b6f.wasm", - "vp_nft.wasm": "vp_nft.2c820c728d241b82bf0ed3c552ee9e7c046bceaa4f7b6f12d3236a1a3d7c1589.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6e762f3fda8aa7a252e2b29a4a312db91ded062d6c18b8b489883733c89dc227.wasm", - "vp_token.wasm": "vp_token.c45cc3848f12fc47713702dc206d1312ad740a6bbee7f141556714f6f89d4985.wasm", - "vp_user.wasm": "vp_user.d6cd2f4b5bc26f96df6aa300fddf4d25e1656920d59896209bd54ae8d407ecde.wasm" + "tx_bond.wasm": "tx_bond.69b997687f93801f7822ad39032ab8fbc29ca989317e94c07c7bf3bc4a2a2ad3.wasm", + "tx_from_intent.wasm": "tx_from_intent.1f8b246e7d0c4e87188018215eb2860082ef99ace78a12c0481a39523a0b7851.wasm", + "tx_ibc.wasm": "tx_ibc.90505336c947d962bb02f4643dbd294263dd3bb88601d766f3cf0f8d17ec92fb.wasm", + "tx_init_account.wasm": "tx_init_account.a1bdaca982e027f068cbd2c17ed9de64396ecc5ba3a14ac7578fded20a829005.wasm", + "tx_init_nft.wasm": "tx_init_nft.18ad3e76dfe4c3d3bdba1069f668bd0d349aa8f99cfa2c7a51e5b6bc782e3c25.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.794302c125d17068b40d25a67f943ff520f0f83bde3233ce24bfc227c61407dc.wasm", + "tx_init_validator.wasm": "tx_init_validator.276634f0ff3c6c123a110459876e8c37be25cbb2d0f28b320a91ae7592aab86f.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.ac9a7edb87c605457a39521c7a4ab0786077043e41de7975a996e872f6519f5d.wasm", + "tx_transfer.wasm": "tx_transfer.e769c543c2f9c0bf76fbe73af8cca79b3d6b92c80e957daa278873f85d5ef1b9.wasm", + "tx_unbond.wasm": "tx_unbond.8a4aa3106a1f594da45fce6be76597735eed366a4aa5c462474dd6536d845761.wasm", + "tx_update_vp.wasm": "tx_update_vp.f75fc727bc0f9e13ce78e8cdcd5ca978f4becf1e7417eada81a42bdc57ad3f03.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1c50c26be4ecf0f8b8893a1436768a77eff1050dbd51ab1c22b4cf458783f6ce.wasm", + "tx_withdraw.wasm": "tx_withdraw.59f41e331871b6f763e511d120f15da384a9c8d1f7e56845bb67d7a01fc6a863.wasm", + "vp_nft.wasm": "vp_nft.ccf80799aa2f9839e1af7b8be1e99d31d793c5ad100fab89af22f34aa6fd9e4b.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.181e1882bb09c5c2be54fec6454c3490a35c305706c11bec95ca739652555c26.wasm", + "vp_token.wasm": "vp_token.72c51248c57b47039d43550dca719b969ee83013fb9c440470e704316a6075b5.wasm", + "vp_user.wasm": "vp_user.6a05a513c383ac3fe39a993fea923d8bd2caa9e259f0ebbb95dc56a21ce813df.wasm" } \ No newline at end of file From 7119de488ece0e05ad43d9ed67c20342645976d8 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 22 Sep 2022 17:17:20 +0200 Subject: [PATCH 358/373] Fixes specs --- .../src/explore/design/ledger/governance.md | 6 ++-- .../specs/src/base-ledger/execution.md | 2 +- .../specs/src/base-ledger/governance.md | 29 ++++++++----------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index 63b466b23e..da26c8e989 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -51,9 +51,9 @@ and follow these rules: - be grater than `currentEpoch`, where current epoch is the epoch in which the transaction is executed and included in a block - be a multiple of `min_proposal_period`. - `endEpoch` must: - - be at least `min_proposal_period` epochs greater than `startEpoch` - - be at most `max_proposal_period` epochs greater than `startEpoch` - - be a multiple of `min_proposal_period` + - be at least `min_proposal_period` epochs greater than `startEpoch` + - be at most `max_proposal_period` epochs greater than `startEpoch` + - be a multiple of `min_proposal_period` - `graceEpoch` must: - be at least `min_grace_epoch` epochs greater than `endEpoch` - `proposalCode` can be empty and must be a valid transaction with size less than `max_proposal_code_size` kibibytes. diff --git a/documentation/specs/src/base-ledger/execution.md b/documentation/specs/src/base-ledger/execution.md index 26cfe65f22..3395ffb858 100644 --- a/documentation/specs/src/base-ledger/execution.md +++ b/documentation/specs/src/base-ledger/execution.md @@ -21,7 +21,7 @@ Supported validity predicates for Namada: - Proof-of-stake (see [spec](../economics/proof-of-stake.md)) - IBC & IbcToken (see [spec](../interoperability/ibc.md)) - Governance (see [spec](./governance.md)) - - Treasury (see [spec](./governance.md#TreasuryAddress)) + - SlashFund (see [spec](./governance.md#SlashFundAddress)) - Protocol parameters - WASM - Fungible token (see [spec](./fungible-token.md)) diff --git a/documentation/specs/src/base-ledger/governance.md b/documentation/specs/src/base-ledger/governance.md index 45b0aa0cb1..591558fcc0 100644 --- a/documentation/specs/src/base-ledger/governance.md +++ b/documentation/specs/src/base-ledger/governance.md @@ -7,7 +7,7 @@ Namada introduces a governance mechanism to propose and apply protocol changes w ### Governance Address Governance adds 2 internal addresses: - `GovernanceAddress` -- `TreasuryAddress` +- `SlashFundAddress` The first internal address contains all the proposals under its address space. The second internal address holds the funds of rejected proposals. @@ -72,7 +72,7 @@ The governance machinery also relies on a subkey stored under the `NAM` token ad ``` This is to leverage the `NAM` VP to check that the funds were correctly locked. -The governance subkey, `/\$GovernanceAddress/proposal/\$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to Treasury. +The governance subkey, `/\$GovernanceAddress/proposal/\$id/funds` will be used after the tally step to know the exact amount of tokens to refund or move to SlashFund. ### GovernanceAddress VP Just like Pos, also governance has his own storage space. The `GovernanceAddress` validity predicate task is to check the integrity and correctness of new proposals. A proposal, to be correct, must satisfy the following: @@ -167,7 +167,7 @@ All the computation above must be made at the epoch specified in the `start_epoc It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/governance/utils.rs#L68). ### Refund and Proposal Execution mechanism -Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. For each ended proposal with a positive outcome, it will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). For each proposal that has been rejected, instead, the locked funds will be moved to the `TreasuryAddress`. Moreover, if the proposal had a positive outcome and `proposal_code` is defined, these changes will be executed right away. +Together with tallying, in the first block at the beginning of each epoch, in the `FinalizeBlock` event, the protocol will manage the execution of accepted proposals and refunding. For each ended proposal with a positive outcome, it will refund the locked funds from `GovernanceAddress` to the proposal author address (specified in the proposal `author` field). For each proposal that has been rejected, instead, the locked funds will be moved to the `SlashFundAddress`. Moreover, if the proposal had a positive outcome and `proposal_code` is defined, these changes will be executed right away. To summarize the execution of governance in the `FinalizeBlock` event: If the proposal outcome is positive and current epoch is equal to the proposal `grace_epoch`, in the `FinalizeBlock` event: @@ -175,33 +175,28 @@ If the proposal outcome is positive and current epoch is equal to the proposal ` - execute any changes specified by `proposal_code` In case the proposal was rejected or if any error, in the `FinalizeBlock` event: -- transfer the locked funds to `TreasuryAddress` +- transfer the locked funds to `SlashFundAddress` The result is then signaled by creating and inserting a [`Tendermint Event`](https://github.com/tendermint/tendermint/blob/ab0835463f1f89dcadf83f9492e98d85583b0e71/docs/spec/abci/abci.md#events. -## TreasuryAddress -Funds locked in `TreasuryAddress` address should be spendable only by proposals. +## SlashFundAddress +Funds locked in `SlashFundAddress` address should be spendable only by proposals. -### TreasuryAddress storage +### SlashFundAddress storage ``` -/\$TreasuryAddress/max_transferable_fund: u64 -/\$TreasuryAddress/?: Vec +/\$SlashFundAddress/?: Vec ``` The funds will be stored under: ``` -/\$NAMAddress/balance/\$TreasuryAddress: u64 +/\$NAMAddress/balance/\$SlashFundAddress: u64 ``` -### TreasuryAddress VP -The treasury validity predicate will approve a transfer only if: -- the transfer has been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) -- the transfered amount is <= `MAX_SPENDABLE_SUM` +### SlashFundAddress VP +The slash_fund validity predicate will approve a transfer only if the transfer has been made by the protocol (by checking the existence of `/$GovernanceAddress/pending/$proposal_id` storage key) -`MAX_SPENDABLE_SUM` is a parameter of the treasury native vp. - -It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/master/shared/src/ledger/treasury/mod.rs#L55). +It is possible to check the actual implementation [here](https://github.com/anoma/namada/blob/main/shared/src/ledger/slash_fund/mod.rs#L70). ## Off-chain protocol From e7c868f0d4a5534251adfb44ae4b970829030b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 18:02:42 +0200 Subject: [PATCH 359/373] ledger/storage/lazy: update lazy_set for updated trait LazyCollection --- .../storage_api/collections/lazy_set.rs | 103 +++++++++++++++++- .../src/ledger/storage_api/collections/mod.rs | 3 +- .../collections/nested_lazy_map.rs | 2 +- 3 files changed, 103 insertions(+), 5 deletions(-) diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs index 9635724543..3a001e6048 100644 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ b/shared/src/ledger/storage_api/collections/lazy_set.rs @@ -1,11 +1,14 @@ //! Lazy set. +use std::fmt::Debug; use std::marker::PhantomData; +use thiserror::Error; + use super::super::Result; use super::{LazyCollection, ReadError}; use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage::{self, KeySeg}; +use crate::types::storage::{self, DbKeySeg, KeySeg}; /// Subkey corresponding to the data elements of the LazySet pub const DATA_SUBKEY: &str = "data"; @@ -29,7 +32,42 @@ pub struct LazySet { phantom: PhantomData, } -impl LazyCollection for LazySet { +/// Possible sub-keys of a [`LazySet`] +#[derive(Clone, Debug)] +pub enum SubKey { + /// Data sub-key with its literal set value + Data(T), +} + +/// Possible actions that can modify a [`LazySet`]. This +/// roughly corresponds to the methods that have `StorageWrite` access. +#[derive(Clone, Debug)] +pub enum Action { + /// Insert or update a value `T` in a [`LazySet`]. + Insert(T), + /// Remove a value `T` from a [`LazySet`]. + Remove(T), +} + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum ValidationError { + #[error("Invalid storage key {0}")] + InvalidSubKey(storage::Key), +} + +impl LazyCollection for LazySet +where + T: storage::KeySeg + Debug, +{ + type Action = Action; + type SubKey = SubKey; + // In a set, the `SubKey` already contains the data, but we have to + // distinguish `Insert` from `Remove` + type SubKeyWithData = Action; + // There is no "value" for LazySet, `T` is written into the key + type Value = (); + /// Create or use an existing set with the given storage `key`. fn open(key: storage::Key) -> Self { Self { @@ -37,6 +75,67 @@ impl LazyCollection for LazySet { phantom: PhantomData, } } + + fn is_valid_sub_key( + &self, + key: &storage::Key, + ) -> storage_api::Result> { + let suffix = match key.split_prefix(&self.key) { + None => { + // not matching prefix, irrelevant + return Ok(None); + } + Some(None) => { + // no suffix, invalid + return Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(); + } + Some(Some(suffix)) => suffix, + }; + + // Match the suffix against expected sub-keys + match &suffix.segments[..] { + [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] + if sub_a == DATA_SUBKEY => + { + if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { + Ok(Some(SubKey::Data(key_in_kv))) + } else { + Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result() + } + } + _ => Err(ValidationError::InvalidSubKey(key.clone())) + .into_storage_result(), + } + } + + fn read_sub_key_data( + env: &ENV, + storage_key: &storage::Key, + sub_key: Self::SubKey, + ) -> storage_api::Result> + where + ENV: for<'a> crate::ledger::vp_env::VpEnv<'a>, + { + // There is no "value" for LazySet, `T` is written into the key + let SubKey::Data(sub_key) = sub_key; + let has_pre = env.has_key_pre(storage_key)?; + let has_post = env.has_key_post(storage_key)?; + if has_pre && !has_post { + Ok(Some(Action::Remove(sub_key))) + } else if !has_pre && has_post { + Ok(Some(Action::Insert(sub_key))) + } else { + Ok(None) + } + } + + fn validate_changed_sub_keys( + keys: Vec, + ) -> storage_api::Result> { + Ok(keys) + } } impl LazySet diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index b77b207c7f..3a9236fc71 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -105,8 +105,7 @@ pub trait LazyCollection { /// call `fn build()` on it to get the validation result. /// This function will return `Ok(true)` if the storage key is a valid /// sub-key of this collection, `Ok(false)` if the storage key doesn't match - /// the prefix of this collection, or fail with - /// [`ValidationError::InvalidSubKey`] if the prefix matches this + /// the prefix of this collection, or error if the prefix matches this /// collection, but the key itself is not recognized. fn accumulate( &self, diff --git a/tests/src/storage_api/collections/nested_lazy_map.rs b/tests/src/storage_api/collections/nested_lazy_map.rs index 80b066c18f..037decce46 100644 --- a/tests/src/storage_api/collections/nested_lazy_map.rs +++ b/tests/src/storage_api/collections/nested_lazy_map.rs @@ -639,7 +639,7 @@ mod tests { } Transition::Remove((key_outer, key_middle, key_inner)) => { let middle = - map.entry(*key_outer).or_insert(Default::default()); + map.entry(*key_outer).or_insert_with(Default::default); let inner = middle.entry(*key_middle).or_insert_with(Default::default); let _popped = inner.remove(key_inner); From a5d4a67d41ea17394256c7ea09b5f8c216fd0ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 18:06:09 +0200 Subject: [PATCH 360/373] remove unfinished lazy_set, lazy_hashset and lazy_hashmap for now --- .../ledger/storage_api/collections/hasher.rs | 9 - .../storage_api/collections/lazy_hashmap.rs | 269 ----------------- .../storage_api/collections/lazy_hashset.rs | 184 ------------ .../storage_api/collections/lazy_map.rs | 3 - .../storage_api/collections/lazy_set.rs | 284 ------------------ .../src/ledger/storage_api/collections/mod.rs | 7 - 6 files changed, 756 deletions(-) delete mode 100644 shared/src/ledger/storage_api/collections/hasher.rs delete mode 100644 shared/src/ledger/storage_api/collections/lazy_hashmap.rs delete mode 100644 shared/src/ledger/storage_api/collections/lazy_hashset.rs delete mode 100644 shared/src/ledger/storage_api/collections/lazy_set.rs diff --git a/shared/src/ledger/storage_api/collections/hasher.rs b/shared/src/ledger/storage_api/collections/hasher.rs deleted file mode 100644 index 0f864259f5..0000000000 --- a/shared/src/ledger/storage_api/collections/hasher.rs +++ /dev/null @@ -1,9 +0,0 @@ -use borsh::BorshSerialize; - -/// Hash borsh encoded data into a storage sub-key. -/// This is a sha256 as an uppercase hexadecimal string. -pub fn hash_for_storage_key(data: impl BorshSerialize) -> String { - let bytes = data.try_to_vec().unwrap(); - let hash = crate::types::hash::Hash::sha256(bytes); - hash.to_string() -} diff --git a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs b/shared/src/ledger/storage_api/collections/lazy_hashmap.rs deleted file mode 100644 index 9a60fd18f0..0000000000 --- a/shared/src/ledger/storage_api/collections/lazy_hashmap.rs +++ /dev/null @@ -1,269 +0,0 @@ -//! Lazy hash map. - -use std::marker::PhantomData; - -use borsh::{BorshDeserialize, BorshSerialize}; - -use super::super::Result; -use super::hasher::hash_for_storage_key; -use super::LazyCollection; -use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage; - -/// Subkey corresponding to the data elements of the LazyMap -pub const DATA_SUBKEY: &str = "data"; - -/// Lazy hash map. -/// -/// This can be used as an alternative to `std::collections::HashMap` and -/// `BTreeMap`. In the lazy map, the elements do not reside in memory but are -/// instead read and written to storage sub-keys of the storage `key` given to -/// construct the map. -/// -/// In the [`LazyHashMap`], the type of key `K` can be anything that -/// [`BorshSerialize`] and [`BorshDeserialize`] and a hex string of sha256 hash -/// over the borsh encoded keys are used as storage key segments. -/// -/// This is different from [`super::LazyMap`], which uses [`storage::KeySeg`] -/// trait. -/// -/// Additionally, [`LazyHashMap`] also writes the unhashed values into the -/// storage together with the values (using an internal `KeyVal` type). -#[derive(Debug)] -pub struct LazyHashMap { - key: storage::Key, - phantom_k: PhantomData, - phantom_v: PhantomData, -} - -/// Struct to hold a key-value pair -#[derive(Debug, BorshSerialize, BorshDeserialize)] -struct KeyVal { - key: K, - val: V, -} - -impl LazyCollection for LazyHashMap { - /// Create or use an existing map with the given storage `key`. - fn open(key: storage::Key) -> Self { - Self { - key, - phantom_k: PhantomData, - phantom_v: PhantomData, - } - } -} - -impl LazyHashMap -where - K: BorshDeserialize + BorshSerialize + 'static, - V: BorshDeserialize + BorshSerialize + 'static, -{ - /// Inserts a key-value pair into the map. - /// - /// If the map did not have this key present, `None` is returned. - /// If the map did have this key present, the value is updated, and the old - /// value is returned. Unlike in `std::collection::HashMap`, the key is also - /// updated; this matters for types that can be `==` without being - /// identical. - pub fn insert( - &self, - storage: &mut S, - key: K, - val: V, - ) -> Result> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let previous = self.get(storage, &key)?; - - let data_key = self.get_data_key(&key); - Self::write_key_val(storage, &data_key, key, val)?; - - Ok(previous) - } - - /// Removes a key from the map, returning the value at the key if the key - /// was previously in the map. - pub fn remove(&self, storage: &mut S, key: &K) -> Result> - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let value = self.get(storage, key)?; - - let data_key = self.get_data_key(key); - storage.delete(&data_key)?; - - Ok(value) - } - - /// Returns the value corresponding to the key, if any. - pub fn get(&self, storage: &S, key: &K) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - let res = self.get_key_val(storage, key)?; - Ok(res.map(|(_key, val)| val)) - } - - /// Returns the key-value corresponding to the key, if any. - pub fn get_key_val(&self, storage: &S, key: &K) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - let data_key = self.get_data_key(key); - Self::read_key_val(storage, &data_key) - } - - /// Returns the key-value corresponding to the given hash of a key, if any. - pub fn get_key_val_by_hash( - &self, - storage: &S, - key_hash: &str, - ) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - let data_key = - self.get_data_prefix().push(&key_hash.to_string()).unwrap(); - Self::read_key_val(storage, &data_key) - } - - /// Returns whether the set contains a value. - pub fn contains(&self, storage: &S, key: &K) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - storage.has_key(&self.get_data_key(key)) - } - - /// Returns whether the map contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let mut iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) - } - - /// Reads the number of elements in the map. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded maps to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() - } - - /// An iterator visiting all key-value elements. The iterator element type - /// is `Result<(K, V)>`, because iterator's call to `next` may fail with - /// e.g. out of gas or data decoding error. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded maps to avoid gas usage increasing with the length of the - /// map. - pub fn iter<'iter>( - &self, - storage: &'iter impl StorageRead<'iter>, - ) -> Result> + 'iter> { - let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; - Ok(iter.map(|key_val_res| { - let (_key, val) = key_val_res?; - let KeyVal { key, val } = val; - Ok((key, val)) - })) - } - - /// Reads a key-value from storage - fn read_key_val( - storage: &S, - storage_key: &storage::Key, - ) -> Result> - where - S: for<'iter> StorageRead<'iter>, - { - let res = storage.read(storage_key)?; - Ok(res.map(|KeyVal { key, val }| (key, val))) - } - - /// Write a key-value into storage - fn write_key_val( - storage: &mut impl StorageWrite, - storage_key: &storage::Key, - key: K, - val: V, - ) -> Result<()> { - storage.write(storage_key, KeyVal { key, val }) - } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of a given element - fn get_data_key(&self, key: &K) -> storage::Key { - let hash_str = hash_for_storage_key(key); - self.get_data_prefix().push(&hash_str).unwrap() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::ledger::storage::testing::TestStorage; - - #[test] - fn test_lazy_hash_map_basics() -> storage_api::Result<()> { - let mut storage = TestStorage::default(); - - let key = storage::Key::parse("test").unwrap(); - let lazy_map = LazyHashMap::::open(key); - - // The map should be empty at first - assert!(lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 0); - assert!(!lazy_map.contains(&storage, &0)?); - assert!(!lazy_map.contains(&storage, &1)?); - assert!(lazy_map.iter(&storage)?.next().is_none()); - assert!(lazy_map.get(&storage, &0)?.is_none()); - assert!(lazy_map.get(&storage, &1)?.is_none()); - assert!(lazy_map.remove(&mut storage, &0)?.is_none()); - assert!(lazy_map.remove(&mut storage, &1)?.is_none()); - - // Insert a new value and check that it's added - let (key, val) = (123, "Test".to_string()); - lazy_map.insert(&mut storage, key, val.clone())?; - assert!(!lazy_map.contains(&storage, &0)?); - assert!(lazy_map.contains(&storage, &key)?); - assert!(!lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 1); - assert_eq!( - lazy_map.iter(&storage)?.next().unwrap()?, - (key, val.clone()) - ); - assert!(lazy_map.get(&storage, &0)?.is_none()); - assert_eq!(lazy_map.get(&storage, &key)?.unwrap(), val); - - // Remove the last value and check that the map is empty again - let removed = lazy_map.remove(&mut storage, &key)?.unwrap(); - assert_eq!(removed, val); - assert!(lazy_map.is_empty(&storage)?); - assert!(lazy_map.len(&storage)? == 0); - assert!(!lazy_map.contains(&storage, &0)?); - assert!(!lazy_map.contains(&storage, &1)?); - assert!(lazy_map.get(&storage, &0)?.is_none()); - assert!(lazy_map.get(&storage, &key)?.is_none()); - assert!(lazy_map.iter(&storage)?.next().is_none()); - assert!(lazy_map.remove(&mut storage, &key)?.is_none()); - - Ok(()) - } -} diff --git a/shared/src/ledger/storage_api/collections/lazy_hashset.rs b/shared/src/ledger/storage_api/collections/lazy_hashset.rs deleted file mode 100644 index 63ac5c845c..0000000000 --- a/shared/src/ledger/storage_api/collections/lazy_hashset.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Lazy hash set. - -use std::marker::PhantomData; - -use borsh::{BorshDeserialize, BorshSerialize}; - -use super::super::Result; -use super::hasher::hash_for_storage_key; -use super::LazyCollection; -use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage; - -/// Subkey corresponding to the data elements of the LazySet -pub const DATA_SUBKEY: &str = "data"; - -/// Lazy hash set. -/// -/// This can be used as an alternative to `std::collections::HashSet` and -/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are -/// instead read and written to storage sub-keys of the storage `key` given to -/// construct the set. -/// -/// In the [`LazyHashSet`], the type of value `T` can be anything that -/// [`BorshSerialize`] and [`BorshDeserialize`] and a hex string of sha256 hash -/// over the borsh encoded values are used as storage key segments. -/// -/// This is different from [`super::LazySet`], which uses [`storage::KeySeg`] -/// trait. -/// -/// Additionally, [`LazyHashSet`] also writes the unhashed values into the -/// storage. -#[derive(Debug)] -pub struct LazyHashSet { - key: storage::Key, - phantom: PhantomData, -} - -impl LazyCollection for LazyHashSet { - /// Create or use an existing set with the given storage `key`. - fn open(key: storage::Key) -> Self { - Self { - key, - phantom: PhantomData, - } - } -} - -impl LazyHashSet -where - T: BorshSerialize + BorshDeserialize + 'static, -{ - /// Adds a value to the set. If the set did not have this value present, - /// `Ok(true)` is returned, `Ok(false)` otherwise. - pub fn insert(&self, storage: &mut S, val: T) -> Result - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - if self.contains(storage, &val)? { - Ok(false) - } else { - let data_key = self.get_data_key(&val); - storage.write(&data_key, &val)?; - Ok(true) - } - } - - /// Removes a value from the set. Returns whether the value was present in - /// the set. - pub fn remove(&self, storage: &mut S, val: &T) -> Result - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let data_key = self.get_data_key(val); - let value: Option = storage.read(&data_key)?; - storage.delete(&data_key)?; - Ok(value.is_some()) - } - - /// Returns whether the set contains a value. - pub fn contains(&self, storage: &S, val: &T) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let value: Option = storage.read(&self.get_data_key(val))?; - Ok(value.is_some()) - } - - /// Reads the number of elements in the set. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() - } - - /// Returns whether the set contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let mut iter = storage.iter_prefix(&self.get_data_prefix())?; - Ok(storage.iter_next(&mut iter)?.is_none()) - } - - /// An iterator visiting all elements. The iterator element type is - /// `Result`, because iterator's call to `next` may fail with e.g. out of - /// gas or data decoding error. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - pub fn iter<'iter>( - &self, - storage: &'iter impl StorageRead<'iter>, - ) -> Result> + 'iter> { - let iter = storage_api::iter_prefix(storage, &self.get_data_prefix())?; - Ok(iter.map(|key_val_res| { - let (_key, val) = key_val_res?; - Ok(val) - })) - } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of a given element - fn get_data_key(&self, val: &T) -> storage::Key { - let hash_str = hash_for_storage_key(val); - self.get_data_prefix().push(&hash_str).unwrap() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::ledger::storage::testing::TestStorage; - - #[test] - fn test_lazy_set_basics() -> storage_api::Result<()> { - let mut storage = TestStorage::default(); - - let key = storage::Key::parse("test").unwrap(); - let lazy_set = LazyHashSet::::open(key); - - // The set should be empty at first - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 0); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.iter(&storage)?.next().is_none()); - assert!(!lazy_set.remove(&mut storage, &0)?); - assert!(!lazy_set.remove(&mut storage, &1)?); - - // Insert a new value and check that it's added - let val = 1337; - lazy_set.insert(&mut storage, val)?; - assert!(!lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 1); - assert_eq!(lazy_set.iter(&storage)?.next().unwrap()?, val.clone()); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.contains(&storage, &val)?); - - // Remove the last value and check that the set is empty again - let is_removed = lazy_set.remove(&mut storage, &val)?; - assert!(is_removed); - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 0); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.is_empty(&storage)?); - assert!(!lazy_set.remove(&mut storage, &0)?); - assert!(!lazy_set.remove(&mut storage, &1)?); - - Ok(()) - } -} diff --git a/shared/src/ledger/storage_api/collections/lazy_map.rs b/shared/src/ledger/storage_api/collections/lazy_map.rs index 6e32b5399b..34a0f7d891 100644 --- a/shared/src/ledger/storage_api/collections/lazy_map.rs +++ b/shared/src/ledger/storage_api/collections/lazy_map.rs @@ -28,9 +28,6 @@ pub const DATA_SUBKEY: &str = "data"; /// In the [`LazyMap`], the type of key `K` can be anything that implements /// [`storage::KeySeg`] and this trait is used to turn the keys into key /// segments. -/// -/// This is different from [`super::LazyHashMap`], which hashes borsh encoded -/// key. #[derive(Debug)] pub struct LazyMap { key: storage::Key, diff --git a/shared/src/ledger/storage_api/collections/lazy_set.rs b/shared/src/ledger/storage_api/collections/lazy_set.rs deleted file mode 100644 index 3a001e6048..0000000000 --- a/shared/src/ledger/storage_api/collections/lazy_set.rs +++ /dev/null @@ -1,284 +0,0 @@ -//! Lazy set. - -use std::fmt::Debug; -use std::marker::PhantomData; - -use thiserror::Error; - -use super::super::Result; -use super::{LazyCollection, ReadError}; -use crate::ledger::storage_api::{self, ResultExt, StorageRead, StorageWrite}; -use crate::types::storage::{self, DbKeySeg, KeySeg}; - -/// Subkey corresponding to the data elements of the LazySet -pub const DATA_SUBKEY: &str = "data"; - -/// Lazy set. -/// -/// This can be used as an alternative to `std::collections::HashSet` and -/// `BTreeSet`. In the lazy set, the elements do not reside in memory but are -/// instead read and written to storage sub-keys of the storage `key` used to -/// construct the set. -/// -/// In the [`LazySet`], the type of value `T` can be anything that implements -/// [`storage::KeySeg`] and this trait is used to turn the values into key -/// segments. -/// -/// This is different from [`super::LazyHashSet`], which hashes borsh encoded -/// values. -#[derive(Debug)] -pub struct LazySet { - key: storage::Key, - phantom: PhantomData, -} - -/// Possible sub-keys of a [`LazySet`] -#[derive(Clone, Debug)] -pub enum SubKey { - /// Data sub-key with its literal set value - Data(T), -} - -/// Possible actions that can modify a [`LazySet`]. This -/// roughly corresponds to the methods that have `StorageWrite` access. -#[derive(Clone, Debug)] -pub enum Action { - /// Insert or update a value `T` in a [`LazySet`]. - Insert(T), - /// Remove a value `T` from a [`LazySet`]. - Remove(T), -} - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum ValidationError { - #[error("Invalid storage key {0}")] - InvalidSubKey(storage::Key), -} - -impl LazyCollection for LazySet -where - T: storage::KeySeg + Debug, -{ - type Action = Action; - type SubKey = SubKey; - // In a set, the `SubKey` already contains the data, but we have to - // distinguish `Insert` from `Remove` - type SubKeyWithData = Action; - // There is no "value" for LazySet, `T` is written into the key - type Value = (); - - /// Create or use an existing set with the given storage `key`. - fn open(key: storage::Key) -> Self { - Self { - key, - phantom: PhantomData, - } - } - - fn is_valid_sub_key( - &self, - key: &storage::Key, - ) -> storage_api::Result> { - let suffix = match key.split_prefix(&self.key) { - None => { - // not matching prefix, irrelevant - return Ok(None); - } - Some(None) => { - // no suffix, invalid - return Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result(); - } - Some(Some(suffix)) => suffix, - }; - - // Match the suffix against expected sub-keys - match &suffix.segments[..] { - [DbKeySeg::StringSeg(sub_a), DbKeySeg::StringSeg(sub_b)] - if sub_a == DATA_SUBKEY => - { - if let Ok(key_in_kv) = storage::KeySeg::parse(sub_b.clone()) { - Ok(Some(SubKey::Data(key_in_kv))) - } else { - Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result() - } - } - _ => Err(ValidationError::InvalidSubKey(key.clone())) - .into_storage_result(), - } - } - - fn read_sub_key_data( - env: &ENV, - storage_key: &storage::Key, - sub_key: Self::SubKey, - ) -> storage_api::Result> - where - ENV: for<'a> crate::ledger::vp_env::VpEnv<'a>, - { - // There is no "value" for LazySet, `T` is written into the key - let SubKey::Data(sub_key) = sub_key; - let has_pre = env.has_key_pre(storage_key)?; - let has_post = env.has_key_post(storage_key)?; - if has_pre && !has_post { - Ok(Some(Action::Remove(sub_key))) - } else if !has_pre && has_post { - Ok(Some(Action::Insert(sub_key))) - } else { - Ok(None) - } - } - - fn validate_changed_sub_keys( - keys: Vec, - ) -> storage_api::Result> { - Ok(keys) - } -} - -impl LazySet -where - T: storage::KeySeg, -{ - /// Adds a value to the set. If the set did not have this value present, - /// `Ok(true)` is returned, `Ok(false)` otherwise. - pub fn insert(&self, storage: &mut S, val: T) -> Result - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - if self.contains(storage, &val)? { - Ok(false) - } else { - let data_key = self.get_data_key(&val); - // The actual value is written into the key, so the value written to - // the storage is empty (unit) - storage.write(&data_key, ())?; - Ok(true) - } - } - - /// Removes a value from the set. Returns whether the value was present in - /// the set. - pub fn remove(&self, storage: &mut S, val: &T) -> Result - where - S: StorageWrite + for<'iter> StorageRead<'iter>, - { - let data_key = self.get_data_key(val); - let value: Option<()> = storage.read(&data_key)?; - storage.delete(&data_key)?; - Ok(value.is_some()) - } - - /// Returns whether the set contains a value. - pub fn contains(&self, storage: &S, val: &T) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - storage.has_key(&self.get_data_key(val)) - } - - /// Returns whether the set contains no elements. - pub fn is_empty(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let mut iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.next().is_none()) - } - - /// Reads the number of elements in the set. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - #[allow(clippy::len_without_is_empty)] - pub fn len(&self, storage: &S) -> Result - where - S: for<'iter> StorageRead<'iter>, - { - let iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - iter.count().try_into().into_storage_result() - } - - /// An iterator visiting all elements. The iterator element type is - /// `Result`, because iterator's call to `next` may fail with e.g. out of - /// gas or data decoding error. - /// - /// Note that this function shouldn't be used in transactions and VPs code - /// on unbounded sets to avoid gas usage increasing with the length of the - /// set. - pub fn iter<'iter>( - &self, - storage: &'iter impl StorageRead<'iter>, - ) -> Result> + 'iter> { - let iter = - storage_api::iter_prefix_bytes(storage, &self.get_data_prefix())?; - Ok(iter.map(|key_val_res| { - let (key, _val) = key_val_res?; - let last_key_seg = key - .last() - .ok_or(ReadError::UnexpectedlyEmptyStorageKey) - .into_storage_result()?; - T::parse(last_key_seg.raw()).into_storage_result() - })) - } - - /// Get the prefix of set's elements storage - fn get_data_prefix(&self) -> storage::Key { - self.key.push(&DATA_SUBKEY.to_owned()).unwrap() - } - - /// Get the sub-key of a given element - fn get_data_key(&self, val: &T) -> storage::Key { - let key_str = val.to_db_key(); - self.get_data_prefix().push(&key_str).unwrap() - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::ledger::storage::testing::TestStorage; - - #[test] - fn test_lazy_set_basics() -> storage_api::Result<()> { - let mut storage = TestStorage::default(); - - let key = storage::Key::parse("test").unwrap(); - let lazy_set = LazySet::::open(key); - - // The set should be empty at first - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 0); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.iter(&storage)?.next().is_none()); - assert!(!lazy_set.remove(&mut storage, &0)?); - assert!(!lazy_set.remove(&mut storage, &1)?); - - // Insert a new value and check that it's added - let val = 1337; - lazy_set.insert(&mut storage, val)?; - assert!(!lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 1); - assert_eq!(lazy_set.iter(&storage)?.next().unwrap()?, val.clone()); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.contains(&storage, &val)?); - - // Remove the last value and check that the set is empty again - let is_removed = lazy_set.remove(&mut storage, &val)?; - assert!(is_removed); - assert!(lazy_set.is_empty(&storage)?); - assert!(lazy_set.len(&storage)? == 0); - assert!(!lazy_set.contains(&storage, &0)?); - assert!(lazy_set.is_empty(&storage)?); - assert!(!lazy_set.remove(&mut storage, &0)?); - assert!(!lazy_set.remove(&mut storage, &1)?); - - Ok(()) - } -} diff --git a/shared/src/ledger/storage_api/collections/mod.rs b/shared/src/ledger/storage_api/collections/mod.rs index 3a9236fc71..688b76bd49 100644 --- a/shared/src/ledger/storage_api/collections/mod.rs +++ b/shared/src/ledger/storage_api/collections/mod.rs @@ -13,17 +13,10 @@ use borsh::BorshDeserialize; use derivative::Derivative; use thiserror::Error; -mod hasher; -// pub mod lazy_hashmap; -// pub mod lazy_hashset; pub mod lazy_map; -// pub mod lazy_set; pub mod lazy_vec; -// pub use lazy_hashmap::LazyHashMap; -// pub use lazy_hashset::LazyHashSet; pub use lazy_map::LazyMap; -// pub use lazy_set::LazySet; pub use lazy_vec::LazyVec; use crate::ledger::storage_api; From 5cedd450be759ee2e766f2374332c204a7955608 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Sep 2022 16:53:24 +0000 Subject: [PATCH 361/373] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index d8c5d1223f..2247dd1683 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.76d289c2ba2a1d35d24cbe160e878512ea982cee51f6f3e592f96384683714f1.wasm", - "tx_from_intent.wasm": "tx_from_intent.f8d8ecc1fd565a692b9ca208eebfdab8b4f1a097d58a8604f1c1f92dea795ed4.wasm", - "tx_ibc.wasm": "tx_ibc.417bea698b37f8bf4c55d99df79ea51c10c5b110c25729ac6def4f9bc65c4842.wasm", - "tx_init_account.wasm": "tx_init_account.16404b828089ee1c5a3df94d8ba1d1569974b7d10c00f839ed0894d2ee0ad802.wasm", - "tx_init_nft.wasm": "tx_init_nft.409379e7d008d770dadc3a7c002b25a92e32a34eb195ef01eb0b76ae96db84bf.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f5b7da549aba6432c2686057f66f550e0881cd114f6192ca0f11fef886bad2cd.wasm", - "tx_init_validator.wasm": "tx_init_validator.7052307657586039fe19cc68f91fb7a3ecfaf34e6a5612ca928f2dfbeb1c2feb.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.4ec445c5108994f2f357c68af5468157f57239a27f04b51c04b5952c09a32f06.wasm", - "tx_transfer.wasm": "tx_transfer.f40c4eac4ed8189e73aa1b19a74b7ec1f18ace52b548b83a28766203d87bf16e.wasm", - "tx_unbond.wasm": "tx_unbond.cb56274fac522963f8c0f70c58a722151d9628ba9e4a7977664b2d6c297ca2e8.wasm", - "tx_update_vp.wasm": "tx_update_vp.685c9eddfee14aeeaf8fadf5cc100be638d0b15488ec20f822ae4b07256bce5c.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.67520bf791c1598f687991622c20f7c0f3142c07382037e225c6d4cdf160165e.wasm", - "tx_withdraw.wasm": "tx_withdraw.1728a2d0fe66a0242816ad0fd8005e09c27a585cb9c9a6f6a1c1c63738aeae8f.wasm", - "vp_nft.wasm": "vp_nft.1d4d3898ae605927af793d50beef268641e1ff8f0e3d91a0b463c5da550fa8aa.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.9440526f730a029d82bd5c5f8eb00fa8f6f03a70aca121d045a1b5f13ad5cf9d.wasm", - "vp_token.wasm": "vp_token.1b5e24692cea7998d3ec8b06ee0c299934c19aeec7543737d94bc803853a477d.wasm", - "vp_user.wasm": "vp_user.09225e083d4f28f93350ce7ff0e67dbae47ea096780a388d0338a0945c3b7222.wasm" + "tx_bond.wasm": "tx_bond.7bfc18f1969d0ba119e7ca2fe00e7ca156ff2b60277a380e97b4cdefeb20ea51.wasm", + "tx_from_intent.wasm": "tx_from_intent.229e4c974480899212c1c30374c1344aa95c0366109ff56b316fcfcc0120c732.wasm", + "tx_ibc.wasm": "tx_ibc.7341c8abc400599cbe4787f7f9fbd84f2ca18959f70d61c2a389f3a4a3ef34d3.wasm", + "tx_init_account.wasm": "tx_init_account.ced4788ea1837d0cacd6ba691f09181555c846f420cb46d32e76cccae9cad0e5.wasm", + "tx_init_nft.wasm": "tx_init_nft.411a1fb5c2f5ef8a9484443fa3f2affddb6b11779c961abc274288e3cd4aba28.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.c3e13023ae8444a10c0fcc50fa27344d6c35367966e41cd1f95a609add0aa26a.wasm", + "tx_init_validator.wasm": "tx_init_validator.707d0798265ba501a813321b5d9a1222bda8448650460e78518e2796f0b42c30.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.0910d261e037e3c0633a3898fc00a08a953d87c8a4b9db2b4041877b91f8317e.wasm", + "tx_transfer.wasm": "tx_transfer.486ffcee9265df25b01751d6007b7f07a083288b985396a8b6fd2aeaacd3e7a8.wasm", + "tx_unbond.wasm": "tx_unbond.dbc10595136c99949a86567561857bb7c465a7a1ea6e21a2b9d261510867ec63.wasm", + "tx_update_vp.wasm": "tx_update_vp.a04692ad8b2715c6262b4e3342591ab7bbb3e6577458979c33d196e8d80146fc.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.25dbae81da4255cae3ffeab6857646af46ef76d70984acfc7c89a3aeb8249679.wasm", + "tx_withdraw.wasm": "tx_withdraw.599ecc125b197b22b27127ce61bc17138a4dd05eb1598a64862496f301c0bc28.wasm", + "vp_nft.wasm": "vp_nft.a7f25944fba3d9a2b00e98482535ed4591282bbf794d64cad18d3c7d15a6318c.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.e28867d79578ce4a32d027b6e50580e8e5e0a19b44c60dc10cacb41dfe07e28c.wasm", + "vp_token.wasm": "vp_token.d24443f5683d0d7d0259dab878f811a56c3d19f3158aecfaae6ce7627cb40884.wasm", + "vp_user.wasm": "vp_user.1aa3756e386a883f523534ac76fb4b75e01c06fcde647c2b6fcca807ba683497.wasm" } \ No newline at end of file From 707db38db5f21674cad24766c4df40a011dc1fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 22 Sep 2022 18:53:49 +0200 Subject: [PATCH 362/373] changelog: #503 --- .changelog/unreleased/features/503-lazy-vec-and-map.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/features/503-lazy-vec-and-map.md diff --git a/.changelog/unreleased/features/503-lazy-vec-and-map.md b/.changelog/unreleased/features/503-lazy-vec-and-map.md new file mode 100644 index 0000000000..d29ee5fd9c --- /dev/null +++ b/.changelog/unreleased/features/503-lazy-vec-and-map.md @@ -0,0 +1,2 @@ +- Added lazy vector and map data structures for ledger storage + ([#503](https://github.com/anoma/namada/pull/503)) \ No newline at end of file From 467fcc87d4ea1969d0bb668e985e3fdd4f52916f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 23 Sep 2022 10:11:31 +0000 Subject: [PATCH 363/373] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index 3d1538fc9f..b9af125eaf 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.69b997687f93801f7822ad39032ab8fbc29ca989317e94c07c7bf3bc4a2a2ad3.wasm", - "tx_from_intent.wasm": "tx_from_intent.1f8b246e7d0c4e87188018215eb2860082ef99ace78a12c0481a39523a0b7851.wasm", - "tx_ibc.wasm": "tx_ibc.90505336c947d962bb02f4643dbd294263dd3bb88601d766f3cf0f8d17ec92fb.wasm", - "tx_init_account.wasm": "tx_init_account.a1bdaca982e027f068cbd2c17ed9de64396ecc5ba3a14ac7578fded20a829005.wasm", - "tx_init_nft.wasm": "tx_init_nft.18ad3e76dfe4c3d3bdba1069f668bd0d349aa8f99cfa2c7a51e5b6bc782e3c25.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.794302c125d17068b40d25a67f943ff520f0f83bde3233ce24bfc227c61407dc.wasm", - "tx_init_validator.wasm": "tx_init_validator.276634f0ff3c6c123a110459876e8c37be25cbb2d0f28b320a91ae7592aab86f.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.ac9a7edb87c605457a39521c7a4ab0786077043e41de7975a996e872f6519f5d.wasm", - "tx_transfer.wasm": "tx_transfer.e769c543c2f9c0bf76fbe73af8cca79b3d6b92c80e957daa278873f85d5ef1b9.wasm", - "tx_unbond.wasm": "tx_unbond.8a4aa3106a1f594da45fce6be76597735eed366a4aa5c462474dd6536d845761.wasm", - "tx_update_vp.wasm": "tx_update_vp.f75fc727bc0f9e13ce78e8cdcd5ca978f4becf1e7417eada81a42bdc57ad3f03.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1c50c26be4ecf0f8b8893a1436768a77eff1050dbd51ab1c22b4cf458783f6ce.wasm", - "tx_withdraw.wasm": "tx_withdraw.59f41e331871b6f763e511d120f15da384a9c8d1f7e56845bb67d7a01fc6a863.wasm", - "vp_nft.wasm": "vp_nft.ccf80799aa2f9839e1af7b8be1e99d31d793c5ad100fab89af22f34aa6fd9e4b.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.181e1882bb09c5c2be54fec6454c3490a35c305706c11bec95ca739652555c26.wasm", - "vp_token.wasm": "vp_token.72c51248c57b47039d43550dca719b969ee83013fb9c440470e704316a6075b5.wasm", - "vp_user.wasm": "vp_user.6a05a513c383ac3fe39a993fea923d8bd2caa9e259f0ebbb95dc56a21ce813df.wasm" + "tx_bond.wasm": "tx_bond.72b81bb51f67b574cf7da27204e1b22a9221c575d6cb25346521c01e763caaf9.wasm", + "tx_from_intent.wasm": "tx_from_intent.01872d339c58bd25149874eb7498c30e9d654c904cb3ff8d1708cb848d9b1c2d.wasm", + "tx_ibc.wasm": "tx_ibc.abc0775b0afa0966503ade2b11e510219c34e55e65ffc0ddaee5111c47d69fd6.wasm", + "tx_init_account.wasm": "tx_init_account.f6a9ee5a298c4ba7ccefeea083c5df42af5c924927245ce3d4f1b8c07665fa63.wasm", + "tx_init_nft.wasm": "tx_init_nft.5d4524436b5f6fcb29912d486dca279c3a8677a47ab7af9f535e4a2955f15469.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b295c83a4bffb87b8c5bc987021c804caa839c3b224cc5bc5225505257ab9fad.wasm", + "tx_init_validator.wasm": "tx_init_validator.de89fa2521bfb06a064577ef0c31a5316d991a6b1413115958083c3328cf4abd.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.65835905408cb8b57ab679aef7816bff51fe38383313a855207081ed9d635e56.wasm", + "tx_transfer.wasm": "tx_transfer.399f825e9d40075fd981dd1be9b48332933223ac5dcc47c1375aa337da881fcb.wasm", + "tx_unbond.wasm": "tx_unbond.3854b888082ac44a971900a4c761aa4917a03fbaa0cdf403028222e97879614f.wasm", + "tx_update_vp.wasm": "tx_update_vp.f462d73148a9ed2e48ffd33d77713981e69d56b391a055b75d367915b2e768d2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.6486326c8772c7a20d5e8640235fb54abaa5dc2b1c624e75d5a1f49e2df9cdab.wasm", + "tx_withdraw.wasm": "tx_withdraw.a04fd246f0871995ebc7291e376cff83bc33228403607e21ab490c3063e905b1.wasm", + "vp_nft.wasm": "vp_nft.f82082aa459fbb211271b03561f0d0fe9222320366dabab75bfedd84e596b477.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7cd43d8cd003fea8614d35cdc3916e8ec05ff974d25b3e6d0dadf4fa74ed3ca6.wasm", + "vp_token.wasm": "vp_token.188e37e0deff4f3e2c5acb2037e43d0dd77080ecadd0353cb28f3e2f787bb0fc.wasm", + "vp_user.wasm": "vp_user.be9fab0f727449620989abe3219188138730cedca36076b6cc40a60f34e19af0.wasm" } \ No newline at end of file From 88513f697956b240456fd7b27a2d73f3e286a5fd Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Sep 2022 12:52:58 +0200 Subject: [PATCH 364/373] Refactors governance e2e tests --- tests/src/e2e/ledger_tests.rs | 73 ++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 777090d4d4..68b8b85cc4 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -997,7 +997,10 @@ fn proposal_submission() -> Result<()> { min_num_of_blocks: 1, min_duration: 1, max_expected_time_per_block: 1, - ..genesis.parameters + vp_whitelist: genesis.parameters.vp_whitelist, + // Enable tx whitelist to test the execution of a + // non-whitelisted tx by governance + tx_whitelist: Some(vec![String::from("test")]), }; GenesisConfig { @@ -1046,7 +1049,6 @@ fn proposal_submission() -> Result<()> { client.assert_success(); // 2. Submit valid proposal - let test_dir = tempfile::tempdir_in(test.test_dir.path()).unwrap(); let proposal_code = wasm_abs_path(TX_PROPOSAL_CODE); let albert = find_address(&test, ALBERT)?; @@ -1070,18 +1072,19 @@ fn proposal_submission() -> Result<()> { "proposal_code_path": proposal_code.to_str().unwrap() } ); - let proposal_file = - tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); - serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); - let proposal_path = proposal_file.path(); - let proposal_ref = proposal_path.to_string_lossy(); + let valid_proposal_json_path = + test.test_dir.path().join("valid_proposal.json"); + generate_proposal_json_file( + valid_proposal_json_path.as_path(), + &valid_proposal_json, + ); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let submit_proposal_args = vec![ "init-proposal", "--data-path", - &proposal_ref, + valid_proposal_json_path.to_str().unwrap(), "--ledger-address", &validator_one_rpc, ]; @@ -1169,17 +1172,17 @@ fn proposal_submission() -> Result<()> { "grace_epoch": 10009_u64, } ); - let invalid_proposal_file = - tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); - serde_json::to_writer(&invalid_proposal_file, &invalid_proposal_json) - .unwrap(); - let invalid_proposal_path = invalid_proposal_file.path(); - let invalid_proposal_ref = invalid_proposal_path.to_string_lossy(); + let invalid_proposal_json_path = + test.test_dir.path().join("invalid_proposal.json"); + generate_proposal_json_file( + invalid_proposal_json_path.as_path(), + &invalid_proposal_json, + ); let submit_proposal_args = vec![ "init-proposal", "--data-path", - &invalid_proposal_ref, + invalid_proposal_json_path.to_str().unwrap(), "--ledger-address", &validator_one_rpc, ]; @@ -1395,8 +1398,6 @@ fn proposal_offline() -> Result<()> { client.assert_success(); // 2. Create an offline - let test_dir = tempfile::tempdir_in(test.test_dir.path()).unwrap(); - let albert = find_address(&test, ALBERT)?; let valid_proposal_json = json!( { @@ -1417,18 +1418,19 @@ fn proposal_offline() -> Result<()> { "grace_epoch": 18_u64 } ); - let proposal_file = - tempfile::NamedTempFile::new_in(test_dir.path()).unwrap(); - serde_json::to_writer(&proposal_file, &valid_proposal_json).unwrap(); - let proposal_path = proposal_file.path(); - let proposal_ref = proposal_path.to_string_lossy(); + let valid_proposal_json_path = + test.test_dir.path().join("valid_proposal.json"); + generate_proposal_json_file( + valid_proposal_json_path.as_path(), + &valid_proposal_json, + ); let validator_one_rpc = get_actor_rpc(&test, &Who::Validator(0)); let offline_proposal_args = vec![ "init-proposal", "--data-path", - &proposal_ref, + valid_proposal_json_path.to_str().unwrap(), "--offline", "--ledger-address", &validator_one_rpc, @@ -1445,12 +1447,10 @@ fn proposal_offline() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } - let proposal_path = test_dir.path().join("proposal"); - let proposal_ref = proposal_path.to_string_lossy(); let submit_proposal_vote = vec![ "vote-proposal", "--data-path", - &proposal_ref, + test.test_dir.path().to_str().unwrap(), "--vote", "yay", "--signer", @@ -1465,17 +1465,14 @@ fn proposal_offline() -> Result<()> { client.assert_success(); let expected_file_name = format!("proposal-vote-{}", albert); - let expected_path_vote = test_dir.path().join(&expected_file_name); + let expected_path_vote = test.test_dir.path().join(&expected_file_name); assert!(expected_path_vote.exists()); - let expected_path_proposal = test_dir.path().join("proposal"); - assert!(expected_path_proposal.exists()); - // 4. Compute offline tally let tally_offline = vec![ "query-proposal-result", "--data-path", - test_dir.path().to_str().unwrap(), + test.test_dir.path().to_str().unwrap(), "--offline", "--ledger-address", &validator_one_rpc, @@ -1488,6 +1485,20 @@ fn proposal_offline() -> Result<()> { Ok(()) } +fn generate_proposal_json_file( + proposal_path: &std::path::Path, + proposal_content: &serde_json::Value, +) { + let intent_writer = std::fs::OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(proposal_path) + .unwrap(); + + serde_json::to_writer(intent_writer, proposal_content).unwrap(); +} + /// In this test we: /// 1. Setup 2 genesis validators /// 2. Initialize a new network with the 2 validators From 218f45d199c1c6e2f90ff5b431cd9b80d84e1728 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Sep 2022 14:20:30 +0200 Subject: [PATCH 365/373] Uses `end_epoch` in `query_proposal_result` --- apps/src/lib/client/rpc.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index d19ded990c..19ed8aaaa6 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -303,20 +303,17 @@ pub async fn query_proposal_result( match args.proposal_id { Some(id) => { - let start_epoch_key = gov_storage::get_voting_start_epoch_key(id); let end_epoch_key = gov_storage::get_voting_end_epoch_key(id); - let start_epoch = - query_storage_value::(&client, &start_epoch_key).await; let end_epoch = query_storage_value::(&client, &end_epoch_key).await; - match (start_epoch, end_epoch) { - (Some(start_epoch), Some(end_epoch)) => { + match end_epoch { + Some(end_epoch) => { if current_epoch > end_epoch { let votes = - get_proposal_votes(&client, start_epoch, id).await; + get_proposal_votes(&client, end_epoch, id).await; let proposal_result = - compute_tally(&client, start_epoch, votes).await; + compute_tally(&client, end_epoch, votes).await; println!("Proposal: {}", id); println!("{:4}Result: {}", "", proposal_result); } else { @@ -324,7 +321,7 @@ pub async fn query_proposal_result( cli::safe_exit(1) } } - _ => { + None => { eprintln!("Error while retriving proposal."); cli::safe_exit(1) } From 5b34bff794bb206e3697f61c2dd331ac7ba3a7af Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Fri, 23 Sep 2022 15:31:15 +0200 Subject: [PATCH 366/373] Misc refactoring --- apps/src/lib/client/rpc.rs | 109 ++++-------------- .../lib/node/ledger/shell/finalize_block.rs | 6 +- apps/src/lib/node/ledger/shell/governance.rs | 5 - shared/src/ledger/governance/utils.rs | 70 +++-------- 4 files changed, 41 insertions(+), 149 deletions(-) diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 19ed8aaaa6..38da9b056d 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -1550,45 +1550,15 @@ pub async fn get_proposal_votes( .await; if let Some(amount) = delegator_token_amount { if vote.is_yay() { - match yay_delegators.get_mut(&voter_address) { - Some(map) => { - map.insert( - validator_address, - VotePower::from(amount), - ); - } - None => { - let delegations_map: HashMap< - Address, - VotePower, - > = HashMap::from([( - validator_address, - VotePower::from(amount), - )]); - yay_delegators - .insert(voter_address, delegations_map); - } - } + let entry = + yay_delegators.entry(voter_address).or_default(); + entry + .insert(validator_address, VotePower::from(amount)); } else { - match nay_delegators.get_mut(&voter_address) { - Some(map) => { - map.insert( - validator_address, - VotePower::from(amount), - ); - } - None => { - let delegations_map: HashMap< - Address, - VotePower, - > = HashMap::from([( - validator_address, - VotePower::from(amount), - )]); - nay_delegators - .insert(voter_address, delegations_map); - } - } + let entry = + nay_delegators.entry(voter_address).or_default(); + entry + .insert(validator_address, VotePower::from(amount)); } } } @@ -1670,49 +1640,17 @@ pub async fn get_proposal_offline_votes( "Delegation key should contain validator address.", ); if proposal_vote.vote.is_yay() { - match yay_delegators.get_mut(&proposal_vote.address) { - Some(map) => { - map.insert( - validator_address, - VotePower::from(amount), - ); - } - None => { - let delegations_map: HashMap< - Address, - VotePower, - > = HashMap::from([( - validator_address, - VotePower::from(amount), - )]); - yay_delegators.insert( - proposal_vote.address.clone(), - delegations_map, - ); - } - } + let entry = yay_delegators + .entry(proposal_vote.address.clone()) + .or_default(); + entry + .insert(validator_address, VotePower::from(amount)); } else { - match nay_delegators.get_mut(&proposal_vote.address) { - Some(map) => { - map.insert( - validator_address, - VotePower::from(amount), - ); - } - None => { - let delegations_map: HashMap< - Address, - VotePower, - > = HashMap::from([( - validator_address, - VotePower::from(amount), - )]); - nay_delegators.insert( - proposal_vote.address.clone(), - delegations_map, - ); - } - } + let entry = nay_delegators + .entry(proposal_vote.address.clone()) + .or_default(); + entry + .insert(validator_address, VotePower::from(amount)); } } } @@ -1865,14 +1803,9 @@ async fn get_validator_stake( .await .expect("Total deltas should be defined"); let epoched_total_voting_power = total_voting_power.get(epoch); - if let Some(epoched_total_voting_power) = epoched_total_voting_power { - match VotePower::try_from(epoched_total_voting_power) { - Ok(voting_power) => voting_power, - Err(_) => VotePower::from(0_u64), - } - } else { - VotePower::from(0_u64) - } + + VotePower::try_from(epoched_total_voting_power.unwrap_or_default()) + .unwrap_or_default() } pub async fn get_delegators_delegation( diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index a52876492c..c15df6e399 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -44,8 +44,10 @@ where let (height, new_epoch) = self.update_state(req.header, req.hash, req.byzantine_validators); - let _proposals_result = - execute_governance_proposals(self, new_epoch, &mut response)?; + if new_epoch { + let _proposals_result = + execute_governance_proposals(self, &mut response)?; + } for processed_tx in &req.txs { let tx = if let Ok(tx) = Tx::try_from(processed_tx.tx.as_ref()) { diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index a1d17110eb..979c3c0df1 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -22,7 +22,6 @@ pub struct ProposalsResult { pub fn execute_governance_proposals( shell: &mut Shell, - new_epoch: bool, response: &mut shim::response::FinalizeBlock, ) -> Result where @@ -31,10 +30,6 @@ where { let mut proposals_result = ProposalsResult::default(); - if !new_epoch { - return Ok(proposals_result); - } - for id in std::mem::take(&mut shell.proposal_data) { let proposal_funds_key = gov_storage::get_funds_key(id); let proposal_end_epoch_key = gov_storage::get_voting_end_epoch_key(id); diff --git a/shared/src/ledger/governance/utils.rs b/shared/src/ledger/governance/utils.rs index 3e75f99e93..03f75be722 100644 --- a/shared/src/ledger/governance/utils.rs +++ b/shared/src/ledger/governance/utils.rs @@ -244,57 +244,21 @@ where ); if let Some(amount) = amount { if vote.is_yay() { - match yay_delegators - .get_mut(voter_address) - { - Some(map) => { - map.insert( - validator_address - .clone(), - VotePower::from(amount), - ); - } - None => { - let delegations_map = - HashMap::from([( - validator_address - .clone(), - VotePower::from( - amount, - ), - )]); - yay_delegators.insert( - voter_address.clone(), - delegations_map, - ); - } - } + let entry = yay_delegators + .entry(voter_address.to_owned()) + .or_default(); + entry.insert( + validator_address.to_owned(), + VotePower::from(amount), + ); } else { - match nay_delegators - .get_mut(&voter_address.clone()) - { - Some(map) => { - map.insert( - validator_address - .clone(), - VotePower::from(amount), - ); - } - None => { - let delegations_map = - HashMap::from([( - validator_address - .clone(), - VotePower::from( - amount, - ), - )]); - nay_delegators.insert( - voter_address.clone(), - delegations_map, - ); - } - } + let entry = nay_delegators + .entry(voter_address.to_owned()) + .or_default(); + entry.insert( + validator_address.to_owned(), + VotePower::from(amount), + ); } } } @@ -383,10 +347,8 @@ where if let Some(total_delta) = total_delta { let epoched_total_delta = total_delta.get(epoch); if let Some(epoched_total_delta) = epoched_total_delta { - match VotePower::try_from(epoched_total_delta) { - Ok(voting_power) => return voting_power, - Err(_) => return VotePower::from(0_u64), - } + return VotePower::try_from(epoched_total_delta) + .unwrap_or_default(); } } } From e4ab8c985eecdb14a3e8d823930a8abd5fc31b54 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 26 Sep 2022 13:30:52 +0200 Subject: [PATCH 367/373] fix e2e tests --- tests/src/e2e/ledger_tests.rs | 15 ++++++++++++--- tests/src/e2e/setup.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index 68b8b85cc4..f9ec4b4212 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -22,6 +22,7 @@ use namada_apps::config::genesis::genesis_config::{ use serde_json::json; use setup::constants::*; +use super::setup::get_all_wasms_hashes; use crate::e2e::helpers::{ find_address, find_voting_power, get_actor_rpc, get_epoch, }; @@ -991,16 +992,24 @@ fn ledger_many_txs_in_a_block() -> Result<()> { /// 13. Check governance address funds are 0 #[test] fn proposal_submission() -> Result<()> { + let working_dir = setup::working_dir(); + let test = setup::network( |genesis| { let parameters = ParametersConfig { min_num_of_blocks: 1, min_duration: 1, max_expected_time_per_block: 1, - vp_whitelist: genesis.parameters.vp_whitelist, + vp_whitelist: Some(get_all_wasms_hashes( + &working_dir, + Some("vp_"), + )), // Enable tx whitelist to test the execution of a // non-whitelisted tx by governance - tx_whitelist: Some(vec![String::from("test")]), + tx_whitelist: Some(get_all_wasms_hashes( + &working_dir, + Some("tx_"), + )), }; GenesisConfig { @@ -1450,7 +1459,7 @@ fn proposal_offline() -> Result<()> { let submit_proposal_vote = vec![ "vote-proposal", "--data-path", - test.test_dir.path().to_str().unwrap(), + valid_proposal_json_path.to_str().unwrap(),, "--vote", "yay", "--signer", diff --git a/tests/src/e2e/setup.rs b/tests/src/e2e/setup.rs index 6499bf9806..d261ebcc0a 100644 --- a/tests/src/e2e/setup.rs +++ b/tests/src/e2e/setup.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::ffi::OsStr; use std::fmt::Display; use std::fs::{File, OpenOptions}; @@ -23,6 +24,7 @@ use namada_apps::client::utils; use namada_apps::config::genesis::genesis_config::{self, GenesisConfig}; use namada_apps::{config, wallet}; use rand::Rng; +use serde_json; use tempfile::{tempdir, TempDir}; use crate::e2e::helpers::generate_bin_command; @@ -860,3 +862,28 @@ pub fn copy_wasm_to_chain_dir<'a>( } } } + +pub fn get_all_wasms_hashes( + working_dir: &Path, + filter: Option<&str>, +) -> Vec { + let checksums_path = working_dir.join("wasm/checksums.json"); + let checksums_content = fs::read_to_string(checksums_path).unwrap(); + let checksums: HashMap = + serde_json::from_str(&checksums_content).unwrap(); + let filter_prefix = filter.unwrap_or_default(); + checksums + .values() + .filter_map(|wasm| { + if wasm.contains(&filter_prefix) { + Some( + wasm.split('.').collect::>()[1] + .to_owned() + .to_uppercase(), + ) + } else { + None + } + }) + .collect() +} From c0e18b933a116a55162006956288ceca541da539 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Mon, 26 Sep 2022 13:43:52 +0200 Subject: [PATCH 368/373] fix e2e tests --- tests/src/e2e/ledger_tests.rs | 4 +++- wasm/checksums.json | 34 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/src/e2e/ledger_tests.rs b/tests/src/e2e/ledger_tests.rs index f9ec4b4212..5fe0e52921 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1456,10 +1456,12 @@ fn proposal_offline() -> Result<()> { epoch = get_epoch(&test, &validator_one_rpc).unwrap(); } + let proposal_path = test.test_dir.path().join("proposal"); + let submit_proposal_vote = vec![ "vote-proposal", "--data-path", - valid_proposal_json_path.to_str().unwrap(),, + proposal_path.to_str().unwrap(), "--vote", "yay", "--signer", diff --git a/wasm/checksums.json b/wasm/checksums.json index b9af125eaf..dfd8e3e0ce 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.72b81bb51f67b574cf7da27204e1b22a9221c575d6cb25346521c01e763caaf9.wasm", - "tx_from_intent.wasm": "tx_from_intent.01872d339c58bd25149874eb7498c30e9d654c904cb3ff8d1708cb848d9b1c2d.wasm", - "tx_ibc.wasm": "tx_ibc.abc0775b0afa0966503ade2b11e510219c34e55e65ffc0ddaee5111c47d69fd6.wasm", - "tx_init_account.wasm": "tx_init_account.f6a9ee5a298c4ba7ccefeea083c5df42af5c924927245ce3d4f1b8c07665fa63.wasm", - "tx_init_nft.wasm": "tx_init_nft.5d4524436b5f6fcb29912d486dca279c3a8677a47ab7af9f535e4a2955f15469.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.b295c83a4bffb87b8c5bc987021c804caa839c3b224cc5bc5225505257ab9fad.wasm", - "tx_init_validator.wasm": "tx_init_validator.de89fa2521bfb06a064577ef0c31a5316d991a6b1413115958083c3328cf4abd.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.65835905408cb8b57ab679aef7816bff51fe38383313a855207081ed9d635e56.wasm", - "tx_transfer.wasm": "tx_transfer.399f825e9d40075fd981dd1be9b48332933223ac5dcc47c1375aa337da881fcb.wasm", - "tx_unbond.wasm": "tx_unbond.3854b888082ac44a971900a4c761aa4917a03fbaa0cdf403028222e97879614f.wasm", - "tx_update_vp.wasm": "tx_update_vp.f462d73148a9ed2e48ffd33d77713981e69d56b391a055b75d367915b2e768d2.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.6486326c8772c7a20d5e8640235fb54abaa5dc2b1c624e75d5a1f49e2df9cdab.wasm", - "tx_withdraw.wasm": "tx_withdraw.a04fd246f0871995ebc7291e376cff83bc33228403607e21ab490c3063e905b1.wasm", - "vp_nft.wasm": "vp_nft.f82082aa459fbb211271b03561f0d0fe9222320366dabab75bfedd84e596b477.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7cd43d8cd003fea8614d35cdc3916e8ec05ff974d25b3e6d0dadf4fa74ed3ca6.wasm", - "vp_token.wasm": "vp_token.188e37e0deff4f3e2c5acb2037e43d0dd77080ecadd0353cb28f3e2f787bb0fc.wasm", - "vp_user.wasm": "vp_user.be9fab0f727449620989abe3219188138730cedca36076b6cc40a60f34e19af0.wasm" + "tx_bond.wasm": "tx_bond.ccbc6090956e0f76327ea9622f8f3e0f2ffd704b0f97f4612977c9a44a9db8d1.wasm", + "tx_from_intent.wasm": "tx_from_intent.46190da394e7188b74cba0f5d6e4575040a9a7ad1cf63e7bb116b9b12450f7f4.wasm", + "tx_ibc.wasm": "tx_ibc.94721e7b33b38742697de698d82cde35e7704f862d9c7d8de7d761216326d446.wasm", + "tx_init_account.wasm": "tx_init_account.cbd4d7e68cc3f9c1056b4e5d00d0286b98ddc105159255ff90d225f46962ef2f.wasm", + "tx_init_nft.wasm": "tx_init_nft.f5f37b6358040d4c4c63f817b54b5072f52f7fe1661172303e1ab93f9ab74285.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.bc8e3ccd6a3c2321ea3a199df0796fbb2d20044de4ec5a1f85f1266be20cac1b.wasm", + "tx_init_validator.wasm": "tx_init_validator.52bed77804568b4009720bc80452c1e7394c29d561497b1c09c84312decf2d19.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.771071501355a6d82031a6d01817c562e93abb7cbf29d8eab5882527426663c2.wasm", + "tx_transfer.wasm": "tx_transfer.6b54952c89732ed902bda78895510cf8a65de24c76b514c1cfd6a22d79f056f6.wasm", + "tx_unbond.wasm": "tx_unbond.dfc98d68f800df2e99ec7d5281d0c0199b64363942b50d264af79115d2239605.wasm", + "tx_update_vp.wasm": "tx_update_vp.f8dbd92128e858de1100500d0e2ae734aa51a9fe97ee2fd6a353853024b966d8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.c168f3615edaa5733edb84169352d7d4979b8aa296718a1be614c0f334f91884.wasm", + "tx_withdraw.wasm": "tx_withdraw.28025d5e28c3f3a456d93042b8d2b865f7b1dba90fa9a8b5a49fc37505350321.wasm", + "vp_nft.wasm": "vp_nft.a27006540f8b687128b6e5019c6f73cfc81d75abab08b92ee70d013f33a9f055.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.952095dc7be7dd0f4db1454f835421d9c2661edf78c8182275363f72b2751837.wasm", + "vp_token.wasm": "vp_token.589171ceb6c3fd2a364a4c68738db737fbd74f8d2a44da9782b1872ab3a2e7f6.wasm", + "vp_user.wasm": "vp_user.ac27cb60a731a7e8526a2f369fa0491ffe9c09e4261090e01095d822c5eba8f2.wasm" } \ No newline at end of file From ccfa965ca87528ec11f91f359c3769aec4095d02 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Mon, 26 Sep 2022 14:48:31 +0200 Subject: [PATCH 369/373] changelog: add #501 --- .changelog/unreleased/improvements/467-governance-fixes.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/467-governance-fixes.md diff --git a/.changelog/unreleased/improvements/467-governance-fixes.md b/.changelog/unreleased/improvements/467-governance-fixes.md new file mode 100644 index 0000000000..441277d632 --- /dev/null +++ b/.changelog/unreleased/improvements/467-governance-fixes.md @@ -0,0 +1,2 @@ +- Fixed governance parameters, tally, tx whitelist and renamed treasury + ([#467](https://github.com/anoma/namada/issues/467)) \ No newline at end of file From 7e3cf9007788308387eb69d5770d1cdf4986b7f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 26 Sep 2022 13:13:02 +0000 Subject: [PATCH 370/373] [ci] wasm checksums update --- wasm/checksums.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wasm/checksums.json b/wasm/checksums.json index dfd8e3e0ce..b9af125eaf 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.ccbc6090956e0f76327ea9622f8f3e0f2ffd704b0f97f4612977c9a44a9db8d1.wasm", - "tx_from_intent.wasm": "tx_from_intent.46190da394e7188b74cba0f5d6e4575040a9a7ad1cf63e7bb116b9b12450f7f4.wasm", - "tx_ibc.wasm": "tx_ibc.94721e7b33b38742697de698d82cde35e7704f862d9c7d8de7d761216326d446.wasm", - "tx_init_account.wasm": "tx_init_account.cbd4d7e68cc3f9c1056b4e5d00d0286b98ddc105159255ff90d225f46962ef2f.wasm", - "tx_init_nft.wasm": "tx_init_nft.f5f37b6358040d4c4c63f817b54b5072f52f7fe1661172303e1ab93f9ab74285.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.bc8e3ccd6a3c2321ea3a199df0796fbb2d20044de4ec5a1f85f1266be20cac1b.wasm", - "tx_init_validator.wasm": "tx_init_validator.52bed77804568b4009720bc80452c1e7394c29d561497b1c09c84312decf2d19.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.771071501355a6d82031a6d01817c562e93abb7cbf29d8eab5882527426663c2.wasm", - "tx_transfer.wasm": "tx_transfer.6b54952c89732ed902bda78895510cf8a65de24c76b514c1cfd6a22d79f056f6.wasm", - "tx_unbond.wasm": "tx_unbond.dfc98d68f800df2e99ec7d5281d0c0199b64363942b50d264af79115d2239605.wasm", - "tx_update_vp.wasm": "tx_update_vp.f8dbd92128e858de1100500d0e2ae734aa51a9fe97ee2fd6a353853024b966d8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.c168f3615edaa5733edb84169352d7d4979b8aa296718a1be614c0f334f91884.wasm", - "tx_withdraw.wasm": "tx_withdraw.28025d5e28c3f3a456d93042b8d2b865f7b1dba90fa9a8b5a49fc37505350321.wasm", - "vp_nft.wasm": "vp_nft.a27006540f8b687128b6e5019c6f73cfc81d75abab08b92ee70d013f33a9f055.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.952095dc7be7dd0f4db1454f835421d9c2661edf78c8182275363f72b2751837.wasm", - "vp_token.wasm": "vp_token.589171ceb6c3fd2a364a4c68738db737fbd74f8d2a44da9782b1872ab3a2e7f6.wasm", - "vp_user.wasm": "vp_user.ac27cb60a731a7e8526a2f369fa0491ffe9c09e4261090e01095d822c5eba8f2.wasm" + "tx_bond.wasm": "tx_bond.72b81bb51f67b574cf7da27204e1b22a9221c575d6cb25346521c01e763caaf9.wasm", + "tx_from_intent.wasm": "tx_from_intent.01872d339c58bd25149874eb7498c30e9d654c904cb3ff8d1708cb848d9b1c2d.wasm", + "tx_ibc.wasm": "tx_ibc.abc0775b0afa0966503ade2b11e510219c34e55e65ffc0ddaee5111c47d69fd6.wasm", + "tx_init_account.wasm": "tx_init_account.f6a9ee5a298c4ba7ccefeea083c5df42af5c924927245ce3d4f1b8c07665fa63.wasm", + "tx_init_nft.wasm": "tx_init_nft.5d4524436b5f6fcb29912d486dca279c3a8677a47ab7af9f535e4a2955f15469.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.b295c83a4bffb87b8c5bc987021c804caa839c3b224cc5bc5225505257ab9fad.wasm", + "tx_init_validator.wasm": "tx_init_validator.de89fa2521bfb06a064577ef0c31a5316d991a6b1413115958083c3328cf4abd.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.65835905408cb8b57ab679aef7816bff51fe38383313a855207081ed9d635e56.wasm", + "tx_transfer.wasm": "tx_transfer.399f825e9d40075fd981dd1be9b48332933223ac5dcc47c1375aa337da881fcb.wasm", + "tx_unbond.wasm": "tx_unbond.3854b888082ac44a971900a4c761aa4917a03fbaa0cdf403028222e97879614f.wasm", + "tx_update_vp.wasm": "tx_update_vp.f462d73148a9ed2e48ffd33d77713981e69d56b391a055b75d367915b2e768d2.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.6486326c8772c7a20d5e8640235fb54abaa5dc2b1c624e75d5a1f49e2df9cdab.wasm", + "tx_withdraw.wasm": "tx_withdraw.a04fd246f0871995ebc7291e376cff83bc33228403607e21ab490c3063e905b1.wasm", + "vp_nft.wasm": "vp_nft.f82082aa459fbb211271b03561f0d0fe9222320366dabab75bfedd84e596b477.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.7cd43d8cd003fea8614d35cdc3916e8ec05ff974d25b3e6d0dadf4fa74ed3ca6.wasm", + "vp_token.wasm": "vp_token.188e37e0deff4f3e2c5acb2037e43d0dd77080ecadd0353cb28f3e2f787bb0fc.wasm", + "vp_user.wasm": "vp_user.be9fab0f727449620989abe3219188138730cedca36076b6cc40a60f34e19af0.wasm" } \ No newline at end of file From 9ac8bc402ba3c961bb468be50a391bffeeade1b9 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 7 Sep 2022 15:02:20 +0200 Subject: [PATCH 371/373] feat: update rocksdb version --- Cargo.lock | 1639 ++++++++++--------- Cargo.toml | 4 + apps/Cargo.toml | 2 +- apps/src/lib/node/ledger/storage/rocksdb.rs | 10 +- shared/src/ledger/storage/mockdb.rs | 10 +- shared/src/ledger/storage/types.rs | 5 +- wasm_for_tests/wasm_source/Cargo.lock | 24 +- 7 files changed, 857 insertions(+), 837 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eafd05b2ff..c2fa1b54da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli 0.26.2", ] [[package]] @@ -23,7 +23,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -34,7 +34,7 @@ checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", ] @@ -58,20 +58,29 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", "once_cell", "version_check 0.9.4", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -83,9 +92,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.57" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" [[package]] name = "ark-bls12-381" @@ -229,9 +238,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "ascii" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf56136a5198c7b01a49e3afcbef6cf84597273d298f54432926024107b0109" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" [[package]] name = "asn1_der" @@ -261,9 +270,9 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-channel" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ "concurrent-queue", "event-listener", @@ -286,26 +295,25 @@ dependencies = [ [[package]] name = "async-global-executor" -version = "2.0.4" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +checksum = "0da5b41ee986eed3f524c380e6d64965aea573882a8907682ad100f7859305ca" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", - "num_cpus", "once_cell", ] [[package]] name = "async-io" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +version = "1.9.0" +source = "git+https://github.com/heliaxdev/async-io.git?rev=9285dad39c9a37ecd0dbd498c5ce5b0e65b02489#9285dad39c9a37ecd0dbd498c5ce5b0e65b02489" dependencies = [ + "autocfg 1.1.0", "concurrent-queue", "futures-lite", "libc", @@ -313,8 +321,9 @@ dependencies = [ "once_cell", "parking", "polling", + "rustversion", "slab", - "socket2 0.4.4", + "socket2 0.4.7", "waker-fn", "winapi 0.3.9", ] @@ -328,44 +337,36 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-mutex" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" -dependencies = [ - "event-listener", -] - [[package]] name = "async-process" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" +version = "1.5.0" +source = "git+https://github.com/heliaxdev/async-process.git?rev=e42c527e87d937da9e01aaeb563c0b948580dc89#e42c527e87d937da9e01aaeb563c0b948580dc89" dependencies = [ "async-io", + "autocfg 1.1.0", "blocking", "cfg-if 1.0.0", "event-listener", "futures-lite", "libc", "once_cell", + "rustversion", "signal-hook", "winapi 0.3.9", ] [[package]] name = "async-std" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-channel", "async-global-executor", "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", "futures-channel", "futures-core", "futures-io", @@ -374,7 +375,6 @@ dependencies = [ "kv-log-macro", "log 0.4.17", "memchr", - "num_cpus", "once_cell", "pin-project-lite 0.2.9", "pin-utils", @@ -419,15 +419,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.2.0" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -456,7 +456,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-sink", "futures-util", "memchr", @@ -480,12 +480,12 @@ checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" [[package]] name = "atty" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ + "hermit-abi", "libc", - "termion", "winapi 0.3.9", ] @@ -506,16 +506,16 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.65" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object", + "object 0.29.0", "rustc-demangle", ] @@ -568,19 +568,19 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.137", + "serde 1.0.145", ] [[package]] name = "bindgen" -version = "0.59.2" +version = "0.60.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" dependencies = [ "bitflags", "cexpr", "clang-sys", - "lazy_static 1.4.0", + "lazy_static", "lazycell", "peeking_take_while", "proc-macro2", @@ -592,9 +592,9 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] @@ -628,7 +628,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -663,7 +663,7 @@ dependencies = [ "cc", "cfg-if 1.0.0", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.5", ] [[package]] @@ -685,16 +685,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] @@ -779,16 +779,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "memchr", "regex-automata", ] [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "byte-tools" @@ -807,9 +807,9 @@ dependencies = [ [[package]] name = "bytecheck" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -817,9 +817,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", @@ -850,9 +850,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bzip2-sys" @@ -871,12 +871,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +[[package]] +name = "camino" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" + [[package]] name = "cargo-watch" -version = "7.7.0" +version = "7.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a4cf2908216028d1d97f49ed180367f009fdb3cd07550d0ef2db42bd6c739f" +checksum = "45fee6e0b2e10066aee5060c0dc74826f9a6447987fc535ca7719ae8883abd8e" dependencies = [ + "camino", "clap 2.34.0", "log 0.4.17", "shell-escape", @@ -886,9 +893,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -928,13 +935,13 @@ dependencies = [ [[package]] name = "chacha20" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", ] [[package]] @@ -952,14 +959,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits 0.2.15", "time 0.1.44", + "wasm-bindgen", "winapi 0.3.9", ] @@ -975,14 +984,14 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] name = "clang-sys" -version = "1.3.3" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" dependencies = [ "glob", "libc", @@ -999,7 +1008,6 @@ dependencies = [ "atty", "bitflags", "strsim 0.8.0", - "term_size", "textwrap 0.11.0", "unicode-width", "vec_map", @@ -1013,7 +1021,7 @@ dependencies = [ "atty", "bitflags", "indexmap", - "lazy_static 1.4.0", + "lazy_static", "os_str_bytes", "strsim 0.10.0", "termcolor", @@ -1022,6 +1030,19 @@ dependencies = [ "vec_map", ] +[[package]] +name = "clearscreen" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c969a6b6dadff9f3349b1f783f553e2411104763ca4789e1c6ca6a41f46a57b0" +dependencies = [ + "nix 0.24.2", + "terminfo", + "thiserror", + "which", + "winapi 0.3.9", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -1059,10 +1080,20 @@ checksum = "b6eee477a4a8a72f4addd4de416eb56d54bc307b284d6601bafdee1f4ea462d1" dependencies = [ "once_cell", "owo-colors", - "tracing-core 0.1.27", + "tracing-core 0.1.29", "tracing-error", ] +[[package]] +name = "command-group" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a8a86f409b4a59df3a3e4bee2de0b83f1755fdd2a25e3a9684c396fc4bed2c" +dependencies = [ + "nix 0.22.3", + "winapi 0.3.9", +] + [[package]] name = "concat-idents" version = "1.1.3" @@ -1075,9 +1106,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" dependencies = [ "cache-padded", ] @@ -1088,10 +1119,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "nom 5.1.2", "rust-ini", - "serde 1.0.137", + "serde 1.0.145", "serde-hjson", "serde_json", "toml", @@ -1146,9 +1177,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -1175,7 +1206,7 @@ dependencies = [ "gimli 0.25.0", "log 0.4.17", "regalloc", - "smallvec 1.8.0", + "smallvec 1.10.0", "target-lexicon", ] @@ -1209,7 +1240,7 @@ checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen", "log 0.4.17", - "smallvec 1.8.0", + "smallvec 1.10.0", "target-lexicon", ] @@ -1224,35 +1255,34 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", ] [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", ] [[package]] name = "crossbeam-epoch" -version = "0.9.8" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg 1.1.0", "cfg-if 1.0.0", - "crossbeam-utils 0.8.8", - "lazy_static 1.4.0", + "crossbeam-utils 0.8.12", "memoffset", "scopeguard", ] @@ -1265,17 +1295,16 @@ checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ "autocfg 1.1.0", "cfg-if 0.1.10", - "lazy_static 1.4.0", + "lazy_static", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if 1.0.0", - "lazy_static 1.4.0", ] [[package]] @@ -1290,19 +1319,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.5", - "rand_core 0.6.3", + "generic-array 0.14.6", + "rand_core 0.6.4", "subtle 2.4.1", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "typenum", ] @@ -1322,7 +1351,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] @@ -1332,7 +1361,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] @@ -1353,9 +1382,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" dependencies = [ "quote", "syn", @@ -1389,24 +1418,24 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.43" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d855aeef205b43f65a5001e0997d81f8efca7badad4fad7d897aa7f0d0651f" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", - "socket2 0.4.4", + "socket2 0.4.7", "winapi 0.3.9", ] [[package]] name = "curl-sys" -version = "0.4.55+curl-7.83.1" +version = "0.4.56+curl-7.83.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" +checksum = "6093e169dd4de29e468fa649fbae11cdcd5551c81fe5bf1b0677adad7ef3d26f" dependencies = [ "cc", "libc", @@ -1438,7 +1467,7 @@ checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle-ng", "zeroize", ] @@ -1455,12 +1484,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" dependencies = [ - "darling_core 0.13.4", - "darling_macro 0.13.4", + "darling_core 0.14.1", + "darling_macro 0.14.1", ] [[package]] @@ -1479,9 +1508,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" dependencies = [ "fnv", "ident_case", @@ -1503,11 +1532,11 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" dependencies = [ - "darling_core 0.13.4", + "darling_core 0.14.1", "quote", "syn", ] @@ -1582,9 +1611,9 @@ dependencies = [ [[package]] name = "diff" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" @@ -1607,16 +1636,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", ] [[package]] name = "digest" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle 2.4.1", ] @@ -1630,6 +1659,16 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.3.7" @@ -1671,7 +1710,7 @@ checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", - "lazy_static 1.4.0", + "lazy_static", "proc-macro-error", "proc-macro2", "quote", @@ -1707,7 +1746,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ - "serde 1.0.137", + "serde 1.0.145", "signature", ] @@ -1719,8 +1758,8 @@ checksum = "758e2a0cd8a6cdf483e1d369e7d081647e00b88d8953e34d8f2cbba05ae28368" dependencies = [ "curve25519-dalek-ng", "hex", - "rand_core 0.6.3", - "serde 1.0.137", + "rand_core 0.6.4", + "serde 1.0.145", "sha2 0.9.9", "thiserror", "zeroize", @@ -1736,7 +1775,7 @@ dependencies = [ "ed25519", "merlin", "rand 0.7.3", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "sha2 0.9.9", "zeroize", @@ -1744,9 +1783,9 @@ dependencies = [ [[package]] name = "either" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elliptic-curve" @@ -1758,27 +1797,14 @@ dependencies = [ "crypto-bigint", "der", "ff", - "generic-array 0.14.5", + "generic-array 0.14.6", "group", - "rand_core 0.6.3", + "rand_core 0.6.4", "sec1", "subtle 2.4.1", "zeroize", ] -[[package]] -name = "embed-resource" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc24ff8d764818e9ab17963b0593c535f077a513f565e75e4352d758bc4d8c0" -dependencies = [ - "cc", - "rustc_version 0.4.0", - "toml", - "vswhom", - "winreg 0.10.1", -] - [[package]] name = "encoding_rs" version = "0.8.31" @@ -1822,34 +1848,25 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" +checksum = "19be8061a06ab6f3a6cf21106c873578bf01bd42ad15e0311a9c76161cb1c753" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" +checksum = "03e7b551eba279bf0fa88b83a46330168c1560a52a94f5126f892f0b364ab3e0" dependencies = [ - "darling 0.13.4", + "darling 0.14.1", "proc-macro2", "quote", "syn", ] -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "log 0.4.17", -] - [[package]] name = "escargot" version = "0.5.7" @@ -1858,15 +1875,15 @@ checksum = "f5584ba17d7ab26a8a7284f13e5bd196294dd2f2d79773cff29b9e9edef601a6" dependencies = [ "log 0.4.17", "once_cell", - "serde 1.0.137", + "serde 1.0.145", "serde_json", ] [[package]] name = "event-listener" -version = "2.5.2" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "expectrl" @@ -1904,9 +1921,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1914,7 +1931,7 @@ dependencies = [ [[package]] name = "ferveo" version = "0.1.1" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -1928,19 +1945,19 @@ dependencies = [ "blake2 0.10.4", "blake2b_simd", "borsh", - "digest 0.10.3", + "digest 0.10.5", "ed25519-dalek", "either", "ferveo-common", "group-threshold-cryptography", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "measure_time", "miracl_core", "num", "rand 0.7.3", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "serde_json", "subproductdomain", @@ -1951,13 +1968,13 @@ dependencies = [ [[package]] name = "ferveo-common" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", "ark-serialize", "ark-std", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", ] @@ -1967,27 +1984,27 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", ] [[package]] name = "file-lock" -version = "2.1.4" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d68ace7c2694114c6d6b1047f87f8422cb84ab21e3166728c1af5a77e2e5325" +checksum = "0815fc2a1924e651b71ae6d13df07b356a671a09ecaf857dbd344a2ba937a496" dependencies = [ "cc", "libc", "mktemp", - "nix 0.24.1", + "nix 0.24.2", ] [[package]] name = "file-serve" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a09c8127b1a49f66ac56a7c9efe420e2ab23e00266ea4144cc2b905076a7f1" +checksum = "e43addbb09a5dcb5609cb44a01a79e67716fe40b50c109f50112ef201a8c7c59" dependencies = [ "log 0.4.17", "mime_guess", @@ -1996,14 +2013,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", - "winapi 0.3.9", + "redox_syscall 0.2.16", + "windows-sys", ] [[package]] @@ -2014,9 +2031,9 @@ checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" [[package]] name = "fixedbitset" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" @@ -2062,12 +2079,11 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" dependencies = [ - "matches", - "percent-encoding 2.1.0", + "percent-encoding 2.2.0", ] [[package]] @@ -2125,9 +2141,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" dependencies = [ "futures-channel", "futures-core", @@ -2140,9 +2156,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", "futures-sink", @@ -2150,15 +2166,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" dependencies = [ "futures-core", "futures-task", @@ -2168,9 +2184,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-lite" @@ -2189,9 +2205,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -2211,15 +2227,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-timer" @@ -2229,9 +2245,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-channel", "futures-core", @@ -2256,9 +2272,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check 0.9.4", @@ -2277,13 +2293,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] @@ -2309,9 +2325,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" [[package]] name = "git2" @@ -2325,7 +2341,7 @@ dependencies = [ "log 0.4.17", "openssl-probe", "openssl-sys", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -2336,9 +2352,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" dependencies = [ "aho-corasick", "bstr", @@ -2366,14 +2382,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle 2.4.1", ] [[package]] name = "group-threshold-cryptography" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-bls12-381", @@ -2383,12 +2399,12 @@ dependencies = [ "ark-serialize", "ark-std", "blake2b_simd", - "chacha20 0.8.1", + "chacha20 0.8.2", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "miracl_core", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rayon", "subproductdomain", "thiserror", @@ -2416,11 +2432,11 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "fnv", "futures-core", "futures-sink", @@ -2429,8 +2445,8 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.3", - "tracing 0.1.35", + "tokio-util 0.7.4", + "tracing 0.1.36", ] [[package]] @@ -2444,41 +2460,37 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "hdrhistogram" -version = "7.5.0" +version = "7.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" +checksum = "7f19b9f54f7c7f55e31401bb647626ce0cf0f67b0004982ce815b3ee72a02aa8" dependencies = [ - "base64 0.13.0", "byteorder", - "crossbeam-channel", - "flate2", - "nom 7.1.1", "num-traits 0.2.15", ] [[package]] name = "headers" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" dependencies = [ "base64 0.13.0", "bitflags", - "bytes 1.1.0", + "bytes 1.2.1", "headers-core", "http", "httpdate", "mime 0.3.16", - "sha-1 0.10.0", + "sha1", ] [[package]] @@ -2574,7 +2586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.5", + "generic-array 0.14.6", "hmac 0.8.1", ] @@ -2595,7 +2607,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "fnv", "itoa", ] @@ -2606,16 +2618,16 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "http", "pin-project-lite 0.2.9", ] [[package]] name = "httparse" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" @@ -2644,11 +2656,11 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.19" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-channel", "futures-core", "futures-util", @@ -2659,10 +2671,10 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite 0.2.9", - "socket2 0.4.4", + "socket2 0.4.7", "tokio", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", "want", ] @@ -2672,11 +2684,11 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "headers", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-rustls", "rustls-native-certs", "tokio", @@ -2693,7 +2705,7 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.19", + "hyper 0.14.20", "log 0.4.17", "rustls", "rustls-native-certs", @@ -2709,7 +2721,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.19", + "hyper 0.14.20", "pin-project-lite 0.2.9", "tokio", "tokio-io-timeout", @@ -2721,19 +2733,32 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.1.0", - "hyper 0.14.19", + "bytes 1.2.1", + "hyper 0.14.20", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd911b35d940d2bd0bea0f9100068e5b97b51a1cbe13d13382f132e0365257a0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "wasm-bindgen", + "winapi 0.3.9", +] + [[package]] name = "ibc" version = "0.12.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "derive_more", "flex-error", "ibc-proto", @@ -2742,17 +2767,17 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "safe-regex", - "serde 1.0.137", + "serde 1.0.145", "serde_derive", "serde_json", - "sha2 0.10.2", + "sha2 0.10.6", "subtle-encoding", "tendermint", "tendermint-light-client-verifier", "tendermint-proto", "tendermint-testgen", - "time 0.3.9", - "tracing 0.1.35", + "time 0.3.14", + "tracing 0.1.36", ] [[package]] @@ -2760,10 +2785,10 @@ name = "ibc-proto" version = "0.16.0" source = "git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc#30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.145", "tendermint-proto", "tonic", ] @@ -2775,7 +2800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce15e4758c46a0453bdf4b3b1dfcce70c43f79d1943c2ee0635b77eb2e7aa233" dependencies = [ "anyhow", - "bytes 1.1.0", + "bytes 1.2.1", "hex", "prost 0.9.0", "ripemd160", @@ -2812,6 +2837,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "if-addrs" version = "0.6.7" @@ -2840,7 +2875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae8ab7f67bad3240049cb24fb9cb0b4c2c6af4c245840917fbbdededeee91179" dependencies = [ "async-io", - "futures 0.3.21", + "futures 0.3.24", "futures-lite", "if-addrs", "ipnet", @@ -2857,13 +2892,13 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.8.2" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg 1.1.0", - "hashbrown 0.11.2", - "serde 1.0.137", + "hashbrown 0.12.3", + "serde 1.0.145", ] [[package]] @@ -2892,7 +2927,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", ] [[package]] @@ -2909,9 +2944,9 @@ dependencies = [ [[package]] name = "integer-encoding" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e85a1509a128c855368e135cffcde7eac17d8e1083f41e2b98c58bc1a5074be" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" [[package]] name = "iovec" @@ -2951,33 +2986,33 @@ dependencies = [ [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" dependencies = [ "wasm-bindgen", ] @@ -2989,7 +3024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" dependencies = [ "log 0.4.17", - "serde 1.0.137", + "serde 1.0.145", "serde_json", ] @@ -3037,12 +3072,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" -[[package]] -name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" - [[package]] name = "lazy_static" version = "1.4.0" @@ -3076,9 +3105,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.121" +version = "0.2.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" [[package]] name = "libgit2-sys" @@ -3110,9 +3139,9 @@ version = "0.38.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "atomic", - "bytes 1.1.0", - "futures 0.3.21", - "lazy_static 1.4.0", + "bytes 1.2.1", + "futures 0.3.24", + "lazy_static", "libp2p-core", "libp2p-deflate", "libp2p-dns", @@ -3137,8 +3166,8 @@ dependencies = [ "libp2p-yamux", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.10", - "smallvec 1.8.0", + "pin-project 1.0.12", + "smallvec 1.10.0", "wasm-timer", ] @@ -3152,23 +3181,23 @@ dependencies = [ "ed25519-dalek", "either", "fnv", - "futures 0.3.21", + "futures 0.3.24", "futures-timer", - "lazy_static 1.4.0", + "lazy_static", "libsecp256k1 0.3.5", "log 0.4.17", "multihash", "multistream-select", "parity-multiaddr", "parking_lot 0.11.2", - "pin-project 1.0.10", + "pin-project 1.0.12", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", "ring", "rw-stream-sink", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.10.0", "thiserror", "unsigned-varint 0.7.1", "void", @@ -3181,7 +3210,7 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "flate2", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", ] @@ -3191,10 +3220,10 @@ version = "0.28.1" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-std-resolver", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", - "smallvec 1.8.0", + "smallvec 1.10.0", "trust-dns-resolver", ] @@ -3205,14 +3234,14 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "cuckoofilter", "fnv", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", ] [[package]] @@ -3223,9 +3252,9 @@ dependencies = [ "asynchronous-codec", "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "fnv", - "futures 0.3.21", + "futures 0.3.24", "hex_fmt", "libp2p-core", "libp2p-swarm", @@ -3235,7 +3264,7 @@ dependencies = [ "rand 0.7.3", "regex", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.10.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3245,13 +3274,13 @@ name = "libp2p-identify" version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "prost 0.7.0", "prost-build 0.7.0", - "smallvec 1.8.0", + "smallvec 1.10.0", "wasm-timer", ] @@ -3262,10 +3291,10 @@ source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d dependencies = [ "arrayvec 0.5.2", "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "either", "fnv", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", @@ -3273,7 +3302,7 @@ dependencies = [ "prost-build 0.7.0", "rand 0.7.3", "sha2 0.9.9", - "smallvec 1.8.0", + "smallvec 1.10.0", "uint", "unsigned-varint 0.7.1", "void", @@ -3288,15 +3317,15 @@ dependencies = [ "async-io", "data-encoding", "dns-parser", - "futures 0.3.21", + "futures 0.3.24", "if-watch", - "lazy_static 1.4.0", + "lazy_static", "libp2p-core", "libp2p-swarm", "log 0.4.17", "rand 0.8.5", - "smallvec 1.8.0", - "socket2 0.4.4", + "smallvec 1.10.0", + "socket2 0.4.7", "void", ] @@ -3306,14 +3335,14 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", "unsigned-varint 0.7.1", ] @@ -3322,10 +3351,10 @@ name = "libp2p-noise" version = "0.31.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "curve25519-dalek", - "futures 0.3.21", - "lazy_static 1.4.0", + "futures 0.3.24", + "lazy_static", "libp2p-core", "log 0.4.17", "prost 0.7.0", @@ -3343,7 +3372,7 @@ name = "libp2p-ping" version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", @@ -3358,8 +3387,8 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "prost 0.7.0", @@ -3373,9 +3402,9 @@ name = "libp2p-pnet" version = "0.21.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "log 0.4.17", - "pin-project 1.0.10", + "pin-project 1.0.12", "rand 0.7.3", "salsa20", "sha3", @@ -3387,17 +3416,17 @@ version = "0.2.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "futures-timer", "libp2p-core", "libp2p-swarm", "log 0.4.17", - "pin-project 1.0.10", + "pin-project 1.0.12", "prost 0.7.0", "prost-build 0.7.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", "unsigned-varint 0.7.1", "void", "wasm-timer", @@ -3409,15 +3438,15 @@ version = "0.11.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-trait", - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "libp2p-core", "libp2p-swarm", "log 0.4.17", "lru", "minicbor", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", "unsigned-varint 0.7.1", "wasm-timer", ] @@ -3428,11 +3457,11 @@ version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "either", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec 1.10.0", "void", "wasm-timer", ] @@ -3452,14 +3481,14 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-io", - "futures 0.3.21", + "futures 0.3.24", "futures-timer", "if-watch", "ipnet", "libc", "libp2p-core", "log 0.4.17", - "socket2 0.4.4", + "socket2 0.4.7", ] [[package]] @@ -3468,7 +3497,7 @@ version = "0.28.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "async-std", - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "log 0.4.17", ] @@ -3478,7 +3507,7 @@ name = "libp2p-wasm-ext" version = "0.28.2" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "js-sys", "libp2p-core", "parity-send-wrapper", @@ -3492,14 +3521,14 @@ version = "0.29.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ "either", - "futures 0.3.21", + "futures 0.3.24", "futures-rustls", "libp2p-core", "log 0.4.17", "quicksink", "rw-stream-sink", "soketto", - "url 2.2.2", + "url 2.3.1", "webpki-roots", ] @@ -3508,7 +3537,7 @@ name = "libp2p-yamux" version = "0.32.0" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "libp2p-core", "parking_lot 0.11.2", "thiserror", @@ -3517,9 +3546,9 @@ dependencies = [ [[package]] name = "librocksdb-sys" -version = "0.6.1+6.28.2" +version = "0.8.0+7.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bc587013734dadb7cf23468e531aa120788b87243648be42e2d3a072186291" +checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" dependencies = [ "bindgen", "bzip2-sys", @@ -3559,7 +3588,7 @@ dependencies = [ "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", "rand 0.8.5", - "serde 1.0.137", + "serde 1.0.145", "sha2 0.9.9", "typenum", ] @@ -3618,12 +3647,11 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" dependencies = [ - "serde 1.0.137", - "serde_test", + "serde 1.0.145", ] [[package]] @@ -3637,9 +3665,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg 1.1.0", "scopeguard", @@ -3721,7 +3749,7 @@ dependencies = [ "indexmap", "linked-hash-map", "regex", - "serde 1.0.137", + "serde 1.0.145", "serde_derive", "serde_yaml", ] @@ -3771,9 +3799,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.4" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" dependencies = [ "libc", ] @@ -3806,15 +3834,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eee18ff0c94dec5f2da5faa939b3b40122c9c38ff6d934d0917b5313ddc7b5e4" dependencies = [ "crossbeam-channel", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", "integer-encoding", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "mio 0.7.14", - "serde 1.0.137", + "serde 1.0.145", "strum", "tungstenite 0.16.0", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -3870,9 +3898,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" dependencies = [ "adler", ] @@ -3911,9 +3939,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log 0.4.17", @@ -3982,7 +4010,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" dependencies = [ "digest 0.9.0", - "generic-array 0.14.5", + "generic-array 0.14.6", "multihash-derive", "sha2 0.9.9", "unsigned-varint 0.5.1", @@ -3994,7 +4022,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate 1.2.1", "proc-macro-error", "proc-macro2", "quote", @@ -4013,11 +4041,11 @@ name = "multistream-select" version = "0.10.3" source = "git+https://github.com/heliaxdev/rust-libp2p.git?rev=1abe349c231eb307d3dbe03f3ffffc6cf5e9084d#1abe349c231eb307d3dbe03f3ffffc6cf5e9084d" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", + "bytes 1.2.1", + "futures 0.3.24", "log 0.4.17", - "pin-project 1.0.10", - "smallvec 1.8.0", + "pin-project 1.0.12", + "smallvec 1.10.0", "unsigned-varint 0.7.1", ] @@ -4043,7 +4071,7 @@ dependencies = [ "ibc", "ibc-proto", "ics23", - "itertools 0.10.3", + "itertools 0.10.5", "libsecp256k1 0.7.0", "loupe", "namada_proof_of_stake", @@ -4054,9 +4082,9 @@ dependencies = [ "prost-types 0.9.0", "pwasm-utils", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rust_decimal", - "serde 1.0.137", + "serde 1.0.145", "serde_json", "sha2 0.9.9", "sparse-merkle-tree", @@ -4066,8 +4094,8 @@ dependencies = [ "test-log", "thiserror", "tonic-build", - "tracing 0.1.35", - "tracing-subscriber 0.3.11", + "tracing 0.1.36", + "tracing-subscriber 0.3.15", "wasmer", "wasmer-cache", "wasmer-compiler-singlepass", @@ -4106,10 +4134,10 @@ dependencies = [ "ferveo-common", "file-lock", "flate2", - "futures 0.3.21", + "futures 0.3.24", "git2", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "jsonpath_lib", "libc", "libloading", @@ -4126,14 +4154,14 @@ dependencies = [ "prost 0.9.0", "prost-types 0.9.0", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "rayon", "regex", "reqwest", "rlimit", "rocksdb", "rpassword", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_regex", @@ -4156,9 +4184,9 @@ dependencies = [ "tonic-build", "tower", "tower-abci", - "tracing 0.1.35", + "tracing 0.1.36", "tracing-log", - "tracing-subscriber 0.3.11", + "tracing-subscriber 0.3.15", "websocket", "winapi 0.3.9", ] @@ -4168,8 +4196,8 @@ name = "namada_encoding_spec" version = "0.7.1" dependencies = [ "borsh", - "itertools 0.10.3", - "lazy_static 1.4.0", + "itertools 0.10.5", + "lazy_static", "madato", "namada", ] @@ -4207,7 +4235,7 @@ dependencies = [ "file-serve", "fs_extra", "hex", - "itertools 0.10.3", + "itertools 0.10.5", "libp2p", "namada", "namada_apps", @@ -4221,8 +4249,8 @@ dependencies = [ "tempfile", "test-log", "toml", - "tracing 0.1.35", - "tracing-subscriber 0.3.11", + "tracing 0.1.36", + "tracing-subscriber 0.3.15", ] [[package]] @@ -4230,7 +4258,7 @@ name = "namada_tx_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] @@ -4248,7 +4276,7 @@ name = "namada_vp_prelude" version = "0.7.1" dependencies = [ "namada_vm_env", - "sha2 0.10.2", + "sha2 0.10.6", ] [[package]] @@ -4257,7 +4285,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "libc", "log 0.4.17", "openssl", @@ -4282,9 +4310,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.20.2" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e06129fb611568ef4e868c14b326274959aa70ff7776e9d55323531c374945" +checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" dependencies = [ "bitflags", "cc", @@ -4295,9 +4323,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77d9f3521ea8e0641a153b3cddaf008dcbf26acd4ed739a2517295e0760d12c7" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" dependencies = [ "bitflags", "cc", @@ -4321,9 +4349,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4411,9 +4439,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits 0.2.15", ] @@ -4452,9 +4480,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg 1.1.0", "num-bigint", @@ -4499,12 +4527,6 @@ dependencies = [ "libc", ] -[[package]] -name = "numtoa" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" - [[package]] name = "object" version = "0.28.4" @@ -4517,11 +4539,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" [[package]] name = "opaque-debug" @@ -4537,9 +4568,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.40" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if 1.0.0", @@ -4569,9 +4600,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.74" +version = "0.9.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1" +checksum = "5230151e44c0f05157effb743e8d517472843121cf9243e8b81393edb5acd9ce" dependencies = [ "autocfg 1.1.0", "cc", @@ -4587,7 +4618,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6624905ddd92e460ff0685567539ed1ac985b2dee4c92c7edcd64fce905b00c" dependencies = [ "ct-codecs", - "getrandom 0.2.6", + "getrandom 0.2.7", "subtle 2.4.1", "zeroize", ] @@ -4623,11 +4654,11 @@ dependencies = [ "byteorder", "data-encoding", "multihash", - "percent-encoding 2.1.0", - "serde 1.0.137", + "percent-encoding 2.2.0", + "serde 1.0.145", "static_assertions", "unsigned-varint 0.7.1", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -4666,7 +4697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.7", + "lock_api 0.4.9", "parking_lot_core 0.8.5", ] @@ -4676,7 +4707,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "lock_api 0.4.7", + "lock_api 0.4.9", "parking_lot_core 0.9.3", ] @@ -4704,8 +4735,8 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", + "redox_syscall 0.2.16", + "smallvec 1.10.0", "winapi 0.3.9", ] @@ -4717,16 +4748,16 @@ checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.13", - "smallvec 1.8.0", + "redox_syscall 0.2.16", + "smallvec 1.10.0", "windows-sys", ] [[package]] name = "paste" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" [[package]] name = "pathdiff" @@ -4775,9 +4806,9 @@ checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" [[package]] name = "percent-encoding" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" @@ -4804,33 +4835,71 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ - "fixedbitset 0.4.1", + "fixedbitset 0.4.2", "indexmap", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" +checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" dependencies = [ - "pin-project-internal 0.4.29", + "pin-project-internal 0.4.30", ] [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" dependencies = [ - "pin-project-internal 1.0.10", + "pin-project-internal 1.0.12", ] [[package]] name = "pin-project-internal" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" +checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" dependencies = [ "proc-macro2", "quote", @@ -4839,9 +4908,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", @@ -4874,13 +4943,14 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "polling" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +version = "2.3.0" +source = "git+https://github.com/heliaxdev/polling.git?rev=02a655775282879459a3460e2646b60c005bca2c#02a655775282879459a3460e2646b60c005bca2c" dependencies = [ + "autocfg 1.1.0", "cfg-if 1.0.0", "libc", "log 0.4.17", + "rustversion", "wepoll-ffi", "winapi 0.3.9", ] @@ -4891,7 +4961,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", "universal-hash", ] @@ -4903,7 +4973,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "opaque-debug 0.3.0", "universal-hash", ] @@ -4921,7 +4991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", - "itertools 0.10.3", + "itertools 0.10.5", "predicates-core", ] @@ -4964,10 +5034,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -4998,9 +5069,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" dependencies = [ "unicode-ident", ] @@ -5013,7 +5084,7 @@ dependencies = [ "bit-set", "bitflags", "byteorder", - "lazy_static 1.4.0", + "lazy_static", "num-traits 0.2.15", "quick-error 2.0.1", "rand 0.8.5", @@ -5030,7 +5101,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost-derive 0.7.0", ] @@ -5040,7 +5111,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost-derive 0.9.0", ] @@ -5050,7 +5121,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "heck 0.3.3", "itertools 0.9.0", "log 0.4.17", @@ -5068,10 +5139,10 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "heck 0.3.3", - "itertools 0.10.3", - "lazy_static 1.4.0", + "itertools 0.10.5", + "lazy_static", "log 0.4.17", "multimap", "petgraph 0.6.2", @@ -5102,7 +5173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.3", + "itertools 0.10.5", "proc-macro2", "quote", "syn", @@ -5114,7 +5185,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.7.0", ] @@ -5124,7 +5195,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "prost 0.9.0", ] @@ -5193,9 +5264,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -5214,7 +5285,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift 0.1.1", "winapi 0.3.9", ] @@ -5230,6 +5301,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", + "rand_pcg 0.2.1", ] [[package]] @@ -5240,7 +5312,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5270,7 +5342,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5299,11 +5371,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -5368,6 +5440,15 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xorshift" version = "0.1.1" @@ -5383,7 +5464,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -5406,7 +5487,7 @@ checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.8", + "crossbeam-utils 0.8.12", "num_cpus", ] @@ -5427,30 +5508,21 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] -[[package]] -name = "redox_termios" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" -dependencies = [ - "redox_syscall 0.2.13", -] - [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.6", - "redox_syscall 0.2.13", + "getrandom 0.2.7", + "redox_syscall 0.2.16", "thiserror", ] @@ -5462,14 +5534,14 @@ checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ "log 0.4.17", "rustc-hash", - "smallvec 1.8.0", + "smallvec 1.10.0", ] [[package]] name = "regex" -version = "1.5.6" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -5487,9 +5559,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.26" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -5523,34 +5595,35 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.10" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" dependencies = [ "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.2.1", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-tls", "ipnet", "js-sys", - "lazy_static 1.4.0", "log 0.4.17", "mime 0.3.16", "native-tls", - "percent-encoding 2.1.0", + "once_cell", + "percent-encoding 2.2.0", "pin-project-lite 0.2.9", - "serde 1.0.137", + "serde 1.0.145", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "url 2.2.2", + "tower-service", + "url 2.3.1", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -5606,12 +5679,12 @@ dependencies = [ [[package]] name = "rkyv" -version = "0.7.38" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517a3034eb2b1499714e9d1e49b2367ad567e07639b69776d35e259d9c27cca6" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown 0.12.1", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -5620,9 +5693,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.38" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505c209ee04111a006431abf39696e640838364d67a107c559ababaf6fd8c9dd" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -5640,9 +5713,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "620f4129485ff1a7128d184bc687470c21c7951b64779ebc9cfdad3dcd920290" +checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" dependencies = [ "libc", "librocksdb-sys", @@ -5666,13 +5739,13 @@ checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" [[package]] name = "rust_decimal" -version = "1.24.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ee7337df68898256ad0d4af4aad178210d9e44d2ff900ce44064a97cd86530" +checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c" dependencies = [ "arrayvec 0.7.2", "num-traits 0.2.15", - "serde 1.0.137", + "serde 1.0.145", ] [[package]] @@ -5705,15 +5778,6 @@ dependencies = [ "semver 0.11.0", ] -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver 1.0.10", -] - [[package]] name = "rustls" version = "0.19.1" @@ -5741,9 +5805,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rusty-fork" @@ -5763,16 +5827,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ - "futures 0.3.21", - "pin-project 0.4.29", + "futures 0.3.24", + "pin-project 0.4.30", "static_assertions", ] [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-proc-macro2" @@ -5851,7 +5915,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "windows-sys", ] @@ -5884,7 +5948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", "zeroize", ] @@ -5930,12 +5994,6 @@ dependencies = [ "semver-parser 0.10.2", ] -[[package]] -name = "semver" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" - [[package]] name = "semver-parser" version = "0.7.0" @@ -5959,9 +6017,9 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.137" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] @@ -5972,7 +6030,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "num-traits 0.1.43", "regex", "serde 0.8.23", @@ -5980,18 +6038,18 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ - "serde 1.0.137", + "serde 1.0.145", ] [[package]] name = "serde_derive" -version = "1.0.137" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", @@ -6000,14 +6058,14 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "indexmap", "itoa", "ryu", - "serde 1.0.137", + "serde 1.0.145", ] [[package]] @@ -6017,29 +6075,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8136f1a4ea815d7eac4101cfd0b16dc0cb5e1fe1b8609dfd728058656b7badf" dependencies = [ "regex", - "serde 1.0.137", + "serde 1.0.145", ] [[package]] name = "serde_repr" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "serde_test" -version = "1.0.137" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe196827aea34242c314d2f0dd49ed00a129225e80dda71b0dbf65d54d25628d" -dependencies = [ - "serde 1.0.137", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -6049,7 +6098,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.137", + "serde 1.0.145", ] [[package]] @@ -6060,7 +6109,7 @@ checksum = "ef8099d3df28273c99a1728190c7a9f19d444c941044f64adf986bee7ec53051" dependencies = [ "dtoa", "linked-hash-map", - "serde 1.0.137", + "serde 1.0.145", "yaml-rust", ] @@ -6084,20 +6133,20 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "digest 0.9.0", "opaque-debug 0.3.0", ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", - "digest 0.10.3", + "cpufeatures 0.2.5", + "digest 0.10.5", ] [[package]] @@ -6120,20 +6169,20 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.2", + "cpufeatures 0.2.5", "digest 0.9.0", "opaque-debug 0.3.0", ] [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.2", - "digest 0.10.3", + "cpufeatures 0.2.5", + "digest 0.10.5", ] [[package]] @@ -6154,7 +6203,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", ] [[package]] @@ -6195,7 +6244,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -6204,11 +6253,20 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "smallvec" @@ -6221,9 +6279,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "snow" @@ -6235,7 +6293,7 @@ dependencies = [ "blake2 0.9.2", "chacha20poly1305", "rand 0.8.5", - "rand_core 0.6.3", + "rand_core 0.6.4", "ring", "rustc_version 0.3.3", "sha2 0.9.9", @@ -6256,9 +6314,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi 0.3.9", @@ -6273,7 +6331,7 @@ dependencies = [ "base64 0.12.3", "bytes 0.5.6", "flate2", - "futures 0.3.21", + "futures 0.3.24", "httparse", "log 0.4.17", "rand 0.7.3", @@ -6318,15 +6376,15 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stderrlog" -version = "0.4.3" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e5ee9b90a5452c570a0b0ac1c99ae9498db7e56e33d74366de7f2a7add7f25" +checksum = "af95cb8a5f79db5b2af2a46f44da7594b5adbcbb65cbf87b8da0959bfdd82460" dependencies = [ "atty", "chrono", "log 0.4.17", "termcolor", - "thread_local 0.3.4", + "thread_local", ] [[package]] @@ -6343,18 +6401,18 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.24.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.24.0" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.0", "proc-macro2", @@ -6366,7 +6424,7 @@ dependencies = [ [[package]] name = "subproductdomain" version = "0.1.0" -source = "git+https://github.com/anoma/ferveo#8363c33d1cf79f93ce9fa89d4b5fe998a5a78c26" +source = "git+https://github.com/anoma/ferveo#1022ab2c7ccc689abcc05e5a08df6fb0c2a3fc65" dependencies = [ "anyhow", "ark-ec", @@ -6405,9 +6463,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.96" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" dependencies = [ "proc-macro2", "quote", @@ -6466,7 +6524,7 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.13", + "redox_syscall 0.2.16", "remove_dir_all", "winapi 0.3.9", ] @@ -6477,18 +6535,18 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "async-trait", - "bytes 1.1.0", + "bytes 1.2.1", "ed25519", "ed25519-dalek", "flex-error", - "futures 0.3.21", + "futures 0.3.24", "k256", "num-traits 0.2.15", "once_cell", "prost 0.9.0", "prost-types 0.9.0", "ripemd160", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "serde_json", "serde_repr", @@ -6497,7 +6555,7 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto", - "time 0.3.9", + "time 0.3.14", "zeroize", ] @@ -6507,11 +6565,11 @@ version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ "flex-error", - "serde 1.0.137", + "serde 1.0.145", "serde_json", "tendermint", "toml", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -6521,10 +6579,10 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "derive_more", "flex-error", - "serde 1.0.137", + "serde 1.0.145", "tendermint", "tendermint-rpc", - "time 0.3.9", + "time 0.3.14", ] [[package]] @@ -6532,16 +6590,16 @@ name = "tendermint-proto" version = "0.23.5" source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc37927218374f94ac8e2a19bd35bec9#95c52476bc37927218374f94ac8e2a19bd35bec9" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "flex-error", "num-derive", "num-traits 0.2.15", "prost 0.9.0", "prost-types 0.9.0", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "subtle-encoding", - "time 0.3.9", + "time 0.3.14", ] [[package]] @@ -6551,17 +6609,17 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "async-trait", "async-tungstenite", - "bytes 1.1.0", + "bytes 1.2.1", "flex-error", - "futures 0.3.21", - "getrandom 0.2.6", + "futures 0.3.24", + "getrandom 0.2.7", "http", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-proxy", "hyper-rustls", "peg", - "pin-project 1.0.10", - "serde 1.0.137", + "pin-project 1.0.12", + "serde 1.0.145", "serde_bytes", "serde_json", "subtle-encoding", @@ -6569,10 +6627,10 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", - "time 0.3.9", + "time 0.3.14", "tokio", - "tracing 0.1.35", - "url 2.2.2", + "tracing 0.1.36", + "url 2.3.1", "uuid", "walkdir", ] @@ -6584,22 +6642,12 @@ source = "git+https://github.com/heliaxdev/tendermint-rs?rev=95c52476bc379272183 dependencies = [ "ed25519-dalek", "gumdrop", - "serde 1.0.137", + "serde 1.0.145", "serde_json", "simple-error", "tempfile", "tendermint", - "time 0.3.9", -] - -[[package]] -name = "term_size" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" -dependencies = [ - "libc", - "winapi 0.3.9", + "time 0.3.14", ] [[package]] @@ -6612,15 +6660,16 @@ dependencies = [ ] [[package]] -name = "termion" -version = "1.5.6" +name = "terminfo" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e" +checksum = "76971977e6121664ec1b960d1313aacfa75642adc93b9d4d53b247bd4cb1747e" dependencies = [ - "libc", - "numtoa", - "redox_syscall 0.2.13", - "redox_termios", + "dirs", + "fnv", + "nom 5.1.2", + "phf", + "phf_codegen", ] [[package]] @@ -6631,9 +6680,9 @@ checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "test-log" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4235dbf7ea878b3ef12dea20a59c134b405a66aafc4fc2c7b9935916e289e735" +checksum = "38f0c854faeb68a048f0f2dc410c5ddae3bf83854ef0e4977d58306a5edef50e" dependencies = [ "proc-macro2", "quote", @@ -6646,7 +6695,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "term_size", "unicode-width", ] @@ -6679,16 +6727,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thread_local" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" -dependencies = [ - "lazy_static 0.2.11", - "unreachable", -] - [[package]] name = "thread_local" version = "1.1.4" @@ -6711,9 +6749,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "itoa", "libc", @@ -6736,8 +6774,8 @@ dependencies = [ "ascii", "chunked_transfer", "log 0.4.17", - "time 0.3.9", - "url 2.2.2", + "time 0.3.14", + "url 2.3.1", ] [[package]] @@ -6757,20 +6795,20 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ - "bytes 1.1.0", + "autocfg 1.1.0", + "bytes 1.2.1", "libc", "memchr", - "mio 0.8.3", + "mio 0.8.4", "num_cpus", - "once_cell", "parking_lot 0.12.1", "pin-project-lite 0.2.9", "signal-hook-registry", - "socket2 0.4.4", + "socket2 0.4.7", "tokio-macros", "winapi 0.3.9", ] @@ -6846,7 +6884,7 @@ checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" dependencies = [ "crossbeam-utils 0.7.2", "futures 0.1.31", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "mio 0.6.23", "num_cpus", @@ -6870,9 +6908,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" dependencies = [ "futures-core", "pin-project-lite 0.2.9", @@ -6910,7 +6948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "tokio", "tokio-stream", @@ -6933,7 +6971,7 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-sink", "log 0.4.17", @@ -6943,16 +6981,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-sink", "pin-project-lite 0.2.9", "tokio", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -6961,7 +6999,7 @@ version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ - "serde 1.0.137", + "serde 1.0.145", ] [[package]] @@ -6973,16 +7011,16 @@ dependencies = [ "async-stream", "async-trait", "base64 0.13.0", - "bytes 1.1.0", + "bytes 1.2.1", "futures-core", "futures-util", "h2", "http", "http-body", - "hyper 0.14.19", + "hyper 0.14.20", "hyper-timeout", - "percent-encoding 2.1.0", - "pin-project 1.0.10", + "percent-encoding 2.2.0", + "pin-project 1.0.12", "prost 0.9.0", "prost-derive 0.9.0", "tokio", @@ -6991,7 +7029,7 @@ dependencies = [ "tower", "tower-layer", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", "tracing-futures 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -7009,23 +7047,23 @@ dependencies = [ [[package]] name = "tower" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", "hdrhistogram", "indexmap", - "pin-project 1.0.10", + "pin-project 1.0.12", "pin-project-lite 0.2.9", "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.3", + "tokio-util 0.7.4", "tower-layer", "tower-service", - "tracing 0.1.35", + "tracing 0.1.36", ] [[package]] @@ -7033,9 +7071,9 @@ name = "tower-abci" version = "0.1.0" source = "git+https://github.com/heliaxdev/tower-abci?rev=f6463388fc319b6e210503b43b3aecf6faf6b200#f6463388fc319b6e210503b43b3aecf6faf6b200" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "pin-project 1.0.10", + "bytes 1.2.1", + "futures 0.3.24", + "pin-project 1.0.12", "prost 0.9.0", "tendermint-proto", "tokio", @@ -7063,9 +7101,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" @@ -7080,15 +7118,15 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log 0.4.17", "pin-project-lite 0.2.9", - "tracing-attributes 0.1.21", - "tracing-core 0.1.27", + "tracing-attributes 0.1.22", + "tracing-core 0.1.29", ] [[package]] @@ -7103,9 +7141,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", @@ -7117,14 +7155,14 @@ name = "tracing-core" version = "0.1.22" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", ] [[package]] name = "tracing-core" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -7136,7 +7174,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4d7c0b83d4a500748fa5879461652b361edf5c9d51ede2a2ac03875ca185e24" dependencies = [ - "tracing 0.1.35", + "tracing 0.1.36", "tracing-subscriber 0.2.25", ] @@ -7146,8 +7184,8 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", - "tracing 0.1.35", + "pin-project 1.0.12", + "tracing 0.1.36", ] [[package]] @@ -7165,9 +7203,9 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", - "tracing-core 0.1.27", + "tracing-core 0.1.29", ] [[package]] @@ -7177,25 +7215,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" dependencies = [ "sharded-slab", - "thread_local 1.1.4", - "tracing-core 0.1.27", + "thread_local", + "tracing-core 0.1.29", ] [[package]] name = "tracing-subscriber" -version = "0.3.11" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ "ansi_term", - "lazy_static 1.4.0", "matchers", + "once_cell", "regex", "sharded-slab", - "smallvec 1.8.0", - "thread_local 1.1.4", - "tracing 0.1.35", - "tracing-core 0.1.27", + "smallvec 1.10.0", + "thread_local", + "tracing 0.1.36", + "tracing-core 0.1.29", "tracing-log", ] @@ -7204,7 +7242,7 @@ name = "tracing-tower" version = "0.1.0" source = "git+https://github.com/tokio-rs/tracing/?tag=tracing-0.1.30#df4ba17d857db8ba1b553f7b293ac8ba967a42f8" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "pin-project-lite 0.2.9", "tower-layer", "tower-make", @@ -7234,13 +7272,13 @@ dependencies = [ "futures-util", "idna 0.2.3", "ipnet", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "rand 0.8.5", - "smallvec 1.8.0", + "smallvec 1.10.0", "thiserror", "tinyvec", - "url 2.2.2", + "url 2.3.1", ] [[package]] @@ -7252,12 +7290,12 @@ dependencies = [ "cfg-if 1.0.0", "futures-util", "ipconfig", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", "lru-cache", "parking_lot 0.11.2", "resolv-conf", - "smallvec 1.8.0", + "smallvec 1.10.0", "thiserror", "trust-dns-proto", ] @@ -7276,14 +7314,14 @@ checksum = "8ada8297e8d70872fa9a551d93250a9f407beb9f37ef86494eb20012a2ff7c24" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "http", "httparse", "input_buffer", "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", - "url 2.2.2", + "url 2.3.1", "utf-8", ] @@ -7295,14 +7333,14 @@ checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64 0.13.0", "byteorder", - "bytes 1.1.0", + "bytes 1.2.1", "http", "httparse", "log 0.4.17", "rand 0.8.5", "sha-1 0.9.8", "thiserror", - "url 2.2.2", + "url 2.3.1", "utf-8", ] @@ -7320,15 +7358,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" dependencies = [ "byteorder", "crunchy", @@ -7362,36 +7400,36 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" [[package]] name = "unicode-normalization" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" @@ -7399,19 +7437,10 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.5", + "generic-array 0.14.6", "subtle 2.4.1", ] -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -dependencies = [ - "void", -] - [[package]] name = "unsigned-varint" version = "0.5.1" @@ -7425,7 +7454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", + "bytes 1.2.1", "futures-io", "futures-util", ] @@ -7449,14 +7478,13 @@ dependencies = [ [[package]] name = "url" -version = "2.2.2" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" dependencies = [ "form_urlencoded", - "idna 0.2.3", - "matches", - "percent-encoding 2.1.0", + "idna 0.3.0", + "percent-encoding 2.2.0", ] [[package]] @@ -7477,7 +7505,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.6", + "getrandom 0.2.7", ] [[package]] @@ -7526,26 +7554,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22025f6d8eb903ebf920ea6933b70b1e495be37e2cb4099e62c80454aaf57c39" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "wait-timeout" version = "0.2.0" @@ -7602,9 +7610,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -7612,13 +7620,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" dependencies = [ "bumpalo", - "lazy_static 1.4.0", "log 0.4.17", + "once_cell", "proc-macro2", "quote", "syn", @@ -7627,9 +7635,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -7639,9 +7647,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7649,9 +7657,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", @@ -7662,15 +7670,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" [[package]] name = "wasm-encoder" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" +checksum = "7e7ca71c70a6de5b10968ae4d298e548366d9cd9588176e6ff8866f3c49c96ee" dependencies = [ "leb128", ] @@ -7681,7 +7689,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "js-sys", "parking_lot 0.11.2", "pin-utils", @@ -7737,9 +7745,9 @@ dependencies = [ "enumset", "loupe", "rkyv", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", - "smallvec 1.8.0", + "smallvec 1.10.0", "target-lexicon", "thiserror", "wasmer-types", @@ -7760,9 +7768,9 @@ dependencies = [ "loupe", "more-asserts", "rayon", - "smallvec 1.8.0", + "smallvec 1.10.0", "target-lexicon", - "tracing 0.1.35", + "tracing 0.1.36", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7777,11 +7785,11 @@ dependencies = [ "byteorder", "dynasm", "dynasmrt", - "lazy_static 1.4.0", + "lazy_static", "loupe", "more-asserts", "rayon", - "smallvec 1.8.0", + "smallvec 1.10.0", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -7807,12 +7815,12 @@ checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" dependencies = [ "backtrace", "enumset", - "lazy_static 1.4.0", + "lazy_static", "loupe", "memmap2", "more-asserts", "rustc-demangle", - "serde 1.0.137", + "serde 1.0.145", "serde_bytes", "target-lexicon", "thiserror", @@ -7833,11 +7841,11 @@ dependencies = [ "leb128", "libloading", "loupe", - "object", + "object 0.28.4", "rkyv", - "serde 1.0.137", + "serde 1.0.145", "tempfile", - "tracing 0.1.35", + "tracing 0.1.36", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -7872,7 +7880,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" dependencies = [ - "object", + "object 0.28.4", "thiserror", "wasmer-compiler", "wasmer-types", @@ -7887,7 +7895,7 @@ dependencies = [ "indexmap", "loupe", "rkyv", - "serde 1.0.137", + "serde 1.0.145", "thiserror", ] @@ -7908,7 +7916,7 @@ dependencies = [ "more-asserts", "region", "rkyv", - "serde 1.0.137", + "serde 1.0.145", "thiserror", "wasmer-types", "winapi 0.3.9", @@ -7928,9 +7936,9 @@ checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wast" -version = "42.0.0" +version = "47.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" +checksum = "117ccfc4262e62a28a13f0548a147f19ffe71e8a08be802af23ae4ea0bedad73" dependencies = [ "leb128", "memchr", @@ -7940,28 +7948,27 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.44" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" +checksum = "7aab4e20c60429fbba9670a6cae0fff9520046ba0aa3e6d0b1cd2653bea14898" dependencies = [ "wast", ] [[package]] name = "watchexec" -version = "1.15.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a0fba7f2b9f4240dadf1c2eb4cf15359ffeb19d4f1841cb2d85a7bb4f82755f" +checksum = "c52e0868bc57765fa91593a173323f464855e53b27f779e1110ba0fb4cb6b406" dependencies = [ - "clap 2.34.0", + "clearscreen", + "command-group", "derive_builder", - "embed-resource", - "env_logger", "glob", "globset", - "lazy_static 1.4.0", + "lazy_static", "log 0.4.17", - "nix 0.20.2", + "nix 0.22.3", "notify", "walkdir", "winapi 0.3.9", @@ -7969,9 +7976,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -7998,9 +8005,9 @@ dependencies = [ [[package]] name = "websocket" -version = "0.26.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e2836502b48713d4e391e7e016df529d46e269878fe5d961b15a1fd6417f1a" +checksum = "92aacab060eea423e4036820ddd28f3f9003b2c4d8048cbda985e5a14e18038d" dependencies = [ "bytes 0.4.12", "futures 0.1.31", @@ -8019,9 +8026,9 @@ dependencies = [ [[package]] name = "websocket-base" -version = "0.26.2" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f3fd505ff930da84156389639932955fb09705b3dccd1a3d60c8e7ff62776" +checksum = "49aec794b07318993d1db16156d5a9c750120597a5ee40c6b928d416186cb138" dependencies = [ "base64 0.10.1", "bitflags", @@ -8048,13 +8055,13 @@ dependencies = [ [[package]] name = "which" -version = "4.2.5" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" dependencies = [ "either", - "lazy_static 1.4.0", "libc", + "once_cell", ] [[package]] @@ -8255,7 +8262,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ - "futures 0.3.21", + "futures 0.3.24", "log 0.4.17", "nohash-hasher", "parking_lot 0.11.2", @@ -8265,9 +8272,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.5" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] @@ -8286,9 +8293,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.3+zstd.1.5.2" +version = "2.0.1+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" dependencies = [ "cc", "libc", diff --git a/Cargo.toml b/Cargo.toml index 2bb0d8ad10..3ce019636e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,10 @@ borsh = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4 borsh-derive = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} borsh-schema-derive-internal = {git = "https://github.com/heliaxdev/borsh-rs.git", rev = "cd5223e5103c4f139e0c54cf8259b7ec5ec4073a"} +# The following 3 crates patch a work-around for https://github.com/smol-rs/polling/issues/38 breaking namada tooling build with nightly 2022-05-20 +polling = {git = "https://github.com/heliaxdev/polling.git", rev = "02a655775282879459a3460e2646b60c005bca2c"} +async-io = {git = "https://github.com/heliaxdev/async-io.git", rev = "9285dad39c9a37ecd0dbd498c5ce5b0e65b02489"} +async-process = {git = "https://github.com/heliaxdev/async-process.git", rev = "e42c527e87d937da9e01aaeb563c0b948580dc89"} # borsh = {path = "../borsh-rs/borsh"} # borsh-derive = {path = "../borsh-rs/borsh-derive"} # borsh-derive-internal = {path = "../borsh-rs/borsh-derive-internal"} diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 65d7d84eed..4e76e87241 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -92,7 +92,7 @@ rayon = "=1.5.1" regex = "1.4.5" reqwest = "0.11.4" rlimit = "0.5.4" -rocksdb = {version = "0.18.0", features = ['zstd'], default-features = false} +rocksdb = {version = "0.19.0", features = ['zstd'], default-features = false} rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" diff --git a/apps/src/lib/node/ledger/storage/rocksdb.rs b/apps/src/lib/node/ledger/storage/rocksdb.rs index 71ed305ea5..6735aff187 100644 --- a/apps/src/lib/node/ledger/storage/rocksdb.rs +++ b/apps/src/lib/node/ledger/storage/rocksdb.rs @@ -323,10 +323,14 @@ impl DB for RocksDB { let mut epoch = None; let mut pred_epochs = None; let mut address_gen = None; - for (key, bytes) in self.0.iterator_opt( + for value in self.0.iterator_opt( IteratorMode::From(prefix.as_bytes(), Direction::Forward), read_opts, ) { + let (key, bytes) = match value { + Ok(data) => data, + Err(e) => return Err(Error::DBError(e.into_string())), + }; let path = &String::from_utf8((*key).to_vec()).map_err(|e| { Error::Temporary { error: format!( @@ -837,7 +841,9 @@ impl<'a> Iterator for PersistentPrefixIterator<'a> { /// Returns the next pair and the gas cost fn next(&mut self) -> Option<(String, Vec, u64)> { match self.0.iter.next() { - Some((key, val)) => { + Some(result) => { + let (key, val) = + result.expect("Prefix iterator shouldn't fail"); let key = String::from_utf8(key.to_vec()) .expect("Cannot convert from bytes to key string"); match key.strip_prefix(&self.0.db_prefix) { diff --git a/shared/src/ledger/storage/mockdb.rs b/shared/src/ledger/storage/mockdb.rs index 3cc7e8863c..63bc03a525 100644 --- a/shared/src/ledger/storage/mockdb.rs +++ b/shared/src/ledger/storage/mockdb.rs @@ -447,15 +447,15 @@ pub struct MockIterator { pub type MockPrefixIterator = PrefixIterator; impl Iterator for MockIterator { - type Item = KVBytes; + type Item = Result; fn next(&mut self) -> Option { for (key, val) in &mut self.iter { if key.starts_with(&self.prefix) { - return Some(( + return Some(Ok(( Box::from(key.as_bytes()), Box::from(val.as_slice()), - )); + ))); } } None @@ -468,7 +468,9 @@ impl Iterator for PrefixIterator { /// Returns the next pair and the gas cost fn next(&mut self) -> Option<(String, Vec, u64)> { match self.iter.next() { - Some((key, val)) => { + Some(result) => { + let (key, val) = + result.expect("Prefix iterator shouldn't fail"); let key = String::from_utf8(key.to_vec()) .expect("Cannot convert from bytes to key string"); match key.strip_prefix(&self.db_prefix) { diff --git a/shared/src/ledger/storage/types.rs b/shared/src/ledger/storage/types.rs index c30c3873fc..0f270d4413 100644 --- a/shared/src/ledger/storage/types.rs +++ b/shared/src/ledger/storage/types.rs @@ -45,9 +45,10 @@ pub struct PrefixIterator { impl PrefixIterator { /// Initialize a new prefix iterator - pub fn new(iter: I, db_prefix: String) -> Self + pub fn new(iter: I, db_prefix: String) -> Self where - I: Iterator, + E: std::error::Error, + I: Iterator>, { PrefixIterator { iter, db_prefix } } diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 9df5195b2f..876455fcaa 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1357,7 +1357,7 @@ dependencies = [ [[package]] name = "libsecp256k1" version = "0.7.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", @@ -1373,7 +1373,7 @@ dependencies = [ [[package]] name = "libsecp256k1-core" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-ecmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "libsecp256k1-gen-genmult" version = "0.3.0" -source = "git+https://github.com/brentstone/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" +source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1527,7 +1527,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1576,7 +1576,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1584,7 +1584,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1593,7 +1593,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1611,7 +1611,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1619,7 +1619,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1629,7 +1629,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "namada_wasm_for_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", From e476d046583aeffea5dabe3907d052e80cfe69fe Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 7 Sep 2022 15:07:08 +0200 Subject: [PATCH 372/373] feat: rocksdb use jemalloc --- Cargo.lock | 12 ++++++++++++ apps/Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c2fa1b54da..d631ecb754 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3556,6 +3556,7 @@ dependencies = [ "glob", "libc", "libz-sys", + "tikv-jemalloc-sys", "zstd-sys", ] @@ -6736,6 +6737,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.1+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931e876f91fed0827f863a2d153897790da0b24d882c721a79cb3beb0b903261" +dependencies = [ + "cc", + "fs_extra", + "libc", +] + [[package]] name = "time" version = "0.1.44" diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 4e76e87241..ee8249e340 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -92,7 +92,7 @@ rayon = "=1.5.1" regex = "1.4.5" reqwest = "0.11.4" rlimit = "0.5.4" -rocksdb = {version = "0.19.0", features = ['zstd'], default-features = false} +rocksdb = {version = "0.19.0", features = ['zstd', 'jemalloc'], default-features = false} rpassword = "5.0.1" serde = {version = "1.0.125", features = ["derive"]} serde_bytes = "0.11.5" From 7b82cb85e3a66c26642c90bee185adbb2c3a8184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 3 Oct 2022 15:59:20 +0200 Subject: [PATCH 373/373] changelog: add #452 --- .changelog/unreleased/miscellaneous/452-update-rocksdb.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/452-update-rocksdb.md diff --git a/.changelog/unreleased/miscellaneous/452-update-rocksdb.md b/.changelog/unreleased/miscellaneous/452-update-rocksdb.md new file mode 100644 index 0000000000..83ad52ce87 --- /dev/null +++ b/.changelog/unreleased/miscellaneous/452-update-rocksdb.md @@ -0,0 +1,2 @@ +- Updated rockDB dependency to 0.19.0 and enabled its jemalloc feature. + ([#452](https://github.com/anoma/namada/pull/452)) \ No newline at end of file