From cb806b16c43bb895b75d2a2aac208dd97a3b941a Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 12 May 2022 17:24:30 +0100 Subject: [PATCH 001/394] 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 33b988c487c..551c7abf15a 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 3d80e10fba2..de53848ea0e 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 177092b600e..71f713183be 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 6e174b7b5c8..87b34eb7291 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/394] 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 551c7abf15a..163df3ff629 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/394] 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 de53848ea0e..9e83e2d90e6 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 9b787a32cb7..2ece764e90d 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/394] 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 163df3ff629..1f5d75d2303 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 71f713183be..92998c7f5d5 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 87b34eb7291..1d3d2eb3616 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/394] 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 00000000000..2e5cce09f34 --- /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/394] 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 619817c90f9..9fe2086fa32 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 00000000000..6c2fa15880c --- /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 96103ce897f..3eefb3a3b62 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 20ac0843502..87c31180a16 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 1cd672951e8..6abf4992c65 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/394] 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 2dfaa748948..a12cb2df7b4 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/394] .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 0fed4e87901..6b5d2d3705f 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/394] [ci]: update drone configuration --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 6b5d2d3705f..9618a39ba28 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/394] 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 d131c4c230e..1218d4805b8 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 33b988c487c..6d8b3cd3f23 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 7c31ef43d85..b315ff3bfd2 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/394] 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 00000000000..1e267d6faf3 --- /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/394] [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 42eb0294e31..694cd9ba7f7 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/394] [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 d85ff0b7c00..683f8356ea0 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 0bc8b729542..ab74fe3a192 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 1b839a6825f..337af293581 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 00000000000..e65ede6c079 --- /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 9b787a32cb7..846fa51ae32 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 79c3b4d5b64..5b280491b94 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 60f3d4b1b53..256b86df108 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 f3e4cd4ed22..49420518261 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/394] [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 683f8356ea0..d591f549f20 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 256b86df108..29a3f20931d 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 c8bf57469f7..8ff1d96993e 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/394] [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 d591f549f20..1bb1d35291e 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 ab74fe3a192..18000c24dd4 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 846fa51ae32..0343da34325 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 29a3f20931d..7957849dde6 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 8ff1d96993e..abe65f9d372 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/394] [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 1bb1d35291e..ac9aa02fddd 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 18000c24dd4..fed5d07f470 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/394] [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 ac9aa02fddd..4f0550ea3ed 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/394] [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 fed5d07f470..5831f187a1e 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/394] [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 5831f187a1e..5e93ce33045 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 6ccfc7a33e5..aa66fa95d62 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/394] [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 5e93ce33045..4e9ae7681fb 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 aa66fa95d62..7f50f85fc7b 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/394] [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 4e9ae7681fb..014ded569b9 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/394] [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 014ded569b9..c4a2f4c83ce 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 7f50f85fc7b..300787fba5d 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/394] [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 33643bf0f0f..c4af49cd270 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/394] [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 c4af49cd270..57618fde44d 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/394] 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 8092b5afd8f..633762a23a3 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 57618fde44d..b969cc7b1d2 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 1342029ec0a..7ecc2606b21 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/394] 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 4f0550ea3ed..f7e0e8116c8 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 c4a2f4c83ce..7d6034f874d 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 b969cc7b1d2..ff736560f19 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/394] Revert gitignore --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.gitignore b/.gitignore index 633762a23a3..8092b5afd8f 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/394] [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 f7e0e8116c8..91bca3ce657 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 7d6034f874d..39b3be7d5fc 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 7957849dde6..300b6311c53 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 abe65f9d372..8a8577abdc5 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/394] [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 91bca3ce657..e5d2438ec0c 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/394] 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 39b3be7d5fc..5515a8b7a54 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/394] 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 ff736560f19..2d7f593b959 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/394] 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 e5d2438ec0c..abcfc614082 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 5515a8b7a54..6b1110732f6 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 e65ede6c079..6c88ffd591b 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 300b6311c53..f417c01789c 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 2d7f593b959..b678330823d 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/394] [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 42eb0294e31..1c9fd7b7fe0 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/394] 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 eb732583efc..3172380cfe4 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 1ed0366a7da..0fd163d3ac6 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 b5a60b533d4..56ea1ba6c45 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/394] 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 d85ff0b7c00..6db6877c01f 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 71702c9b8ea..4a7be99d865 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 56ea1ba6c45..4a1479c56d8 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/394] 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 00000000000..d13b82e697f --- /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/394] 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 00000000000..a15f3430253 --- /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/394] 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 123eb036f44..482e62647be 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/394] 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 482e62647be..86ab4753ce1 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/394] 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 1342029ec0a..8e90acad522 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/394] 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 86ab4753ce1..483107bf256 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/394] 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 00000000000..cdf8ae3e32b --- /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 c2aa89e504a..3bb240f7d5a 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/394] 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 cdf8ae3e32b..b55ae3794c0 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 3bb240f7d5a..817d5c2e7b3 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/394] 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 817d5c2e7b3..97e815418cb 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/394] 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 b55ae3794c0..faca7f3c5b8 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/394] 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 faca7f3c5b8..370ea5cadff 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/394] 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 97e815418cb..8a93afc5ba9 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/394] 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 370ea5cadff..56384dedb69 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 8a93afc5ba9..71ea28f0a8a 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/394] 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 56384dedb69..f20db6a3a63 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/394] 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 f20db6a3a63..53af49abedd 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 71ea28f0a8a..60694b4c803 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/394] 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 60694b4c803..e2ed3ac1597 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/394] 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 e2ed3ac1597..93f36222fc3 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/394] 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 53af49abedd..5b47dad3402 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/394] 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 93f36222fc3..b6b1d372764 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/394] 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 b6b1d372764..480e5a7cb96 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/394] 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 5b47dad3402..e953764ab45 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/394] 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 e953764ab45..6a2587dbaa3 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 480e5a7cb96..3a52f3bdb11 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/394] 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 6a2587dbaa3..c9e0e0209cf 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/394] 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 c9e0e0209cf..96de93af603 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/394] 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 96de93af603..1e0114d04e5 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/394] 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 1e0114d04e5..36cd05d1be1 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/394] 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 3a52f3bdb11..64084cc7281 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/394] 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 36cd05d1be1..e73e4c74de3 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/394] 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 64084cc7281..a5815a9b988 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/394] 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 e73e4c74de3..2af3c11f05e 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/394] 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 2af3c11f05e..0bd3c5ef7eb 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/394] 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 0bd3c5ef7eb..bea3f9e9689 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/394] 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 bea3f9e9689..f10a2d06fe4 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/394] 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 a5815a9b988..42c43a2906b 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/394] 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 f10a2d06fe4..8b3776458e4 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 42c43a2906b..e4cdbd491c2 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/394] 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 e4cdbd491c2..a58bf5df95e 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/394] 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 8b3776458e4..1ea7a6dc8a5 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 a58bf5df95e..8c770b4c9e6 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/394] 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 8c770b4c9e6..09c7872103d 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/394] 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 09c7872103d..039a28c185c 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/394] 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 1ea7a6dc8a5..57ee92ff5b4 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 039a28c185c..fa0c4f9ab83 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/394] 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 fa0c4f9ab83..d39ad5b99f1 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/394] 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 00000000000..6d0ee997473 --- /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/394] [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 be86985717f..467defb3a34 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/394] 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 f150e6eac0a..a83d3f54ca5 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 2b0aba06966..28f1cf18257 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/394] 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 00000000000..6696c5946af --- /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/394] 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 31d67389e0d..8eab58b3a11 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 1464b22c8b2..7a4dffac767 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 e4e61a098a5..d875bfc1a71 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 d63acc035f7..241fd034da7 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 a4ea87e21e6..d513da44ef9 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 2f82e6fd8c2..a35324da054 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 ac37e33b89a..f11d57d1b0f 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 c21e2bdd0e0..f59c5ed032b 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 fcda82a6a58..bf9d222920e 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 a0febc6bafc..469104ce65a 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 3c82e7293e0..98f6c7f71c1 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 5513f52089d..125b50d07a1 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 6b594015886..b7185cc1100 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 a7202d20c17..ee2234928ac 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 2507c114609..39f70c17871 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/394] 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 514785c3e0c..ae49503e78e 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 95f73686687..5eef95b34de 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 a3f55518d1b..2225898ff4d 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 21ccb654332..34652ac8256 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 08ba80a6d28..dd864704039 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 d910af7bb8e..6575c74082d 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 370c6d51f80..d5af3bcee10 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 12bd6d45b32..7372012ff5d 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 5335f7d4503..6f1fd96707e 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 d16e80e8d35..c377648b656 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 b81430fc3b3..987077d5cda 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 662291b1d5a..94aec1a00c0 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 9751d3b8b65..493c4855da0 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 03f5aa36c7e..e84674b0593 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 3836a11f97a..ce3cbd592d7 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 c7ba3177b46..6afba892e17 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 347c3bff531..a989338751c 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 f72dcf615ae..2d6134dbb10 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 c175a49a443..001d2eb6b38 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 9e41db598bb..11086633725 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 71ad018a2f3..a643501d9eb 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 99ed7e1ad89..59abc3d1a9b 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 7e7395fd782..bea7ba56af8 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 c38146cd355..136f925df4a 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 38135693917..0a01528b007 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 d87f7a918fd..3c5ffb41f1d 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 183fa457b0a..fa0f4a011f5 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 56e9862eb9b..0b3d19a7427 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 5199e120a67..5c2d2fe4d75 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 6e2198ceb68..e2fed30379e 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 47cdbb36ff2..ec91ff5b146 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 70e753e8131..b276dd409ee 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 feb0e88324c..5da19c0758e 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 f72044b80a9..cc0c45cf8c2 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 a83d3f54ca5..c453c13cf4e 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 28f1cf18257..b7e4c60e9c9 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/394] make: remove "*-abci-plus-plus" --- Makefile | 93 ++------------------------------------------------------ 1 file changed, 2 insertions(+), 91 deletions(-) diff --git a/Makefile b/Makefile index ea42642e83b..3474f6c8e6a 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/394] 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 11086633725..f12b16d9eb5 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 59abc3d1a9b..f52459741d2 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/394] 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 c453c13cf4e..552e675e676 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/394] 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 8ad0498304b..8e74d3f8511 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/394] 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 6afba892e17..5db7e8b38df 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 2d6134dbb10..d93ec23c565 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 f12b16d9eb5..ac09f8863aa 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 028d46470cc..fc87bc8d51a 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/394] 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 5db7e8b38df..1f758a2f2e8 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 001d2eb6b38..7c15180d2ce 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 ac09f8863aa..25375244ba6 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 f52459741d2..ffe90c1d611 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/394] 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 e84674b0593..f71e73ea748 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/394] [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 1495d6f6f54..5d670022b79 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/394] 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 fdc4671ed22..551bae0e61a 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/394] 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 f9c14142721..6db34248ca3 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 7a4dffac767..71de7734469 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 95454d3d2a1..2a1b52e238f 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/394] 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 34652ac8256..44dcd81b78b 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 5fec7dca988..a79450139e9 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 2a1b52e238f..70536e0a295 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/394] 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 00000000000..d13b82e697f --- /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/394] [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 5eef95b34de..ba503b9a955 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/394] 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 ba503b9a955..68b1c65050b 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/394] 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 68b1c65050b..8149817d64b 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 f47ec42696b..22b95b7c30e 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/394] 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 22b95b7c30e..e873db19ed3 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/394] 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 9807516a930..2ee8ba27ecf 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 8149817d64b..8703abedd40 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/394] 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 2ee8ba27ecf..c080f23cd88 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 8703abedd40..07d7083440b 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/394] 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 e873db19ed3..f47ec42696b 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/394] 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 00000000000..4a2903f6f92 --- /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/394] new Cargo.lock after rebase --- Cargo.lock | 242 +---------------------------------------------------- 1 file changed, 1 insertion(+), 241 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6db34248ca3..dd5934d171c 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/394] 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 c080f23cd88..83543326292 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 07d7083440b..bc990380a71 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/394] 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 83543326292..e32564db7d2 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 bc990380a71..a67d00e6c4c 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/394] 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 f9c14142721..cdcd3e61326 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 241fd034da7..86a96aa743d 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 bf9d222920e..56d188bd092 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 98f6c7f71c1..6132ba77363 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 b7185cc1100..e6f814b267e 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 39f70c17871..162ff406530 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/394] 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 12e5093bd25..2bc796f10c9 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 a1343cd7e52..f27b6b36c13 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/394] 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 2bc796f10c9..48db89005a3 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 f27b6b36c13..e7571bb0506 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/394] 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 e7571bb0506..9deccf75a92 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/394] [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 5d670022b79..91c2317ccfa 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/394] 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 00000000000..27cb40bf55b --- /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/394] 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 cdcd3e61326..e43983c3df2 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 86a96aa743d..3b9153095c7 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 27e7c29b891..1986799142f 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 48db89005a3..4f3f9f02d77 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 9deccf75a92..6a95d2eb2d5 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 00000000000..a7efa973fc0 --- /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 56d188bd092..c7734c35873 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 6132ba77363..cc9146fd62b 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 e6f814b267e..8f2ed44ca55 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 162ff406530..96927ae10c8 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/394] 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 5eef95b34de..127673b9f35 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/394] 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 9807516a930..3889489956f 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 6f1fd96707e..8022a4a27f5 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 c377648b656..3082b21c44a 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 5fec7dca988..7b0afd899d6 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 6ecb396004f..49fd2bbeafa 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 95454d3d2a1..cabaa6eb493 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/394] 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 4f3f9f02d77..d8f3765acd2 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 a7efa973fc0..c12d6152cca 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/394] 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 c12d6152cca..48d4b61defc 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/394] 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 127673b9f35..0c5ba692dbc 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 8022a4a27f5..92ec1dc77d1 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 cabaa6eb493..63e4f0d4b65 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 d8f3765acd2..dbcf9fe04c9 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 6a95d2eb2d5..f90cc4aa2e4 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 48d4b61defc..572831d97cc 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/394] 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 3082b21c44a..819243799f2 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/394] 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 572831d97cc..a91f521bb1a 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/394] 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 1986799142f..2543f0c0563 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 f90cc4aa2e4..9ce1b03838d 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/394] 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 819243799f2..f4071b8d85d 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/394] 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 f4071b8d85d..48cdfce7be9 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/394] 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 0c5ba692dbc..837895d8fd7 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 92ec1dc77d1..6404e095555 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/394] 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 48cdfce7be9..96967617c21 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/394] 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 2543f0c0563..e536a7171d7 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 9ce1b03838d..a1323a4bc69 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 a91f521bb1a..33482cafc28 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/394] 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 837895d8fd7..f546860cfde 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/394] 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 6404e095555..74d9d9dab05 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 96967617c21..637cbd3e558 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 49fd2bbeafa..4a6b4a46799 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 63e4f0d4b65..70af3814263 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/394] 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 f546860cfde..8b0360e1a09 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 74d9d9dab05..f6d79d2b557 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 70af3814263..d60af1be967 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/394] 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 637cbd3e558..5dde08ff76d 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/394] 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 33482cafc28..a91f521bb1a 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/394] 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 e536a7171d7..3cdec73bb96 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/394] 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 5dde08ff76d..044639bd648 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/394] 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 044639bd648..4bda774614f 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/394] 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 8b0360e1a09..f546860cfde 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 f6d79d2b557..6404e095555 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/394] 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 4bda774614f..61bc45ad9b0 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/394] 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 61bc45ad9b0..4b4b5b2af04 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/394] 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 a1323a4bc69..c88fe85ffa9 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 a91f521bb1a..af70f44b9ae 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/394] 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 552e675e676..edd171f6aa3 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/394] 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 d60af1be967..76a20534193 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/394] 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 7b0afd899d6..5bdf8e62611 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 af70f44b9ae..3aece8051f1 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 edd171f6aa3..62b96f70bc8 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/394] 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 3aece8051f1..677fc3c4b38 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/394] 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 4b4b5b2af04..5aebd1d26e4 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/394] 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 5aebd1d26e4..4b8f5d6f07f 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 5bdf8e62611..b5c68dd2c1d 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/394] 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 136f925df4a..25dd49ba632 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/394] 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 62b96f70bc8..22eb4f4ac57 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/394] 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 c88fe85ffa9..1e126673469 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/394] 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 d04eeff9abd..8d5d9ff2349 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 5eef95b34de..ec716a13859 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 c377648b656..322976c728f 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 b6cb424457d..cc95499bc6b 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/394] 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 322976c728f..ff9f9a526a5 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/394] `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 ff9f9a526a5..697387fd15b 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/394] 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 00000000000..20ae41a073d --- /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/394] 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 d04eeff9abd..bca54f2c6ee 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 407a6b7378d..1597b5fbc15 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 9807516a930..7eb879f96ff 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 5eef95b34de..45e4b95b33c 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 8189b633bfc..dd911cc8d39 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 f47ec42696b..56965d72efa 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 c377648b656..a72cb4b20e5 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 5bd3dc803f2..067669aec8f 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 d93ec23c565..58e87f5aea6 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 5fec7dca988..df707dfb608 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 95454d3d2a1..5d16552c0f1 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 552e675e676..a39a06344d4 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 5c0a52c30bf..c53726d5e76 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/394] 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 00000000000..3e7f9eb996c --- /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/394] 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 e32564db7d2..15d0ef2d038 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/394] 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 677fc3c4b38..18d74dd5e85 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/394] 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 1e126673469..a287e2f8e3f 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/394] 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 e43983c3df2..577fd171e27 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 3b9153095c7..8db53e88918 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/394] 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 a287e2f8e3f..d8ccbb5a64b 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 c7734c35873..69f39c3149c 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 cc9146fd62b..2a11bdbe7ea 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 8f2ed44ca55..bf93c417bc0 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 96927ae10c8..1184027373d 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/394] [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 91c2317ccfa..51e1ae8ed60 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/394] 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 51e1ae8ed60..e1b0bf27cc4 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/394] 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 00000000000..3c61ceb518d --- /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/394] 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 1c521e75157..0f1f43189e0 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/394] 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 00000000000..417e0f8af80 --- /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/394] 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 1f758a2f2e8..592b5d6cf80 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 a989338751c..a86681fbb5c 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 d93ec23c565..16690c9e176 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 a643501d9eb..d50dd2405e1 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/394] 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 16690c9e176..b4143cb9700 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/394] 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 00000000000..97297a93e13 --- /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/394] 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 d93ec23c565..3d64a8cb58e 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/394] 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 6f1fd96707e..52df27ce31e 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 c377648b656..89119ead791 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 136f925df4a..ffaca7dd374 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 a1343cd7e52..d8e781e0b25 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/394] 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 a137eb8a911..7fadf6bff17 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 cfe1126b88f..366ce489b50 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 8e4bba42239..4ec53dafa4a 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/394] 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 be1844c6cd8..83878f69653 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/394] 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 b4143cb9700..8ada136300f 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 d8e781e0b25..59dd6b2cd5a 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/394] 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 8ada136300f..be3ac0ba594 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/394] 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 577fd171e27..ef6dac4c3ff 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 7a4dffac767..a07918be133 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 8db53e88918..9ea1eed52fe 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 69f39c3149c..b49df82fa92 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 2a11bdbe7ea..b1e4fbc04ab 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 bf93c417bc0..52324468f1c 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 1184027373d..c2017046d1d 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/394] 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 3d64a8cb58e..eb47160f9c2 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/394] 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 d8ccbb5a64b..666cc3fb5e1 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/394] 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 6404e095555..cbb4c5b2a78 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 4a6b4a46799..72f719d1e45 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/394] 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 cbb4c5b2a78..69c8c967bf8 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 4b8f5d6f07f..9043a14db73 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 b5c68dd2c1d..b3048ef97d8 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/394] 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 a07918be133..9f9b65ffd5d 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 9ea1eed52fe..a4736558a23 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 18d74dd5e85..e6607eda846 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 b49df82fa92..d41c7c2f4fc 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 b1e4fbc04ab..7fafa8a32cb 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 52324468f1c..65deccc0dd1 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 c2017046d1d..9df5195b2f1 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/394] 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 69c8c967bf8..1d41ebbc77a 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/394] 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 e1b0bf27cc4..7255ff1b23b 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/394] 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 a4736558a23..b7ba23d542f 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 e6607eda846..99bcbb3f678 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/394] 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 89119ead791..e22d716a6bc 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 552e675e676..efb763eff89 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/394] 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 e22d716a6bc..f6cdaaf9c26 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 a41384c60affb0e8125ef4962c748618333f42bb Mon Sep 17 00:00:00 2001 From: Alberto Centelles Date: Mon, 15 Aug 2022 17:51:42 +0100 Subject: [PATCH 183/394] Fix links --- README.md | 5 ++--- documentation/README.md | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 939653c5966..30a65890270 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,6 @@ 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/) ## Warning @@ -38,10 +37,10 @@ make install After installation, the main `anoma` executable will be available on path. -To find how to use it, check out the [User Guide section of the docs](https://docs.anoma.net/user-guide/). +To find how to use it, check out the [User Guide section of the docs](https://docs.namada.net/user-guide/index.html). For more detailed instructions and more install options, see the [Install -section](https://docs.anoma.net/user-guide/install.html) of the User +section](https://docs.namada.net/user-guide/install.html) of the User Guide. ## ⚙️ Development diff --git a/documentation/README.md b/documentation/README.md index a740b8d3878..99dd836220e 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -2,7 +2,6 @@ - `docs` contains user and operator documentation. -- `dev` contains developer documentation for building on top of Namada. - `spec` contains the specifications for Namada. From c1aca2d2f139021cc85b383a539b430ebc1175fc Mon Sep 17 00:00:00 2001 From: Alberto Centelles Date: Mon, 15 Aug 2022 18:49:36 +0100 Subject: [PATCH 184/394] Change anoma to namada in install docs --- documentation/docs/src/user-guide/install.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/src/user-guide/install.md b/documentation/docs/src/user-guide/install.md index ee3ce622dff..dcf6966cb4e 100644 --- a/documentation/docs/src/user-guide/install.md +++ b/documentation/docs/src/user-guide/install.md @@ -56,8 +56,8 @@ During internal and private testnets, checkout the latest testnet branch using ` ``` ```shell -git clone https://github.com/anoma/anoma.git -cd anoma +git clone https://github.com/anoma/namada.git +cd namada make install ``` From 6b3a98f886fe7a34a12758ad30a36c6a86b3e762 Mon Sep 17 00:00:00 2001 From: Alberto Centelles Date: Tue, 16 Aug 2022 13:21:17 +0100 Subject: [PATCH 185/394] Add dev docs back --- README.md | 5 +++-- documentation/README.md | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 30a65890270..939653c5966 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ 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/) ## Warning @@ -37,10 +38,10 @@ make install After installation, the main `anoma` executable will be available on path. -To find how to use it, check out the [User Guide section of the docs](https://docs.namada.net/user-guide/index.html). +To find how to use it, check out the [User Guide section of the docs](https://docs.anoma.net/user-guide/). For more detailed instructions and more install options, see the [Install -section](https://docs.namada.net/user-guide/install.html) of the User +section](https://docs.anoma.net/user-guide/install.html) of the User Guide. ## ⚙️ Development diff --git a/documentation/README.md b/documentation/README.md index 99dd836220e..a740b8d3878 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -2,6 +2,7 @@ - `docs` contains user and operator documentation. +- `dev` contains developer documentation for building on top of Namada. - `spec` contains the specifications for Namada. 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 186/394] 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 ac9f89c31ea..ee9cd547042 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 187/394] 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 00000000000..ca42bc57ef7 --- /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 facdeeb7b38fecc0a73657cc6e62af977f1ccad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 2 Aug 2022 17:52:24 +0200 Subject: [PATCH 188/394] re-add dev docs from cd3886453ac60655622805e1fecf35f5eceb8c5e --- documentation/dev/.gitignore | 1 + documentation/dev/Makefile | 16 + documentation/dev/README.md | 11 + documentation/dev/assets/custom.css | 10 + documentation/dev/assets/mdbook-admonish.css | 316 +++ documentation/dev/assets/mermaid-init.js | 1 + documentation/dev/assets/mermaid.min.js | 32 + documentation/dev/book.toml | 32 + documentation/dev/src/README.md | 40 + documentation/dev/src/SUMMARY.md | 63 + documentation/dev/src/archive/README.md | 3 + .../dev/src/archive/domain-name-addresses.md | 21 + documentation/dev/src/explore/README.md | 5 + .../dev/src/explore/design/README.md | 3 + .../dev/src/explore/design/actors.md | 41 + .../src/explore/design/crypto-primitives.md | 7 + documentation/dev/src/explore/design/dkg.md | 2 + .../dev/src/explore/design/glossary.md | 17 + .../dev/src/explore/design/gossip.md | 17 + .../explore/design/gossip_process.excalidraw | 966 ++++++++ .../dev/src/explore/design/gossip_process.svg | 16 + .../design/intent_gossip/example.excalidraw | 2202 +++++++++++++++++ .../explore/design/intent_gossip/example.svg | 16 + .../design/intent_gossip/fungible_token.md | 35 + .../intent_gossip/gossip_process.excalidraw | 966 ++++++++ .../design/intent_gossip/gossip_process.svg | 16 + .../explore/design/intent_gossip/incentive.md | 9 + .../explore/design/intent_gossip/intent.md | 28 + .../design/intent_gossip/intent_gossip.md | 51 + .../intent_life_cycle.excalidraw | 1686 +++++++++++++ .../intent_gossip/intent_life_cycle.svg | 16 + .../design/intent_gossip/matchmaker.md | 80 + .../intent_gossip/matchmaker_graph.excalidraw | 1648 ++++++++++++ .../design/intent_gossip/matchmaker_graph.png | Bin 0 -> 156552 bytes .../design/intent_gossip/matchmaker_graph.svg | 16 + .../matchmaker_process.excalidraw | 1883 ++++++++++++++ .../intent_gossip/matchmaker_process.svg | 16 + .../src/explore/design/intent_gossip/topic.md | 11 + .../dev/src/explore/design/ledger.md | 80 + .../dev/src/explore/design/ledger/accounts.md | 54 + .../dev/src/explore/design/ledger/epochs.md | 15 + .../explore/design/ledger/fractal-scaling.md | 5 + .../explore/design/ledger/front-running.md | 7 + .../src/explore/design/ledger/governance.md | 104 + .../design/ledger/ledger_threads.excalidraw | 1340 ++++++++++ .../explore/design/ledger/ledger_threads.svg | 16 + .../src/explore/design/ledger/parameters.md | 13 + .../explore/design/ledger/pos-integration.md | 245 ++ .../dev/src/explore/design/ledger/storage.md | 146 ++ .../design/ledger/storage/data-schema.md | 93 + .../dev/src/explore/design/ledger/tx.md | 88 + .../dev/src/explore/design/ledger/vp.md | 53 + .../dev/src/explore/design/ledger/wasm-vm.md | 125 + .../wasm-vm/storage-write-log.excalidraw | 2194 ++++++++++++++++ .../ledger/wasm-vm/storage-write-log.svg | 16 + .../dev/src/explore/design/overview.md | 10 + .../explore/design/overview/crates.excalidraw | 1560 ++++++++++++ .../src/explore/design/overview/crates.svg | 16 + documentation/dev/src/explore/design/pos.md | 282 +++ .../dev/src/explore/design/summary.png | Bin 0 -> 149153 bytes .../design/testnet-launch-procedure/README.md | 30 + .../dev/src/explore/design/testnet-setup.md | 39 + .../dev/src/explore/design/upgrade-system.md | 5 + .../dev/src/explore/libraries/README.md | 3 + .../dev/src/explore/libraries/async.md | 16 + .../dev/src/explore/libraries/cli.md | 19 + documentation/dev/src/explore/libraries/db.md | 95 + .../dev/src/explore/libraries/errors.md | 36 + .../dev/src/explore/libraries/logging.md | 29 + .../dev/src/explore/libraries/network.md | 22 + .../dev/src/explore/libraries/packaging.md | 27 + .../src/explore/libraries/serialization.md | 81 + .../dev/src/explore/libraries/wasm.md | 40 + .../dev/src/explore/prototypes/README.md | 23 + .../dev/src/explore/prototypes/base-ledger.md | 112 + .../src/explore/prototypes/gossip-layer.md | 61 + .../dev/src/explore/resources/README.md | 11 + .../dev/src/explore/resources/ide.md | 133 + documentation/dev/src/rustdoc-logo.png | Bin 0 -> 5188 bytes documentation/dev/src/specs/README.md | 7 + documentation/dev/src/specs/crypto.md | 16 + documentation/dev/src/specs/encoding.md | 48 + .../dev/src/specs/encoding/.gitignore | 1 + documentation/dev/src/specs/ledger.md | 284 +++ .../src/specs/ledger/default-transactions.md | 57 + .../ledger/default-validity-predicates.md | 7 + documentation/dev/src/specs/ledger/rpc.md | 45 + documentation/dev/src/specs/overview.md | 19 + documentation/dev/theme/favicon.png | Bin 0 -> 5037 bytes documentation/dev/theme/favicon.svg | 6 + 90 files changed, 18033 insertions(+) create mode 100644 documentation/dev/.gitignore create mode 100644 documentation/dev/Makefile create mode 100644 documentation/dev/README.md create mode 100644 documentation/dev/assets/custom.css create mode 100644 documentation/dev/assets/mdbook-admonish.css create mode 100644 documentation/dev/assets/mermaid-init.js create mode 100644 documentation/dev/assets/mermaid.min.js create mode 100644 documentation/dev/book.toml create mode 100644 documentation/dev/src/README.md create mode 100644 documentation/dev/src/SUMMARY.md create mode 100644 documentation/dev/src/archive/README.md create mode 100644 documentation/dev/src/archive/domain-name-addresses.md create mode 100644 documentation/dev/src/explore/README.md create mode 100644 documentation/dev/src/explore/design/README.md create mode 100644 documentation/dev/src/explore/design/actors.md create mode 100644 documentation/dev/src/explore/design/crypto-primitives.md create mode 100644 documentation/dev/src/explore/design/dkg.md create mode 100644 documentation/dev/src/explore/design/glossary.md create mode 100644 documentation/dev/src/explore/design/gossip.md create mode 100644 documentation/dev/src/explore/design/gossip_process.excalidraw create mode 100644 documentation/dev/src/explore/design/gossip_process.svg create mode 100644 documentation/dev/src/explore/design/intent_gossip/example.excalidraw create mode 100644 documentation/dev/src/explore/design/intent_gossip/example.svg create mode 100644 documentation/dev/src/explore/design/intent_gossip/fungible_token.md create mode 100644 documentation/dev/src/explore/design/intent_gossip/gossip_process.excalidraw create mode 100644 documentation/dev/src/explore/design/intent_gossip/gossip_process.svg create mode 100644 documentation/dev/src/explore/design/intent_gossip/incentive.md create mode 100644 documentation/dev/src/explore/design/intent_gossip/intent.md create mode 100644 documentation/dev/src/explore/design/intent_gossip/intent_gossip.md create mode 100644 documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.excalidraw create mode 100644 documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.svg create mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker.md create mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.excalidraw create mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.png create mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.svg create mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_process.excalidraw create mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_process.svg create mode 100644 documentation/dev/src/explore/design/intent_gossip/topic.md create mode 100644 documentation/dev/src/explore/design/ledger.md create mode 100644 documentation/dev/src/explore/design/ledger/accounts.md create mode 100644 documentation/dev/src/explore/design/ledger/epochs.md create mode 100644 documentation/dev/src/explore/design/ledger/fractal-scaling.md create mode 100644 documentation/dev/src/explore/design/ledger/front-running.md create mode 100644 documentation/dev/src/explore/design/ledger/governance.md create mode 100644 documentation/dev/src/explore/design/ledger/ledger_threads.excalidraw create mode 100644 documentation/dev/src/explore/design/ledger/ledger_threads.svg create mode 100644 documentation/dev/src/explore/design/ledger/parameters.md create mode 100644 documentation/dev/src/explore/design/ledger/pos-integration.md create mode 100644 documentation/dev/src/explore/design/ledger/storage.md create mode 100644 documentation/dev/src/explore/design/ledger/storage/data-schema.md create mode 100644 documentation/dev/src/explore/design/ledger/tx.md create mode 100644 documentation/dev/src/explore/design/ledger/vp.md create mode 100644 documentation/dev/src/explore/design/ledger/wasm-vm.md create mode 100644 documentation/dev/src/explore/design/ledger/wasm-vm/storage-write-log.excalidraw create mode 100644 documentation/dev/src/explore/design/ledger/wasm-vm/storage-write-log.svg create mode 100644 documentation/dev/src/explore/design/overview.md create mode 100644 documentation/dev/src/explore/design/overview/crates.excalidraw create mode 100644 documentation/dev/src/explore/design/overview/crates.svg create mode 100644 documentation/dev/src/explore/design/pos.md create mode 100644 documentation/dev/src/explore/design/summary.png create mode 100644 documentation/dev/src/explore/design/testnet-launch-procedure/README.md create mode 100644 documentation/dev/src/explore/design/testnet-setup.md create mode 100644 documentation/dev/src/explore/design/upgrade-system.md create mode 100644 documentation/dev/src/explore/libraries/README.md create mode 100644 documentation/dev/src/explore/libraries/async.md create mode 100644 documentation/dev/src/explore/libraries/cli.md create mode 100644 documentation/dev/src/explore/libraries/db.md create mode 100644 documentation/dev/src/explore/libraries/errors.md create mode 100644 documentation/dev/src/explore/libraries/logging.md create mode 100644 documentation/dev/src/explore/libraries/network.md create mode 100644 documentation/dev/src/explore/libraries/packaging.md create mode 100644 documentation/dev/src/explore/libraries/serialization.md create mode 100644 documentation/dev/src/explore/libraries/wasm.md create mode 100644 documentation/dev/src/explore/prototypes/README.md create mode 100644 documentation/dev/src/explore/prototypes/base-ledger.md create mode 100644 documentation/dev/src/explore/prototypes/gossip-layer.md create mode 100644 documentation/dev/src/explore/resources/README.md create mode 100644 documentation/dev/src/explore/resources/ide.md create mode 100644 documentation/dev/src/rustdoc-logo.png create mode 100644 documentation/dev/src/specs/README.md create mode 100644 documentation/dev/src/specs/crypto.md create mode 100644 documentation/dev/src/specs/encoding.md create mode 100644 documentation/dev/src/specs/encoding/.gitignore create mode 100644 documentation/dev/src/specs/ledger.md create mode 100644 documentation/dev/src/specs/ledger/default-transactions.md create mode 100644 documentation/dev/src/specs/ledger/default-validity-predicates.md create mode 100644 documentation/dev/src/specs/ledger/rpc.md create mode 100644 documentation/dev/src/specs/overview.md create mode 100644 documentation/dev/theme/favicon.png create mode 100644 documentation/dev/theme/favicon.svg diff --git a/documentation/dev/.gitignore b/documentation/dev/.gitignore new file mode 100644 index 00000000000..3006b271da6 --- /dev/null +++ b/documentation/dev/.gitignore @@ -0,0 +1 @@ +book/ diff --git a/documentation/dev/Makefile b/documentation/dev/Makefile new file mode 100644 index 00000000000..804a2f00d8f --- /dev/null +++ b/documentation/dev/Makefile @@ -0,0 +1,16 @@ +cargo = $(env) cargo + +build: + mdbook build + +serve: + mdbook serve --open + +dev-deps: + $(cargo) install mdbook + $(cargo) install mdbook-mermaid + $(cargo) install mdbook-linkcheck + $(cargo) install mdbook-open-on-gh + $(cargo) install mdbook-admonish + +.PHONY: build serve diff --git a/documentation/dev/README.md b/documentation/dev/README.md new file mode 100644 index 00000000000..99d845411ce --- /dev/null +++ b/documentation/dev/README.md @@ -0,0 +1,11 @@ +See the [Introduction](./src/). + +In short: +- `make dev-deps` install dependencies +- `make serve` open the rendered mdBook in your default browser + +Using Nix: + +```bash +nix develop ..#anoma-docs -c make serve +``` diff --git a/documentation/dev/assets/custom.css b/documentation/dev/assets/custom.css new file mode 100644 index 00000000000..cf7a00c8701 --- /dev/null +++ b/documentation/dev/assets/custom.css @@ -0,0 +1,10 @@ +pre.mermaid { + background: white; +} + +footer { + font-size: 0.8em; + text-align: center; + border-top: 1px solid black; + padding: 10px 0; +} \ No newline at end of file diff --git a/documentation/dev/assets/mdbook-admonish.css b/documentation/dev/assets/mdbook-admonish.css new file mode 100644 index 00000000000..5d83c334d6b --- /dev/null +++ b/documentation/dev/assets/mdbook-admonish.css @@ -0,0 +1,316 @@ +:root { + --md-admonition-icon--note: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--abstract: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--info: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--tip: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--success: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--question: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--warning: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--failure: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--danger: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--bug: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--example: + url("data:image/svg+xml;charset=utf-8,"); + --md-admonition-icon--quote: + url("data:image/svg+xml;charset=utf-8,"); +} + +:is(.admonition) { + display: flow-root; + margin: 1.5625em 0; + padding: 0 1.2rem; + color: var(--fg); + page-break-inside: avoid; + background-color: var(--bg); + border: 0 solid black; + border-inline-start-width: 0.4rem; + border-radius: 0.2rem; + box-shadow: 0 0.2rem 1rem rgba(0, 0, 0, 0.05), 0 0 0.1rem rgba(0, 0, 0, 0.1); +} +@media print { + :is(.admonition) { + box-shadow: none; + } +} +:is(.admonition) > * { + box-sizing: border-box; +} +:is(.admonition) :is(.admonition) { + margin-top: 1em; + margin-bottom: 1em; +} +:is(.admonition) > .tabbed-set:only-child { + margin-top: 0; +} +html :is(.admonition) > :last-child { + margin-bottom: 1.2rem; +} + +a.admonition-anchor-link:link, a.admonition-anchor-link:visited { + color: var(--fg); +} +a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { + text-decoration: none; +} + +:is(.admonition-title, summary) { + position: relative; + margin-block: 0; + margin-inline: -1.6rem -1.2rem; + padding-block: 0.8rem; + padding-inline: 4.4rem 1.2rem; + font-weight: 700; + background-color: rgba(68, 138, 255, 0.1); + display: flex; +} +:is(.admonition-title, summary) p { + margin: 0; +} +html :is(.admonition-title, summary):last-child { + margin-bottom: 0; +} +:is(.admonition-title, summary)::before { + position: absolute; + top: 0.625em; + inset-inline-start: 1.6rem; + width: 2rem; + height: 2rem; + background-color: #448aff; + mask-image: url('data:image/svg+xml;charset=utf-8,'); + -webkit-mask-image: url('data:image/svg+xml;charset=utf-8,'); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; +} + +:is(.admonition):is(.note) { + border-color: #448aff; +} + +:is(.note) > :is(.admonition-title, summary) { + background-color: rgba(68, 138, 255, 0.1); +} +:is(.note) > :is(.admonition-title, summary)::before { + background-color: #448aff; + mask-image: var(--md-admonition-icon--note); + -webkit-mask-image: var(--md-admonition-icon--note); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.abstract, .summary, .tldr) { + border-color: #00b0ff; +} + +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary) { + background-color: rgba(0, 176, 255, 0.1); +} +:is(.abstract, .summary, .tldr) > :is(.admonition-title, summary)::before { + background-color: #00b0ff; + mask-image: var(--md-admonition-icon--abstract); + -webkit-mask-image: var(--md-admonition-icon--abstract); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.info, .todo) { + border-color: #00b8d4; +} + +:is(.info, .todo) > :is(.admonition-title, summary) { + background-color: rgba(0, 184, 212, 0.1); +} +:is(.info, .todo) > :is(.admonition-title, summary)::before { + background-color: #00b8d4; + mask-image: var(--md-admonition-icon--info); + -webkit-mask-image: var(--md-admonition-icon--info); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.tip, .hint, .important) { + border-color: #00bfa5; +} + +:is(.tip, .hint, .important) > :is(.admonition-title, summary) { + background-color: rgba(0, 191, 165, 0.1); +} +:is(.tip, .hint, .important) > :is(.admonition-title, summary)::before { + background-color: #00bfa5; + mask-image: var(--md-admonition-icon--tip); + -webkit-mask-image: var(--md-admonition-icon--tip); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.success, .check, .done) { + border-color: #00c853; +} + +:is(.success, .check, .done) > :is(.admonition-title, summary) { + background-color: rgba(0, 200, 83, 0.1); +} +:is(.success, .check, .done) > :is(.admonition-title, summary)::before { + background-color: #00c853; + mask-image: var(--md-admonition-icon--success); + -webkit-mask-image: var(--md-admonition-icon--success); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.question, .help, .faq) { + border-color: #64dd17; +} + +:is(.question, .help, .faq) > :is(.admonition-title, summary) { + background-color: rgba(100, 221, 23, 0.1); +} +:is(.question, .help, .faq) > :is(.admonition-title, summary)::before { + background-color: #64dd17; + mask-image: var(--md-admonition-icon--question); + -webkit-mask-image: var(--md-admonition-icon--question); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.warning, .caution, .attention) { + border-color: #ff9100; +} + +:is(.warning, .caution, .attention) > :is(.admonition-title, summary) { + background-color: rgba(255, 145, 0, 0.1); +} +:is(.warning, .caution, .attention) > :is(.admonition-title, summary)::before { + background-color: #ff9100; + mask-image: var(--md-admonition-icon--warning); + -webkit-mask-image: var(--md-admonition-icon--warning); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.failure, .fail, .missing) { + border-color: #ff5252; +} + +:is(.failure, .fail, .missing) > :is(.admonition-title, summary) { + background-color: rgba(255, 82, 82, 0.1); +} +:is(.failure, .fail, .missing) > :is(.admonition-title, summary)::before { + background-color: #ff5252; + mask-image: var(--md-admonition-icon--failure); + -webkit-mask-image: var(--md-admonition-icon--failure); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.danger, .error) { + border-color: #ff1744; +} + +:is(.danger, .error) > :is(.admonition-title, summary) { + background-color: rgba(255, 23, 68, 0.1); +} +:is(.danger, .error) > :is(.admonition-title, summary)::before { + background-color: #ff1744; + mask-image: var(--md-admonition-icon--danger); + -webkit-mask-image: var(--md-admonition-icon--danger); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.bug) { + border-color: #f50057; +} + +:is(.bug) > :is(.admonition-title, summary) { + background-color: rgba(245, 0, 87, 0.1); +} +:is(.bug) > :is(.admonition-title, summary)::before { + background-color: #f50057; + mask-image: var(--md-admonition-icon--bug); + -webkit-mask-image: var(--md-admonition-icon--bug); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.example) { + border-color: #7c4dff; +} + +:is(.example) > :is(.admonition-title, summary) { + background-color: rgba(124, 77, 255, 0.1); +} +:is(.example) > :is(.admonition-title, summary)::before { + background-color: #7c4dff; + mask-image: var(--md-admonition-icon--example); + -webkit-mask-image: var(--md-admonition-icon--example); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +:is(.admonition):is(.quote, .cite) { + border-color: #9e9e9e; +} + +:is(.quote, .cite) > :is(.admonition-title, summary) { + background-color: rgba(158, 158, 158, 0.1); +} +:is(.quote, .cite) > :is(.admonition-title, summary)::before { + background-color: #9e9e9e; + mask-image: var(--md-admonition-icon--quote); + -webkit-mask-image: var(--md-admonition-icon--quote); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-repeat: no-repeat; +} + +.navy :is(.admonition) { + background-color: var(--sidebar-bg); +} + +.ayu :is(.admonition), .coal :is(.admonition) { + background-color: var(--theme-hover); +} + +.rust :is(.admonition) { + background-color: var(--sidebar-bg); + color: var(--sidebar-fg); +} +.rust .admonition-anchor-link:link, .rust .admonition-anchor-link:visited { + color: var(--sidebar-fg); +} diff --git a/documentation/dev/assets/mermaid-init.js b/documentation/dev/assets/mermaid-init.js new file mode 100644 index 00000000000..256e4c73593 --- /dev/null +++ b/documentation/dev/assets/mermaid-init.js @@ -0,0 +1 @@ +mermaid.initialize({ startOnLoad: true, theme: "neutral", logLevel: "warn" }); diff --git a/documentation/dev/assets/mermaid.min.js b/documentation/dev/assets/mermaid.min.js new file mode 100644 index 00000000000..14ef691fa91 --- /dev/null +++ b/documentation/dev/assets/mermaid.min.js @@ -0,0 +1,32 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}("undefined"!=typeof self?self:this,(function(){return function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)n.d(r,i,function(e){return t[e]}.bind(null,i));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=383)}([function(t,e,n){"use strict";n.r(e);var r=function(t,e){return te?1:t>=e?0:NaN},i=function(t){var e;return 1===t.length&&(e=t,t=function(t,n){return r(e(t),n)}),{left:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[a],n)<0?r=a+1:i=a}return r},right:function(e,n,r,i){for(null==r&&(r=0),null==i&&(i=e.length);r>>1;t(e[a],n)>0?i=a:r=a+1}return r}}};var a=i(r),o=a.right,s=a.left,c=o,u=function(t,e){null==e&&(e=l);for(var n=0,r=t.length-1,i=t[0],a=new Array(r<0?0:r);nt?1:e>=t?0:NaN},d=function(t){return null===t?NaN:+t},p=function(t,e){var n,r,i=t.length,a=0,o=-1,s=0,c=0;if(null==e)for(;++o1)return c/(a-1)},g=function(t,e){var n=p(t,e);return n?Math.sqrt(n):n},y=function(t,e){var n,r,i,a=t.length,o=-1;if(null==e){for(;++o=n)for(r=i=n;++on&&(r=n),i=n)for(r=i=n;++on&&(r=n),i0)return[t];if((r=e0)for(t=Math.ceil(t/o),e=Math.floor(e/o),a=new Array(i=Math.ceil(e-t+1));++s=0?(a>=w?10:a>=E?5:a>=T?2:1)*Math.pow(10,i):-Math.pow(10,-i)/(a>=w?10:a>=E?5:a>=T?2:1)}function S(t,e,n){var r=Math.abs(e-t)/Math.max(0,n),i=Math.pow(10,Math.floor(Math.log(r)/Math.LN10)),a=r/i;return a>=w?i*=10:a>=E?i*=5:a>=T&&(i*=2),eh;)f.pop(),--d;var p,g=new Array(d+1);for(i=0;i<=d;++i)(p=g[i]=[]).x0=i>0?f[i-1]:l,p.x1=i=1)return+n(t[r-1],r-1,t);var r,i=(r-1)*e,a=Math.floor(i),o=+n(t[a],a,t);return o+(+n(t[a+1],a+1,t)-o)*(i-a)}},N=function(t,e,n){return t=b.call(t,d).sort(r),Math.ceil((n-e)/(2*(D(t,.75)-D(t,.25))*Math.pow(t.length,-1/3)))},B=function(t,e,n){return Math.ceil((n-e)/(3.5*g(t)*Math.pow(t.length,-1/3)))},L=function(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a=n)for(r=n;++ar&&(r=n)}else for(;++a=n)for(r=n;++ar&&(r=n);return r},F=function(t,e){var n,r=t.length,i=r,a=-1,o=0;if(null==e)for(;++a=0;)for(e=(r=t[i]).length;--e>=0;)n[--o]=r[e];return n},j=function(t,e){var n,r,i=t.length,a=-1;if(null==e){for(;++a=n)for(r=n;++an&&(r=n)}else for(;++a=n)for(r=n;++an&&(r=n);return r},R=function(t,e){for(var n=e.length,r=new Array(n);n--;)r[n]=t[e[n]];return r},Y=function(t,e){if(n=t.length){var n,i,a=0,o=0,s=t[o];for(null==e&&(e=r);++a=0&&(n=t.slice(r+1),t=t.slice(0,r)),t&&!e.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))}function ct(t,e){for(var n,r=0,i=t.length;r0)for(var n,r,i=new Array(n),a=0;ae?1:t>=e?0:NaN}var _t="http://www.w3.org/1999/xhtml",kt={svg:"http://www.w3.org/2000/svg",xhtml:_t,xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/"},wt=function(t){var e=t+="",n=e.indexOf(":");return n>=0&&"xmlns"!==(e=t.slice(0,n))&&(t=t.slice(n+1)),kt.hasOwnProperty(e)?{space:kt[e],local:t}:t};function Et(t){return function(){this.removeAttribute(t)}}function Tt(t){return function(){this.removeAttributeNS(t.space,t.local)}}function Ct(t,e){return function(){this.setAttribute(t,e)}}function At(t,e){return function(){this.setAttributeNS(t.space,t.local,e)}}function St(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttribute(t):this.setAttribute(t,n)}}function Mt(t,e){return function(){var n=e.apply(this,arguments);null==n?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,n)}}var Ot=function(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView};function Dt(t){return function(){this.style.removeProperty(t)}}function Nt(t,e,n){return function(){this.style.setProperty(t,e,n)}}function Bt(t,e,n){return function(){var r=e.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,n)}}function Lt(t,e){return t.style.getPropertyValue(e)||Ot(t).getComputedStyle(t,null).getPropertyValue(e)}function Ft(t){return function(){delete this[t]}}function Pt(t,e){return function(){this[t]=e}}function It(t,e){return function(){var n=e.apply(this,arguments);null==n?delete this[t]:this[t]=n}}function jt(t){return t.trim().split(/^|\s+/)}function Rt(t){return t.classList||new Yt(t)}function Yt(t){this._node=t,this._names=jt(t.getAttribute("class")||"")}function zt(t,e){for(var n=Rt(t),r=-1,i=e.length;++r=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function Vt(){this.textContent=""}function Gt(t){return function(){this.textContent=t}}function qt(t){return function(){var e=t.apply(this,arguments);this.textContent=null==e?"":e}}function Xt(){this.innerHTML=""}function Zt(t){return function(){this.innerHTML=t}}function Jt(t){return function(){var e=t.apply(this,arguments);this.innerHTML=null==e?"":e}}function Kt(){this.nextSibling&&this.parentNode.appendChild(this)}function Qt(){this.previousSibling&&this.parentNode.insertBefore(this,this.parentNode.firstChild)}function te(t){return function(){var e=this.ownerDocument,n=this.namespaceURI;return n===_t&&e.documentElement.namespaceURI===_t?e.createElement(t):e.createElementNS(n,t)}}function ee(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}var ne=function(t){var e=wt(t);return(e.local?ee:te)(e)};function re(){return null}function ie(){var t=this.parentNode;t&&t.removeChild(this)}function ae(){var t=this.cloneNode(!1),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}function oe(){var t=this.cloneNode(!0),e=this.parentNode;return e?e.insertBefore(t,this.nextSibling):t}var se={},ce=null;"undefined"!=typeof document&&("onmouseenter"in document.documentElement||(se={mouseenter:"mouseover",mouseleave:"mouseout"}));function ue(t,e,n){return t=le(t,e,n),function(e){var n=e.relatedTarget;n&&(n===this||8&n.compareDocumentPosition(this))||t.call(this,e)}}function le(t,e,n){return function(r){var i=ce;ce=r;try{t.call(this,this.__data__,e,n)}finally{ce=i}}}function he(t){return t.trim().split(/^|\s+/).map((function(t){var e="",n=t.indexOf(".");return n>=0&&(e=t.slice(n+1),t=t.slice(0,n)),{type:t,name:e}}))}function fe(t){return function(){var e=this.__on;if(e){for(var n,r=0,i=-1,a=e.length;r=_&&(_=x+1);!(b=v[_])&&++_=0;)(r=i[a])&&(o&&4^r.compareDocumentPosition(o)&&o.parentNode.insertBefore(r,o),o=r);return this},sort:function(t){function e(e,n){return e&&n?t(e.__data__,n.__data__):!e-!n}t||(t=xt);for(var n=this._groups,r=n.length,i=new Array(r),a=0;a1?this.each((null==e?Dt:"function"==typeof e?Bt:Nt)(t,e,null==n?"":n)):Lt(this.node(),t)},property:function(t,e){return arguments.length>1?this.each((null==e?Ft:"function"==typeof e?It:Pt)(t,e)):this.node()[t]},classed:function(t,e){var n=jt(t+"");if(arguments.length<2){for(var r=Rt(this.node()),i=-1,a=n.length;++i>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===n?new qe(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===n?new qe(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=Le.exec(t))?new qe(e[1],e[2],e[3],1):(e=Fe.exec(t))?new qe(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=Pe.exec(t))?He(e[1],e[2],e[3],e[4]):(e=Ie.exec(t))?He(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=je.exec(t))?Ke(e[1],e[2]/100,e[3]/100,1):(e=Re.exec(t))?Ke(e[1],e[2]/100,e[3]/100,e[4]):Ye.hasOwnProperty(t)?We(Ye[t]):"transparent"===t?new qe(NaN,NaN,NaN,0):null}function We(t){return new qe(t>>16&255,t>>8&255,255&t,1)}function He(t,e,n,r){return r<=0&&(t=e=n=NaN),new qe(t,e,n,r)}function Ve(t){return t instanceof Me||(t=$e(t)),t?new qe((t=t.rgb()).r,t.g,t.b,t.opacity):new qe}function Ge(t,e,n,r){return 1===arguments.length?Ve(t):new qe(t,e,n,null==r?1:r)}function qe(t,e,n,r){this.r=+t,this.g=+e,this.b=+n,this.opacity=+r}function Xe(){return"#"+Je(this.r)+Je(this.g)+Je(this.b)}function Ze(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"rgb(":"rgba(")+Math.max(0,Math.min(255,Math.round(this.r)||0))+", "+Math.max(0,Math.min(255,Math.round(this.g)||0))+", "+Math.max(0,Math.min(255,Math.round(this.b)||0))+(1===t?")":", "+t+")")}function Je(t){return((t=Math.max(0,Math.min(255,Math.round(t)||0)))<16?"0":"")+t.toString(16)}function Ke(t,e,n,r){return r<=0?t=e=n=NaN:n<=0||n>=1?t=e=NaN:e<=0&&(t=NaN),new en(t,e,n,r)}function Qe(t){if(t instanceof en)return new en(t.h,t.s,t.l,t.opacity);if(t instanceof Me||(t=$e(t)),!t)return new en;if(t instanceof en)return t;var e=(t=t.rgb()).r/255,n=t.g/255,r=t.b/255,i=Math.min(e,n,r),a=Math.max(e,n,r),o=NaN,s=a-i,c=(a+i)/2;return s?(o=e===a?(n-r)/s+6*(n0&&c<1?0:o,new en(o,s,c,t.opacity)}function tn(t,e,n,r){return 1===arguments.length?Qe(t):new en(t,e,n,null==r?1:r)}function en(t,e,n,r){this.h=+t,this.s=+e,this.l=+n,this.opacity=+r}function nn(t,e,n){return 255*(t<60?e+(n-e)*t/60:t<180?n:t<240?e+(n-e)*(240-t)/60:e)}function rn(t,e,n,r,i){var a=t*t,o=a*t;return((1-3*t+3*a-o)*e+(4-6*a+3*o)*n+(1+3*t+3*a-3*o)*r+o*i)/6}Ae(Me,$e,{copy:function(t){return Object.assign(new this.constructor,this,t)},displayable:function(){return this.rgb().displayable()},hex:ze,formatHex:ze,formatHsl:function(){return Qe(this).formatHsl()},formatRgb:Ue,toString:Ue}),Ae(qe,Ge,Se(Me,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new qe(this.r*t,this.g*t,this.b*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new qe(this.r*t,this.g*t,this.b*t,this.opacity)},rgb:function(){return this},displayable:function(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Xe,formatHex:Xe,formatRgb:Ze,toString:Ze})),Ae(en,tn,Se(Me,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new en(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new en(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=this.h%360+360*(this.h<0),e=isNaN(t)||isNaN(this.s)?0:this.s,n=this.l,r=n+(n<.5?n:1-n)*e,i=2*n-r;return new qe(nn(t>=240?t-240:t+120,i,r),nn(t,i,r),nn(t<120?t+240:t-120,i,r),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl:function(){var t=this.opacity;return(1===(t=isNaN(t)?1:Math.max(0,Math.min(1,t)))?"hsl(":"hsla(")+(this.h||0)+", "+100*(this.s||0)+"%, "+100*(this.l||0)+"%"+(1===t?")":", "+t+")")}}));var an=function(t){var e=t.length-1;return function(n){var r=n<=0?n=0:n>=1?(n=1,e-1):Math.floor(n*e),i=t[r],a=t[r+1],o=r>0?t[r-1]:2*i-a,s=r180||n<-180?n-360*Math.round(n/360):n):sn(isNaN(t)?e:t)}function ln(t){return 1==(t=+t)?hn:function(e,n){return n-e?function(t,e,n){return t=Math.pow(t,n),e=Math.pow(e,n)-t,n=1/n,function(r){return Math.pow(t+r*e,n)}}(e,n,t):sn(isNaN(e)?n:e)}}function hn(t,e){var n=e-t;return n?cn(t,n):sn(isNaN(t)?e:t)}var fn=function t(e){var n=ln(e);function r(t,e){var r=n((t=Ge(t)).r,(e=Ge(e)).r),i=n(t.g,e.g),a=n(t.b,e.b),o=hn(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=i(e),t.b=a(e),t.opacity=o(e),t+""}}return r.gamma=t,r}(1);function dn(t){return function(e){var n,r,i=e.length,a=new Array(i),o=new Array(i),s=new Array(i);for(n=0;na&&(i=e.slice(a,i),s[o]?s[o]+=i:s[++o]=i),(n=n[0])===(r=r[0])?s[o]?s[o]+=r:s[++o]=r:(s[++o]=null,c.push({i:o,x:_n(n,r)})),a=En.lastIndex;return a=0&&e._call.call(null,t),e=e._next;--Bn}function Vn(){In=(Pn=Rn.now())+jn,Bn=Ln=0;try{Hn()}finally{Bn=0,function(){var t,e,n=Tn,r=1/0;for(;n;)n._call?(r>n._time&&(r=n._time),t=n,n=n._next):(e=n._next,n._next=null,n=t?t._next=e:Tn=e);Cn=t,qn(r)}(),In=0}}function Gn(){var t=Rn.now(),e=t-Pn;e>1e3&&(jn-=e,Pn=t)}function qn(t){Bn||(Ln&&(Ln=clearTimeout(Ln)),t-In>24?(t<1/0&&(Ln=setTimeout(Vn,t-Rn.now()-jn)),Fn&&(Fn=clearInterval(Fn))):(Fn||(Pn=Rn.now(),Fn=setInterval(Gn,1e3)),Bn=1,Yn(Vn)))}$n.prototype=Wn.prototype={constructor:$n,restart:function(t,e,n){if("function"!=typeof t)throw new TypeError("callback is not a function");n=(null==n?zn():+n)+(null==e?0:+e),this._next||Cn===this||(Cn?Cn._next=this:Tn=this,Cn=this),this._call=t,this._time=n,qn()},stop:function(){this._call&&(this._call=null,this._time=1/0,qn())}};var Xn=function(t,e,n){var r=new $n;return e=null==e?0:+e,r.restart((function(n){r.stop(),t(n+e)}),e,n),r},Zn=lt("start","end","cancel","interrupt"),Jn=[],Kn=function(t,e,n,r,i,a){var o=t.__transition;if(o){if(n in o)return}else t.__transition={};!function(t,e,n){var r,i=t.__transition;function a(c){var u,l,h,f;if(1!==n.state)return s();for(u in i)if((f=i[u]).name===n.name){if(3===f.state)return Xn(a);4===f.state?(f.state=6,f.timer.stop(),f.on.call("interrupt",t,t.__data__,f.index,f.group),delete i[u]):+u0)throw new Error("too late; already scheduled");return n}function tr(t,e){var n=er(t,e);if(n.state>3)throw new Error("too late; already running");return n}function er(t,e){var n=t.__transition;if(!n||!(n=n[e]))throw new Error("transition not found");return n}var nr,rr,ir,ar,or=function(t,e){var n,r,i,a=t.__transition,o=!0;if(a){for(i in e=null==e?null:e+"",a)(n=a[i]).name===e?(r=n.state>2&&n.state<5,n.state=6,n.timer.stop(),n.on.call(r?"interrupt":"cancel",t,t.__data__,n.index,n.group),delete a[i]):o=!1;o&&delete t.__transition}},sr=180/Math.PI,cr={translateX:0,translateY:0,rotate:0,skewX:0,scaleX:1,scaleY:1},ur=function(t,e,n,r,i,a){var o,s,c;return(o=Math.sqrt(t*t+e*e))&&(t/=o,e/=o),(c=t*n+e*r)&&(n-=t*c,r-=e*c),(s=Math.sqrt(n*n+r*r))&&(n/=s,r/=s,c/=s),t*r180?e+=360:e-t>180&&(t+=360),a.push({i:n.push(i(n)+"rotate(",null,r)-2,x:_n(t,e)})):e&&n.push(i(n)+"rotate("+e+r)}(a.rotate,o.rotate,s,c),function(t,e,n,a){t!==e?a.push({i:n.push(i(n)+"skewX(",null,r)-2,x:_n(t,e)}):e&&n.push(i(n)+"skewX("+e+r)}(a.skewX,o.skewX,s,c),function(t,e,n,r,a,o){if(t!==n||e!==r){var s=a.push(i(a)+"scale(",null,",",null,")");o.push({i:s-4,x:_n(t,n)},{i:s-2,x:_n(e,r)})}else 1===n&&1===r||a.push(i(a)+"scale("+n+","+r+")")}(a.scaleX,a.scaleY,o.scaleX,o.scaleY,s,c),a=o=null,function(t){for(var e,n=-1,r=c.length;++n=0&&(t=t.slice(0,e)),!t||"start"===t}))}(e)?Qn:tr;return function(){var o=a(this,t),s=o.on;s!==r&&(i=(r=s).copy()).on(e,n),o.on=i}}var Br=_e.prototype.constructor;function Lr(t){return function(){this.style.removeProperty(t)}}function Fr(t,e,n){return function(r){this.style.setProperty(t,e.call(this,r),n)}}function Pr(t,e,n){var r,i;function a(){var a=e.apply(this,arguments);return a!==i&&(r=(i=a)&&Fr(t,a,n)),r}return a._value=e,a}function Ir(t){return function(e){this.textContent=t.call(this,e)}}function jr(t){var e,n;function r(){var r=t.apply(this,arguments);return r!==n&&(e=(n=r)&&Ir(r)),e}return r._value=t,r}var Rr=0;function Yr(t,e,n,r){this._groups=t,this._parents=e,this._name=n,this._id=r}function zr(t){return _e().transition(t)}function Ur(){return++Rr}var $r=_e.prototype;function Wr(t){return t*t*t}function Hr(t){return--t*t*t+1}function Vr(t){return((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2}Yr.prototype=zr.prototype={constructor:Yr,select:function(t){var e=this._name,n=this._id;"function"!=typeof t&&(t=ft(t));for(var r=this._groups,i=r.length,a=new Array(i),o=0;o1&&n.name===e)return new Yr([[t]],Xr,e,+r);return null},Jr=function(t){return function(){return t}},Kr=function(t,e,n){this.target=t,this.type=e,this.selection=n};function Qr(){ce.stopImmediatePropagation()}var ti=function(){ce.preventDefault(),ce.stopImmediatePropagation()},ei={name:"drag"},ni={name:"space"},ri={name:"handle"},ii={name:"center"};function ai(t){return[+t[0],+t[1]]}function oi(t){return[ai(t[0]),ai(t[1])]}function si(t){return function(e){return Dn(e,ce.touches,t)}}var ci={name:"x",handles:["w","e"].map(yi),input:function(t,e){return null==t?null:[[+t[0],e[0][1]],[+t[1],e[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},ui={name:"y",handles:["n","s"].map(yi),input:function(t,e){return null==t?null:[[e[0][0],+t[0]],[e[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},li={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(yi),input:function(t){return null==t?null:oi(t)},output:function(t){return t}},hi={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},fi={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},di={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},pi={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},gi={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function yi(t){return{type:t}}function vi(){return!ce.ctrlKey&&!ce.button}function mi(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function bi(){return navigator.maxTouchPoints||"ontouchstart"in this}function xi(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function _i(t){return t[0][0]===t[1][0]||t[0][1]===t[1][1]}function ki(t){var e=t.__brush;return e?e.dim.output(e.selection):null}function wi(){return Ci(ci)}function Ei(){return Ci(ui)}var Ti=function(){return Ci(li)};function Ci(t){var e,n=mi,r=vi,i=bi,a=!0,o=lt("start","brush","end"),s=6;function c(e){var n=e.property("__brush",g).selectAll(".overlay").data([yi("overlay")]);n.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",hi.overlay).merge(n).each((function(){var t=xi(this).extent;ke(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),e.selectAll(".selection").data([yi("selection")]).enter().append("rect").attr("class","selection").attr("cursor",hi.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=e.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return hi[t.type]})),e.each(u).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",f).filter(i).on("touchstart.brush",f).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function u(){var t=ke(this),e=xi(this).selection;e?(t.selectAll(".selection").style("display",null).attr("x",e[0][0]).attr("y",e[0][1]).attr("width",e[1][0]-e[0][0]).attr("height",e[1][1]-e[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?e[1][0]-s/2:e[0][0]-s/2})).attr("y",(function(t){return"s"===t.type[0]?e[1][1]-s/2:e[0][1]-s/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?e[1][0]-e[0][0]+s:s})).attr("height",(function(t){return"e"===t.type||"w"===t.type?e[1][1]-e[0][1]+s:s}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function l(t,e,n){return!n&&t.__brush.emitter||new h(t,e)}function h(t,e){this.that=t,this.args=e,this.state=t.__brush,this.active=0}function f(){if((!e||ce.touches)&&r.apply(this,arguments)){var n,i,o,s,c,h,f,d,p,g,y,v=this,m=ce.target.__data__.type,b="selection"===(a&&ce.metaKey?m="overlay":m)?ei:a&&ce.altKey?ii:ri,x=t===ui?null:pi[m],_=t===ci?null:gi[m],k=xi(v),w=k.extent,E=k.selection,T=w[0][0],C=w[0][1],A=w[1][0],S=w[1][1],M=0,O=0,D=x&&_&&a&&ce.shiftKey,N=ce.touches?si(ce.changedTouches[0].identifier):Nn,B=N(v),L=B,F=l(v,arguments,!0).beforestart();"overlay"===m?(E&&(p=!0),k.selection=E=[[n=t===ui?T:B[0],o=t===ci?C:B[1]],[c=t===ui?A:n,f=t===ci?S:o]]):(n=E[0][0],o=E[0][1],c=E[1][0],f=E[1][1]),i=n,s=o,h=c,d=f;var P=ke(v).attr("pointer-events","none"),I=P.selectAll(".overlay").attr("cursor",hi[m]);if(ce.touches)F.moved=R,F.ended=z;else{var j=ke(ce.view).on("mousemove.brush",R,!0).on("mouseup.brush",z,!0);a&&j.on("keydown.brush",U,!0).on("keyup.brush",$,!0),Te(ce.view)}Qr(),or(v),u.call(v),F.start()}function R(){var t=N(v);!D||g||y||(Math.abs(t[0]-L[0])>Math.abs(t[1]-L[1])?y=!0:g=!0),L=t,p=!0,ti(),Y()}function Y(){var t;switch(M=L[0]-B[0],O=L[1]-B[1],b){case ni:case ei:x&&(M=Math.max(T-n,Math.min(A-c,M)),i=n+M,h=c+M),_&&(O=Math.max(C-o,Math.min(S-f,O)),s=o+O,d=f+O);break;case ri:x<0?(M=Math.max(T-n,Math.min(A-n,M)),i=n+M,h=c):x>0&&(M=Math.max(T-c,Math.min(A-c,M)),i=n,h=c+M),_<0?(O=Math.max(C-o,Math.min(S-o,O)),s=o+O,d=f):_>0&&(O=Math.max(C-f,Math.min(S-f,O)),s=o,d=f+O);break;case ii:x&&(i=Math.max(T,Math.min(A,n-M*x)),h=Math.max(T,Math.min(A,c+M*x))),_&&(s=Math.max(C,Math.min(S,o-O*_)),d=Math.max(C,Math.min(S,f+O*_)))}h0&&(n=i-M),_<0?f=d-O:_>0&&(o=s-O),b=ni,I.attr("cursor",hi.selection),Y());break;default:return}ti()}function $(){switch(ce.keyCode){case 16:D&&(g=y=D=!1,Y());break;case 18:b===ii&&(x<0?c=h:x>0&&(n=i),_<0?f=d:_>0&&(o=s),b=ri,Y());break;case 32:b===ni&&(ce.altKey?(x&&(c=h-M*x,n=i+M*x),_&&(f=d-O*_,o=s+O*_),b=ii):(x<0?c=h:x>0&&(n=i),_<0?f=d:_>0&&(o=s),b=ri),I.attr("cursor",hi[m]),Y());break;default:return}ti()}}function d(){l(this,arguments).moved()}function p(){l(this,arguments).ended()}function g(){var e=this.__brush||{selection:null};return e.extent=oi(n.apply(this,arguments)),e.dim=t,e}return c.move=function(e,n){e.selection?e.on("start.brush",(function(){l(this,arguments).beforestart().start()})).on("interrupt.brush end.brush",(function(){l(this,arguments).end()})).tween("brush",(function(){var e=this,r=e.__brush,i=l(e,arguments),a=r.selection,o=t.input("function"==typeof n?n.apply(this,arguments):n,r.extent),s=Sn(a,o);function c(t){r.selection=1===t&&null===o?null:s(t),u.call(e),i.brush()}return null!==a&&null!==o?c:c(1)})):e.each((function(){var e=this,r=arguments,i=e.__brush,a=t.input("function"==typeof n?n.apply(e,r):n,i.extent),o=l(e,r).beforestart();or(e),i.selection=null===a?null:a,u.call(e),o.start().brush().end()}))},c.clear=function(t){c.move(t,null)},h.prototype={beforestart:function(){return 1==++this.active&&(this.state.emitter=this,this.starting=!0),this},start:function(){return this.starting?(this.starting=!1,this.emit("start")):this.emit("brush"),this},brush:function(){return this.emit("brush"),this},end:function(){return 0==--this.active&&(delete this.state.emitter,this.emit("end")),this},emit:function(e){pe(new Kr(c,e,t.output(this.state.selection)),o.apply,o,[e,this.that,this.args])}},c.extent=function(t){return arguments.length?(n="function"==typeof t?t:Jr(oi(t)),c):n},c.filter=function(t){return arguments.length?(r="function"==typeof t?t:Jr(!!t),c):r},c.touchable=function(t){return arguments.length?(i="function"==typeof t?t:Jr(!!t),c):i},c.handleSize=function(t){return arguments.length?(s=+t,c):s},c.keyModifiers=function(t){return arguments.length?(a=!!t,c):a},c.on=function(){var t=o.on.apply(o,arguments);return t===o?c:t},c}var Ai=Math.cos,Si=Math.sin,Mi=Math.PI,Oi=Mi/2,Di=2*Mi,Ni=Math.max;function Bi(t){return function(e,n){return t(e.source.value+e.target.value,n.source.value+n.target.value)}}var Li=function(){var t=0,e=null,n=null,r=null;function i(i){var a,o,s,c,u,l,h=i.length,f=[],d=k(h),p=[],g=[],y=g.groups=new Array(h),v=new Array(h*h);for(a=0,u=-1;++u1e-6)if(Math.abs(l*s-c*u)>1e-6&&i){var f=n-a,d=r-o,p=s*s+c*c,g=f*f+d*d,y=Math.sqrt(p),v=Math.sqrt(h),m=i*Math.tan((Ii-Math.acos((p+h-g)/(2*y*v)))/2),b=m/v,x=m/y;Math.abs(b-1)>1e-6&&(this._+="L"+(t+b*u)+","+(e+b*l)),this._+="A"+i+","+i+",0,0,"+ +(l*f>u*d)+","+(this._x1=t+x*s)+","+(this._y1=e+x*c)}else this._+="L"+(this._x1=t)+","+(this._y1=e);else;},arc:function(t,e,n,r,i,a){t=+t,e=+e,a=!!a;var o=(n=+n)*Math.cos(r),s=n*Math.sin(r),c=t+o,u=e+s,l=1^a,h=a?r-i:i-r;if(n<0)throw new Error("negative radius: "+n);null===this._x1?this._+="M"+c+","+u:(Math.abs(this._x1-c)>1e-6||Math.abs(this._y1-u)>1e-6)&&(this._+="L"+c+","+u),n&&(h<0&&(h=h%ji+ji),h>Ri?this._+="A"+n+","+n+",0,1,"+l+","+(t-o)+","+(e-s)+"A"+n+","+n+",0,1,"+l+","+(this._x1=c)+","+(this._y1=u):h>1e-6&&(this._+="A"+n+","+n+",0,"+ +(h>=Ii)+","+l+","+(this._x1=t+n*Math.cos(i))+","+(this._y1=e+n*Math.sin(i))))},rect:function(t,e,n,r){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +n+"v"+ +r+"h"+-n+"Z"},toString:function(){return this._}};var Ui=zi;function $i(t){return t.source}function Wi(t){return t.target}function Hi(t){return t.radius}function Vi(t){return t.startAngle}function Gi(t){return t.endAngle}var qi=function(){var t=$i,e=Wi,n=Hi,r=Vi,i=Gi,a=null;function o(){var o,s=Fi.call(arguments),c=t.apply(this,s),u=e.apply(this,s),l=+n.apply(this,(s[0]=c,s)),h=r.apply(this,s)-Oi,f=i.apply(this,s)-Oi,d=l*Ai(h),p=l*Si(h),g=+n.apply(this,(s[0]=u,s)),y=r.apply(this,s)-Oi,v=i.apply(this,s)-Oi;if(a||(a=o=Ui()),a.moveTo(d,p),a.arc(0,0,l,h,f),h===y&&f===v||(a.quadraticCurveTo(0,0,g*Ai(y),g*Si(y)),a.arc(0,0,g,y,v)),a.quadraticCurveTo(0,0,d,p),a.closePath(),o)return a=null,o+""||null}return o.radius=function(t){return arguments.length?(n="function"==typeof t?t:Pi(+t),o):n},o.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:Pi(+t),o):r},o.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:Pi(+t),o):i},o.source=function(e){return arguments.length?(t=e,o):t},o.target=function(t){return arguments.length?(e=t,o):e},o.context=function(t){return arguments.length?(a=null==t?null:t,o):a},o};function Xi(){}function Zi(t,e){var n=new Xi;if(t instanceof Xi)t.each((function(t,e){n.set(e,t)}));else if(Array.isArray(t)){var r,i=-1,a=t.length;if(null==e)for(;++i=r.length)return null!=t&&n.sort(t),null!=e?e(n):n;for(var c,u,l,h=-1,f=n.length,d=r[i++],p=Ji(),g=o();++hr.length)return n;var o,s=i[a-1];return null!=e&&a>=r.length?o=n.entries():(o=[],n.each((function(e,n){o.push({key:n,values:t(e,a)})}))),null!=s?o.sort((function(t,e){return s(t.key,e.key)})):o}(a(t,0,ea,na),0)},key:function(t){return r.push(t),n},sortKeys:function(t){return i[r.length-1]=t,n},sortValues:function(e){return t=e,n},rollup:function(t){return e=t,n}}};function Qi(){return{}}function ta(t,e,n){t[e]=n}function ea(){return Ji()}function na(t,e,n){t.set(e,n)}function ra(){}var ia=Ji.prototype;function aa(t,e){var n=new ra;if(t instanceof ra)t.each((function(t){n.add(t)}));else if(t){var r=-1,i=t.length;if(null==e)for(;++r6/29*(6/29)*(6/29)?Math.pow(t,1/3):t/(6/29*3*(6/29))+4/29}function va(t){return t>6/29?t*t*t:6/29*3*(6/29)*(t-4/29)}function ma(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function ba(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function xa(t){if(t instanceof wa)return new wa(t.h,t.c,t.l,t.opacity);if(t instanceof ga||(t=fa(t)),0===t.a&&0===t.b)return new wa(NaN,0r!=d>r&&n<(f-u)*(r-l)/(d-l)+u&&(i=-i)}return i}function Ia(t,e,n){var r,i,a,o;return function(t,e,n){return(e[0]-t[0])*(n[1]-t[1])==(n[0]-t[0])*(e[1]-t[1])}(t,e,n)&&(i=t[r=+(t[0]===e[0])],a=n[r],o=e[r],i<=a&&a<=o||o<=a&&a<=i)}var ja=function(){},Ra=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]],Ya=function(){var t=1,e=1,n=M,r=s;function i(t){var e=n(t);if(Array.isArray(e))e=e.slice().sort(Ba);else{var r=y(t),i=r[0],o=r[1];e=S(i,o,e),e=k(Math.floor(i/e)*e,Math.floor(o/e)*e,e)}return e.map((function(e){return a(t,e)}))}function a(n,i){var a=[],s=[];return function(n,r,i){var a,s,c,u,l,h,f=new Array,d=new Array;a=s=-1,u=n[0]>=r,Ra[u<<1].forEach(p);for(;++a=r,Ra[c|u<<1].forEach(p);Ra[u<<0].forEach(p);for(;++s=r,l=n[s*t]>=r,Ra[u<<1|l<<2].forEach(p);++a=r,h=l,l=n[s*t+a+1]>=r,Ra[c|u<<1|l<<2|h<<3].forEach(p);Ra[u|l<<3].forEach(p)}a=-1,l=n[s*t]>=r,Ra[l<<2].forEach(p);for(;++a=r,Ra[l<<2|h<<3].forEach(p);function p(t){var e,n,r=[t[0][0]+a,t[0][1]+s],c=[t[1][0]+a,t[1][1]+s],u=o(r),l=o(c);(e=d[u])?(n=f[l])?(delete d[e.end],delete f[n.start],e===n?(e.ring.push(c),i(e.ring)):f[e.start]=d[n.end]={start:e.start,end:n.end,ring:e.ring.concat(n.ring)}):(delete d[e.end],e.ring.push(c),d[e.end=l]=e):(e=f[l])?(n=d[u])?(delete f[e.start],delete d[n.end],e===n?(e.ring.push(c),i(e.ring)):f[n.start]=d[e.end]={start:n.start,end:e.end,ring:n.ring.concat(e.ring)}):(delete f[e.start],e.ring.unshift(r),f[e.start=u]=e):f[u]=d[l]={start:u,end:l,ring:[r,c]}}Ra[l<<3].forEach(p)}(n,i,(function(t){r(t,n,i),function(t){for(var e=0,n=t.length,r=t[n-1][1]*t[0][0]-t[n-1][0]*t[0][1];++e0?a.push([t]):s.push(t)})),s.forEach((function(t){for(var e,n=0,r=a.length;n0&&o0&&s0&&a>0))throw new Error("invalid size");return t=r,e=a,i},i.thresholds=function(t){return arguments.length?(n="function"==typeof t?t:Array.isArray(t)?La(Na.call(t)):La(t),i):n},i.smooth=function(t){return arguments.length?(r=t?s:ja,i):r===s},i};function za(t,e,n){for(var r=t.width,i=t.height,a=1+(n<<1),o=0;o=n&&(s>=a&&(c-=t.data[s-a+o*r]),e.data[s-n+o*r]=c/Math.min(s+1,r-1+a-s,a))}function Ua(t,e,n){for(var r=t.width,i=t.height,a=1+(n<<1),o=0;o=n&&(s>=a&&(c-=t.data[o+(s-a)*r]),e.data[o+(s-n)*r]=c/Math.min(s+1,i-1+a-s,a))}function $a(t){return t[0]}function Wa(t){return t[1]}function Ha(){return 1}var Va=function(){var t=$a,e=Wa,n=Ha,r=960,i=500,a=20,o=2,s=3*a,c=r+2*s>>o,u=i+2*s>>o,l=La(20);function h(r){var i=new Float32Array(c*u),h=new Float32Array(c*u);r.forEach((function(r,a,l){var h=+t(r,a,l)+s>>o,f=+e(r,a,l)+s>>o,d=+n(r,a,l);h>=0&&h=0&&f>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o),za({width:c,height:u,data:i},{width:c,height:u,data:h},a>>o),Ua({width:c,height:u,data:h},{width:c,height:u,data:i},a>>o);var d=l(i);if(!Array.isArray(d)){var p=L(i);d=S(0,p,d),(d=k(0,Math.floor(p/d)*d,d)).shift()}return Ya().thresholds(d).size([c,u])(i).map(f)}function f(t){return t.value*=Math.pow(2,-2*o),t.coordinates.forEach(d),t}function d(t){t.forEach(p)}function p(t){t.forEach(g)}function g(t){t[0]=t[0]*Math.pow(2,o)-s,t[1]=t[1]*Math.pow(2,o)-s}function y(){return c=r+2*(s=3*a)>>o,u=i+2*s>>o,h}return h.x=function(e){return arguments.length?(t="function"==typeof e?e:La(+e),h):t},h.y=function(t){return arguments.length?(e="function"==typeof t?t:La(+t),h):e},h.weight=function(t){return arguments.length?(n="function"==typeof t?t:La(+t),h):n},h.size=function(t){if(!arguments.length)return[r,i];var e=Math.ceil(t[0]),n=Math.ceil(t[1]);if(!(e>=0||e>=0))throw new Error("invalid size");return r=e,i=n,y()},h.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return o=Math.floor(Math.log(t)/Math.LN2),y()},h.thresholds=function(t){return arguments.length?(l="function"==typeof t?t:Array.isArray(t)?La(Na.call(t)):La(t),h):l},h.bandwidth=function(t){if(!arguments.length)return Math.sqrt(a*(a+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return a=Math.round((Math.sqrt(4*t*t+1)-1)/2),y()},h},Ga=function(t){return function(){return t}};function qa(t,e,n,r,i,a,o,s,c,u){this.target=t,this.type=e,this.subject=n,this.identifier=r,this.active=i,this.x=a,this.y=o,this.dx=s,this.dy=c,this._=u}function Xa(){return!ce.ctrlKey&&!ce.button}function Za(){return this.parentNode}function Ja(t){return null==t?{x:ce.x,y:ce.y}:t}function Ka(){return navigator.maxTouchPoints||"ontouchstart"in this}qa.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};var Qa=function(){var t,e,n,r,i=Xa,a=Za,o=Ja,s=Ka,c={},u=lt("start","drag","end"),l=0,h=0;function f(t){t.on("mousedown.drag",d).filter(s).on("touchstart.drag",y).on("touchmove.drag",v).on("touchend.drag touchcancel.drag",m).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function d(){if(!r&&i.apply(this,arguments)){var o=b("mouse",a.apply(this,arguments),Nn,this,arguments);o&&(ke(ce.view).on("mousemove.drag",p,!0).on("mouseup.drag",g,!0),Te(ce.view),we(),n=!1,t=ce.clientX,e=ce.clientY,o("start"))}}function p(){if(Ee(),!n){var r=ce.clientX-t,i=ce.clientY-e;n=r*r+i*i>h}c.mouse("drag")}function g(){ke(ce.view).on("mousemove.drag mouseup.drag",null),Ce(ce.view,n),Ee(),c.mouse("end")}function y(){if(i.apply(this,arguments)){var t,e,n=ce.changedTouches,r=a.apply(this,arguments),o=n.length;for(t=0;t9999?"+"+io(e,6):io(e,4))+"-"+io(t.getUTCMonth()+1,2)+"-"+io(t.getUTCDate(),2)+(a?"T"+io(n,2)+":"+io(r,2)+":"+io(i,2)+"."+io(a,3)+"Z":i?"T"+io(n,2)+":"+io(r,2)+":"+io(i,2)+"Z":r||n?"T"+io(n,2)+":"+io(r,2)+"Z":"")}var oo=function(t){var e=new RegExp('["'+t+"\n\r]"),n=t.charCodeAt(0);function r(t,e){var r,i=[],a=t.length,o=0,s=0,c=a<=0,u=!1;function l(){if(c)return eo;if(u)return u=!1,to;var e,r,i=o;if(34===t.charCodeAt(i)){for(;o++=a?c=!0:10===(r=t.charCodeAt(o++))?u=!0:13===r&&(u=!0,10===t.charCodeAt(o)&&++o),t.slice(i+1,e-1).replace(/""/g,'"')}for(;o=(a=(g+v)/2))?g=a:v=a,(l=n>=(o=(y+m)/2))?y=o:m=o,i=d,!(d=d[h=l<<1|u]))return i[h]=p,t;if(s=+t._x.call(null,d.data),c=+t._y.call(null,d.data),e===s&&n===c)return p.next=d,i?i[h]=p:t._root=p,t;do{i=i?i[h]=new Array(4):t._root=new Array(4),(u=e>=(a=(g+v)/2))?g=a:v=a,(l=n>=(o=(y+m)/2))?y=o:m=o}while((h=l<<1|u)==(f=(c>=o)<<1|s>=a));return i[f]=d,i[h]=p,t}var _s=function(t,e,n,r,i){this.node=t,this.x0=e,this.y0=n,this.x1=r,this.y1=i};function ks(t){return t[0]}function ws(t){return t[1]}function Es(t,e,n){var r=new Ts(null==e?ks:e,null==n?ws:n,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Ts(t,e,n,r,i,a){this._x=t,this._y=e,this._x0=n,this._y0=r,this._x1=i,this._y1=a,this._root=void 0}function Cs(t){for(var e={data:t.data},n=e;t=t.next;)n=n.next={data:t.data};return e}var As=Es.prototype=Ts.prototype;function Ss(t){return t.x+t.vx}function Ms(t){return t.y+t.vy}As.copy=function(){var t,e,n=new Ts(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return n;if(!r.length)return n._root=Cs(r),n;for(t=[{source:r,target:n._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(e=r.source[i])&&(e.length?t.push({source:e,target:r.target[i]=new Array(4)}):r.target[i]=Cs(e));return n},As.add=function(t){var e=+this._x.call(null,t),n=+this._y.call(null,t);return xs(this.cover(e,n),e,n,t)},As.addAll=function(t){var e,n,r,i,a=t.length,o=new Array(a),s=new Array(a),c=1/0,u=1/0,l=-1/0,h=-1/0;for(n=0;nl&&(l=r),ih&&(h=i));if(c>l||u>h)return this;for(this.cover(c,u).cover(l,h),n=0;nt||t>=i||r>e||e>=a;)switch(s=(ef||(a=c.y0)>d||(o=c.x1)=v)<<1|t>=y)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-u],p[p.length-1-u]=c)}else{var m=t-+this._x.call(null,g.data),b=e-+this._y.call(null,g.data),x=m*m+b*b;if(x=(s=(p+y)/2))?p=s:y=s,(l=o>=(c=(g+v)/2))?g=c:v=c,e=d,!(d=d[h=l<<1|u]))return this;if(!d.length)break;(e[h+1&3]||e[h+2&3]||e[h+3&3])&&(n=e,f=h)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):e?(i?e[h]=i:delete e[h],(d=e[0]||e[1]||e[2]||e[3])&&d===(e[3]||e[2]||e[1]||e[0])&&!d.length&&(n?n[f]=d:this._root=d),this):(this._root=i,this)},As.removeAll=function(t){for(var e=0,n=t.length;ec+d||iu+d||as.index){var p=c-o.x-o.vx,g=u-o.y-o.vy,y=p*p+g*g;yt.r&&(t.r=t[e].r)}function s(){if(e){var r,i,a=e.length;for(n=new Array(a),r=0;r1?(null==n?s.remove(t):s.set(t,d(n)),e):s.get(t)},find:function(e,n,r){var i,a,o,s,c,u=0,l=t.length;for(null==r?r=1/0:r*=r,u=0;u1?(u.on(t,n),e):u.on(t)}}},js=function(){var t,e,n,r,i=ms(-30),a=1,o=1/0,s=.81;function c(r){var i,a=t.length,o=Es(t,Ls,Fs).visitAfter(l);for(n=r,i=0;i=o)){(t.data!==e||t.next)&&(0===l&&(d+=(l=bs())*l),0===h&&(d+=(h=bs())*h),d1?r[0]+r.slice(2):r,+t.slice(n+1)]},$s=function(t){return(t=Us(Math.abs(t)))?t[1]:NaN},Ws=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Hs(t){if(!(e=Ws.exec(t)))throw new Error("invalid format: "+t);var e;return new Vs({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function Vs(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}Hs.prototype=Vs.prototype,Vs.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var Gs,qs,Xs,Zs,Js=function(t,e){var n=Us(t,e);if(!n)return t+"";var r=n[0],i=n[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")},Ks={"%":function(t,e){return(100*t).toFixed(e)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,e){return t.toExponential(e)},f:function(t,e){return t.toFixed(e)},g:function(t,e){return t.toPrecision(e)},o:function(t){return Math.round(t).toString(8)},p:function(t,e){return Js(100*t,e)},r:Js,s:function(t,e){var n=Us(t,e);if(!n)return t+"";var r=n[0],i=n[1],a=i-(Gs=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,o=r.length;return a===o?r:a>o?r+new Array(a-o+1).join("0"):a>0?r.slice(0,a)+"."+r.slice(a):"0."+new Array(1-a).join("0")+Us(t,Math.max(0,e+a-1))[0]},X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}},Qs=function(t){return t},tc=Array.prototype.map,ec=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"],nc=function(t){var e,n,r=void 0===t.grouping||void 0===t.thousands?Qs:(e=tc.call(t.grouping,Number),n=t.thousands+"",function(t,r){for(var i=t.length,a=[],o=0,s=e[0],c=0;i>0&&s>0&&(c+s+1>r&&(s=Math.max(1,r-c)),a.push(t.substring(i-=s,i+s)),!((c+=s+1)>r));)s=e[o=(o+1)%e.length];return a.reverse().join(n)}),i=void 0===t.currency?"":t.currency[0]+"",a=void 0===t.currency?"":t.currency[1]+"",o=void 0===t.decimal?".":t.decimal+"",s=void 0===t.numerals?Qs:function(t){return function(e){return e.replace(/[0-9]/g,(function(e){return t[+e]}))}}(tc.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",u=void 0===t.minus?"-":t.minus+"",l=void 0===t.nan?"NaN":t.nan+"";function h(t){var e=(t=Hs(t)).fill,n=t.align,h=t.sign,f=t.symbol,d=t.zero,p=t.width,g=t.comma,y=t.precision,v=t.trim,m=t.type;"n"===m?(g=!0,m="g"):Ks[m]||(void 0===y&&(y=12),v=!0,m="g"),(d||"0"===e&&"="===n)&&(d=!0,e="0",n="=");var b="$"===f?i:"#"===f&&/[boxX]/.test(m)?"0"+m.toLowerCase():"",x="$"===f?a:/[%p]/.test(m)?c:"",_=Ks[m],k=/[defgprs%]/.test(m);function w(t){var i,a,c,f=b,w=x;if("c"===m)w=_(t)+w,t="";else{var E=(t=+t)<0;if(t=isNaN(t)?l:_(Math.abs(t),y),v&&(t=function(t){t:for(var e,n=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(e+1):t}(t)),E&&0==+t&&(E=!1),f=(E?"("===h?h:u:"-"===h||"("===h?"":h)+f,w=("s"===m?ec[8+Gs/3]:"")+w+(E&&"("===h?")":""),k)for(i=-1,a=t.length;++i(c=t.charCodeAt(i))||c>57){w=(46===c?o+t.slice(i+1):t.slice(i))+w,t=t.slice(0,i);break}}g&&!d&&(t=r(t,1/0));var T=f.length+t.length+w.length,C=T>1)+f+t+w+C.slice(T);break;default:t=C+f+t+w}return s(t)}return y=void 0===y?6:/[gprs]/.test(m)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),w.toString=function(){return t+""},w}return{format:h,formatPrefix:function(t,e){var n=h(((t=Hs(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor($s(e)/3))),i=Math.pow(10,-r),a=ec[8+r/3];return function(t){return n(i*t)+a}}}};function rc(t){return qs=nc(t),Xs=qs.format,Zs=qs.formatPrefix,qs}rc({decimal:".",thousands:",",grouping:[3],currency:["$",""],minus:"-"});var ic=function(t){return Math.max(0,-$s(Math.abs(t)))},ac=function(t,e){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor($s(e)/3)))-$s(Math.abs(t)))},oc=function(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,$s(e)-$s(t))+1},sc=function(){return new cc};function cc(){this.reset()}cc.prototype={constructor:cc,reset:function(){this.s=this.t=0},add:function(t){lc(uc,t,this.t),lc(this,uc.s,this.s),this.s?this.t+=uc.t:this.s=uc.t},valueOf:function(){return this.s}};var uc=new cc;function lc(t,e,n){var r=t.s=e+n,i=r-e,a=r-i;t.t=e-a+(n-i)}var hc=Math.PI,fc=hc/2,dc=hc/4,pc=2*hc,gc=180/hc,yc=hc/180,vc=Math.abs,mc=Math.atan,bc=Math.atan2,xc=Math.cos,_c=Math.ceil,kc=Math.exp,wc=(Math.floor,Math.log),Ec=Math.pow,Tc=Math.sin,Cc=Math.sign||function(t){return t>0?1:t<0?-1:0},Ac=Math.sqrt,Sc=Math.tan;function Mc(t){return t>1?0:t<-1?hc:Math.acos(t)}function Oc(t){return t>1?fc:t<-1?-fc:Math.asin(t)}function Dc(t){return(t=Tc(t/2))*t}function Nc(){}function Bc(t,e){t&&Fc.hasOwnProperty(t.type)&&Fc[t.type](t,e)}var Lc={Feature:function(t,e){Bc(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r=0?1:-1,i=r*n,a=xc(e=(e*=yc)/2+dc),o=Tc(e),s=Uc*o,c=zc*a+s*xc(i),u=s*r*Tc(i);Wc.add(bc(u,c)),Yc=t,zc=a,Uc=o}var Jc=function(t){return Hc.reset(),$c(t,Vc),2*Hc};function Kc(t){return[bc(t[1],t[0]),Oc(t[2])]}function Qc(t){var e=t[0],n=t[1],r=xc(n);return[r*xc(e),r*Tc(e),Tc(n)]}function tu(t,e){return t[0]*e[0]+t[1]*e[1]+t[2]*e[2]}function eu(t,e){return[t[1]*e[2]-t[2]*e[1],t[2]*e[0]-t[0]*e[2],t[0]*e[1]-t[1]*e[0]]}function nu(t,e){t[0]+=e[0],t[1]+=e[1],t[2]+=e[2]}function ru(t,e){return[t[0]*e,t[1]*e,t[2]*e]}function iu(t){var e=Ac(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=e,t[1]/=e,t[2]/=e}var au,ou,su,cu,uu,lu,hu,fu,du,pu,gu=sc(),yu={point:vu,lineStart:bu,lineEnd:xu,polygonStart:function(){yu.point=_u,yu.lineStart=ku,yu.lineEnd=wu,gu.reset(),Vc.polygonStart()},polygonEnd:function(){Vc.polygonEnd(),yu.point=vu,yu.lineStart=bu,yu.lineEnd=xu,Wc<0?(au=-(su=180),ou=-(cu=90)):gu>1e-6?cu=90:gu<-1e-6&&(ou=-90),pu[0]=au,pu[1]=su},sphere:function(){au=-(su=180),ou=-(cu=90)}};function vu(t,e){du.push(pu=[au=t,su=t]),ecu&&(cu=e)}function mu(t,e){var n=Qc([t*yc,e*yc]);if(fu){var r=eu(fu,n),i=eu([r[1],-r[0],0],r);iu(i),i=Kc(i);var a,o=t-uu,s=o>0?1:-1,c=i[0]*gc*s,u=vc(o)>180;u^(s*uucu&&(cu=a):u^(s*uu<(c=(c+360)%360-180)&&ccu&&(cu=e)),u?tEu(au,su)&&(su=t):Eu(t,su)>Eu(au,su)&&(au=t):su>=au?(tsu&&(su=t)):t>uu?Eu(au,t)>Eu(au,su)&&(su=t):Eu(t,su)>Eu(au,su)&&(au=t)}else du.push(pu=[au=t,su=t]);ecu&&(cu=e),fu=n,uu=t}function bu(){yu.point=mu}function xu(){pu[0]=au,pu[1]=su,yu.point=vu,fu=null}function _u(t,e){if(fu){var n=t-uu;gu.add(vc(n)>180?n+(n>0?360:-360):n)}else lu=t,hu=e;Vc.point(t,e),mu(t,e)}function ku(){Vc.lineStart()}function wu(){_u(lu,hu),Vc.lineEnd(),vc(gu)>1e-6&&(au=-(su=180)),pu[0]=au,pu[1]=su,fu=null}function Eu(t,e){return(e-=t)<0?e+360:e}function Tu(t,e){return t[0]-e[0]}function Cu(t,e){return t[0]<=t[1]?t[0]<=e&&e<=t[1]:eEu(r[0],r[1])&&(r[1]=i[1]),Eu(i[0],r[1])>Eu(r[0],r[1])&&(r[0]=i[0])):a.push(r=i);for(o=-1/0,e=0,r=a[n=a.length-1];e<=n;r=i,++e)i=a[e],(s=Eu(r[1],i[0]))>o&&(o=s,au=i[0],su=r[1])}return du=pu=null,au===1/0||ou===1/0?[[NaN,NaN],[NaN,NaN]]:[[au,ou],[su,cu]]},Wu={sphere:Nc,point:Hu,lineStart:Gu,lineEnd:Zu,polygonStart:function(){Wu.lineStart=Ju,Wu.lineEnd=Ku},polygonEnd:function(){Wu.lineStart=Gu,Wu.lineEnd=Zu}};function Hu(t,e){t*=yc;var n=xc(e*=yc);Vu(n*xc(t),n*Tc(t),Tc(e))}function Vu(t,e,n){++Au,Mu+=(t-Mu)/Au,Ou+=(e-Ou)/Au,Du+=(n-Du)/Au}function Gu(){Wu.point=qu}function qu(t,e){t*=yc;var n=xc(e*=yc);Yu=n*xc(t),zu=n*Tc(t),Uu=Tc(e),Wu.point=Xu,Vu(Yu,zu,Uu)}function Xu(t,e){t*=yc;var n=xc(e*=yc),r=n*xc(t),i=n*Tc(t),a=Tc(e),o=bc(Ac((o=zu*a-Uu*i)*o+(o=Uu*r-Yu*a)*o+(o=Yu*i-zu*r)*o),Yu*r+zu*i+Uu*a);Su+=o,Nu+=o*(Yu+(Yu=r)),Bu+=o*(zu+(zu=i)),Lu+=o*(Uu+(Uu=a)),Vu(Yu,zu,Uu)}function Zu(){Wu.point=Hu}function Ju(){Wu.point=Qu}function Ku(){tl(ju,Ru),Wu.point=Hu}function Qu(t,e){ju=t,Ru=e,t*=yc,e*=yc,Wu.point=tl;var n=xc(e);Yu=n*xc(t),zu=n*Tc(t),Uu=Tc(e),Vu(Yu,zu,Uu)}function tl(t,e){t*=yc;var n=xc(e*=yc),r=n*xc(t),i=n*Tc(t),a=Tc(e),o=zu*a-Uu*i,s=Uu*r-Yu*a,c=Yu*i-zu*r,u=Ac(o*o+s*s+c*c),l=Oc(u),h=u&&-l/u;Fu+=h*o,Pu+=h*s,Iu+=h*c,Su+=l,Nu+=l*(Yu+(Yu=r)),Bu+=l*(zu+(zu=i)),Lu+=l*(Uu+(Uu=a)),Vu(Yu,zu,Uu)}var el=function(t){Au=Su=Mu=Ou=Du=Nu=Bu=Lu=Fu=Pu=Iu=0,$c(t,Wu);var e=Fu,n=Pu,r=Iu,i=e*e+n*n+r*r;return i<1e-12&&(e=Nu,n=Bu,r=Lu,Su<1e-6&&(e=Mu,n=Ou,r=Du),(i=e*e+n*n+r*r)<1e-12)?[NaN,NaN]:[bc(n,e)*gc,Oc(r/Ac(i))*gc]},nl=function(t){return function(){return t}},rl=function(t,e){function n(n,r){return n=t(n,r),e(n[0],n[1])}return t.invert&&e.invert&&(n.invert=function(n,r){return(n=e.invert(n,r))&&t.invert(n[0],n[1])}),n};function il(t,e){return[vc(t)>hc?t+Math.round(-t/pc)*pc:t,e]}function al(t,e,n){return(t%=pc)?e||n?rl(sl(t),cl(e,n)):sl(t):e||n?cl(e,n):il}function ol(t){return function(e,n){return[(e+=t)>hc?e-pc:e<-hc?e+pc:e,n]}}function sl(t){var e=ol(t);return e.invert=ol(-t),e}function cl(t,e){var n=xc(t),r=Tc(t),i=xc(e),a=Tc(e);function o(t,e){var o=xc(e),s=xc(t)*o,c=Tc(t)*o,u=Tc(e),l=u*n+s*r;return[bc(c*i-l*a,s*n-u*r),Oc(l*i+c*a)]}return o.invert=function(t,e){var o=xc(e),s=xc(t)*o,c=Tc(t)*o,u=Tc(e),l=u*i-c*a;return[bc(c*i+u*a,s*n+l*r),Oc(l*n-s*r)]},o}il.invert=il;var ul=function(t){function e(e){return(e=t(e[0]*yc,e[1]*yc))[0]*=gc,e[1]*=gc,e}return t=al(t[0]*yc,t[1]*yc,t.length>2?t[2]*yc:0),e.invert=function(e){return(e=t.invert(e[0]*yc,e[1]*yc))[0]*=gc,e[1]*=gc,e},e};function ll(t,e,n,r,i,a){if(n){var o=xc(e),s=Tc(e),c=r*n;null==i?(i=e+r*pc,a=e-c/2):(i=hl(o,i),a=hl(o,a),(r>0?ia)&&(i+=r*pc));for(var u,l=i;r>0?l>a:l1&&e.push(e.pop().concat(e.shift()))},result:function(){var n=e;return e=[],t=null,n}}},pl=function(t,e){return vc(t[0]-e[0])<1e-6&&vc(t[1]-e[1])<1e-6};function gl(t,e,n,r){this.x=t,this.z=e,this.o=n,this.e=r,this.v=!1,this.n=this.p=null}var yl=function(t,e,n,r,i){var a,o,s=[],c=[];if(t.forEach((function(t){if(!((e=t.length-1)<=0)){var e,n,r=t[0],o=t[e];if(pl(r,o)){for(i.lineStart(),a=0;a=0;--a)i.point((l=u[a])[0],l[1]);else r(f.x,f.p.x,-1,i);f=f.p}u=(f=f.o).z,d=!d}while(!f.v);i.lineEnd()}}};function vl(t){if(e=t.length){for(var e,n,r=0,i=t[0];++r=0?1:-1,T=E*w,C=T>hc,A=g*_;if(ml.add(bc(A*E*Tc(T),y*k+A*xc(T))),o+=C?w+E*pc:w,C^d>=n^b>=n){var S=eu(Qc(f),Qc(m));iu(S);var M=eu(a,S);iu(M);var O=(C^w>=0?-1:1)*Oc(M[2]);(r>O||r===O&&(S[0]||S[1]))&&(s+=C^w>=0?1:-1)}}return(o<-1e-6||o<1e-6&&ml<-1e-6)^1&s},_l=function(t,e,n,r){return function(i){var a,o,s,c=e(i),u=dl(),l=e(u),h=!1,f={point:d,lineStart:g,lineEnd:y,polygonStart:function(){f.point=v,f.lineStart=m,f.lineEnd=b,o=[],a=[]},polygonEnd:function(){f.point=d,f.lineStart=g,f.lineEnd=y,o=I(o);var t=xl(a,r);o.length?(h||(i.polygonStart(),h=!0),yl(o,wl,t,n,i)):t&&(h||(i.polygonStart(),h=!0),i.lineStart(),n(null,null,1,i),i.lineEnd()),h&&(i.polygonEnd(),h=!1),o=a=null},sphere:function(){i.polygonStart(),i.lineStart(),n(null,null,1,i),i.lineEnd(),i.polygonEnd()}};function d(e,n){t(e,n)&&i.point(e,n)}function p(t,e){c.point(t,e)}function g(){f.point=p,c.lineStart()}function y(){f.point=d,c.lineEnd()}function v(t,e){s.push([t,e]),l.point(t,e)}function m(){l.lineStart(),s=[]}function b(){v(s[0][0],s[0][1]),l.lineEnd();var t,e,n,r,c=l.clean(),f=u.result(),d=f.length;if(s.pop(),a.push(s),s=null,d)if(1&c){if((e=(n=f[0]).length-1)>0){for(h||(i.polygonStart(),h=!0),i.lineStart(),t=0;t1&&2&c&&f.push(f.pop().concat(f.shift())),o.push(f.filter(kl))}return f}};function kl(t){return t.length>1}function wl(t,e){return((t=t.x)[0]<0?t[1]-fc-1e-6:fc-t[1])-((e=e.x)[0]<0?e[1]-fc-1e-6:fc-e[1])}var El=_l((function(){return!0}),(function(t){var e,n=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),e=1},point:function(a,o){var s=a>0?hc:-hc,c=vc(a-n);vc(c-hc)<1e-6?(t.point(n,r=(r+o)/2>0?fc:-fc),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),t.point(a,r),e=0):i!==s&&c>=hc&&(vc(n-i)<1e-6&&(n-=1e-6*i),vc(a-s)<1e-6&&(a-=1e-6*s),r=function(t,e,n,r){var i,a,o=Tc(t-n);return vc(o)>1e-6?mc((Tc(e)*(a=xc(r))*Tc(n)-Tc(r)*(i=xc(e))*Tc(t))/(i*a*o)):(e+r)/2}(n,r,a,o),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(s,r),e=0),t.point(n=a,r=o),i=s},lineEnd:function(){t.lineEnd(),n=r=NaN},clean:function(){return 2-e}}}),(function(t,e,n,r){var i;if(null==t)i=n*fc,r.point(-hc,i),r.point(0,i),r.point(hc,i),r.point(hc,0),r.point(hc,-i),r.point(0,-i),r.point(-hc,-i),r.point(-hc,0),r.point(-hc,i);else if(vc(t[0]-e[0])>1e-6){var a=t[0]0,i=vc(e)>1e-6;function a(t,n){return xc(t)*xc(n)>e}function o(t,n,r){var i=[1,0,0],a=eu(Qc(t),Qc(n)),o=tu(a,a),s=a[0],c=o-s*s;if(!c)return!r&&t;var u=e*o/c,l=-e*s/c,h=eu(i,a),f=ru(i,u);nu(f,ru(a,l));var d=h,p=tu(f,d),g=tu(d,d),y=p*p-g*(tu(f,f)-1);if(!(y<0)){var v=Ac(y),m=ru(d,(-p-v)/g);if(nu(m,f),m=Kc(m),!r)return m;var b,x=t[0],_=n[0],k=t[1],w=n[1];_0^m[1]<(vc(m[0]-x)<1e-6?k:w):k<=m[1]&&m[1]<=w:E>hc^(x<=m[0]&&m[0]<=_)){var C=ru(d,(-p+v)/g);return nu(C,f),[m,Kc(C)]}}}function s(e,n){var i=r?t:hc-t,a=0;return e<-i?a|=1:e>i&&(a|=2),n<-i?a|=4:n>i&&(a|=8),a}return _l(a,(function(t){var e,n,c,u,l;return{lineStart:function(){u=c=!1,l=1},point:function(h,f){var d,p=[h,f],g=a(h,f),y=r?g?0:s(h,f):g?s(h+(h<0?hc:-hc),f):0;if(!e&&(u=c=g)&&t.lineStart(),g!==c&&(!(d=o(e,p))||pl(e,d)||pl(p,d))&&(p[0]+=1e-6,p[1]+=1e-6,g=a(p[0],p[1])),g!==c)l=0,g?(t.lineStart(),d=o(p,e),t.point(d[0],d[1])):(d=o(e,p),t.point(d[0],d[1]),t.lineEnd()),e=d;else if(i&&e&&r^g){var v;y&n||!(v=o(p,e,!0))||(l=0,r?(t.lineStart(),t.point(v[0][0],v[0][1]),t.point(v[1][0],v[1][1]),t.lineEnd()):(t.point(v[1][0],v[1][1]),t.lineEnd(),t.lineStart(),t.point(v[0][0],v[0][1])))}!g||e&&pl(e,p)||t.point(p[0],p[1]),e=p,c=g,n=y},lineEnd:function(){c&&t.lineEnd(),e=null},clean:function(){return l|(u&&c)<<1}}}),(function(e,r,i,a){ll(a,t,n,i,e,r)}),r?[0,-t]:[-hc,t-hc])};function Cl(t,e,n,r){function i(i,a){return t<=i&&i<=n&&e<=a&&a<=r}function a(i,a,s,u){var l=0,h=0;if(null==i||(l=o(i,s))!==(h=o(a,s))||c(i,a)<0^s>0)do{u.point(0===l||3===l?t:n,l>1?r:e)}while((l=(l+s+4)%4)!==h);else u.point(a[0],a[1])}function o(r,i){return vc(r[0]-t)<1e-6?i>0?0:3:vc(r[0]-n)<1e-6?i>0?2:1:vc(r[1]-e)<1e-6?i>0?1:0:i>0?3:2}function s(t,e){return c(t.x,e.x)}function c(t,e){var n=o(t,1),r=o(e,1);return n!==r?n-r:0===n?e[1]-t[1]:1===n?t[0]-e[0]:2===n?t[1]-e[1]:e[0]-t[0]}return function(o){var c,u,l,h,f,d,p,g,y,v,m,b=o,x=dl(),_={point:k,lineStart:function(){_.point=w,u&&u.push(l=[]);v=!0,y=!1,p=g=NaN},lineEnd:function(){c&&(w(h,f),d&&y&&x.rejoin(),c.push(x.result()));_.point=k,y&&b.lineEnd()},polygonStart:function(){b=x,c=[],u=[],m=!0},polygonEnd:function(){var e=function(){for(var e=0,n=0,i=u.length;nr&&(f-a)*(r-o)>(d-o)*(t-a)&&++e:d<=r&&(f-a)*(r-o)<(d-o)*(t-a)&&--e;return e}(),n=m&&e,i=(c=I(c)).length;(n||i)&&(o.polygonStart(),n&&(o.lineStart(),a(null,null,1,o),o.lineEnd()),i&&yl(c,s,e,a,o),o.polygonEnd());b=o,c=u=l=null}};function k(t,e){i(t,e)&&b.point(t,e)}function w(a,o){var s=i(a,o);if(u&&l.push([a,o]),v)h=a,f=o,d=s,v=!1,s&&(b.lineStart(),b.point(a,o));else if(s&&y)b.point(a,o);else{var c=[p=Math.max(-1e9,Math.min(1e9,p)),g=Math.max(-1e9,Math.min(1e9,g))],x=[a=Math.max(-1e9,Math.min(1e9,a)),o=Math.max(-1e9,Math.min(1e9,o))];!function(t,e,n,r,i,a){var o,s=t[0],c=t[1],u=0,l=1,h=e[0]-s,f=e[1]-c;if(o=n-s,h||!(o>0)){if(o/=h,h<0){if(o0){if(o>l)return;o>u&&(u=o)}if(o=i-s,h||!(o<0)){if(o/=h,h<0){if(o>l)return;o>u&&(u=o)}else if(h>0){if(o0)){if(o/=f,f<0){if(o0){if(o>l)return;o>u&&(u=o)}if(o=a-c,f||!(o<0)){if(o/=f,f<0){if(o>l)return;o>u&&(u=o)}else if(f>0){if(o0&&(t[0]=s+u*h,t[1]=c+u*f),l<1&&(e[0]=s+l*h,e[1]=c+l*f),!0}}}}}(c,x,t,e,n,r)?s&&(b.lineStart(),b.point(a,o),m=!1):(y||(b.lineStart(),b.point(c[0],c[1])),b.point(x[0],x[1]),s||b.lineEnd(),m=!1)}p=a,g=o,y=s}return _}}var Al,Sl,Ml,Ol=function(){var t,e,n,r=0,i=0,a=960,o=500;return n={stream:function(n){return t&&e===n?t:t=Cl(r,i,a,o)(e=n)},extent:function(s){return arguments.length?(r=+s[0][0],i=+s[0][1],a=+s[1][0],o=+s[1][1],t=e=null,n):[[r,i],[a,o]]}}},Dl=sc(),Nl={sphere:Nc,point:Nc,lineStart:function(){Nl.point=Ll,Nl.lineEnd=Bl},lineEnd:Nc,polygonStart:Nc,polygonEnd:Nc};function Bl(){Nl.point=Nl.lineEnd=Nc}function Ll(t,e){Al=t*=yc,Sl=Tc(e*=yc),Ml=xc(e),Nl.point=Fl}function Fl(t,e){t*=yc;var n=Tc(e*=yc),r=xc(e),i=vc(t-Al),a=xc(i),o=r*Tc(i),s=Ml*n-Sl*r*a,c=Sl*n+Ml*r*a;Dl.add(bc(Ac(o*o+s*s),c)),Al=t,Sl=n,Ml=r}var Pl=function(t){return Dl.reset(),$c(t,Nl),+Dl},Il=[null,null],jl={type:"LineString",coordinates:Il},Rl=function(t,e){return Il[0]=t,Il[1]=e,Pl(jl)},Yl={Feature:function(t,e){return Ul(t.geometry,e)},FeatureCollection:function(t,e){for(var n=t.features,r=-1,i=n.length;++r0&&(i=Rl(t[a],t[a-1]))>0&&n<=i&&r<=i&&(n+r-i)*(1-Math.pow((n-r)/i,2))<1e-12*i)return!0;n=r}return!1}function Hl(t,e){return!!xl(t.map(Vl),Gl(e))}function Vl(t){return(t=t.map(Gl)).pop(),t}function Gl(t){return[t[0]*yc,t[1]*yc]}var ql=function(t,e){return(t&&Yl.hasOwnProperty(t.type)?Yl[t.type]:Ul)(t,e)};function Xl(t,e,n){var r=k(t,e-1e-6,n).concat(e);return function(t){return r.map((function(e){return[t,e]}))}}function Zl(t,e,n){var r=k(t,e-1e-6,n).concat(e);return function(t){return r.map((function(e){return[e,t]}))}}function Jl(){var t,e,n,r,i,a,o,s,c,u,l,h,f=10,d=f,p=90,g=360,y=2.5;function v(){return{type:"MultiLineString",coordinates:m()}}function m(){return k(_c(r/p)*p,n,p).map(l).concat(k(_c(s/g)*g,o,g).map(h)).concat(k(_c(e/f)*f,t,f).filter((function(t){return vc(t%p)>1e-6})).map(c)).concat(k(_c(a/d)*d,i,d).filter((function(t){return vc(t%g)>1e-6})).map(u))}return v.lines=function(){return m().map((function(t){return{type:"LineString",coordinates:t}}))},v.outline=function(){return{type:"Polygon",coordinates:[l(r).concat(h(o).slice(1),l(n).reverse().slice(1),h(s).reverse().slice(1))]}},v.extent=function(t){return arguments.length?v.extentMajor(t).extentMinor(t):v.extentMinor()},v.extentMajor=function(t){return arguments.length?(r=+t[0][0],n=+t[1][0],s=+t[0][1],o=+t[1][1],r>n&&(t=r,r=n,n=t),s>o&&(t=s,s=o,o=t),v.precision(y)):[[r,s],[n,o]]},v.extentMinor=function(n){return arguments.length?(e=+n[0][0],t=+n[1][0],a=+n[0][1],i=+n[1][1],e>t&&(n=e,e=t,t=n),a>i&&(n=a,a=i,i=n),v.precision(y)):[[e,a],[t,i]]},v.step=function(t){return arguments.length?v.stepMajor(t).stepMinor(t):v.stepMinor()},v.stepMajor=function(t){return arguments.length?(p=+t[0],g=+t[1],v):[p,g]},v.stepMinor=function(t){return arguments.length?(f=+t[0],d=+t[1],v):[f,d]},v.precision=function(f){return arguments.length?(y=+f,c=Xl(a,i,90),u=Zl(e,t,y),l=Xl(s,o,90),h=Zl(r,n,y),v):y},v.extentMajor([[-180,1e-6-90],[180,90-1e-6]]).extentMinor([[-180,-80-1e-6],[180,80+1e-6]])}function Kl(){return Jl()()}var Ql,th,eh,nh,rh=function(t,e){var n=t[0]*yc,r=t[1]*yc,i=e[0]*yc,a=e[1]*yc,o=xc(r),s=Tc(r),c=xc(a),u=Tc(a),l=o*xc(n),h=o*Tc(n),f=c*xc(i),d=c*Tc(i),p=2*Oc(Ac(Dc(a-r)+o*c*Dc(i-n))),g=Tc(p),y=p?function(t){var e=Tc(t*=p)/g,n=Tc(p-t)/g,r=n*l+e*f,i=n*h+e*d,a=n*s+e*u;return[bc(i,r)*gc,bc(a,Ac(r*r+i*i))*gc]}:function(){return[n*gc,r*gc]};return y.distance=p,y},ih=function(t){return t},ah=sc(),oh=sc(),sh={point:Nc,lineStart:Nc,lineEnd:Nc,polygonStart:function(){sh.lineStart=ch,sh.lineEnd=hh},polygonEnd:function(){sh.lineStart=sh.lineEnd=sh.point=Nc,ah.add(vc(oh)),oh.reset()},result:function(){var t=ah/2;return ah.reset(),t}};function ch(){sh.point=uh}function uh(t,e){sh.point=lh,Ql=eh=t,th=nh=e}function lh(t,e){oh.add(nh*t-eh*e),eh=t,nh=e}function hh(){lh(Ql,th)}var fh=sh,dh=1/0,ph=dh,gh=-dh,yh=gh;var vh,mh,bh,xh,_h={point:function(t,e){tgh&&(gh=t);eyh&&(yh=e)},lineStart:Nc,lineEnd:Nc,polygonStart:Nc,polygonEnd:Nc,result:function(){var t=[[dh,ph],[gh,yh]];return gh=yh=-(ph=dh=1/0),t}},kh=0,wh=0,Eh=0,Th=0,Ch=0,Ah=0,Sh=0,Mh=0,Oh=0,Dh={point:Nh,lineStart:Bh,lineEnd:Ph,polygonStart:function(){Dh.lineStart=Ih,Dh.lineEnd=jh},polygonEnd:function(){Dh.point=Nh,Dh.lineStart=Bh,Dh.lineEnd=Ph},result:function(){var t=Oh?[Sh/Oh,Mh/Oh]:Ah?[Th/Ah,Ch/Ah]:Eh?[kh/Eh,wh/Eh]:[NaN,NaN];return kh=wh=Eh=Th=Ch=Ah=Sh=Mh=Oh=0,t}};function Nh(t,e){kh+=t,wh+=e,++Eh}function Bh(){Dh.point=Lh}function Lh(t,e){Dh.point=Fh,Nh(bh=t,xh=e)}function Fh(t,e){var n=t-bh,r=e-xh,i=Ac(n*n+r*r);Th+=i*(bh+t)/2,Ch+=i*(xh+e)/2,Ah+=i,Nh(bh=t,xh=e)}function Ph(){Dh.point=Nh}function Ih(){Dh.point=Rh}function jh(){Yh(vh,mh)}function Rh(t,e){Dh.point=Yh,Nh(vh=bh=t,mh=xh=e)}function Yh(t,e){var n=t-bh,r=e-xh,i=Ac(n*n+r*r);Th+=i*(bh+t)/2,Ch+=i*(xh+e)/2,Ah+=i,Sh+=(i=xh*t-bh*e)*(bh+t),Mh+=i*(xh+e),Oh+=3*i,Nh(bh=t,xh=e)}var zh=Dh;function Uh(t){this._context=t}Uh.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._context.moveTo(t,e),this._point=1;break;case 1:this._context.lineTo(t,e);break;default:this._context.moveTo(t+this._radius,e),this._context.arc(t,e,this._radius,0,pc)}},result:Nc};var $h,Wh,Hh,Vh,Gh,qh=sc(),Xh={point:Nc,lineStart:function(){Xh.point=Zh},lineEnd:function(){$h&&Jh(Wh,Hh),Xh.point=Nc},polygonStart:function(){$h=!0},polygonEnd:function(){$h=null},result:function(){var t=+qh;return qh.reset(),t}};function Zh(t,e){Xh.point=Jh,Wh=Vh=t,Hh=Gh=e}function Jh(t,e){Vh-=t,Gh-=e,qh.add(Ac(Vh*Vh+Gh*Gh)),Vh=t,Gh=e}var Kh=Xh;function Qh(){this._string=[]}function tf(t){return"m0,"+t+"a"+t+","+t+" 0 1,1 0,"+-2*t+"a"+t+","+t+" 0 1,1 0,"+2*t+"z"}Qh.prototype={_radius:4.5,_circle:tf(4.5),pointRadius:function(t){return(t=+t)!==this._radius&&(this._radius=t,this._circle=null),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,e){switch(this._point){case 0:this._string.push("M",t,",",e),this._point=1;break;case 1:this._string.push("L",t,",",e);break;default:null==this._circle&&(this._circle=tf(this._radius)),this._string.push("M",t,",",e,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}return null}};var ef=function(t,e){var n,r,i=4.5;function a(t){return t&&("function"==typeof i&&r.pointRadius(+i.apply(this,arguments)),$c(t,n(r))),r.result()}return a.area=function(t){return $c(t,n(fh)),fh.result()},a.measure=function(t){return $c(t,n(Kh)),Kh.result()},a.bounds=function(t){return $c(t,n(_h)),_h.result()},a.centroid=function(t){return $c(t,n(zh)),zh.result()},a.projection=function(e){return arguments.length?(n=null==e?(t=null,ih):(t=e).stream,a):t},a.context=function(t){return arguments.length?(r=null==t?(e=null,new Qh):new Uh(e=t),"function"!=typeof i&&r.pointRadius(i),a):e},a.pointRadius=function(t){return arguments.length?(i="function"==typeof t?t:(r.pointRadius(+t),+t),a):i},a.projection(t).context(e)},nf=function(t){return{stream:rf(t)}};function rf(t){return function(e){var n=new af;for(var r in t)n[r]=t[r];return n.stream=e,n}}function af(){}function of(t,e,n){var r=t.clipExtent&&t.clipExtent();return t.scale(150).translate([0,0]),null!=r&&t.clipExtent(null),$c(n,t.stream(_h)),e(_h.result()),null!=r&&t.clipExtent(r),t}function sf(t,e,n){return of(t,(function(n){var r=e[1][0]-e[0][0],i=e[1][1]-e[0][1],a=Math.min(r/(n[1][0]-n[0][0]),i/(n[1][1]-n[0][1])),o=+e[0][0]+(r-a*(n[1][0]+n[0][0]))/2,s=+e[0][1]+(i-a*(n[1][1]+n[0][1]))/2;t.scale(150*a).translate([o,s])}),n)}function cf(t,e,n){return sf(t,[[0,0],e],n)}function uf(t,e,n){return of(t,(function(n){var r=+e,i=r/(n[1][0]-n[0][0]),a=(r-i*(n[1][0]+n[0][0]))/2,o=-i*n[0][1];t.scale(150*i).translate([a,o])}),n)}function lf(t,e,n){return of(t,(function(n){var r=+e,i=r/(n[1][1]-n[0][1]),a=-i*n[0][0],o=(r-i*(n[1][1]+n[0][1]))/2;t.scale(150*i).translate([a,o])}),n)}af.prototype={constructor:af,point:function(t,e){this.stream.point(t,e)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};var hf=xc(30*yc),ff=function(t,e){return+e?function(t,e){function n(r,i,a,o,s,c,u,l,h,f,d,p,g,y){var v=u-r,m=l-i,b=v*v+m*m;if(b>4*e&&g--){var x=o+f,_=s+d,k=c+p,w=Ac(x*x+_*_+k*k),E=Oc(k/=w),T=vc(vc(k)-1)<1e-6||vc(a-h)<1e-6?(a+h)/2:bc(_,x),C=t(T,E),A=C[0],S=C[1],M=A-r,O=S-i,D=m*M-v*O;(D*D/b>e||vc((v*M+m*O)/b-.5)>.3||o*f+s*d+c*p2?t[2]%360*yc:0,A()):[y*gc,v*gc,m*gc]},T.angle=function(t){return arguments.length?(b=t%360*yc,A()):b*gc},T.precision=function(t){return arguments.length?(o=ff(s,E=t*t),S()):Ac(E)},T.fitExtent=function(t,e){return sf(T,t,e)},T.fitSize=function(t,e){return cf(T,t,e)},T.fitWidth=function(t,e){return uf(T,t,e)},T.fitHeight=function(t,e){return lf(T,t,e)},function(){return e=t.apply(this,arguments),T.invert=e.invert&&C,A()}}function mf(t){var e=0,n=hc/3,r=vf(t),i=r(e,n);return i.parallels=function(t){return arguments.length?r(e=t[0]*yc,n=t[1]*yc):[e*gc,n*gc]},i}function bf(t,e){var n=Tc(t),r=(n+Tc(e))/2;if(vc(r)<1e-6)return function(t){var e=xc(t);function n(t,n){return[t*e,Tc(n)/e]}return n.invert=function(t,n){return[t/e,Oc(n*e)]},n}(t);var i=1+n*(2*r-n),a=Ac(i)/r;function o(t,e){var n=Ac(i-2*r*Tc(e))/r;return[n*Tc(t*=r),a-n*xc(t)]}return o.invert=function(t,e){var n=a-e;return[bc(t,vc(n))/r*Cc(n),Oc((i-(t*t+n*n)*r*r)/(2*r))]},o}var xf=function(){return mf(bf).scale(155.424).center([0,33.6442])},_f=function(){return xf().parallels([29.5,45.5]).scale(1070).translate([480,250]).rotate([96,0]).center([-.6,38.7])};var kf=function(){var t,e,n,r,i,a,o=_f(),s=xf().rotate([154,0]).center([-2,58.5]).parallels([55,65]),c=xf().rotate([157,0]).center([-3,19.9]).parallels([8,18]),u={point:function(t,e){a=[t,e]}};function l(t){var e=t[0],o=t[1];return a=null,n.point(e,o),a||(r.point(e,o),a)||(i.point(e,o),a)}function h(){return t=e=null,l}return l.invert=function(t){var e=o.scale(),n=o.translate(),r=(t[0]-n[0])/e,i=(t[1]-n[1])/e;return(i>=.12&&i<.234&&r>=-.425&&r<-.214?s:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:o).invert(t)},l.stream=function(n){return t&&e===n?t:(r=[o.stream(e=n),s.stream(n),c.stream(n)],i=r.length,t={point:function(t,e){for(var n=-1;++n0?e<1e-6-fc&&(e=1e-6-fc):e>fc-1e-6&&(e=fc-1e-6);var n=i/Ec(Nf(e),r);return[n*Tc(r*t),i-n*xc(r*t)]}return a.invert=function(t,e){var n=i-e,a=Cc(r)*Ac(t*t+n*n);return[bc(t,vc(n))/r*Cc(n),2*mc(Ec(i/a,1/r))-fc]},a}var Lf=function(){return mf(Bf).scale(109.5).parallels([30,30])};function Ff(t,e){return[t,e]}Ff.invert=Ff;var Pf=function(){return yf(Ff).scale(152.63)};function If(t,e){var n=xc(t),r=t===e?Tc(t):(n-xc(e))/(e-t),i=n/r+t;if(vc(r)<1e-6)return Ff;function a(t,e){var n=i-e,a=r*t;return[n*Tc(a),i-n*xc(a)]}return a.invert=function(t,e){var n=i-e;return[bc(t,vc(n))/r*Cc(n),i-Cc(r)*Ac(t*t+n*n)]},a}var jf=function(){return mf(If).scale(131.154).center([0,13.9389])},Rf=1.340264,Yf=-.081106,zf=893e-6,Uf=.003796,$f=Ac(3)/2;function Wf(t,e){var n=Oc($f*Tc(e)),r=n*n,i=r*r*r;return[t*xc(n)/($f*(Rf+3*Yf*r+i*(7*zf+9*Uf*r))),n*(Rf+Yf*r+i*(zf+Uf*r))]}Wf.invert=function(t,e){for(var n,r=e,i=r*r,a=i*i*i,o=0;o<12&&(a=(i=(r-=n=(r*(Rf+Yf*i+a*(zf+Uf*i))-e)/(Rf+3*Yf*i+a*(7*zf+9*Uf*i)))*r)*i*i,!(vc(n)<1e-12));++o);return[$f*t*(Rf+3*Yf*i+a*(7*zf+9*Uf*i))/xc(r),Oc(Tc(r)/$f)]};var Hf=function(){return yf(Wf).scale(177.158)};function Vf(t,e){var n=xc(e),r=xc(t)*n;return[n*Tc(t)/r,Tc(e)/r]}Vf.invert=Ef(mc);var Gf=function(){return yf(Vf).scale(144.049).clipAngle(60)};function qf(t,e,n,r){return 1===t&&1===e&&0===n&&0===r?ih:rf({point:function(i,a){this.stream.point(i*t+n,a*e+r)}})}var Xf=function(){var t,e,n,r,i,a,o=1,s=0,c=0,u=1,l=1,h=ih,f=null,d=ih;function p(){return r=i=null,a}return a={stream:function(t){return r&&i===t?r:r=h(d(i=t))},postclip:function(r){return arguments.length?(d=r,f=t=e=n=null,p()):d},clipExtent:function(r){return arguments.length?(d=null==r?(f=t=e=n=null,ih):Cl(f=+r[0][0],t=+r[0][1],e=+r[1][0],n=+r[1][1]),p()):null==f?null:[[f,t],[e,n]]},scale:function(t){return arguments.length?(h=qf((o=+t)*u,o*l,s,c),p()):o},translate:function(t){return arguments.length?(h=qf(o*u,o*l,s=+t[0],c=+t[1]),p()):[s,c]},reflectX:function(t){return arguments.length?(h=qf(o*(u=t?-1:1),o*l,s,c),p()):u<0},reflectY:function(t){return arguments.length?(h=qf(o*u,o*(l=t?-1:1),s,c),p()):l<0},fitExtent:function(t,e){return sf(a,t,e)},fitSize:function(t,e){return cf(a,t,e)},fitWidth:function(t,e){return uf(a,t,e)},fitHeight:function(t,e){return lf(a,t,e)}}};function Zf(t,e){var n=e*e,r=n*n;return[t*(.8707-.131979*n+r*(r*(.003971*n-.001529*r)-.013791)),e*(1.007226+n*(.015085+r*(.028874*n-.044475-.005916*r)))]}Zf.invert=function(t,e){var n,r=e,i=25;do{var a=r*r,o=a*a;r-=n=(r*(1.007226+a*(.015085+o*(.028874*a-.044475-.005916*o)))-e)/(1.007226+a*(.045255+o*(.259866*a-.311325-.005916*11*o)))}while(vc(n)>1e-6&&--i>0);return[t/(.8707+(a=r*r)*(a*(a*a*a*(.003971-.001529*a)-.013791)-.131979)),r]};var Jf=function(){return yf(Zf).scale(175.295)};function Kf(t,e){return[xc(e)*Tc(t),Tc(e)]}Kf.invert=Ef(Oc);var Qf=function(){return yf(Kf).scale(249.5).clipAngle(90+1e-6)};function td(t,e){var n=xc(e),r=1+xc(t)*n;return[n*Tc(t)/r,Tc(e)/r]}td.invert=Ef((function(t){return 2*mc(t)}));var ed=function(){return yf(td).scale(250).clipAngle(142)};function nd(t,e){return[wc(Sc((fc+e)/2)),-t]}nd.invert=function(t,e){return[-e,2*mc(kc(t))-fc]};var rd=function(){var t=Df(nd),e=t.center,n=t.rotate;return t.center=function(t){return arguments.length?e([-t[1],t[0]]):[(t=e())[1],-t[0]]},t.rotate=function(t){return arguments.length?n([t[0],t[1],t.length>2?t[2]+90:90]):[(t=n())[0],t[1],t[2]-90]},n([0,0,90]).scale(159.155)};function id(t,e){return t.parent===e.parent?1:2}function ad(t,e){return t+e.x}function od(t,e){return Math.max(t,e.y)}var sd=function(){var t=id,e=1,n=1,r=!1;function i(i){var a,o=0;i.eachAfter((function(e){var n=e.children;n?(e.x=function(t){return t.reduce(ad,0)/t.length}(n),e.y=function(t){return 1+t.reduce(od,0)}(n)):(e.x=a?o+=t(e,a):0,e.y=0,a=e)}));var s=function(t){for(var e;e=t.children;)t=e[0];return t}(i),c=function(t){for(var e;e=t.children;)t=e[e.length-1];return t}(i),u=s.x-t(s,c)/2,l=c.x+t(c,s)/2;return i.eachAfter(r?function(t){t.x=(t.x-i.x)*e,t.y=(i.y-t.y)*n}:function(t){t.x=(t.x-u)/(l-u)*e,t.y=(1-(i.y?t.y/i.y:1))*n})}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i};function cd(t){var e=0,n=t.children,r=n&&n.length;if(r)for(;--r>=0;)e+=n[r].value;else e=1;t.value=e}function ud(t,e){var n,r,i,a,o,s=new dd(t),c=+t.value&&(s.value=t.value),u=[s];for(null==e&&(e=ld);n=u.pop();)if(c&&(n.value=+n.data.value),(i=e(n.data))&&(o=i.length))for(n.children=new Array(o),a=o-1;a>=0;--a)u.push(r=n.children[a]=new dd(i[a])),r.parent=n,r.depth=n.depth+1;return s.eachBefore(fd)}function ld(t){return t.children}function hd(t){t.data=t.data.data}function fd(t){var e=0;do{t.height=e}while((t=t.parent)&&t.height<++e)}function dd(t){this.data=t,this.depth=this.height=0,this.parent=null}dd.prototype=ud.prototype={constructor:dd,count:function(){return this.eachAfter(cd)},each:function(t){var e,n,r,i,a=this,o=[a];do{for(e=o.reverse(),o=[];a=e.pop();)if(t(a),n=a.children)for(r=0,i=n.length;r=0;--n)i.push(e[n]);return this},sum:function(t){return this.eachAfter((function(e){for(var n=+t(e.data)||0,r=e.children,i=r&&r.length;--i>=0;)n+=r[i].value;e.value=n}))},sort:function(t){return this.eachBefore((function(e){e.children&&e.children.sort(t)}))},path:function(t){for(var e=this,n=function(t,e){if(t===e)return t;var n=t.ancestors(),r=e.ancestors(),i=null;t=n.pop(),e=r.pop();for(;t===e;)i=t,t=n.pop(),e=r.pop();return i}(e,t),r=[e];e!==n;)e=e.parent,r.push(e);for(var i=r.length;t!==n;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e},descendants:function(){var t=[];return this.each((function(e){t.push(e)})),t},leaves:function(){var t=[];return this.eachBefore((function(e){e.children||t.push(e)})),t},links:function(){var t=this,e=[];return t.each((function(n){n!==t&&e.push({source:n.parent,target:n})})),e},copy:function(){return ud(this).eachBefore(hd)}};var pd=Array.prototype.slice;var gd=function(t){for(var e,n,r=0,i=(t=function(t){for(var e,n,r=t.length;r;)n=Math.random()*r--|0,e=t[r],t[r]=t[n],t[n]=e;return t}(pd.call(t))).length,a=[];r0&&n*n>r*r+i*i}function bd(t,e){for(var n=0;n(o*=o)?(r=(u+o-i)/(2*u),a=Math.sqrt(Math.max(0,o/u-r*r)),n.x=t.x-r*s-a*c,n.y=t.y-r*c+a*s):(r=(u+i-o)/(2*u),a=Math.sqrt(Math.max(0,i/u-r*r)),n.x=e.x+r*s-a*c,n.y=e.y+r*c+a*s)):(n.x=e.x+n.r,n.y=e.y)}function Ed(t,e){var n=t.r+e.r-1e-6,r=e.x-t.x,i=e.y-t.y;return n>0&&n*n>r*r+i*i}function Td(t){var e=t._,n=t.next._,r=e.r+n.r,i=(e.x*n.r+n.x*e.r)/r,a=(e.y*n.r+n.y*e.r)/r;return i*i+a*a}function Cd(t){this._=t,this.next=null,this.previous=null}function Ad(t){if(!(i=t.length))return 0;var e,n,r,i,a,o,s,c,u,l,h;if((e=t[0]).x=0,e.y=0,!(i>1))return e.r;if(n=t[1],e.x=-n.r,n.x=e.r,n.y=0,!(i>2))return e.r+n.r;wd(n,e,r=t[2]),e=new Cd(e),n=new Cd(n),r=new Cd(r),e.next=r.previous=n,n.next=e.previous=r,r.next=n.previous=e;t:for(s=3;s0)throw new Error("cycle");return a}return n.id=function(e){return arguments.length?(t=Od(e),n):t},n.parentId=function(t){return arguments.length?(e=Od(t),n):e},n};function Vd(t,e){return t.parent===e.parent?1:2}function Gd(t){var e=t.children;return e?e[0]:t.t}function qd(t){var e=t.children;return e?e[e.length-1]:t.t}function Xd(t,e,n){var r=n/(e.i-t.i);e.c-=r,e.s+=n,t.c+=r,e.z+=n,e.m+=n}function Zd(t,e,n){return t.a.parent===e.parent?t.a:n}function Jd(t,e){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=e}Jd.prototype=Object.create(dd.prototype);var Kd=function(){var t=Vd,e=1,n=1,r=null;function i(i){var c=function(t){for(var e,n,r,i,a,o=new Jd(t,0),s=[o];e=s.pop();)if(r=e._.children)for(e.children=new Array(a=r.length),i=a-1;i>=0;--i)s.push(n=e.children[i]=new Jd(r[i],i)),n.parent=e;return(o.parent=new Jd(null,0)).children=[o],o}(i);if(c.eachAfter(a),c.parent.m=-c.z,c.eachBefore(o),r)i.eachBefore(s);else{var u=i,l=i,h=i;i.eachBefore((function(t){t.xl.x&&(l=t),t.depth>h.depth&&(h=t)}));var f=u===l?1:t(u,l)/2,d=f-u.x,p=e/(l.x+f+d),g=n/(h.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*g}))}return i}function a(e){var n=e.children,r=e.parent.children,i=e.i?r[e.i-1]:null;if(n){!function(t){for(var e,n=0,r=0,i=t.children,a=i.length;--a>=0;)(e=i[a]).z+=n,e.m+=n,n+=e.s+(r+=e.c)}(e);var a=(n[0].z+n[n.length-1].z)/2;i?(e.z=i.z+t(e._,i._),e.m=e.z-a):e.z=a}else i&&(e.z=i.z+t(e._,i._));e.parent.A=function(e,n,r){if(n){for(var i,a=e,o=e,s=n,c=a.parent.children[0],u=a.m,l=o.m,h=s.m,f=c.m;s=qd(s),a=Gd(a),s&&a;)c=Gd(c),(o=qd(o)).a=e,(i=s.z+h-a.z-u+t(s._,a._))>0&&(Xd(Zd(s,e,r),e,i),u+=i,l+=i),h+=s.m,u+=a.m,f+=c.m,l+=o.m;s&&!qd(o)&&(o.t=s,o.m+=h-l),a&&!Gd(c)&&(c.t=a,c.m+=u-f,r=e)}return r}(e,i,e.parent.A||r[0])}function o(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function s(t){t.x*=e,t.y=t.depth*n}return i.separation=function(e){return arguments.length?(t=e,i):t},i.size=function(t){return arguments.length?(r=!1,e=+t[0],n=+t[1],i):r?null:[e,n]},i.nodeSize=function(t){return arguments.length?(r=!0,e=+t[0],n=+t[1],i):r?[e,n]:null},i},Qd=function(t,e,n,r,i){for(var a,o=t.children,s=-1,c=o.length,u=t.value&&(i-n)/t.value;++sf&&(f=s),y=l*l*g,(d=Math.max(f/y,y/h))>p){l-=s;break}p=d}v.push(o={value:l,dice:c1?e:1)},n}(tp),rp=function(){var t=np,e=!1,n=1,r=1,i=[0],a=Dd,o=Dd,s=Dd,c=Dd,u=Dd;function l(t){return t.x0=t.y0=0,t.x1=n,t.y1=r,t.eachBefore(h),i=[0],e&&t.eachBefore(jd),t}function h(e){var n=i[e.depth],r=e.x0+n,l=e.y0+n,h=e.x1-n,f=e.y1-n;h=n-1){var l=s[e];return l.x0=i,l.y0=a,l.x1=o,void(l.y1=c)}var h=u[e],f=r/2+h,d=e+1,p=n-1;for(;d>>1;u[g]c-a){var m=(i*v+o*y)/r;t(e,d,y,i,a,m,c),t(d,n,v,m,a,o,c)}else{var b=(a*v+c*y)/r;t(e,d,y,i,a,o,b),t(d,n,v,i,b,o,c)}}(0,c,t.value,e,n,r,i)},ap=function(t,e,n,r,i){(1&t.depth?Qd:Rd)(t,e,n,r,i)},op=function t(e){function n(t,n,r,i,a){if((o=t._squarify)&&o.ratio===e)for(var o,s,c,u,l,h=-1,f=o.length,d=t.value;++h1?e:1)},n}(tp),sp=function(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}},cp=function(t,e){var n=un(+t,+e);return function(t){var e=n(t);return e-360*Math.floor(e/360)}},up=function(t,e){return t=+t,e=+e,function(n){return Math.round(t*(1-n)+e*n)}},lp=Math.SQRT2;function hp(t){return((t=Math.exp(t))+1/t)/2}var fp=function(t,e){var n,r,i=t[0],a=t[1],o=t[2],s=e[0],c=e[1],u=e[2],l=s-i,h=c-a,f=l*l+h*h;if(f<1e-12)r=Math.log(u/o)/lp,n=function(t){return[i+t*l,a+t*h,o*Math.exp(lp*t*r)]};else{var d=Math.sqrt(f),p=(u*u-o*o+4*f)/(2*o*2*d),g=(u*u-o*o-4*f)/(2*u*2*d),y=Math.log(Math.sqrt(p*p+1)-p),v=Math.log(Math.sqrt(g*g+1)-g);r=(v-y)/lp,n=function(t){var e,n=t*r,s=hp(y),c=o/(2*d)*(s*(e=lp*n+y,((e=Math.exp(2*e))-1)/(e+1))-function(t){return((t=Math.exp(t))-1/t)/2}(y));return[i+c*l,a+c*h,o*s/hp(lp*n+y)]}}return n.duration=1e3*r,n};function dp(t){return function(e,n){var r=t((e=tn(e)).h,(n=tn(n)).h),i=hn(e.s,n.s),a=hn(e.l,n.l),o=hn(e.opacity,n.opacity);return function(t){return e.h=r(t),e.s=i(t),e.l=a(t),e.opacity=o(t),e+""}}}var pp=dp(un),gp=dp(hn);function yp(t,e){var n=hn((t=pa(t)).l,(e=pa(e)).l),r=hn(t.a,e.a),i=hn(t.b,e.b),a=hn(t.opacity,e.opacity);return function(e){return t.l=n(e),t.a=r(e),t.b=i(e),t.opacity=a(e),t+""}}function vp(t){return function(e,n){var r=t((e=ka(e)).h,(n=ka(n)).h),i=hn(e.c,n.c),a=hn(e.l,n.l),o=hn(e.opacity,n.opacity);return function(t){return e.h=r(t),e.c=i(t),e.l=a(t),e.opacity=o(t),e+""}}}var mp=vp(un),bp=vp(hn);function xp(t){return function e(n){function r(e,r){var i=t((e=Oa(e)).h,(r=Oa(r)).h),a=hn(e.s,r.s),o=hn(e.l,r.l),s=hn(e.opacity,r.opacity);return function(t){return e.h=i(t),e.s=a(t),e.l=o(Math.pow(t,n)),e.opacity=s(t),e+""}}return n=+n,r.gamma=e,r}(1)}var _p=xp(un),kp=xp(hn);function wp(t,e){for(var n=0,r=e.length-1,i=e[0],a=new Array(r<0?0:r);n1&&(e=t[a[o-2]],n=t[a[o-1]],r=t[s],(n[0]-e[0])*(r[1]-e[1])-(n[1]-e[1])*(r[0]-e[0])<=0);)--o;a[o++]=s}return a.slice(0,o)}var Mp=function(t){if((n=t.length)<3)return null;var e,n,r=new Array(n),i=new Array(n);for(e=0;e=0;--e)u.push(t[r[a[e]][2]]);for(e=+s;es!=u>s&&o<(c-n)*(s-r)/(u-r)+n&&(l=!l),c=n,u=r;return l},Dp=function(t){for(var e,n,r=-1,i=t.length,a=t[i-1],o=a[0],s=a[1],c=0;++r1);return t+n*a*Math.sqrt(-2*Math.log(i)/i)}}return n.source=t,n}(Np),Fp=function t(e){function n(){var t=Lp.source(e).apply(this,arguments);return function(){return Math.exp(t())}}return n.source=t,n}(Np),Pp=function t(e){function n(t){return function(){for(var n=0,r=0;rr&&(e=n,n=r,r=e),function(t){return Math.max(n,Math.min(r,t))}}function tg(t,e,n){var r=t[0],i=t[1],a=e[0],o=e[1];return i2?eg:tg,i=a=null,h}function h(e){return isNaN(e=+e)?n:(i||(i=r(o.map(t),s,c)))(t(u(e)))}return h.invert=function(n){return u(e((a||(a=r(s,o.map(t),_n)))(n)))},h.domain=function(t){return arguments.length?(o=Up.call(t,Xp),u===Jp||(u=Qp(o)),l()):o.slice()},h.range=function(t){return arguments.length?(s=$p.call(t),l()):s.slice()},h.rangeRound=function(t){return s=$p.call(t),c=up,l()},h.clamp=function(t){return arguments.length?(u=t?Qp(o):Jp,h):u!==Jp},h.interpolate=function(t){return arguments.length?(c=t,l()):c},h.unknown=function(t){return arguments.length?(n=t,h):n},function(n,r){return t=n,e=r,l()}}function ig(t,e){return rg()(t,e)}var ag=function(t,e,n,r){var i,a=S(t,e,n);switch((r=Hs(null==r?",f":r)).type){case"s":var o=Math.max(Math.abs(t),Math.abs(e));return null!=r.precision||isNaN(i=ac(a,o))||(r.precision=i),Zs(r,o);case"":case"e":case"g":case"p":case"r":null!=r.precision||isNaN(i=oc(a,Math.max(Math.abs(t),Math.abs(e))))||(r.precision=i-("e"===r.type));break;case"f":case"%":null!=r.precision||isNaN(i=ic(a))||(r.precision=i-2*("%"===r.type))}return Xs(r)};function og(t){var e=t.domain;return t.ticks=function(t){var n=e();return C(n[0],n[n.length-1],null==t?10:t)},t.tickFormat=function(t,n){var r=e();return ag(r[0],r[r.length-1],null==t?10:t,n)},t.nice=function(n){null==n&&(n=10);var r,i=e(),a=0,o=i.length-1,s=i[a],c=i[o];return c0?r=A(s=Math.floor(s/r)*r,c=Math.ceil(c/r)*r,n):r<0&&(r=A(s=Math.ceil(s*r)/r,c=Math.floor(c*r)/r,n)),r>0?(i[a]=Math.floor(s/r)*r,i[o]=Math.ceil(c/r)*r,e(i)):r<0&&(i[a]=Math.ceil(s*r)/r,i[o]=Math.floor(c*r)/r,e(i)),t},t}function sg(){var t=ig(Jp,Jp);return t.copy=function(){return ng(t,sg())},Rp.apply(t,arguments),og(t)}function cg(t){var e;function n(t){return isNaN(t=+t)?e:t}return n.invert=n,n.domain=n.range=function(e){return arguments.length?(t=Up.call(e,Xp),n):t.slice()},n.unknown=function(t){return arguments.length?(e=t,n):e},n.copy=function(){return cg(t).unknown(e)},t=arguments.length?Up.call(t,Xp):[0,1],og(n)}var ug=function(t,e){var n,r=0,i=(t=t.slice()).length-1,a=t[r],o=t[i];return o0){for(;fc)break;g.push(h)}}else for(;f=1;--l)if(!((h=u*l)c)break;g.push(h)}}else g=C(f,d,Math.min(d-f,p)).map(n);return r?g.reverse():g},r.tickFormat=function(t,i){if(null==i&&(i=10===a?".0e":","),"function"!=typeof i&&(i=Xs(i)),t===1/0)return i;null==t&&(t=10);var o=Math.max(1,a*t/r.ticks().length);return function(t){var r=t/n(Math.round(e(t)));return r*a0?i[r-1]:e[0],r=r?[i[r-1],n]:[i[o-1],i[o]]},o.unknown=function(e){return arguments.length?(t=e,o):o},o.thresholds=function(){return i.slice()},o.copy=function(){return Mg().domain([e,n]).range(a).unknown(t)},Rp.apply(og(o),arguments)}function Og(){var t,e=[.5],n=[0,1],r=1;function i(i){return i<=i?n[c(e,i,0,r)]:t}return i.domain=function(t){return arguments.length?(e=$p.call(t),r=Math.min(e.length,n.length-1),i):e.slice()},i.range=function(t){return arguments.length?(n=$p.call(t),r=Math.min(e.length,n.length-1),i):n.slice()},i.invertExtent=function(t){var r=n.indexOf(t);return[e[r-1],e[r]]},i.unknown=function(e){return arguments.length?(t=e,i):t},i.copy=function(){return Og().domain(e).range(n).unknown(t)},Rp.apply(i,arguments)}var Dg=new Date,Ng=new Date;function Bg(t,e,n,r){function i(e){return t(e=0===arguments.length?new Date:new Date(+e)),e}return i.floor=function(e){return t(e=new Date(+e)),e},i.ceil=function(n){return t(n=new Date(n-1)),e(n,1),t(n),n},i.round=function(t){var e=i(t),n=i.ceil(t);return t-e0))return s;do{s.push(o=new Date(+n)),e(n,a),t(n)}while(o=e)for(;t(e),!n(e);)e.setTime(e-1)}),(function(t,r){if(t>=t)if(r<0)for(;++r<=0;)for(;e(t,-1),!n(t););else for(;--r>=0;)for(;e(t,1),!n(t););}))},n&&(i.count=function(e,r){return Dg.setTime(+e),Ng.setTime(+r),t(Dg),t(Ng),Math.floor(n(Dg,Ng))},i.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?function(e){return r(e)%t==0}:function(e){return i.count(0,e)%t==0}):i:null}),i}var Lg=Bg((function(t){t.setMonth(0,1),t.setHours(0,0,0,0)}),(function(t,e){t.setFullYear(t.getFullYear()+e)}),(function(t,e){return e.getFullYear()-t.getFullYear()}),(function(t){return t.getFullYear()}));Lg.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bg((function(e){e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)}),(function(e,n){e.setFullYear(e.getFullYear()+n*t)})):null};var Fg=Lg,Pg=Lg.range,Ig=Bg((function(t){t.setDate(1),t.setHours(0,0,0,0)}),(function(t,e){t.setMonth(t.getMonth()+e)}),(function(t,e){return e.getMonth()-t.getMonth()+12*(e.getFullYear()-t.getFullYear())}),(function(t){return t.getMonth()})),jg=Ig,Rg=Ig.range;function Yg(t){return Bg((function(e){e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+7*e)}),(function(t,e){return(e-t-6e4*(e.getTimezoneOffset()-t.getTimezoneOffset()))/6048e5}))}var zg=Yg(0),Ug=Yg(1),$g=Yg(2),Wg=Yg(3),Hg=Yg(4),Vg=Yg(5),Gg=Yg(6),qg=zg.range,Xg=Ug.range,Zg=$g.range,Jg=Wg.range,Kg=Hg.range,Qg=Vg.range,ty=Gg.range,ey=Bg((function(t){t.setHours(0,0,0,0)}),(function(t,e){t.setDate(t.getDate()+e)}),(function(t,e){return(e-t-6e4*(e.getTimezoneOffset()-t.getTimezoneOffset()))/864e5}),(function(t){return t.getDate()-1})),ny=ey,ry=ey.range,iy=Bg((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-6e4*t.getMinutes())}),(function(t,e){t.setTime(+t+36e5*e)}),(function(t,e){return(e-t)/36e5}),(function(t){return t.getHours()})),ay=iy,oy=iy.range,sy=Bg((function(t){t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())}),(function(t,e){t.setTime(+t+6e4*e)}),(function(t,e){return(e-t)/6e4}),(function(t){return t.getMinutes()})),cy=sy,uy=sy.range,ly=Bg((function(t){t.setTime(t-t.getMilliseconds())}),(function(t,e){t.setTime(+t+1e3*e)}),(function(t,e){return(e-t)/1e3}),(function(t){return t.getUTCSeconds()})),hy=ly,fy=ly.range,dy=Bg((function(){}),(function(t,e){t.setTime(+t+e)}),(function(t,e){return e-t}));dy.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?Bg((function(e){e.setTime(Math.floor(e/t)*t)}),(function(e,n){e.setTime(+e+n*t)}),(function(e,n){return(n-e)/t})):dy:null};var py=dy,gy=dy.range;function yy(t){return Bg((function(e){e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+7*e)}),(function(t,e){return(e-t)/6048e5}))}var vy=yy(0),my=yy(1),by=yy(2),xy=yy(3),_y=yy(4),ky=yy(5),wy=yy(6),Ey=vy.range,Ty=my.range,Cy=by.range,Ay=xy.range,Sy=_y.range,My=ky.range,Oy=wy.range,Dy=Bg((function(t){t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCDate(t.getUTCDate()+e)}),(function(t,e){return(e-t)/864e5}),(function(t){return t.getUTCDate()-1})),Ny=Dy,By=Dy.range,Ly=Bg((function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),(function(t,e){t.setUTCFullYear(t.getUTCFullYear()+e)}),(function(t,e){return e.getUTCFullYear()-t.getUTCFullYear()}),(function(t){return t.getUTCFullYear()}));Ly.every=function(t){return isFinite(t=Math.floor(t))&&t>0?Bg((function(e){e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)}),(function(e,n){e.setUTCFullYear(e.getUTCFullYear()+n*t)})):null};var Fy=Ly,Py=Ly.range;function Iy(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function jy(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function Ry(t,e,n){return{y:t,m:e,d:n,H:0,M:0,S:0,L:0}}function Yy(t){var e=t.dateTime,n=t.date,r=t.time,i=t.periods,a=t.days,o=t.shortDays,s=t.months,c=t.shortMonths,u=Ky(i),l=Qy(i),h=Ky(a),f=Qy(a),d=Ky(o),p=Qy(o),g=Ky(s),y=Qy(s),v=Ky(c),m=Qy(c),b={a:function(t){return o[t.getDay()]},A:function(t){return a[t.getDay()]},b:function(t){return c[t.getMonth()]},B:function(t){return s[t.getMonth()]},c:null,d:xv,e:xv,f:Tv,H:_v,I:kv,j:wv,L:Ev,m:Cv,M:Av,p:function(t){return i[+(t.getHours()>=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:em,s:nm,S:Sv,u:Mv,U:Ov,V:Dv,w:Nv,W:Bv,x:null,X:null,y:Lv,Y:Fv,Z:Pv,"%":tm},x={a:function(t){return o[t.getUTCDay()]},A:function(t){return a[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return s[t.getUTCMonth()]},c:null,d:Iv,e:Iv,f:Uv,H:jv,I:Rv,j:Yv,L:zv,m:$v,M:Wv,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:em,s:nm,S:Hv,u:Vv,U:Gv,V:qv,w:Xv,W:Zv,x:null,X:null,y:Jv,Y:Kv,Z:Qv,"%":tm},_={a:function(t,e,n){var r=d.exec(e.slice(n));return r?(t.w=p[r[0].toLowerCase()],n+r[0].length):-1},A:function(t,e,n){var r=h.exec(e.slice(n));return r?(t.w=f[r[0].toLowerCase()],n+r[0].length):-1},b:function(t,e,n){var r=v.exec(e.slice(n));return r?(t.m=m[r[0].toLowerCase()],n+r[0].length):-1},B:function(t,e,n){var r=g.exec(e.slice(n));return r?(t.m=y[r[0].toLowerCase()],n+r[0].length):-1},c:function(t,n,r){return E(t,e,n,r)},d:lv,e:lv,f:yv,H:fv,I:fv,j:hv,L:gv,m:uv,M:dv,p:function(t,e,n){var r=u.exec(e.slice(n));return r?(t.p=l[r[0].toLowerCase()],n+r[0].length):-1},q:cv,Q:mv,s:bv,S:pv,u:ev,U:nv,V:rv,w:tv,W:iv,x:function(t,e,r){return E(t,n,e,r)},X:function(t,e,n){return E(t,r,e,n)},y:ov,Y:av,Z:sv,"%":vv};function k(t,e){return function(n){var r,i,a,o=[],s=-1,c=0,u=t.length;for(n instanceof Date||(n=new Date(+n));++s53)return null;"w"in a||(a.w=1),"Z"in a?(i=(r=jy(Ry(a.y,0,1))).getUTCDay(),r=i>4||0===i?my.ceil(r):my(r),r=Ny.offset(r,7*(a.V-1)),a.y=r.getUTCFullYear(),a.m=r.getUTCMonth(),a.d=r.getUTCDate()+(a.w+6)%7):(i=(r=Iy(Ry(a.y,0,1))).getDay(),r=i>4||0===i?Ug.ceil(r):Ug(r),r=ny.offset(r,7*(a.V-1)),a.y=r.getFullYear(),a.m=r.getMonth(),a.d=r.getDate()+(a.w+6)%7)}else("W"in a||"U"in a)&&("w"in a||(a.w="u"in a?a.u%7:"W"in a?1:0),i="Z"in a?jy(Ry(a.y,0,1)).getUTCDay():Iy(Ry(a.y,0,1)).getDay(),a.m=0,a.d="W"in a?(a.w+6)%7+7*a.W-(i+5)%7:a.w+7*a.U-(i+6)%7);return"Z"in a?(a.H+=a.Z/100|0,a.M+=a.Z%100,jy(a)):Iy(a)}}function E(t,e,n,r){for(var i,a,o=0,s=e.length,c=n.length;o=c)return-1;if(37===(i=e.charCodeAt(o++))){if(i=e.charAt(o++),!(a=_[i in Vy?e.charAt(o++):i])||(r=a(t,n,r))<0)return-1}else if(i!=n.charCodeAt(r++))return-1}return r}return(b.x=k(n,b),b.X=k(r,b),b.c=k(e,b),x.x=k(n,x),x.X=k(r,x),x.c=k(e,x),{format:function(t){var e=k(t+="",b);return e.toString=function(){return t},e},parse:function(t){var e=w(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=k(t+="",x);return e.toString=function(){return t},e},utcParse:function(t){var e=w(t+="",!0);return e.toString=function(){return t},e}})}var zy,Uy,$y,Wy,Hy,Vy={"-":"",_:" ",0:"0"},Gy=/^\s*\d+/,qy=/^%/,Xy=/[\\^$*+?|[\]().{}]/g;function Zy(t,e,n){var r=t<0?"-":"",i=(r?-t:t)+"",a=i.length;return r+(a68?1900:2e3),n+r[0].length):-1}function sv(t,e,n){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(n,n+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),n+r[0].length):-1}function cv(t,e,n){var r=Gy.exec(e.slice(n,n+1));return r?(t.q=3*r[0]-3,n+r[0].length):-1}function uv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.m=r[0]-1,n+r[0].length):-1}function lv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.d=+r[0],n+r[0].length):-1}function hv(t,e,n){var r=Gy.exec(e.slice(n,n+3));return r?(t.m=0,t.d=+r[0],n+r[0].length):-1}function fv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.H=+r[0],n+r[0].length):-1}function dv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.M=+r[0],n+r[0].length):-1}function pv(t,e,n){var r=Gy.exec(e.slice(n,n+2));return r?(t.S=+r[0],n+r[0].length):-1}function gv(t,e,n){var r=Gy.exec(e.slice(n,n+3));return r?(t.L=+r[0],n+r[0].length):-1}function yv(t,e,n){var r=Gy.exec(e.slice(n,n+6));return r?(t.L=Math.floor(r[0]/1e3),n+r[0].length):-1}function vv(t,e,n){var r=qy.exec(e.slice(n,n+1));return r?n+r[0].length:-1}function mv(t,e,n){var r=Gy.exec(e.slice(n));return r?(t.Q=+r[0],n+r[0].length):-1}function bv(t,e,n){var r=Gy.exec(e.slice(n));return r?(t.s=+r[0],n+r[0].length):-1}function xv(t,e){return Zy(t.getDate(),e,2)}function _v(t,e){return Zy(t.getHours(),e,2)}function kv(t,e){return Zy(t.getHours()%12||12,e,2)}function wv(t,e){return Zy(1+ny.count(Fg(t),t),e,3)}function Ev(t,e){return Zy(t.getMilliseconds(),e,3)}function Tv(t,e){return Ev(t,e)+"000"}function Cv(t,e){return Zy(t.getMonth()+1,e,2)}function Av(t,e){return Zy(t.getMinutes(),e,2)}function Sv(t,e){return Zy(t.getSeconds(),e,2)}function Mv(t){var e=t.getDay();return 0===e?7:e}function Ov(t,e){return Zy(zg.count(Fg(t)-1,t),e,2)}function Dv(t,e){var n=t.getDay();return t=n>=4||0===n?Hg(t):Hg.ceil(t),Zy(Hg.count(Fg(t),t)+(4===Fg(t).getDay()),e,2)}function Nv(t){return t.getDay()}function Bv(t,e){return Zy(Ug.count(Fg(t)-1,t),e,2)}function Lv(t,e){return Zy(t.getFullYear()%100,e,2)}function Fv(t,e){return Zy(t.getFullYear()%1e4,e,4)}function Pv(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+Zy(e/60|0,"0",2)+Zy(e%60,"0",2)}function Iv(t,e){return Zy(t.getUTCDate(),e,2)}function jv(t,e){return Zy(t.getUTCHours(),e,2)}function Rv(t,e){return Zy(t.getUTCHours()%12||12,e,2)}function Yv(t,e){return Zy(1+Ny.count(Fy(t),t),e,3)}function zv(t,e){return Zy(t.getUTCMilliseconds(),e,3)}function Uv(t,e){return zv(t,e)+"000"}function $v(t,e){return Zy(t.getUTCMonth()+1,e,2)}function Wv(t,e){return Zy(t.getUTCMinutes(),e,2)}function Hv(t,e){return Zy(t.getUTCSeconds(),e,2)}function Vv(t){var e=t.getUTCDay();return 0===e?7:e}function Gv(t,e){return Zy(vy.count(Fy(t)-1,t),e,2)}function qv(t,e){var n=t.getUTCDay();return t=n>=4||0===n?_y(t):_y.ceil(t),Zy(_y.count(Fy(t),t)+(4===Fy(t).getUTCDay()),e,2)}function Xv(t){return t.getUTCDay()}function Zv(t,e){return Zy(my.count(Fy(t)-1,t),e,2)}function Jv(t,e){return Zy(t.getUTCFullYear()%100,e,2)}function Kv(t,e){return Zy(t.getUTCFullYear()%1e4,e,4)}function Qv(){return"+0000"}function tm(){return"%"}function em(t){return+t}function nm(t){return Math.floor(+t/1e3)}function rm(t){return zy=Yy(t),Uy=zy.format,$y=zy.parse,Wy=zy.utcFormat,Hy=zy.utcParse,zy}rm({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function im(t){return new Date(t)}function am(t){return t instanceof Date?+t:+new Date(+t)}function om(t,e,n,r,a,o,s,c,u){var l=ig(Jp,Jp),h=l.invert,f=l.domain,d=u(".%L"),p=u(":%S"),g=u("%I:%M"),y=u("%I %p"),v=u("%a %d"),m=u("%b %d"),b=u("%B"),x=u("%Y"),_=[[s,1,1e3],[s,5,5e3],[s,15,15e3],[s,30,3e4],[o,1,6e4],[o,5,3e5],[o,15,9e5],[o,30,18e5],[a,1,36e5],[a,3,108e5],[a,6,216e5],[a,12,432e5],[r,1,864e5],[r,2,1728e5],[n,1,6048e5],[e,1,2592e6],[e,3,7776e6],[t,1,31536e6]];function k(i){return(s(i)1)&&(t-=Math.floor(t));var e=Math.abs(t-.5);return qb.h=360*t-100,qb.s=1.5-1.5*e,qb.l=.8-.9*e,qb+""},Zb=Ge(),Jb=Math.PI/3,Kb=2*Math.PI/3,Qb=function(t){var e;return t=(.5-t)*Math.PI,Zb.r=255*(e=Math.sin(t))*e,Zb.g=255*(e=Math.sin(t+Jb))*e,Zb.b=255*(e=Math.sin(t+Kb))*e,Zb+""},tx=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"};function ex(t){var e=t.length;return function(n){return t[Math.max(0,Math.min(e-1,Math.floor(n*e)))]}}var nx=ex(Nm("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),rx=ex(Nm("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),ix=ex(Nm("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),ax=ex(Nm("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921")),ox=function(t){return ke(ne(t).call(document.documentElement))},sx=0;function cx(){return new ux}function ux(){this._="@"+(++sx).toString(36)}ux.prototype=cx.prototype={constructor:ux,get:function(t){for(var e=this._;!(e in t);)if(!(t=t.parentNode))return;return t[e]},set:function(t,e){return t[this._]=e},remove:function(t){return this._ in t&&delete t[this._]},toString:function(){return this._}};var lx=function(t){return"string"==typeof t?new be([document.querySelectorAll(t)],[document.documentElement]):new be([null==t?[]:t],me)},hx=function(t,e){null==e&&(e=Mn().touches);for(var n=0,r=e?e.length:0,i=new Array(r);n1?0:t<-1?xx:Math.acos(t)}function Ex(t){return t>=1?_x:t<=-1?-_x:Math.asin(t)}function Tx(t){return t.innerRadius}function Cx(t){return t.outerRadius}function Ax(t){return t.startAngle}function Sx(t){return t.endAngle}function Mx(t){return t&&t.padAngle}function Ox(t,e,n,r,i,a,o,s){var c=n-t,u=r-e,l=o-i,h=s-a,f=h*c-l*u;if(!(f*f<1e-12))return[t+(f=(l*(e-a)-h*(t-i))/f)*c,e+f*u]}function Dx(t,e,n,r,i,a,o){var s=t-n,c=e-r,u=(o?a:-a)/bx(s*s+c*c),l=u*c,h=-u*s,f=t+l,d=e+h,p=n+l,g=r+h,y=(f+p)/2,v=(d+g)/2,m=p-f,b=g-d,x=m*m+b*b,_=i-a,k=f*g-p*d,w=(b<0?-1:1)*bx(yx(0,_*_*x-k*k)),E=(k*b-m*w)/x,T=(-k*m-b*w)/x,C=(k*b+m*w)/x,A=(-k*m+b*w)/x,S=E-y,M=T-v,O=C-y,D=A-v;return S*S+M*M>O*O+D*D&&(E=C,T=A),{cx:E,cy:T,x01:-l,y01:-h,x11:E*(i/_-1),y11:T*(i/_-1)}}var Nx=function(){var t=Tx,e=Cx,n=fx(0),r=null,i=Ax,a=Sx,o=Mx,s=null;function c(){var c,u,l=+t.apply(this,arguments),h=+e.apply(this,arguments),f=i.apply(this,arguments)-_x,d=a.apply(this,arguments)-_x,p=dx(d-f),g=d>f;if(s||(s=c=Ui()),h1e-12)if(p>kx-1e-12)s.moveTo(h*gx(f),h*mx(f)),s.arc(0,0,h,f,d,!g),l>1e-12&&(s.moveTo(l*gx(d),l*mx(d)),s.arc(0,0,l,d,f,g));else{var y,v,m=f,b=d,x=f,_=d,k=p,w=p,E=o.apply(this,arguments)/2,T=E>1e-12&&(r?+r.apply(this,arguments):bx(l*l+h*h)),C=vx(dx(h-l)/2,+n.apply(this,arguments)),A=C,S=C;if(T>1e-12){var M=Ex(T/l*mx(E)),O=Ex(T/h*mx(E));(k-=2*M)>1e-12?(x+=M*=g?1:-1,_-=M):(k=0,x=_=(f+d)/2),(w-=2*O)>1e-12?(m+=O*=g?1:-1,b-=O):(w=0,m=b=(f+d)/2)}var D=h*gx(m),N=h*mx(m),B=l*gx(_),L=l*mx(_);if(C>1e-12){var F,P=h*gx(b),I=h*mx(b),j=l*gx(x),R=l*mx(x);if(p1e-12?S>1e-12?(y=Dx(j,R,D,N,h,S,g),v=Dx(P,I,B,L,h,S,g),s.moveTo(y.cx+y.x01,y.cy+y.y01),S1e-12&&k>1e-12?A>1e-12?(y=Dx(B,L,P,I,l,-A,g),v=Dx(D,N,j,R,l,-A,g),s.lineTo(y.cx+y.x01,y.cy+y.y01),A=l;--h)s.point(y[h],v[h]);s.lineEnd(),s.areaEnd()}g&&(y[u]=+t(f,u,c),v[u]=+n(f,u,c),s.point(e?+e(f,u,c):y[u],r?+r(f,u,c):v[u]))}if(d)return s=null,d+""||null}function u(){return Ix().defined(i).curve(o).context(a)}return c.x=function(n){return arguments.length?(t="function"==typeof n?n:fx(+n),e=null,c):t},c.x0=function(e){return arguments.length?(t="function"==typeof e?e:fx(+e),c):t},c.x1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:fx(+t),c):e},c.y=function(t){return arguments.length?(n="function"==typeof t?t:fx(+t),r=null,c):n},c.y0=function(t){return arguments.length?(n="function"==typeof t?t:fx(+t),c):n},c.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:fx(+t),c):r},c.lineX0=c.lineY0=function(){return u().x(t).y(n)},c.lineY1=function(){return u().x(t).y(r)},c.lineX1=function(){return u().x(e).y(n)},c.defined=function(t){return arguments.length?(i="function"==typeof t?t:fx(!!t),c):i},c.curve=function(t){return arguments.length?(o=t,null!=a&&(s=o(a)),c):o},c.context=function(t){return arguments.length?(null==t?a=s=null:s=o(a=t),c):a},c},Rx=function(t,e){return et?1:e>=t?0:NaN},Yx=function(t){return t},zx=function(){var t=Yx,e=Rx,n=null,r=fx(0),i=fx(kx),a=fx(0);function o(o){var s,c,u,l,h,f=o.length,d=0,p=new Array(f),g=new Array(f),y=+r.apply(this,arguments),v=Math.min(kx,Math.max(-kx,i.apply(this,arguments)-y)),m=Math.min(Math.abs(v)/f,a.apply(this,arguments)),b=m*(v<0?-1:1);for(s=0;s0&&(d+=h);for(null!=e?p.sort((function(t,n){return e(g[t],g[n])})):null!=n&&p.sort((function(t,e){return n(o[t],o[e])})),s=0,u=d?(v-f*b)/d:0;s0?h*u:0)+b,g[c]={data:o[c],index:s,value:h,startAngle:y,endAngle:l,padAngle:m};return g}return o.value=function(e){return arguments.length?(t="function"==typeof e?e:fx(+e),o):t},o.sortValues=function(t){return arguments.length?(e=t,n=null,o):e},o.sort=function(t){return arguments.length?(n=t,e=null,o):n},o.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:fx(+t),o):r},o.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:fx(+t),o):i},o.padAngle=function(t){return arguments.length?(a="function"==typeof t?t:fx(+t),o):a},o},Ux=Wx(Lx);function $x(t){this._curve=t}function Wx(t){function e(e){return new $x(t(e))}return e._curve=t,e}function Hx(t){var e=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?e(Wx(t)):e()._curve},t}$x.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,e){this._curve.point(e*Math.sin(t),e*-Math.cos(t))}};var Vx=function(){return Hx(Ix().curve(Ux))},Gx=function(){var t=jx().curve(Ux),e=t.curve,n=t.lineX0,r=t.lineX1,i=t.lineY0,a=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Hx(n())},delete t.lineX0,t.lineEndAngle=function(){return Hx(r())},delete t.lineX1,t.lineInnerRadius=function(){return Hx(i())},delete t.lineY0,t.lineOuterRadius=function(){return Hx(a())},delete t.lineY1,t.curve=function(t){return arguments.length?e(Wx(t)):e()._curve},t},qx=function(t,e){return[(e=+e)*Math.cos(t-=Math.PI/2),e*Math.sin(t)]},Xx=Array.prototype.slice;function Zx(t){return t.source}function Jx(t){return t.target}function Kx(t){var e=Zx,n=Jx,r=Fx,i=Px,a=null;function o(){var o,s=Xx.call(arguments),c=e.apply(this,s),u=n.apply(this,s);if(a||(a=o=Ui()),t(a,+r.apply(this,(s[0]=c,s)),+i.apply(this,s),+r.apply(this,(s[0]=u,s)),+i.apply(this,s)),o)return a=null,o+""||null}return o.source=function(t){return arguments.length?(e=t,o):e},o.target=function(t){return arguments.length?(n=t,o):n},o.x=function(t){return arguments.length?(r="function"==typeof t?t:fx(+t),o):r},o.y=function(t){return arguments.length?(i="function"==typeof t?t:fx(+t),o):i},o.context=function(t){return arguments.length?(a=null==t?null:t,o):a},o}function Qx(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e=(e+r)/2,n,e,i,r,i)}function t_(t,e,n,r,i){t.moveTo(e,n),t.bezierCurveTo(e,n=(n+i)/2,r,n,r,i)}function e_(t,e,n,r,i){var a=qx(e,n),o=qx(e,n=(n+i)/2),s=qx(r,n),c=qx(r,i);t.moveTo(a[0],a[1]),t.bezierCurveTo(o[0],o[1],s[0],s[1],c[0],c[1])}function n_(){return Kx(Qx)}function r_(){return Kx(t_)}function i_(){var t=Kx(e_);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t}var a_={draw:function(t,e){var n=Math.sqrt(e/xx);t.moveTo(n,0),t.arc(0,0,n,0,kx)}},o_={draw:function(t,e){var n=Math.sqrt(e/5)/2;t.moveTo(-3*n,-n),t.lineTo(-n,-n),t.lineTo(-n,-3*n),t.lineTo(n,-3*n),t.lineTo(n,-n),t.lineTo(3*n,-n),t.lineTo(3*n,n),t.lineTo(n,n),t.lineTo(n,3*n),t.lineTo(-n,3*n),t.lineTo(-n,n),t.lineTo(-3*n,n),t.closePath()}},s_=Math.sqrt(1/3),c_=2*s_,u_={draw:function(t,e){var n=Math.sqrt(e/c_),r=n*s_;t.moveTo(0,-n),t.lineTo(r,0),t.lineTo(0,n),t.lineTo(-r,0),t.closePath()}},l_=Math.sin(xx/10)/Math.sin(7*xx/10),h_=Math.sin(kx/10)*l_,f_=-Math.cos(kx/10)*l_,d_={draw:function(t,e){var n=Math.sqrt(.8908130915292852*e),r=h_*n,i=f_*n;t.moveTo(0,-n),t.lineTo(r,i);for(var a=1;a<5;++a){var o=kx*a/5,s=Math.cos(o),c=Math.sin(o);t.lineTo(c*n,-s*n),t.lineTo(s*r-c*i,c*r+s*i)}t.closePath()}},p_={draw:function(t,e){var n=Math.sqrt(e),r=-n/2;t.rect(r,r,n,n)}},g_=Math.sqrt(3),y_={draw:function(t,e){var n=-Math.sqrt(e/(3*g_));t.moveTo(0,2*n),t.lineTo(-g_*n,-n),t.lineTo(g_*n,-n),t.closePath()}},v_=Math.sqrt(3)/2,m_=1/Math.sqrt(12),b_=3*(m_/2+1),x_={draw:function(t,e){var n=Math.sqrt(e/b_),r=n/2,i=n*m_,a=r,o=n*m_+n,s=-a,c=o;t.moveTo(r,i),t.lineTo(a,o),t.lineTo(s,c),t.lineTo(-.5*r-v_*i,v_*r+-.5*i),t.lineTo(-.5*a-v_*o,v_*a+-.5*o),t.lineTo(-.5*s-v_*c,v_*s+-.5*c),t.lineTo(-.5*r+v_*i,-.5*i-v_*r),t.lineTo(-.5*a+v_*o,-.5*o-v_*a),t.lineTo(-.5*s+v_*c,-.5*c-v_*s),t.closePath()}},__=[a_,o_,u_,p_,d_,y_,x_],k_=function(){var t=fx(a_),e=fx(64),n=null;function r(){var r;if(n||(n=r=Ui()),t.apply(this,arguments).draw(n,+e.apply(this,arguments)),r)return n=null,r+""||null}return r.type=function(e){return arguments.length?(t="function"==typeof e?e:fx(e),r):t},r.size=function(t){return arguments.length?(e="function"==typeof t?t:fx(+t),r):e},r.context=function(t){return arguments.length?(n=null==t?null:t,r):n},r},w_=function(){};function E_(t,e,n){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+n)/6)}function T_(t){this._context=t}T_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:E_(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var C_=function(t){return new T_(t)};function A_(t){this._context=t}A_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var S_=function(t){return new A_(t)};function M_(t){this._context=t}M_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var n=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(n,r):this._context.moveTo(n,r);break;case 3:this._point=4;default:E_(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};var O_=function(t){return new M_(t)};function D_(t,e){this._basis=new T_(t),this._beta=e}D_.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,e=this._y,n=t.length-1;if(n>0)for(var r,i=t[0],a=e[0],o=t[n]-i,s=e[n]-a,c=-1;++c<=n;)r=c/n,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*o),this._beta*e[c]+(1-this._beta)*(a+r*s));this._x=this._y=null,this._basis.lineEnd()},point:function(t,e){this._x.push(+t),this._y.push(+e)}};var N_=function t(e){function n(t){return 1===e?new T_(t):new D_(t,e)}return n.beta=function(e){return t(+e)},n}(.85);function B_(t,e,n){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-e),t._y2+t._k*(t._y1-n),t._x2,t._y2)}function L_(t,e){this._context=t,this._k=(1-e)/6}L_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:B_(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2,this._x1=t,this._y1=e;break;case 2:this._point=3;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var F_=function t(e){function n(t){return new L_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function P_(t,e){this._context=t,this._k=(1-e)/6}P_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var I_=function t(e){function n(t){return new P_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function j_(t,e){this._context=t,this._k=(1-e)/6}j_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:B_(this,t,e)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var R_=function t(e){function n(t){return new j_(t,e)}return n.tension=function(e){return t(+e)},n}(0);function Y_(t,e,n){var r=t._x1,i=t._y1,a=t._x2,o=t._y2;if(t._l01_a>1e-12){var s=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*s-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*s-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>1e-12){var u=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,l=3*t._l23_a*(t._l23_a+t._l12_a);a=(a*u+t._x1*t._l23_2a-e*t._l12_2a)/l,o=(o*u+t._y1*t._l23_2a-n*t._l12_2a)/l}t._context.bezierCurveTo(r,i,a,o,t._x2,t._y2)}function z_(t,e){this._context=t,this._alpha=e}z_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var U_=function t(e){function n(t){return e?new z_(t,e):new L_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function $_(t,e){this._context=t,this._alpha=e}$_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=e;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=e);break;case 2:this._point=3,this._x5=t,this._y5=e;break;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var W_=function t(e){function n(t){return e?new $_(t,e):new P_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function H_(t,e){this._context=t,this._alpha=e}H_.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){if(t=+t,e=+e,this._point){var n=this._x2-t,r=this._y2-e;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(n*n+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Y_(this,t,e)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=e}};var V_=function t(e){function n(t){return e?new H_(t,e):new j_(t,0)}return n.alpha=function(e){return t(+e)},n}(.5);function G_(t){this._context=t}G_.prototype={areaStart:w_,areaEnd:w_,lineStart:function(){this._point=0},lineEnd:function(){this._point&&this._context.closePath()},point:function(t,e){t=+t,e=+e,this._point?this._context.lineTo(t,e):(this._point=1,this._context.moveTo(t,e))}};var q_=function(t){return new G_(t)};function X_(t){return t<0?-1:1}function Z_(t,e,n){var r=t._x1-t._x0,i=e-t._x1,a=(t._y1-t._y0)/(r||i<0&&-0),o=(n-t._y1)/(i||r<0&&-0),s=(a*i+o*r)/(r+i);return(X_(a)+X_(o))*Math.min(Math.abs(a),Math.abs(o),.5*Math.abs(s))||0}function J_(t,e){var n=t._x1-t._x0;return n?(3*(t._y1-t._y0)/n-e)/2:e}function K_(t,e,n){var r=t._x0,i=t._y0,a=t._x1,o=t._y1,s=(a-r)/3;t._context.bezierCurveTo(r+s,i+s*e,a-s,o-s*n,a,o)}function Q_(t){this._context=t}function tk(t){this._context=new ek(t)}function ek(t){this._context=t}function nk(t){return new Q_(t)}function rk(t){return new tk(t)}function ik(t){this._context=t}function ak(t){var e,n,r=t.length-1,i=new Array(r),a=new Array(r),o=new Array(r);for(i[0]=0,a[0]=2,o[0]=t[0]+2*t[1],e=1;e=0;--e)i[e]=(o[e]-i[e+1])/a[e];for(a[r-1]=(t[r]+i[r-1])/2,e=0;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var n=this._x*(1-this._t)+t*this._t;this._context.lineTo(n,this._y),this._context.lineTo(n,e)}}this._x=t,this._y=e}};var ck=function(t){return new sk(t,.5)};function uk(t){return new sk(t,0)}function lk(t){return new sk(t,1)}var hk=function(t,e){if((i=t.length)>1)for(var n,r,i,a=1,o=t[e[0]],s=o.length;a=0;)n[e]=e;return n};function dk(t,e){return t[e]}var pk=function(){var t=fx([]),e=fk,n=hk,r=dk;function i(i){var a,o,s=t.apply(this,arguments),c=i.length,u=s.length,l=new Array(u);for(a=0;a0){for(var n,r,i,a=0,o=t[0].length;a0)for(var n,r,i,a,o,s,c=0,u=t[e[0]].length;c0?(r[0]=a,r[1]=a+=i):i<0?(r[1]=o,r[0]=o+=i):(r[0]=0,r[1]=i)},vk=function(t,e){if((n=t.length)>0){for(var n,r=0,i=t[e[0]],a=i.length;r0&&(r=(n=t[e[0]]).length)>0){for(var n,r,i,a=0,o=1;oa&&(a=e,r=n);return r}var _k=function(t){var e=t.map(kk);return fk(t).sort((function(t,n){return e[t]-e[n]}))};function kk(t){for(var e,n=0,r=-1,i=t.length;++r0)){if(a/=f,f<0){if(a0){if(a>h)return;a>l&&(l=a)}if(a=r-c,f||!(a<0)){if(a/=f,f<0){if(a>h)return;a>l&&(l=a)}else if(f>0){if(a0)){if(a/=d,d<0){if(a0){if(a>h)return;a>l&&(l=a)}if(a=i-u,d||!(a<0)){if(a/=d,d<0){if(a>h)return;a>l&&(l=a)}else if(d>0){if(a0||h<1)||(l>0&&(t[0]=[c+l*f,u+l*d]),h<1&&(t[1]=[c+h*f,u+h*d]),!0)}}}}}function Uk(t,e,n,r,i){var a=t[1];if(a)return!0;var o,s,c=t[0],u=t.left,l=t.right,h=u[0],f=u[1],d=l[0],p=l[1],g=(h+d)/2,y=(f+p)/2;if(p===f){if(g=r)return;if(h>d){if(c){if(c[1]>=i)return}else c=[g,n];a=[g,i]}else{if(c){if(c[1]1)if(h>d){if(c){if(c[1]>=i)return}else c=[(n-s)/o,n];a=[(i-s)/o,i]}else{if(c){if(c[1]=r)return}else c=[e,o*e+s];a=[r,o*r+s]}else{if(c){if(c[0]=-lw)){var d=c*c+u*u,p=l*l+h*h,g=(h*d-u*p)/f,y=(c*p-l*d)/f,v=Gk.pop()||new qk;v.arc=t,v.site=i,v.x=g+o,v.y=(v.cy=y+s)+Math.sqrt(g*g+y*y),t.circle=v;for(var m=null,b=sw._;b;)if(v.yuw)s=s.L;else{if(!((i=a-iw(s,o))>uw)){r>-uw?(e=s.P,n=s):i>-uw?(e=s,n=s.N):e=n=s;break}if(!s.R){e=s;break}s=s.R}!function(t){ow[t.index]={site:t,halfedges:[]}}(t);var c=Qk(t);if(aw.insert(e,c),e||n){if(e===n)return Zk(e),n=Qk(e.site),aw.insert(c,n),c.edge=n.edge=jk(e.site,c.site),Xk(e),void Xk(n);if(n){Zk(e),Zk(n);var u=e.site,l=u[0],h=u[1],f=t[0]-l,d=t[1]-h,p=n.site,g=p[0]-l,y=p[1]-h,v=2*(f*y-d*g),m=f*f+d*d,b=g*g+y*y,x=[(y*m-d*b)/v+l,(f*b-g*m)/v+h];Yk(n.edge,u,p,x),c.edge=jk(u,t,null,x),n.edge=jk(t,p,null,x),Xk(e),Xk(n)}else c.edge=jk(e.site,c.site)}}function rw(t,e){var n=t.site,r=n[0],i=n[1],a=i-e;if(!a)return r;var o=t.P;if(!o)return-1/0;var s=(n=o.site)[0],c=n[1],u=c-e;if(!u)return s;var l=s-r,h=1/a-1/u,f=l/u;return h?(-f+Math.sqrt(f*f-2*h*(l*l/(-2*u)-c+u/2+i-a/2)))/h+r:(r+s)/2}function iw(t,e){var n=t.N;if(n)return rw(n,e);var r=t.site;return r[1]===e?r[0]:1/0}var aw,ow,sw,cw,uw=1e-6,lw=1e-12;function hw(t,e){return e[1]-t[1]||e[0]-t[0]}function fw(t,e){var n,r,i,a=t.sort(hw).pop();for(cw=[],ow=new Array(t.length),aw=new Ik,sw=new Ik;;)if(i=Vk,a&&(!i||a[1]uw||Math.abs(i[0][1]-i[1][1])>uw)||delete cw[a]}(o,s,c,u),function(t,e,n,r){var i,a,o,s,c,u,l,h,f,d,p,g,y=ow.length,v=!0;for(i=0;iuw||Math.abs(g-f)>uw)&&(c.splice(s,0,cw.push(Rk(o,d,Math.abs(p-t)uw?[t,Math.abs(h-t)uw?[Math.abs(f-r)uw?[n,Math.abs(h-n)uw?[Math.abs(f-e)=s)return null;var c=t-i.site[0],u=e-i.site[1],l=c*c+u*u;do{i=a.cells[r=o],o=null,i.halfedges.forEach((function(n){var r=a.edges[n],s=r.left;if(s!==i.site&&s||(s=r.right)){var c=t-s[0],u=e-s[1],h=c*c+u*u;hr?(r+i)/2:Math.min(0,r)||Math.max(0,i),o>a?(a+o)/2:Math.min(0,a)||Math.max(0,o))}var Aw=function(){var t,e,n=_w,r=kw,i=Cw,a=Ew,o=Tw,s=[0,1/0],c=[[-1/0,-1/0],[1/0,1/0]],u=250,l=fp,h=lt("start","zoom","end"),f=0;function d(t){t.property("__zoom",ww).on("wheel.zoom",x).on("mousedown.zoom",_).on("dblclick.zoom",k).filter(o).on("touchstart.zoom",w).on("touchmove.zoom",E).on("touchend.zoom touchcancel.zoom",T).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function p(t,e){return(e=Math.max(s[0],Math.min(s[1],e)))===t.k?t:new yw(e,t.x,t.y)}function g(t,e,n){var r=e[0]-n[0]*t.k,i=e[1]-n[1]*t.k;return r===t.x&&i===t.y?t:new yw(t.k,r,i)}function y(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function v(t,e,n){t.on("start.zoom",(function(){m(this,arguments).start()})).on("interrupt.zoom end.zoom",(function(){m(this,arguments).end()})).tween("zoom",(function(){var t=this,i=arguments,a=m(t,i),o=r.apply(t,i),s=null==n?y(o):"function"==typeof n?n.apply(t,i):n,c=Math.max(o[1][0]-o[0][0],o[1][1]-o[0][1]),u=t.__zoom,h="function"==typeof e?e.apply(t,i):e,f=l(u.invert(s).concat(c/u.k),h.invert(s).concat(c/h.k));return function(t){if(1===t)t=h;else{var e=f(t),n=c/e[2];t=new yw(n,s[0]-e[0]*n,s[1]-e[1]*n)}a.zoom(null,t)}}))}function m(t,e,n){return!n&&t.__zooming||new b(t,e)}function b(t,e){this.that=t,this.args=e,this.active=0,this.extent=r.apply(t,e),this.taps=0}function x(){if(n.apply(this,arguments)){var t=m(this,arguments),e=this.__zoom,r=Math.max(s[0],Math.min(s[1],e.k*Math.pow(2,a.apply(this,arguments)))),o=Nn(this);if(t.wheel)t.mouse[0][0]===o[0]&&t.mouse[0][1]===o[1]||(t.mouse[1]=e.invert(t.mouse[0]=o)),clearTimeout(t.wheel);else{if(e.k===r)return;t.mouse=[o,e.invert(o)],or(this),t.start()}xw(),t.wheel=setTimeout(u,150),t.zoom("mouse",i(g(p(e,r),t.mouse[0],t.mouse[1]),t.extent,c))}function u(){t.wheel=null,t.end()}}function _(){if(!e&&n.apply(this,arguments)){var t=m(this,arguments,!0),r=ke(ce.view).on("mousemove.zoom",u,!0).on("mouseup.zoom",l,!0),a=Nn(this),o=ce.clientX,s=ce.clientY;Te(ce.view),bw(),t.mouse=[a,this.__zoom.invert(a)],or(this),t.start()}function u(){if(xw(),!t.moved){var e=ce.clientX-o,n=ce.clientY-s;t.moved=e*e+n*n>f}t.zoom("mouse",i(g(t.that.__zoom,t.mouse[0]=Nn(t.that),t.mouse[1]),t.extent,c))}function l(){r.on("mousemove.zoom mouseup.zoom",null),Ce(ce.view,t.moved),xw(),t.end()}}function k(){if(n.apply(this,arguments)){var t=this.__zoom,e=Nn(this),a=t.invert(e),o=t.k*(ce.shiftKey?.5:2),s=i(g(p(t,o),e,a),r.apply(this,arguments),c);xw(),u>0?ke(this).transition().duration(u).call(v,s,e):ke(this).call(d.transform,s)}}function w(){if(n.apply(this,arguments)){var e,r,i,a,o=ce.touches,s=o.length,c=m(this,arguments,ce.changedTouches.length===s);for(bw(),r=0;rh&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},M={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),56;case 1:return this.begin("type_directive"),57;case 2:return this.popState(),this.begin("arg_directive"),14;case 3:return this.popState(),this.popState(),59;case 4:return 58;case 5:return 5;case 6:case 7:case 8:case 9:case 10:break;case 11:return this.begin("ID"),16;case 12:return e.yytext=e.yytext.trim(),this.begin("ALIAS"),48;case 13:return this.popState(),this.popState(),this.begin("LINE"),18;case 14:return this.popState(),this.popState(),5;case 15:return this.begin("LINE"),27;case 16:return this.begin("LINE"),29;case 17:return this.begin("LINE"),30;case 18:return this.begin("LINE"),31;case 19:return this.begin("LINE"),36;case 20:return this.begin("LINE"),33;case 21:return this.begin("LINE"),35;case 22:return this.popState(),19;case 23:return 28;case 24:return 43;case 25:return 44;case 26:return 39;case 27:return 37;case 28:return this.begin("ID"),22;case 29:return this.begin("ID"),23;case 30:return 25;case 31:return 7;case 32:return 21;case 33:return 42;case 34:return 5;case 35:return e.yytext=e.yytext.trim(),48;case 36:return 51;case 37:return 52;case 38:return 49;case 39:return 50;case 40:return 53;case 41:return 54;case 42:return 55;case 43:return 46;case 44:return 47;case 45:return 5;case 46:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:participant\b)/i,/^(?:[^\->:\n,;]+?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:and\b)/i,/^(?:(?:[:]?(?:no)?wrap)?[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\b)/i,/^(?:sequenceDiagram\b)/i,/^(?:autonumber\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\->:\n,;]+((?!(-x|--x))[\-]*[^\+\->:\n,;]+)*)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?::(?:(?:no)?wrap)?[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1,8],inclusive:!1},type_directive:{rules:[2,3,8],inclusive:!1},arg_directive:{rules:[3,4,8],inclusive:!1},ID:{rules:[7,8,12],inclusive:!1},ALIAS:{rules:[7,8,13,14],inclusive:!1},LINE:{rules:[7,8,22],inclusive:!1},INITIAL:{rules:[0,5,6,8,9,10,11,15,16,17,18,19,20,21,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],inclusive:!0}}};function O(){this.yy={}}return S.lexer=M,O.prototype=S,S.Parser=O,new O}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){var r=n(198);t.exports={Graph:r.Graph,json:n(301),alg:n(302),version:r.version}},function(t,e,n){var r;try{r={cloneDeep:n(313),constant:n(86),defaults:n(154),each:n(87),filter:n(128),find:n(314),flatten:n(156),forEach:n(126),forIn:n(319),has:n(93),isUndefined:n(139),last:n(320),map:n(140),mapValues:n(321),max:n(322),merge:n(324),min:n(329),minBy:n(330),now:n(331),pick:n(161),range:n(162),reduce:n(142),sortBy:n(338),uniqueId:n(163),values:n(147),zipObject:n(343)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){var n=Array.isArray;t.exports=n},function(t,e,n){ +/** + * @license + * Copyright (c) 2012-2013 Chris Pettitt + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +t.exports={graphlib:n(311),dagre:n(153),intersect:n(368),render:n(370),util:n(12),version:n(382)}},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){"use strict";var r=n(4),i=n(17).Graph;function a(t,e,n,i){var a;do{a=r.uniqueId(i)}while(t.hasNode(a));return n.dummy=e,t.setNode(a,n),a}function o(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:a,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,u=t.height/2;if(!o&&!s)throw new Error("Not possible to find intersection inside of the rectangle");Math.abs(s)*c>Math.abs(o)*u?(s<0&&(u=-u),n=u*o/s,r=u):(o<0&&(c=-c),n=c,r=c*s/o);return{x:i+n,y:a+r}},buildLayerMatrix:function(t){var e=r.map(r.range(o(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),a=i.rank;r.isUndefined(a)||(e[a][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,a=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%a!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};arguments.length>=4&&(i.rank=n,i.order=r);return a(t,"border",i,e)},maxRank:o,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(173),i=n(174),a=n(175),o={channel:r.default,lang:i.default,unit:a.default};e.default=o},function(t,e,n){var r;try{r={clone:n(199),constant:n(86),each:n(87),filter:n(128),has:n(93),isArray:n(5),isEmpty:n(276),isFunction:n(37),isUndefined:n(139),keys:n(30),map:n(140),reduce:n(142),size:n(279),transform:n(285),union:n(286),values:n(147)}}catch(t){}r||(r=window._),t.exports=r},function(t,e){t.exports=function(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}},function(t,e,n){var r=n(43);t.exports={isSubgraph:function(t,e){return!!t.children(e).length},edgeToId:function(t){return a(t.v)+":"+a(t.w)+":"+a(t.name)},applyStyle:function(t,e){e&&t.attr("style",e)},applyClass:function(t,e,n){e&&t.attr("class",e).attr("class",n+" "+t.attr("class"))},applyTransition:function(t,e){var n=e.graph();if(r.isPlainObject(n)){var i=n.transition;if(r.isFunction(i))return i(t)}return t}};var i=/:/g;function a(t){return t?String(t).replace(i,"\\:"):""}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,7],n=[1,6],r=[1,14],i=[1,25],a=[1,28],o=[1,26],s=[1,27],c=[1,29],u=[1,30],l=[1,31],h=[1,32],f=[1,34],d=[1,35],p=[1,36],g=[10,19],y=[1,48],v=[1,49],m=[1,50],b=[1,51],x=[1,52],_=[1,53],k=[10,19,25,32,33,41,44,45,46,47,48,49,54,56],w=[10,19,23,25,32,33,37,41,44,45,46,47,48,49,54,56,71,72,73],E=[10,13,17,19],T=[41,71,72,73],C=[41,48,49,71,72,73],A=[41,44,45,46,47,71,72,73],S=[10,19,25],M=[1,85],O={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,directive:5,graphConfig:6,openDirective:7,typeDirective:8,closeDirective:9,NEWLINE:10,":":11,argDirective:12,open_directive:13,type_directive:14,arg_directive:15,close_directive:16,CLASS_DIAGRAM:17,statements:18,EOF:19,statement:20,className:21,alphaNumToken:22,GENERICTYPE:23,relationStatement:24,LABEL:25,classStatement:26,methodStatement:27,annotationStatement:28,clickStatement:29,cssClassStatement:30,CLASS:31,STYLE_SEPARATOR:32,STRUCT_START:33,members:34,STRUCT_STOP:35,ANNOTATION_START:36,ANNOTATION_END:37,MEMBER:38,SEPARATOR:39,relation:40,STR:41,relationType:42,lineType:43,AGGREGATION:44,EXTENSION:45,COMPOSITION:46,DEPENDENCY:47,LINE:48,DOTTED_LINE:49,CALLBACK:50,LINK:51,LINK_TARGET:52,CLICK:53,CALLBACK_NAME:54,CALLBACK_ARGS:55,HREF:56,CSSCLASS:57,commentToken:58,textToken:59,graphCodeTokens:60,textNoTagsToken:61,TAGSTART:62,TAGEND:63,"==":64,"--":65,PCT:66,DEFAULT:67,SPACE:68,MINUS:69,keywords:70,UNICODE_TEXT:71,NUM:72,ALPHA:73,$accept:0,$end:1},terminals_:{2:"error",10:"NEWLINE",11:":",13:"open_directive",14:"type_directive",15:"arg_directive",16:"close_directive",17:"CLASS_DIAGRAM",19:"EOF",23:"GENERICTYPE",25:"LABEL",31:"CLASS",32:"STYLE_SEPARATOR",33:"STRUCT_START",35:"STRUCT_STOP",36:"ANNOTATION_START",37:"ANNOTATION_END",38:"MEMBER",39:"SEPARATOR",41:"STR",44:"AGGREGATION",45:"EXTENSION",46:"COMPOSITION",47:"DEPENDENCY",48:"LINE",49:"DOTTED_LINE",50:"CALLBACK",51:"LINK",52:"LINK_TARGET",53:"CLICK",54:"CALLBACK_NAME",55:"CALLBACK_ARGS",56:"HREF",57:"CSSCLASS",60:"graphCodeTokens",62:"TAGSTART",63:"TAGEND",64:"==",65:"--",66:"PCT",67:"DEFAULT",68:"SPACE",69:"MINUS",70:"keywords",71:"UNICODE_TEXT",72:"NUM",73:"ALPHA"},productions_:[0,[3,1],[3,2],[4,1],[5,4],[5,6],[7,1],[8,1],[12,1],[9,1],[6,4],[18,1],[18,2],[18,3],[21,1],[21,2],[21,3],[21,2],[20,1],[20,2],[20,1],[20,1],[20,1],[20,1],[20,1],[20,1],[26,2],[26,4],[26,5],[26,7],[28,4],[34,1],[34,2],[27,1],[27,2],[27,1],[27,1],[24,3],[24,4],[24,4],[24,5],[40,3],[40,2],[40,2],[40,1],[42,1],[42,1],[42,1],[42,1],[43,1],[43,1],[29,3],[29,4],[29,3],[29,4],[29,4],[29,5],[29,3],[29,4],[29,4],[29,5],[29,3],[29,4],[29,4],[29,5],[30,3],[58,1],[58,1],[59,1],[59,1],[59,1],[59,1],[59,1],[59,1],[59,1],[61,1],[61,1],[61,1],[61,1],[22,1],[22,1],[22,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:r.parseDirective("%%{","open_directive");break;case 7:r.parseDirective(a[s],"type_directive");break;case 8:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 9:r.parseDirective("}%%","close_directive","class");break;case 14:this.$=a[s];break;case 15:this.$=a[s-1]+a[s];break;case 16:this.$=a[s-2]+"~"+a[s-1]+a[s];break;case 17:this.$=a[s-1]+"~"+a[s];break;case 18:r.addRelation(a[s]);break;case 19:a[s-1].title=r.cleanupLabel(a[s]),r.addRelation(a[s-1]);break;case 26:r.addClass(a[s]);break;case 27:r.addClass(a[s-2]),r.setCssClass(a[s-2],a[s]);break;case 28:r.addClass(a[s-3]),r.addMembers(a[s-3],a[s-1]);break;case 29:r.addClass(a[s-5]),r.setCssClass(a[s-5],a[s-3]),r.addMembers(a[s-5],a[s-1]);break;case 30:r.addAnnotation(a[s],a[s-2]);break;case 31:this.$=[a[s]];break;case 32:a[s].push(a[s-1]),this.$=a[s];break;case 33:break;case 34:r.addMember(a[s-1],r.cleanupLabel(a[s]));break;case 35:case 36:break;case 37:this.$={id1:a[s-2],id2:a[s],relation:a[s-1],relationTitle1:"none",relationTitle2:"none"};break;case 38:this.$={id1:a[s-3],id2:a[s],relation:a[s-1],relationTitle1:a[s-2],relationTitle2:"none"};break;case 39:this.$={id1:a[s-3],id2:a[s],relation:a[s-2],relationTitle1:"none",relationTitle2:a[s-1]};break;case 40:this.$={id1:a[s-4],id2:a[s],relation:a[s-2],relationTitle1:a[s-3],relationTitle2:a[s-1]};break;case 41:this.$={type1:a[s-2],type2:a[s],lineType:a[s-1]};break;case 42:this.$={type1:"none",type2:a[s],lineType:a[s-1]};break;case 43:this.$={type1:a[s-1],type2:"none",lineType:a[s]};break;case 44:this.$={type1:"none",type2:"none",lineType:a[s]};break;case 45:this.$=r.relationType.AGGREGATION;break;case 46:this.$=r.relationType.EXTENSION;break;case 47:this.$=r.relationType.COMPOSITION;break;case 48:this.$=r.relationType.DEPENDENCY;break;case 49:this.$=r.lineType.LINE;break;case 50:this.$=r.lineType.DOTTED_LINE;break;case 51:case 57:this.$=a[s-2],r.setClickEvent(a[s-1],a[s]);break;case 52:case 58:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 53:case 61:this.$=a[s-2],r.setLink(a[s-1],a[s]);break;case 54:this.$=a[s-3],r.setLink(a[s-2],a[s-1],a[s]);break;case 55:case 63:this.$=a[s-3],r.setLink(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 56:case 64:this.$=a[s-4],r.setLink(a[s-3],a[s-2],a[s]),r.setTooltip(a[s-3],a[s-1]);break;case 59:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 60:this.$=a[s-4],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setTooltip(a[s-3],a[s]);break;case 62:this.$=a[s-3],r.setLink(a[s-2],a[s-1],a[s]);break;case 65:r.setCssClass(a[s-1],a[s])}},table:[{3:1,4:2,5:3,6:4,7:5,13:e,17:n},{1:[3]},{1:[2,1]},{3:8,4:2,5:3,6:4,7:5,13:e,17:n},{1:[2,3]},{8:9,14:[1,10]},{10:[1,11]},{14:[2,6]},{1:[2,2]},{9:12,11:[1,13],16:r},t([11,16],[2,7]),{5:23,7:5,13:e,18:15,20:16,21:24,22:33,24:17,26:18,27:19,28:20,29:21,30:22,31:i,36:a,38:o,39:s,50:c,51:u,53:l,57:h,71:f,72:d,73:p},{10:[1,37]},{12:38,15:[1,39]},{10:[2,9]},{19:[1,40]},{10:[1,41],19:[2,11]},t(g,[2,18],{25:[1,42]}),t(g,[2,20]),t(g,[2,21]),t(g,[2,22]),t(g,[2,23]),t(g,[2,24]),t(g,[2,25]),t(g,[2,33],{40:43,42:46,43:47,25:[1,45],41:[1,44],44:y,45:v,46:m,47:b,48:x,49:_}),{21:54,22:33,71:f,72:d,73:p},t(g,[2,35]),t(g,[2,36]),{22:55,71:f,72:d,73:p},{21:56,22:33,71:f,72:d,73:p},{21:57,22:33,71:f,72:d,73:p},{21:58,22:33,71:f,72:d,73:p},{41:[1,59]},t(k,[2,14],{22:33,21:60,23:[1,61],71:f,72:d,73:p}),t(w,[2,79]),t(w,[2,80]),t(w,[2,81]),t(E,[2,4]),{9:62,16:r},{16:[2,8]},{1:[2,10]},{5:23,7:5,13:e,18:63,19:[2,12],20:16,21:24,22:33,24:17,26:18,27:19,28:20,29:21,30:22,31:i,36:a,38:o,39:s,50:c,51:u,53:l,57:h,71:f,72:d,73:p},t(g,[2,19]),{21:64,22:33,41:[1,65],71:f,72:d,73:p},{40:66,42:46,43:47,44:y,45:v,46:m,47:b,48:x,49:_},t(g,[2,34]),{43:67,48:x,49:_},t(T,[2,44],{42:68,44:y,45:v,46:m,47:b}),t(C,[2,45]),t(C,[2,46]),t(C,[2,47]),t(C,[2,48]),t(A,[2,49]),t(A,[2,50]),t(g,[2,26],{32:[1,69],33:[1,70]}),{37:[1,71]},{41:[1,72]},{41:[1,73]},{54:[1,74],56:[1,75]},{22:76,71:f,72:d,73:p},t(k,[2,15]),t(k,[2,17],{22:33,21:77,71:f,72:d,73:p}),{10:[1,78]},{19:[2,13]},t(S,[2,37]),{21:79,22:33,71:f,72:d,73:p},{21:80,22:33,41:[1,81],71:f,72:d,73:p},t(T,[2,43],{42:82,44:y,45:v,46:m,47:b}),t(T,[2,42]),{22:83,71:f,72:d,73:p},{34:84,38:M},{21:86,22:33,71:f,72:d,73:p},t(g,[2,51],{41:[1,87]}),t(g,[2,53],{41:[1,89],52:[1,88]}),t(g,[2,57],{41:[1,90],55:[1,91]}),t(g,[2,61],{41:[1,93],52:[1,92]}),t(g,[2,65]),t(k,[2,16]),t(E,[2,5]),t(S,[2,39]),t(S,[2,38]),{21:94,22:33,71:f,72:d,73:p},t(T,[2,41]),t(g,[2,27],{33:[1,95]}),{35:[1,96]},{34:97,35:[2,31],38:M},t(g,[2,30]),t(g,[2,52]),t(g,[2,54]),t(g,[2,55],{52:[1,98]}),t(g,[2,58]),t(g,[2,59],{41:[1,99]}),t(g,[2,62]),t(g,[2,63],{52:[1,100]}),t(S,[2,40]),{34:101,38:M},t(g,[2,28]),{35:[2,32]},t(g,[2,56]),t(g,[2,60]),t(g,[2,64]),{35:[1,102]},t(g,[2,29])],defaultActions:{2:[2,1],4:[2,3],7:[2,6],8:[2,2],14:[2,9],39:[2,8],40:[2,10],63:[2,13],97:[2,32]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},D={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),13;case 1:return this.begin("type_directive"),14;case 2:return this.popState(),this.begin("arg_directive"),11;case 3:return this.popState(),this.popState(),16;case 4:return 15;case 5:case 6:break;case 7:return 10;case 8:break;case 9:case 10:return 17;case 11:return this.begin("struct"),33;case 12:return"EOF_IN_STRUCT";case 13:return"OPEN_IN_STRUCT";case 14:return this.popState(),35;case 15:break;case 16:return"MEMBER";case 17:return 31;case 18:return 57;case 19:return 50;case 20:return 51;case 21:return 53;case 22:return 36;case 23:return 37;case 24:this.begin("generic");break;case 25:this.popState();break;case 26:return"GENERICTYPE";case 27:this.begin("string");break;case 28:this.popState();break;case 29:return"STR";case 30:this.begin("href");break;case 31:this.popState();break;case 32:return 56;case 33:this.begin("callback_name");break;case 34:this.popState();break;case 35:this.popState(),this.begin("callback_args");break;case 36:return 54;case 37:this.popState();break;case 38:return 55;case 39:case 40:case 41:case 42:return 52;case 43:case 44:return 45;case 45:case 46:return 47;case 47:return 46;case 48:return 44;case 49:return 48;case 50:return 49;case 51:return 25;case 52:return 32;case 53:return 69;case 54:return"DOT";case 55:return"PLUS";case 56:return 66;case 57:case 58:return"EQUALS";case 59:return 73;case 60:return"PUNCTUATION";case 61:return 72;case 62:return 71;case 63:return 68;case 64:return 19}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:(\r?\n)+)/,/^(?:\s+)/,/^(?:classDiagram-v2\b)/,/^(?:classDiagram\b)/,/^(?:[{])/,/^(?:$)/,/^(?:[{])/,/^(?:[}])/,/^(?:[\n])/,/^(?:[^{}\n]*)/,/^(?:class\b)/,/^(?:cssClass\b)/,/^(?:callback\b)/,/^(?:link\b)/,/^(?:click\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:[~])/,/^(?:[~])/,/^(?:[^~]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:--)/,/^(?:\.\.)/,/^(?::{1}[^:\n;]+)/,/^(?::{3})/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:[!"#$%&'*+,-.`?\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:$)/],conditions:{arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callback_args:{rules:[37,38],inclusive:!1},callback_name:{rules:[34,35,36],inclusive:!1},href:{rules:[31,32],inclusive:!1},struct:{rules:[12,13,14,15,16],inclusive:!1},generic:{rules:[25,26],inclusive:!1},string:{rules:[28,29],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,17,18,19,20,21,22,23,24,27,30,33,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64],inclusive:!0}}};function N(){this.yy={}}return O.lexer=D,N.prototype=O,O.Parser=N,new N}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e){var n,r,i=t.exports={};function a(){throw new Error("setTimeout has not been defined")}function o(){throw new Error("clearTimeout has not been defined")}function s(t){if(n===setTimeout)return setTimeout(t,0);if((n===a||!n)&&setTimeout)return n=setTimeout,setTimeout(t,0);try{return n(t,0)}catch(e){try{return n.call(null,t,0)}catch(e){return n.call(this,t,0)}}}!function(){try{n="function"==typeof setTimeout?setTimeout:a}catch(t){n=a}try{r="function"==typeof clearTimeout?clearTimeout:o}catch(t){r=o}}();var c,u=[],l=!1,h=-1;function f(){l&&c&&(l=!1,c.length?u=c.concat(u):h=-1,u.length&&d())}function d(){if(!l){var t=s(f);l=!0;for(var e=u.length;e;){for(c=u,u=[];++h1)for(var n=1;n=0;r--){var i=t[r];"."===i?t.splice(r,1):".."===i?(t.splice(r,1),n++):n&&(t.splice(r,1),n--)}if(e)for(;n--;n)t.unshift("..");return t}function r(t,e){if(t.filter)return t.filter(e);for(var n=[],r=0;r=-1&&!i;a--){var o=a>=0?arguments[a]:t.cwd();if("string"!=typeof o)throw new TypeError("Arguments to path.resolve must be strings");o&&(e=o+"/"+e,i="/"===o.charAt(0))}return(i?"/":"")+(e=n(r(e.split("/"),(function(t){return!!t})),!i).join("/"))||"."},e.normalize=function(t){var a=e.isAbsolute(t),o="/"===i(t,-1);return(t=n(r(t.split("/"),(function(t){return!!t})),!a).join("/"))||a||(t="."),t&&o&&(t+="/"),(a?"/":"")+t},e.isAbsolute=function(t){return"/"===t.charAt(0)},e.join=function(){var t=Array.prototype.slice.call(arguments,0);return e.normalize(r(t,(function(t,e){if("string"!=typeof t)throw new TypeError("Arguments to path.join must be strings");return t})).join("/"))},e.relative=function(t,n){function r(t){for(var e=0;e=0&&""===t[n];n--);return e>n?[]:t.slice(e,n-e+1)}t=e.resolve(t).substr(1),n=e.resolve(n).substr(1);for(var i=r(t.split("/")),a=r(n.split("/")),o=Math.min(i.length,a.length),s=o,c=0;c=1;--a)if(47===(e=t.charCodeAt(a))){if(!i){r=a;break}}else i=!1;return-1===r?n?"/":".":n&&1===r?"/":t.slice(0,r)},e.basename=function(t,e){var n=function(t){"string"!=typeof t&&(t+="");var e,n=0,r=-1,i=!0;for(e=t.length-1;e>=0;--e)if(47===t.charCodeAt(e)){if(!i){n=e+1;break}}else-1===r&&(i=!1,r=e+1);return-1===r?"":t.slice(n,r)}(t);return e&&n.substr(-1*e.length)===e&&(n=n.substr(0,n.length-e.length)),n},e.extname=function(t){"string"!=typeof t&&(t+="");for(var e=-1,n=0,r=-1,i=!0,a=0,o=t.length-1;o>=0;--o){var s=t.charCodeAt(o);if(47!==s)-1===r&&(i=!1,r=o+1),46===s?-1===e?e=o:1!==a&&(a=1):-1!==e&&(a=-1);else if(!i){n=o+1;break}}return-1===e||-1===r||0===a||1===a&&e===r-1&&e===n+1?"":t.slice(e,r)};var i="b"==="ab".substr(-1)?function(t,e,n){return t.substr(e,n)}:function(t,e,n){return e<0&&(e=t.length+e),t.substr(e,n)}}).call(this,n(14))},function(t,e){t.exports=function(t){return null!=t&&"object"==typeof t}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],c=[1,19],u=[1,20],l=[1,21],h=[1,22],f=[1,28],d=[1,23],p=[1,24],g=[1,25],y=[1,26],v=[1,29],m=[1,32],b=[1,4,5,14,15,17,19,20,22,23,24,25,26,36,39],x=[1,4,5,12,13,14,15,17,19,20,22,23,24,25,26,36,39],_=[1,4,5,7,14,15,17,19,20,22,23,24,25,26,36,39],k=[4,5,14,15,17,19,20,22,23,24,25,26,36,39],w={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,directive:6,SD:7,document:8,line:9,statement:10,idStatement:11,DESCR:12,"--\x3e":13,HIDE_EMPTY:14,scale:15,WIDTH:16,COMPOSIT_STATE:17,STRUCT_START:18,STRUCT_STOP:19,STATE_DESCR:20,AS:21,ID:22,FORK:23,JOIN:24,CONCURRENT:25,note:26,notePosition:27,NOTE_TEXT:28,openDirective:29,typeDirective:30,closeDirective:31,":":32,argDirective:33,eol:34,";":35,EDGE_STATE:36,left_of:37,right_of:38,open_directive:39,type_directive:40,arg_directive:41,close_directive:42,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",7:"SD",12:"DESCR",13:"--\x3e",14:"HIDE_EMPTY",15:"scale",16:"WIDTH",17:"COMPOSIT_STATE",18:"STRUCT_START",19:"STRUCT_STOP",20:"STATE_DESCR",21:"AS",22:"ID",23:"FORK",24:"JOIN",25:"CONCURRENT",26:"note",28:"NOTE_TEXT",32:":",35:";",36:"EDGE_STATE",37:"left_of",38:"right_of",39:"open_directive",40:"type_directive",41:"arg_directive",42:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[10,1],[10,2],[10,3],[10,4],[10,1],[10,2],[10,1],[10,4],[10,3],[10,6],[10,1],[10,1],[10,1],[10,4],[10,4],[10,1],[6,3],[6,5],[34,1],[34,1],[11,1],[11,1],[27,1],[27,1],[29,1],[30,1],[33,1],[31,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.setRootDoc(a[s]),a[s];case 5:this.$=[];break;case 6:"nl"!=a[s]&&(a[s-1].push(a[s]),this.$=a[s-1]);break;case 7:case 8:this.$=a[s];break;case 9:this.$="nl";break;case 10:this.$={stmt:"state",id:a[s],type:"default",description:""};break;case 11:this.$={stmt:"state",id:a[s-1],type:"default",description:r.trimColon(a[s])};break;case 12:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-2],type:"default",description:""},state2:{stmt:"state",id:a[s],type:"default",description:""}};break;case 13:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-3],type:"default",description:""},state2:{stmt:"state",id:a[s-1],type:"default",description:""},description:a[s].substr(1).trim()};break;case 17:this.$={stmt:"state",id:a[s-3],type:"default",description:"",doc:a[s-1]};break;case 18:var c=a[s],u=a[s-2].trim();if(a[s].match(":")){var l=a[s].split(":");c=l[0],u=[u,l[1]]}this.$={stmt:"state",id:c,type:"default",description:u};break;case 19:this.$={stmt:"state",id:a[s-3],type:"default",description:a[s-5],doc:a[s-1]};break;case 20:this.$={stmt:"state",id:a[s],type:"fork"};break;case 21:this.$={stmt:"state",id:a[s],type:"join"};break;case 22:this.$={stmt:"state",id:r.getDividerId(),type:"divider"};break;case 23:this.$={stmt:"state",id:a[s-1].trim(),note:{position:a[s-2].trim(),text:a[s].trim()}};break;case 30:case 31:this.$=a[s];break;case 34:r.parseDirective("%%{","open_directive");break;case 35:r.parseDirective(a[s],"type_directive");break;case 36:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 37:r.parseDirective("}%%","close_directive","state")}},table:[{3:1,4:e,5:n,6:4,7:r,29:6,39:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,29:6,39:i},{3:9,4:e,5:n,6:4,7:r,29:6,39:i},{3:10,4:e,5:n,6:4,7:r,29:6,39:i},t([1,4,5,14,15,17,20,22,23,24,25,26,36,39],a,{8:11}),{30:12,40:[1,13]},{40:[2,34]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},{31:30,32:[1,31],42:m},t([32,42],[2,35]),t(b,[2,6]),{6:27,10:33,11:18,14:c,15:u,17:l,20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,8]),t(b,[2,9]),t(b,[2,10],{12:[1,34],13:[1,35]}),t(b,[2,14]),{16:[1,36]},t(b,[2,16],{18:[1,37]}),{21:[1,38]},t(b,[2,20]),t(b,[2,21]),t(b,[2,22]),{27:39,28:[1,40],37:[1,41],38:[1,42]},t(b,[2,25]),t(x,[2,30]),t(x,[2,31]),t(_,[2,26]),{33:43,41:[1,44]},t(_,[2,37]),t(b,[2,7]),t(b,[2,11]),{11:45,22:f,36:v},t(b,[2,15]),t(k,a,{8:46}),{22:[1,47]},{22:[1,48]},{21:[1,49]},{22:[2,32]},{22:[2,33]},{31:50,42:m},{42:[2,36]},t(b,[2,12],{12:[1,51]}),{4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,19:[1,52],20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,18],{18:[1,53]}),{28:[1,54]},{22:[1,55]},t(_,[2,27]),t(b,[2,13]),t(b,[2,17]),t(k,a,{8:56}),t(b,[2,23]),t(b,[2,24]),{4:o,5:s,6:27,9:14,10:16,11:18,14:c,15:u,17:l,19:[1,57],20:h,22:f,23:d,24:p,25:g,26:y,29:6,36:v,39:i},t(b,[2,19])],defaultActions:{7:[2,34],8:[2,1],9:[2,2],10:[2,3],41:[2,32],42:[2,33],44:[2,36]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},E={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),39;case 1:return this.begin("type_directive"),40;case 2:return this.popState(),this.begin("arg_directive"),32;case 3:return this.popState(),this.popState(),42;case 4:return 41;case 5:break;case 6:console.log("Crap after close");break;case 7:return 5;case 8:case 9:case 10:case 11:break;case 12:return this.pushState("SCALE"),15;case 13:return 16;case 14:this.popState();break;case 15:this.pushState("STATE");break;case 16:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 17:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 18:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 19:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 20:this.begin("STATE_STRING");break;case 21:return this.popState(),this.pushState("STATE_ID"),"AS";case 22:return this.popState(),"ID";case 23:this.popState();break;case 24:return"STATE_DESCR";case 25:return 17;case 26:this.popState();break;case 27:return this.popState(),this.pushState("struct"),18;case 28:return this.popState(),19;case 29:break;case 30:return this.begin("NOTE"),26;case 31:return this.popState(),this.pushState("NOTE_ID"),37;case 32:return this.popState(),this.pushState("NOTE_ID"),38;case 33:this.popState(),this.pushState("FLOATING_NOTE");break;case 34:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 35:break;case 36:return"NOTE_TEXT";case 37:return this.popState(),"ID";case 38:return this.popState(),this.pushState("NOTE_TEXT"),22;case 39:return this.popState(),e.yytext=e.yytext.substr(2).trim(),28;case 40:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),28;case 41:case 42:return 7;case 43:return 14;case 44:return 36;case 45:return 22;case 46:return e.yytext=e.yytext.trim(),12;case 47:return 13;case 48:return 25;case 49:return 5;case 50:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:[\s]+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:["])/i,/^(?:\s*as\s+)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:[\s\S]*?end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:stateDiagram-v2\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[9,10],inclusive:!1},close_directive:{rules:[9,10],inclusive:!1},arg_directive:{rules:[3,4,9,10],inclusive:!1},type_directive:{rules:[2,3,9,10],inclusive:!1},open_directive:{rules:[1,9,10],inclusive:!1},struct:{rules:[9,10,15,28,29,30,44,45,46,47,48],inclusive:!1},FLOATING_NOTE_ID:{rules:[37],inclusive:!1},FLOATING_NOTE:{rules:[34,35,36],inclusive:!1},NOTE_TEXT:{rules:[39,40],inclusive:!1},NOTE_ID:{rules:[38],inclusive:!1},NOTE:{rules:[31,32,33],inclusive:!1},SCALE:{rules:[13,14],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[22],inclusive:!1},STATE_STRING:{rules:[23,24],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[9,10,16,17,18,19,20,21,25,26,27],inclusive:!1},ID:{rules:[9,10],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,10,11,12,15,27,30,41,42,43,44,45,46,47,49,50],inclusive:!0}}};function T(){this.yy={}}return w.lexer=E,T.prototype=w,w.Parser=T,new T}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t){t.exports=function(){"use strict";var e,r;function i(){return e.apply(null,arguments)}function a(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function o(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function c(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function u(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function l(t,e){var n,r=[];for(n=0;n>>0,r=0;ryt(t)?(a=t+1,s-yt(t)):(a=t,s),{year:a,dayOfYear:o}}function Ft(t,e,n){var r,i,a=Bt(t.year(),e,n),o=Math.floor((t.dayOfYear()-a-1)/7)+1;return o<1?r=o+Pt(i=t.year()-1,e,n):o>Pt(t.year(),e,n)?(r=o-Pt(t.year(),e,n),i=t.year()+1):(i=t.year(),r=o),{week:r,year:i}}function Pt(t,e,n){var r=Bt(t,e,n),i=Bt(t+1,e,n);return(yt(t)-r+i)/7}function It(t,e){return t.slice(e,7).concat(t.slice(0,e))}W("w",["ww",2],"wo","week"),W("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),j("week",5),j("isoWeek",5),lt("w",K),lt("ww",K,q),lt("W",K),lt("WW",K,q),gt(["w","ww","W","WW"],(function(t,e,n,r){e[r.substr(0,1)]=w(t)})),W("d",0,"do","day"),W("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),W("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),W("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),W("e",0,0,"weekday"),W("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),j("day",11),j("weekday",11),j("isoWeekday",11),lt("d",K),lt("e",K),lt("E",K),lt("dd",(function(t,e){return e.weekdaysMinRegex(t)})),lt("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),lt("dddd",(function(t,e){return e.weekdaysRegex(t)})),gt(["dd","ddd","dddd"],(function(t,e,n,r){var i=n._locale.weekdaysParse(t,r,n._strict);null!=i?e.d=i:p(n).invalidWeekday=t})),gt(["d","e","E"],(function(t,e,n,r){e[r]=w(t)}));var jt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Rt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Yt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),zt=ct,Ut=ct,$t=ct;function Wt(){function t(t,e){return e.length-t.length}var e,n,r,i,a,o=[],s=[],c=[],u=[];for(e=0;e<7;e++)n=d([2e3,1]).day(e),r=this.weekdaysMin(n,""),i=this.weekdaysShort(n,""),a=this.weekdays(n,""),o.push(r),s.push(i),c.push(a),u.push(r),u.push(i),u.push(a);for(o.sort(t),s.sort(t),c.sort(t),u.sort(t),e=0;e<7;e++)s[e]=ft(s[e]),c[e]=ft(c[e]),u[e]=ft(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+c.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function Ht(){return this.hours()%12||12}function Vt(t,e){W(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function Gt(t,e){return e._meridiemParse}W("H",["HH",2],0,"hour"),W("h",["hh",2],0,Ht),W("k",["kk",2],0,(function(){return this.hours()||24})),W("hmm",0,0,(function(){return""+Ht.apply(this)+R(this.minutes(),2)})),W("hmmss",0,0,(function(){return""+Ht.apply(this)+R(this.minutes(),2)+R(this.seconds(),2)})),W("Hmm",0,0,(function(){return""+this.hours()+R(this.minutes(),2)})),W("Hmmss",0,0,(function(){return""+this.hours()+R(this.minutes(),2)+R(this.seconds(),2)})),Vt("a",!0),Vt("A",!1),L("hour","h"),j("hour",13),lt("a",Gt),lt("A",Gt),lt("H",K),lt("h",K),lt("k",K),lt("HH",K,q),lt("hh",K,q),lt("kk",K,q),lt("hmm",Q),lt("hmmss",tt),lt("Hmm",Q),lt("Hmmss",tt),pt(["H","HH"],3),pt(["k","kk"],(function(t,e,n){var r=w(t);e[3]=24===r?0:r})),pt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),pt(["h","hh"],(function(t,e,n){e[3]=w(t),p(n).bigHour=!0})),pt("hmm",(function(t,e,n){var r=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r)),p(n).bigHour=!0})),pt("hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r,2)),e[5]=w(t.substr(i)),p(n).bigHour=!0})),pt("Hmm",(function(t,e,n){var r=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r))})),pt("Hmmss",(function(t,e,n){var r=t.length-4,i=t.length-2;e[3]=w(t.substr(0,r)),e[4]=w(t.substr(r,2)),e[5]=w(t.substr(i))}));var qt,Xt=xt("Hours",!0),Zt={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Tt,monthsShort:Ct,week:{dow:0,doy:6},weekdays:jt,weekdaysMin:Yt,weekdaysShort:Rt,meridiemParse:/[ap]\.?m?\.?/i},Jt={},Kt={};function Qt(t){return t?t.toLowerCase().replace("_","-"):t}function te(e){var r=null;if(!Jt[e]&&void 0!==t&&t&&t.exports)try{r=qt._abbr,n(171)("./"+e),ee(r)}catch(e){}return Jt[e]}function ee(t,e){var n;return t&&((n=s(e)?re(t):ne(t,e))?qt=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),qt._abbr}function ne(t,e){if(null===e)return delete Jt[t],null;var n,r=Zt;if(e.abbr=t,null!=Jt[t])M("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),r=Jt[t]._config;else if(null!=e.parentLocale)if(null!=Jt[e.parentLocale])r=Jt[e.parentLocale]._config;else{if(null==(n=te(e.parentLocale)))return Kt[e.parentLocale]||(Kt[e.parentLocale]=[]),Kt[e.parentLocale].push({name:t,config:e}),null;r=n._config}return Jt[t]=new N(D(r,e)),Kt[t]&&Kt[t].forEach((function(t){ne(t.name,t.config)})),ee(t),Jt[t]}function re(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return qt;if(!a(t)){if(e=te(t))return e;t=[t]}return function(t){for(var e,n,r,i,a=0;a=e&&E(i,n,!0)>=e-1)break;e--}a++}return qt}(t)}function ie(t){var e,n=t._a;return n&&-2===p(t).overflow&&(e=n[1]<0||11wt(n[0],n[1])?2:n[3]<0||24Pt(n,a,o)?p(t)._overflowWeeks=!0:null!=c?p(t)._overflowWeekday=!0:(s=Lt(n,r,i,a,o),t._a[0]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(o=ae(t._a[0],r[0]),(t._dayOfYear>yt(o)||0===t._dayOfYear)&&(p(t)._overflowDayOfYear=!0),n=Nt(o,0,t._dayOfYear),t._a[1]=n.getUTCMonth(),t._a[2]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=r[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[3]&&0===t._a[4]&&0===t._a[5]&&0===t._a[6]&&(t._nextDay=!0,t._a[3]=0),t._d=(t._useUTC?Nt:function(t,e,n,r,i,a,o){var s;return t<100&&0<=t?(s=new Date(t+400,e,n,r,i,a,o),isFinite(s.getFullYear())&&s.setFullYear(t)):s=new Date(t,e,n,r,i,a,o),s}).apply(null,s),a=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[3]=24),t._w&&void 0!==t._w.d&&t._w.d!==a&&(p(t).weekdayMismatch=!0)}}var se=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ce=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,ue=/Z|[+-]\d\d(?::?\d\d)?/,le=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],he=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],fe=/^\/?Date\((\-?\d+)/i;function de(t){var e,n,r,i,a,o,s=t._i,c=se.exec(s)||ce.exec(s);if(c){for(p(t).iso=!0,e=0,n=le.length;en.valueOf():n.valueOf()this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},on.isLocal=function(){return!!this.isValid()&&!this._isUTC},on.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},on.isUtc=Be,on.isUTC=Be,on.zoneAbbr=function(){return this._isUTC?"UTC":""},on.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},on.dates=C("dates accessor is deprecated. Use date instead.",Qe),on.months=C("months accessor is deprecated. Use month instead",St),on.years=C("years accessor is deprecated. Use year instead",bt),on.zone=C("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),on.isDSTShifted=C("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(m(t,this),(t=me(t))._a){var e=t._isUTC?d(t._a):xe(t._a);this._isDSTShifted=this.isValid()&&0h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},qt={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),12;case 1:return this.begin("type_directive"),13;case 2:return this.popState(),this.begin("arg_directive"),10;case 3:return this.popState(),this.popState(),15;case 4:return 14;case 5:case 6:break;case 7:this.begin("string");break;case 8:this.popState();break;case 9:return"STR";case 10:return 75;case 11:return 84;case 12:return 76;case 13:return 93;case 14:return 77;case 15:return 78;case 16:this.begin("href");break;case 17:this.popState();break;case 18:return 89;case 19:this.begin("callbackname");break;case 20:this.popState();break;case 21:this.popState(),this.begin("callbackargs");break;case 22:return 87;case 23:this.popState();break;case 24:return 88;case 25:this.begin("click");break;case 26:this.popState();break;case 27:return 79;case 28:case 29:return t.lex.firstGraph()&&this.begin("dir"),24;case 30:return 38;case 31:return 42;case 32:case 33:case 34:case 35:return 90;case 36:return this.popState(),25;case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:return this.popState(),26;case 47:return 94;case 48:return 102;case 49:return 47;case 50:return 99;case 51:return 46;case 52:return 20;case 53:return 95;case 54:return 113;case 55:case 56:case 57:return 70;case 58:case 59:case 60:return 69;case 61:return 51;case 62:return 52;case 63:return 53;case 64:return 54;case 65:return 55;case 66:return 56;case 67:return 57;case 68:return 58;case 69:return 100;case 70:return 103;case 71:return 114;case 72:return 111;case 73:return 104;case 74:case 75:return 112;case 76:return 105;case 77:return 61;case 78:return 81;case 79:return"SEP";case 80:return 80;case 81:return 98;case 82:return 63;case 83:return 62;case 84:return 65;case 85:return 64;case 86:return 109;case 87:return 110;case 88:return 71;case 89:return 49;case 90:return 50;case 91:return 40;case 92:return 41;case 93:return 59;case 94:return 60;case 95:return 120;case 96:return 21;case 97:return 22;case 98:return 23}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)[^\n]*)/,/^(?:[^\}]%%[^\n]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\[)/,/^(?:\]\))/,/^(?:\[\[)/,/^(?:\]\])/,/^(?:\[\()/,/^(?:\)\])/,/^(?:-)/,/^(?:\.)/,/^(?:[\_])/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:[A-Za-z]+)/,/^(?:\\\])/,/^(?:\[\/)/,/^(?:\/\])/,/^(?:\[\\)/,/^(?:[!"#$%&'*+,-.`?\\_/])/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[23,24],inclusive:!1},callbackname:{rules:[20,21,22],inclusive:!1},href:{rules:[17,18],inclusive:!1},click:{rules:[26,27],inclusive:!1},vertex:{rules:[],inclusive:!1},dir:{rules:[36,37,38,39,40,41,42,43,44,45,46],inclusive:!1},string:{rules:[8,9],inclusive:!1},INITIAL:{rules:[0,5,6,7,10,11,12,13,14,15,16,19,25,28,29,30,31,32,33,34,35,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98],inclusive:!0}}};function Xt(){this.yy={}}return Gt.lexer=qt,Xt.prototype=Gt,Gt.Parser=Xt,new Xt}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,5],r=[7,9,11,12,13,14,15,16,17,18,20,27,32],i=[1,15],a=[1,16],o=[1,17],s=[1,18],c=[1,19],u=[1,20],l=[1,21],h=[1,23],f=[1,25],d=[1,28],p=[5,7,9,11,12,13,14,15,16,17,18,20,27,32],g={trace:function(){},yy:{},symbols_:{error:2,start:3,directive:4,gantt:5,document:6,EOF:7,line:8,SPACE:9,statement:10,NL:11,dateFormat:12,inclusiveEndDates:13,axisFormat:14,excludes:15,todayMarker:16,title:17,section:18,clickStatement:19,taskTxt:20,taskData:21,openDirective:22,typeDirective:23,closeDirective:24,":":25,argDirective:26,click:27,callbackname:28,callbackargs:29,href:30,clickStatementDebug:31,open_directive:32,type_directive:33,arg_directive:34,close_directive:35,$accept:0,$end:1},terminals_:{2:"error",5:"gantt",7:"EOF",9:"SPACE",11:"NL",12:"dateFormat",13:"inclusiveEndDates",14:"axisFormat",15:"excludes",16:"todayMarker",17:"title",18:"section",20:"taskTxt",21:"taskData",25:":",27:"click",28:"callbackname",29:"callbackargs",30:"href",32:"open_directive",33:"type_directive",34:"arg_directive",35:"close_directive"},productions_:[0,[3,2],[3,3],[6,0],[6,2],[8,2],[8,1],[8,1],[8,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,2],[10,1],[4,4],[4,6],[19,2],[19,3],[19,3],[19,4],[19,3],[19,4],[19,2],[31,2],[31,3],[31,3],[31,4],[31,3],[31,4],[31,2],[22,1],[23,1],[26,1],[24,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 2:return a[s-1];case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 9:r.setDateFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 10:r.enableInclusiveEndDates(),this.$=a[s].substr(18);break;case 11:r.setAxisFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 12:r.setExcludes(a[s].substr(9)),this.$=a[s].substr(9);break;case 13:r.setTodayMarker(a[s].substr(12)),this.$=a[s].substr(12);break;case 14:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 15:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 17:r.addTask(a[s-1],a[s]),this.$="task";break;case 21:this.$=a[s-1],r.setClickEvent(a[s-1],a[s],null);break;case 22:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 23:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],null),r.setLink(a[s-2],a[s]);break;case 24:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setLink(a[s-3],a[s]);break;case 25:this.$=a[s-2],r.setClickEvent(a[s-2],a[s],null),r.setLink(a[s-2],a[s-1]);break;case 26:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-1],a[s]),r.setLink(a[s-3],a[s-2]);break;case 27:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 28:case 34:this.$=a[s-1]+" "+a[s];break;case 29:case 30:case 32:this.$=a[s-2]+" "+a[s-1]+" "+a[s];break;case 31:case 33:this.$=a[s-3]+" "+a[s-2]+" "+a[s-1]+" "+a[s];break;case 35:r.parseDirective("%%{","open_directive");break;case 36:r.parseDirective(a[s],"type_directive");break;case 37:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 38:r.parseDirective("}%%","close_directive","gantt")}},table:[{3:1,4:2,5:e,22:4,32:n},{1:[3]},{3:6,4:2,5:e,22:4,32:n},t(r,[2,3],{6:7}),{23:8,33:[1,9]},{33:[2,35]},{1:[2,1]},{4:24,7:[1,10],8:11,9:[1,12],10:13,11:[1,14],12:i,13:a,14:o,15:s,16:c,17:u,18:l,19:22,20:h,22:4,27:f,32:n},{24:26,25:[1,27],35:d},t([25,35],[2,36]),t(r,[2,8],{1:[2,2]}),t(r,[2,4]),{4:24,10:29,12:i,13:a,14:o,15:s,16:c,17:u,18:l,19:22,20:h,22:4,27:f,32:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,9]),t(r,[2,10]),t(r,[2,11]),t(r,[2,12]),t(r,[2,13]),t(r,[2,14]),t(r,[2,15]),t(r,[2,16]),{21:[1,30]},t(r,[2,18]),{28:[1,31],30:[1,32]},{11:[1,33]},{26:34,34:[1,35]},{11:[2,38]},t(r,[2,5]),t(r,[2,17]),t(r,[2,21],{29:[1,36],30:[1,37]}),t(r,[2,27],{28:[1,38]}),t(p,[2,19]),{24:39,35:d},{35:[2,37]},t(r,[2,22],{30:[1,40]}),t(r,[2,23]),t(r,[2,25],{29:[1,41]}),{11:[1,42]},t(r,[2,24]),t(r,[2,26]),t(p,[2,20])],defaultActions:{5:[2,35],6:[2,1],28:[2,38],35:[2,37]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),32;case 1:return this.begin("type_directive"),33;case 2:return this.popState(),this.begin("arg_directive"),25;case 3:return this.popState(),this.popState(),35;case 4:return 34;case 5:case 6:case 7:break;case 8:return 11;case 9:case 10:case 11:break;case 12:this.begin("href");break;case 13:this.popState();break;case 14:return 30;case 15:this.begin("callbackname");break;case 16:this.popState();break;case 17:this.popState(),this.begin("callbackargs");break;case 18:return 28;case 19:this.popState();break;case 20:return 29;case 21:this.begin("click");break;case 22:this.popState();break;case 23:return 27;case 24:return 5;case 25:return 12;case 26:return 13;case 27:return 14;case 28:return 15;case 29:return 16;case 30:return"date";case 31:return 17;case 32:return 18;case 33:return 20;case 34:return 21;case 35:return 25;case 36:return 7;case 37:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)*[^\n]*)/i,/^(?:[^\}]%%*[^\n]*)/i,/^(?:%%*[^\n]*[\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:todayMarker\s[^\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[19,20],inclusive:!1},callbackname:{rules:[16,17,18],inclusive:!1},href:{rules:[13,14],inclusive:!1},click:{rules:[22,23],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,15,21,24,25,26,27,28,29,30,31,32,33,34,35,36,37],inclusive:!0}}};function v(){this.yy={}}return g.lexer=y,v.prototype=g,g.Parser=v,new v}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,17,18,19,21],i=[1,15],a=[1,16],o=[1,17],s=[1,21],c=[4,6,9,11,17,18,19,21],u={trace:function(){},yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,title:17,section:18,taskName:19,taskData:20,open_directive:21,type_directive:22,arg_directive:23,close_directive:24,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",17:"title",18:"section",19:"taskName",20:"taskData",21:"open_directive",22:"type_directive",23:"arg_directive",24:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,1],[10,2],[10,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 11:r.setTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 12:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 13:r.addTask(a[s-1],a[s]),this.$="task";break;case 15:r.parseDirective("%%{","open_directive");break;case 16:r.parseDirective(a[s],"type_directive");break;case 17:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 18:r.parseDirective("}%%","close_directive","journey")}},table:[{3:1,4:e,7:3,12:4,21:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,21:n},{13:8,22:[1,9]},{22:[2,15]},{6:[1,10],7:18,8:11,9:[1,12],10:13,11:[1,14],12:4,17:i,18:a,19:o,21:n},{1:[2,2]},{14:19,15:[1,20],24:s},t([15,24],[2,16]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:18,10:22,12:4,17:i,18:a,19:o,21:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,12]),{20:[1,23]},t(r,[2,14]),{11:[1,24]},{16:25,23:[1,26]},{11:[2,18]},t(r,[2,5]),t(r,[2,13]),t(c,[2,9]),{14:27,24:s},{24:[2,17]},{11:[1,28]},t(c,[2,10])],defaultActions:{5:[2,15],7:[2,2],21:[2,18],26:[2,17]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},l={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),21;case 1:return this.begin("type_directive"),22;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),24;case 4:return 23;case 5:case 6:break;case 7:return 11;case 8:case 9:break;case 10:return 4;case 11:return 17;case 12:return 18;case 13:return 19;case 14:return 20;case 15:return 15;case 16:return 6;case 17:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:journey\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,13,14,15,16,17],inclusive:!0}}};function h(){this.yy={}}return u.lexer=l,h.prototype=u,u.Parser=h,new h}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t,e){return r.default.lang.round(i.default.parse(t)[e])}},function(t,e,n){var r=n(112),i=n(82),a=n(24);t.exports=function(t){return a(t)?r(t):i(t)}},function(t,e,n){var r;if(!r)try{r=n(0)}catch(t){}r||(r=window.d3),t.exports=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t,e,n){var a=i.default.parse(t),o=a[e],s=r.default.channel.clamp[e](o+n);return o!==s&&(a[e]=s),i.default.stringify(a)}},function(t,e,n){var r=n(210),i=n(216);t.exports=function(t,e){var n=i(t,e);return r(n)?n:void 0}},function(t,e,n){var r=n(38),i=n(212),a=n(213),o=r?r.toStringTag:void 0;t.exports=function(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":o&&o in Object(t)?i(t):a(t)}},function(t,e){t.exports=function(t){return t}},function(t,e){t.exports=function(t,e){return t===e||t!=t&&e!=e}},function(t,e,n){var r=n(34),i=n(11);t.exports=function(t){if(!i(t))return!1;var e=r(t);return"[object Function]"==e||"[object GeneratorFunction]"==e||"[object AsyncFunction]"==e||"[object Proxy]"==e}},function(t,e,n){var r=n(16).Symbol;t.exports=r},function(t,e,n){(function(t){var r=n(16),i=n(232),a=e&&!e.nodeType&&e,o=a&&"object"==typeof t&&t&&!t.nodeType&&t,s=o&&o.exports===a?r.Buffer:void 0,c=(s?s.isBuffer:void 0)||i;t.exports=c}).call(this,n(7)(t))},function(t,e,n){var r=n(112),i=n(236),a=n(24);t.exports=function(t){return a(t)?r(t,!0):i(t)}},function(t,e,n){var r=n(241),i=n(77),a=n(242),o=n(121),s=n(243),c=n(34),u=n(110),l=u(r),h=u(i),f=u(a),d=u(o),p=u(s),g=c;(r&&"[object DataView]"!=g(new r(new ArrayBuffer(1)))||i&&"[object Map]"!=g(new i)||a&&"[object Promise]"!=g(a.resolve())||o&&"[object Set]"!=g(new o)||s&&"[object WeakMap]"!=g(new s))&&(g=function(t){var e=c(t),n="[object Object]"==e?t.constructor:void 0,r=n?u(n):"";if(r)switch(r){case l:return"[object DataView]";case h:return"[object Map]";case f:return"[object Promise]";case d:return"[object Set]";case p:return"[object WeakMap]"}return e}),t.exports=g},function(t,e,n){var r=n(34),i=n(21);t.exports=function(t){return"symbol"==typeof t||i(t)&&"[object Symbol]"==r(t)}},function(t,e,n){var r;try{r={defaults:n(154),each:n(87),isFunction:n(37),isPlainObject:n(158),pick:n(161),has:n(93),range:n(162),uniqueId:n(163)}}catch(t){}r||(r=window._),t.exports=r},function(t){t.exports=JSON.parse('{"name":"mermaid","version":"8.8.4","description":"Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.","main":"dist/mermaid.core.js","keywords":["diagram","markdown","flowchart","sequence diagram","gantt","class diagram","git graph"],"scripts":{"build:development":"webpack --progress --colors","build:production":"yarn build:development -p --config webpack.config.prod.babel.js","build":"yarn build:development && yarn build:production","postbuild":"documentation build src/mermaidAPI.js src/config.js --shallow -f md --markdown-toc false > docs/Setup.md","build:watch":"yarn build --watch","minify":"minify ./dist/mermaid.js > ./dist/mermaid.min.js","release":"yarn build","lint":"eslint src","e2e:depr":"yarn lint && jest e2e --config e2e/jest.config.js","cypress":"percy exec -- cypress run","e2e":"start-server-and-test dev http://localhost:9000/ cypress","e2e-upd":"yarn lint && jest e2e -u --config e2e/jest.config.js","dev":"webpack-dev-server --config webpack.config.e2e.js","test":"yarn lint && jest src/.*","test:watch":"jest --watch src","prepublishOnly":"yarn build && yarn test","prepare":"yarn build"},"repository":{"type":"git","url":"https://github.com/knsv/mermaid"},"author":"Knut Sveidqvist","license":"MIT","standard":{"ignore":["**/parser/*.js","dist/**/*.js","cypress/**/*.js"],"globals":["page"]},"dependencies":{"@braintree/sanitize-url":"^3.1.0","d3":"^5.7.0","dagre":"^0.8.4","dagre-d3":"^0.6.4","entity-decode":"^2.0.2","graphlib":"^2.1.7","he":"^1.2.0","khroma":"^1.1.0","minify":"^4.1.1","moment-mini":"^2.22.1","stylis":"^3.5.2"},"devDependencies":{"@babel/core":"^7.2.2","@babel/preset-env":"^7.8.4","@babel/register":"^7.0.0","@percy/cypress":"*","babel-core":"7.0.0-bridge.0","babel-eslint":"^10.1.0","babel-jest":"^24.9.0","babel-loader":"^8.0.4","coveralls":"^3.0.2","css-loader":"^2.0.1","css-to-string-loader":"^0.1.3","cypress":"4.0.1","documentation":"^12.0.1","eslint":"^6.3.0","eslint-config-prettier":"^6.3.0","eslint-plugin-prettier":"^3.1.0","husky":"^1.2.1","identity-obj-proxy":"^3.0.0","jest":"^24.9.0","jison":"^0.4.18","moment":"^2.23.0","node-sass":"^4.12.0","prettier":"^1.18.2","puppeteer":"^1.17.0","sass-loader":"^7.1.0","start-server-and-test":"^1.10.6","terser-webpack-plugin":"^2.2.2","webpack":"^4.41.2","webpack-bundle-analyzer":"^3.7.0","webpack-cli":"^3.1.2","webpack-dev-server":"^3.4.1","webpack-node-externals":"^1.7.2","yarn-upgrade-all":"^0.5.0"},"files":["dist"],"yarn-upgrade-all":{"ignore":["babel-core"]},"sideEffects":["**/*.css","**/*.scss"],"husky":{"hooks":{"pre-push":"yarn test"}}}')},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=new(n(176).default)({r:0,g:0,b:0,a:0},"transparent");e.default=r},function(t,e,n){var r=n(58),i=n(59);t.exports=function(t,e,n,a){var o=!n;n||(n={});for(var s=-1,c=e.length;++s-1&&t%1==0&&t-1}(s)?s:(n=s.match(a))?(e=n[0],r.test(e)?"about:blank":s):"about:blank"}}},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[2,3],n=[1,7],r=[7,12,15,17,19,20,21],i=[7,11,12,15,17,19,20,21],a=[2,20],o=[1,32],s={trace:function(){},yy:{},symbols_:{error:2,start:3,GG:4,":":5,document:6,EOF:7,DIR:8,options:9,body:10,OPT:11,NL:12,line:13,statement:14,COMMIT:15,commit_arg:16,BRANCH:17,ID:18,CHECKOUT:19,MERGE:20,RESET:21,reset_arg:22,STR:23,HEAD:24,reset_parents:25,CARET:26,$accept:0,$end:1},terminals_:{2:"error",4:"GG",5:":",7:"EOF",8:"DIR",11:"OPT",12:"NL",15:"COMMIT",17:"BRANCH",18:"ID",19:"CHECKOUT",20:"MERGE",21:"RESET",23:"STR",24:"HEAD",26:"CARET"},productions_:[0,[3,4],[3,5],[6,0],[6,2],[9,2],[9,1],[10,0],[10,2],[13,2],[13,1],[14,2],[14,2],[14,2],[14,2],[14,2],[16,0],[16,1],[22,2],[22,2],[25,0],[25,2]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 2:return r.setDirection(a[s-3]),a[s-1];case 4:r.setOptions(a[s-1]),this.$=a[s];break;case 5:a[s-1]+=a[s],this.$=a[s-1];break;case 7:this.$=[];break;case 8:a[s-1].push(a[s]),this.$=a[s-1];break;case 9:this.$=a[s-1];break;case 11:r.commit(a[s]);break;case 12:r.branch(a[s]);break;case 13:r.checkout(a[s]);break;case 14:r.merge(a[s]);break;case 15:r.reset(a[s]);break;case 16:this.$="";break;case 17:this.$=a[s];break;case 18:this.$=a[s-1]+":"+a[s];break;case 19:this.$=a[s-1]+":"+r.count,r.count=0;break;case 20:r.count=0;break;case 21:r.count+=1}},table:[{3:1,4:[1,2]},{1:[3]},{5:[1,3],8:[1,4]},{6:5,7:e,9:6,12:n},{5:[1,8]},{7:[1,9]},t(r,[2,7],{10:10,11:[1,11]}),t(i,[2,6]),{6:12,7:e,9:6,12:n},{1:[2,1]},{7:[2,4],12:[1,15],13:13,14:14,15:[1,16],17:[1,17],19:[1,18],20:[1,19],21:[1,20]},t(i,[2,5]),{7:[1,21]},t(r,[2,8]),{12:[1,22]},t(r,[2,10]),{12:[2,16],16:23,23:[1,24]},{18:[1,25]},{18:[1,26]},{18:[1,27]},{18:[1,30],22:28,24:[1,29]},{1:[2,2]},t(r,[2,9]),{12:[2,11]},{12:[2,17]},{12:[2,12]},{12:[2,13]},{12:[2,14]},{12:[2,15]},{12:a,25:31,26:o},{12:a,25:33,26:o},{12:[2,18]},{12:a,25:34,26:o},{12:[2,19]},{12:[2,21]}],defaultActions:{9:[2,1],21:[2,2],23:[2,11],24:[2,17],25:[2,12],26:[2,13],27:[2,14],28:[2,15],31:[2,18],33:[2,19],34:[2,21]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},c={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 12;case 1:case 2:case 3:break;case 4:return 4;case 5:return 15;case 6:return 17;case 7:return 20;case 8:return 21;case 9:return 19;case 10:case 11:return 8;case 12:return 5;case 13:return 26;case 14:this.begin("options");break;case 15:this.popState();break;case 16:return 11;case 17:this.begin("string");break;case 18:this.popState();break;case 19:return 23;case 20:return 18;case 21:return 7}},rules:[/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gitGraph\b)/i,/^(?:commit\b)/i,/^(?:branch\b)/i,/^(?:merge\b)/i,/^(?:reset\b)/i,/^(?:checkout\b)/i,/^(?:LR\b)/i,/^(?:BT\b)/i,/^(?::)/i,/^(?:\^)/i,/^(?:options\r?\n)/i,/^(?:end\r?\n)/i,/^(?:[^\n]+\r?\n)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[a-zA-Z][-_\.a-zA-Z0-9]*[-_a-zA-Z0-9])/i,/^(?:$)/i],conditions:{options:{rules:[15,16],inclusive:!1},string:{rules:[18,19],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,17,20,21],inclusive:!0}}};function u(){this.yy={}}return s.lexer=c,u.prototype=s,s.Parser=u,new u}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10],n={trace:function(){},yy:{},symbols_:{error:2,start:3,info:4,document:5,EOF:6,line:7,statement:8,NL:9,showInfo:10,$accept:0,$end:1},terminals_:{2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],performAction:function(t,e,n,r,i,a,o){a.length;switch(i){case 1:return r;case 4:break;case 6:r.setInfo(!0)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 4;case 1:return 9;case 2:return"space";case 3:return 10;case 4:return 6;case 5:return"TXT"}},rules:[/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,4],n=[1,5],r=[1,6],i=[1,7],a=[1,9],o=[1,10,12,19,20,21,22],s=[1,6,10,12,19,20,21,22],c=[19,20,21],u=[1,22],l=[6,19,20,21,22],h={trace:function(){},yy:{},symbols_:{error:2,start:3,eol:4,directive:5,PIE:6,document:7,line:8,statement:9,txt:10,value:11,title:12,title_value:13,openDirective:14,typeDirective:15,closeDirective:16,":":17,argDirective:18,NEWLINE:19,";":20,EOF:21,open_directive:22,type_directive:23,arg_directive:24,close_directive:25,$accept:0,$end:1},terminals_:{2:"error",6:"PIE",10:"txt",11:"value",12:"title",13:"title_value",17:":",19:"NEWLINE",20:";",21:"EOF",22:"open_directive",23:"type_directive",24:"arg_directive",25:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[7,0],[7,2],[8,2],[9,0],[9,2],[9,2],[9,1],[5,3],[5,5],[4,1],[4,1],[4,1],[14,1],[15,1],[18,1],[16,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:this.$=a[s-1];break;case 8:r.addSection(a[s-1],r.cleanupValue(a[s]));break;case 9:this.$=a[s].trim(),r.setTitle(this.$);break;case 16:r.parseDirective("%%{","open_directive");break;case 17:r.parseDirective(a[s],"type_directive");break;case 18:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 19:r.parseDirective("}%%","close_directive","pie")}},table:[{3:1,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},{1:[3]},{3:10,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},{3:11,4:2,5:3,6:e,14:8,19:n,20:r,21:i,22:a},t(o,[2,4],{7:12}),t(s,[2,13]),t(s,[2,14]),t(s,[2,15]),{15:13,23:[1,14]},{23:[2,16]},{1:[2,1]},{1:[2,2]},t(c,[2,7],{14:8,8:15,9:16,5:19,1:[2,3],10:[1,17],12:[1,18],22:a}),{16:20,17:[1,21],25:u},t([17,25],[2,17]),t(o,[2,5]),{4:23,19:n,20:r,21:i},{11:[1,24]},{13:[1,25]},t(c,[2,10]),t(l,[2,11]),{18:26,24:[1,27]},t(l,[2,19]),t(o,[2,6]),t(c,[2,8]),t(c,[2,9]),{16:28,25:u},{25:[2,18]},t(l,[2,12])],defaultActions:{9:[2,16],10:[2,1],11:[2,2],27:[2,18]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},f={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),22;case 1:return this.begin("type_directive"),23;case 2:return this.popState(),this.begin("arg_directive"),17;case 3:return this.popState(),this.popState(),25;case 4:return 24;case 5:case 6:break;case 7:return 19;case 8:case 9:break;case 10:return this.begin("title"),12;case 11:return this.popState(),"title_value";case 12:this.begin("string");break;case 13:this.popState();break;case 14:return"txt";case 15:return 6;case 16:return"value";case 17:return 21}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:[\s]+)/i,/^(?:title\b)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:pie\b)/i,/^(?::[\s]*[\d]+(?:\.[\d]+)?)/i,/^(?:$)/i],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},title:{rules:[11],inclusive:!1},string:{rules:[13,14],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,12,15,16,17],inclusive:!0}}};function d(){this.yy={}}return h.lexer=f,d.prototype=h,h.Parser=d,new d}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){(function(t,r){var i=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,23,37],i=[1,17],a=[1,20],o=[1,25],s=[1,26],c=[1,27],u=[1,28],l=[1,37],h=[23,34,35],f=[4,6,9,11,23,37],d=[30,31,32,33],p=[22,27],g={trace:function(){},yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,entityName:17,relSpec:18,role:19,BLOCK_START:20,attributes:21,BLOCK_STOP:22,ALPHANUM:23,attribute:24,attributeType:25,attributeName:26,ATTRIBUTE_WORD:27,cardinality:28,relType:29,ZERO_OR_ONE:30,ZERO_OR_MORE:31,ONE_OR_MORE:32,ONLY_ONE:33,NON_IDENTIFYING:34,IDENTIFYING:35,WORD:36,open_directive:37,type_directive:38,arg_directive:39,close_directive:40,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",20:"BLOCK_START",22:"BLOCK_STOP",23:"ALPHANUM",27:"ATTRIBUTE_WORD",30:"ZERO_OR_ONE",31:"ZERO_OR_MORE",32:"ONE_OR_MORE",33:"ONLY_ONE",34:"NON_IDENTIFYING",35:"IDENTIFYING",36:"WORD",37:"open_directive",38:"type_directive",39:"arg_directive",40:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,5],[10,4],[10,3],[10,1],[17,1],[21,1],[21,2],[24,2],[25,1],[26,1],[18,3],[28,1],[28,1],[28,1],[28,1],[29,1],[29,1],[19,1],[19,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:break;case 3:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 7:case 8:this.$=[];break;case 12:r.addEntity(a[s-4]),r.addEntity(a[s-2]),r.addRelationship(a[s-4],a[s],a[s-2],a[s-3]);break;case 13:r.addEntity(a[s-3]),r.addAttributes(a[s-3],a[s-1]);break;case 14:r.addEntity(a[s-2]);break;case 15:r.addEntity(a[s]);break;case 16:this.$=a[s];break;case 17:this.$=[a[s]];break;case 18:a[s].push(a[s-1]),this.$=a[s];break;case 19:this.$={attributeType:a[s-1],attributeName:a[s]};break;case 20:case 21:this.$=a[s];break;case 22:this.$={cardA:a[s],relType:a[s-1],cardB:a[s-2]};break;case 23:this.$=r.Cardinality.ZERO_OR_ONE;break;case 24:this.$=r.Cardinality.ZERO_OR_MORE;break;case 25:this.$=r.Cardinality.ONE_OR_MORE;break;case 26:this.$=r.Cardinality.ONLY_ONE;break;case 27:this.$=r.Identification.NON_IDENTIFYING;break;case 28:this.$=r.Identification.IDENTIFYING;break;case 29:this.$=a[s].replace(/"/g,"");break;case 30:this.$=a[s];break;case 31:r.parseDirective("%%{","open_directive");break;case 32:r.parseDirective(a[s],"type_directive");break;case 33:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 34:r.parseDirective("}%%","close_directive","er")}},table:[{3:1,4:e,7:3,12:4,37:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,37:n},{13:8,38:[1,9]},{38:[2,31]},{6:[1,10],7:15,8:11,9:[1,12],10:13,11:[1,14],12:4,17:16,23:i,37:n},{1:[2,2]},{14:18,15:[1,19],40:a},t([15,40],[2,32]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:15,10:21,12:4,17:16,23:i,37:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,15],{18:22,28:24,20:[1,23],30:o,31:s,32:c,33:u}),t([6,9,11,15,20,23,30,31,32,33,37],[2,16]),{11:[1,29]},{16:30,39:[1,31]},{11:[2,34]},t(r,[2,5]),{17:32,23:i},{21:33,22:[1,34],24:35,25:36,27:l},{29:38,34:[1,39],35:[1,40]},t(h,[2,23]),t(h,[2,24]),t(h,[2,25]),t(h,[2,26]),t(f,[2,9]),{14:41,40:a},{40:[2,33]},{15:[1,42]},{22:[1,43]},t(r,[2,14]),{21:44,22:[2,17],24:35,25:36,27:l},{26:45,27:[1,46]},{27:[2,20]},{28:47,30:o,31:s,32:c,33:u},t(d,[2,27]),t(d,[2,28]),{11:[1,48]},{19:49,23:[1,51],36:[1,50]},t(r,[2,13]),{22:[2,18]},t(p,[2,19]),t(p,[2,21]),{23:[2,22]},t(f,[2,10]),t(r,[2,12]),t(r,[2,29]),t(r,[2,30])],defaultActions:{5:[2,31],7:[2,2],20:[2,34],31:[2,33],37:[2,20],44:[2,18],47:[2,22]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,u=0,l=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var v=p.yylloc;a.push(v);var m=p.options&&p.options.ranges;function b(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var x,_,k,w,E,T,C,A,S,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==x&&(x=b()),w=o[k]&&o[k][x]),void 0===w||!w.length||!w[0]){var O="";for(T in S=[],o[k])this.terminals_[T]&&T>h&&S.push("'"+this.terminals_[T]+"'");O=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+S.join(", ")+", got '"+(this.terminals_[x]||x)+"'":"Parse error on line "+(c+1)+": Unexpected "+(x==f?"end of input":"'"+(this.terminals_[x]||x)+"'"),this.parseError(O,{text:p.match,token:this.terminals_[x]||x,line:p.yylineno,loc:v,expected:S})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+x);switch(w[0]){case 1:n.push(x),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),x=null,_?(x=_,_=null):(u=p.yyleng,s=p.yytext,c=p.yylineno,v=p.yylloc,l>0&&l--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},m&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(E=this.performAction.apply(M,[s,u,c,g.yy,w[1],i,a].concat(d))))return E;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),A=o[n[n.length-2]][n[n.length-1]],n.push(A);break;case 3:return!0}}return!0}},y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){var t=this.next();return t||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),37;case 1:return this.begin("type_directive"),38;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),40;case 4:return 39;case 5:case 6:break;case 7:return 11;case 8:break;case 9:return 9;case 10:return 36;case 11:return 4;case 12:return this.begin("block"),20;case 13:break;case 14:return 27;case 15:break;case 16:return this.popState(),22;case 17:return e.yytext[0];case 18:return 30;case 19:return 31;case 20:return 32;case 21:return 33;case 22:return 30;case 23:return 31;case 24:return 32;case 25:return 34;case 26:return 35;case 27:case 28:return 34;case 29:return 23;case 30:return e.yytext[0];case 31:return 6}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:\s+)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\|o\b)/i,/^(?:\}o\b)/i,/^(?:\}\|)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:.)/i,/^(?:$)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},block:{rules:[13,14,15,16,17],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,18,19,20,21,22,23,24,25,26,27,28,29,30,31],inclusive:!0}}};function v(){this.yy={}}return g.lexer=y,v.prototype=g,g.Parser=v,new v}();e.parser=i,e.Parser=i.Parser,e.parse=function(){return i.parse.apply(i,arguments)},e.main=function(r){r[1]||(console.log("Usage: "+r[0]+" FILE"),t.exit(1));var i=n(19).readFileSync(n(20).normalize(r[1]),"utf8");return e.parser.parse(i)},n.c[n.s]===r&&e.main(t.argv.slice(1))}).call(this,n(14),n(7)(t))},function(t,e,n){"use strict";var r;Object.defineProperty(e,"__esModule",{value:!0}),function(t){t[t.ALL=0]="ALL",t[t.RGB=1]="RGB",t[t.HSL=2]="HSL"}(r||(r={})),e.TYPE=r},function(t,e,n){"use strict";var r=n(10);t.exports=i;function i(t){this._isDirected=!r.has(t,"directed")||t.directed,this._isMultigraph=!!r.has(t,"multigraph")&&t.multigraph,this._isCompound=!!r.has(t,"compound")&&t.compound,this._label=void 0,this._defaultNodeLabelFn=r.constant(void 0),this._defaultEdgeLabelFn=r.constant(void 0),this._nodes={},this._isCompound&&(this._parent={},this._children={},this._children["\0"]={}),this._in={},this._preds={},this._out={},this._sucs={},this._edgeObjs={},this._edgeLabels={}}function a(t,e){t[e]?t[e]++:t[e]=1}function o(t,e){--t[e]||delete t[e]}function s(t,e,n,i){var a=""+e,o=""+n;if(!t&&a>o){var s=a;a=o,o=s}return a+""+o+""+(r.isUndefined(i)?"\0":i)}function c(t,e,n,r){var i=""+e,a=""+n;if(!t&&i>a){var o=i;i=a,a=o}var s={v:i,w:a};return r&&(s.name=r),s}function u(t,e){return s(t,e.v,e.w,e.name)}i.prototype._nodeCount=0,i.prototype._edgeCount=0,i.prototype.isDirected=function(){return this._isDirected},i.prototype.isMultigraph=function(){return this._isMultigraph},i.prototype.isCompound=function(){return this._isCompound},i.prototype.setGraph=function(t){return this._label=t,this},i.prototype.graph=function(){return this._label},i.prototype.setDefaultNodeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultNodeLabelFn=t,this},i.prototype.nodeCount=function(){return this._nodeCount},i.prototype.nodes=function(){return r.keys(this._nodes)},i.prototype.sources=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._in[e])}))},i.prototype.sinks=function(){var t=this;return r.filter(this.nodes(),(function(e){return r.isEmpty(t._out[e])}))},i.prototype.setNodes=function(t,e){var n=arguments,i=this;return r.each(t,(function(t){n.length>1?i.setNode(t,e):i.setNode(t)})),this},i.prototype.setNode=function(t,e){return r.has(this._nodes,t)?(arguments.length>1&&(this._nodes[t]=e),this):(this._nodes[t]=arguments.length>1?e:this._defaultNodeLabelFn(t),this._isCompound&&(this._parent[t]="\0",this._children[t]={},this._children["\0"][t]=!0),this._in[t]={},this._preds[t]={},this._out[t]={},this._sucs[t]={},++this._nodeCount,this)},i.prototype.node=function(t){return this._nodes[t]},i.prototype.hasNode=function(t){return r.has(this._nodes,t)},i.prototype.removeNode=function(t){var e=this;if(r.has(this._nodes,t)){var n=function(t){e.removeEdge(e._edgeObjs[t])};delete this._nodes[t],this._isCompound&&(this._removeFromParentsChildList(t),delete this._parent[t],r.each(this.children(t),(function(t){e.setParent(t)})),delete this._children[t]),r.each(r.keys(this._in[t]),n),delete this._in[t],delete this._preds[t],r.each(r.keys(this._out[t]),n),delete this._out[t],delete this._sucs[t],--this._nodeCount}return this},i.prototype.setParent=function(t,e){if(!this._isCompound)throw new Error("Cannot set parent in a non-compound graph");if(r.isUndefined(e))e="\0";else{for(var n=e+="";!r.isUndefined(n);n=this.parent(n))if(n===t)throw new Error("Setting "+e+" as parent of "+t+" would create a cycle");this.setNode(e)}return this.setNode(t),this._removeFromParentsChildList(t),this._parent[t]=e,this._children[e][t]=!0,this},i.prototype._removeFromParentsChildList=function(t){delete this._children[this._parent[t]][t]},i.prototype.parent=function(t){if(this._isCompound){var e=this._parent[t];if("\0"!==e)return e}},i.prototype.children=function(t){if(r.isUndefined(t)&&(t="\0"),this._isCompound){var e=this._children[t];if(e)return r.keys(e)}else{if("\0"===t)return this.nodes();if(this.hasNode(t))return[]}},i.prototype.predecessors=function(t){var e=this._preds[t];if(e)return r.keys(e)},i.prototype.successors=function(t){var e=this._sucs[t];if(e)return r.keys(e)},i.prototype.neighbors=function(t){var e=this.predecessors(t);if(e)return r.union(e,this.successors(t))},i.prototype.isLeaf=function(t){return 0===(this.isDirected()?this.successors(t):this.neighbors(t)).length},i.prototype.filterNodes=function(t){var e=new this.constructor({directed:this._isDirected,multigraph:this._isMultigraph,compound:this._isCompound});e.setGraph(this.graph());var n=this;r.each(this._nodes,(function(n,r){t(r)&&e.setNode(r,n)})),r.each(this._edgeObjs,(function(t){e.hasNode(t.v)&&e.hasNode(t.w)&&e.setEdge(t,n.edge(t))}));var i={};return this._isCompound&&r.each(e.nodes(),(function(t){e.setParent(t,function t(r){var a=n.parent(r);return void 0===a||e.hasNode(a)?(i[r]=a,a):a in i?i[a]:t(a)}(t))})),e},i.prototype.setDefaultEdgeLabel=function(t){return r.isFunction(t)||(t=r.constant(t)),this._defaultEdgeLabelFn=t,this},i.prototype.edgeCount=function(){return this._edgeCount},i.prototype.edges=function(){return r.values(this._edgeObjs)},i.prototype.setPath=function(t,e){var n=this,i=arguments;return r.reduce(t,(function(t,r){return i.length>1?n.setEdge(t,r,e):n.setEdge(t,r),r})),this},i.prototype.setEdge=function(){var t,e,n,i,o=!1,u=arguments[0];"object"==typeof u&&null!==u&&"v"in u?(t=u.v,e=u.w,n=u.name,2===arguments.length&&(i=arguments[1],o=!0)):(t=u,e=arguments[1],n=arguments[3],arguments.length>2&&(i=arguments[2],o=!0)),t=""+t,e=""+e,r.isUndefined(n)||(n=""+n);var l=s(this._isDirected,t,e,n);if(r.has(this._edgeLabels,l))return o&&(this._edgeLabels[l]=i),this;if(!r.isUndefined(n)&&!this._isMultigraph)throw new Error("Cannot set a named edge when isMultigraph = false");this.setNode(t),this.setNode(e),this._edgeLabels[l]=o?i:this._defaultEdgeLabelFn(t,e,n);var h=c(this._isDirected,t,e,n);return t=h.v,e=h.w,Object.freeze(h),this._edgeObjs[l]=h,a(this._preds[e],t),a(this._sucs[t],e),this._in[e][l]=h,this._out[t][l]=h,this._edgeCount++,this},i.prototype.edge=function(t,e,n){var r=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n);return this._edgeLabels[r]},i.prototype.hasEdge=function(t,e,n){var i=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n);return r.has(this._edgeLabels,i)},i.prototype.removeEdge=function(t,e,n){var r=1===arguments.length?u(this._isDirected,arguments[0]):s(this._isDirected,t,e,n),i=this._edgeObjs[r];return i&&(t=i.v,e=i.w,delete this._edgeLabels[r],delete this._edgeObjs[r],o(this._preds[e],t),o(this._sucs[t],e),delete this._in[e][r],delete this._out[t][r],this._edgeCount--),this},i.prototype.inEdges=function(t,e){var n=this._in[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.v===e})):i}},i.prototype.outEdges=function(t,e){var n=this._out[t];if(n){var i=r.values(n);return e?r.filter(i,(function(t){return t.w===e})):i}},i.prototype.nodeEdges=function(t,e){var n=this.inEdges(t,e);if(n)return n.concat(this.outEdges(t,e))}},function(t,e,n){var r=n(33)(n(16),"Map");t.exports=r},function(t,e,n){var r=n(217),i=n(224),a=n(226),o=n(227),s=n(228);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e-1&&t%1==0&&t<=9007199254740991}},function(t,e,n){(function(t){var r=n(109),i=e&&!e.nodeType&&e,a=i&&"object"==typeof t&&t&&!t.nodeType&&t,o=a&&a.exports===i&&r.process,s=function(){try{var t=a&&a.require&&a.require("util").types;return t||o&&o.binding&&o.binding("util")}catch(t){}}();t.exports=s}).call(this,n(7)(t))},function(t,e,n){var r=n(62),i=n(234),a=Object.prototype.hasOwnProperty;t.exports=function(t){if(!r(t))return i(t);var e=[];for(var n in Object(t))a.call(t,n)&&"constructor"!=n&&e.push(n);return e}},function(t,e,n){var r=n(116),i=n(117),a=Object.prototype.propertyIsEnumerable,o=Object.getOwnPropertySymbols,s=o?function(t){return null==t?[]:(t=Object(t),r(o(t),(function(e){return a.call(t,e)})))}:i;t.exports=s},function(t,e){t.exports=function(t,e){for(var n=-1,r=e.length,i=t.length;++n0&&a(l)?n>1?t(l,n-1,a,o,s):r(s,l):o||(s[s.length]=l)}return s}},function(t,e,n){var r=n(42);t.exports=function(t,e,n){for(var i=-1,a=t.length;++i4,u=c?1:17,l=c?8:4,h=s?0:-1,f=c?255:15;return i.default.set({r:(r>>l*(h+3)&f)*u,g:(r>>l*(h+2)&f)*u,b:(r>>l*(h+1)&f)*u,a:s?(r&f)*u/255:1},t)}}},stringify:function(t){return t.a<1?"#"+a.DEC2HEX[Math.round(t.r)]+a.DEC2HEX[Math.round(t.g)]+a.DEC2HEX[Math.round(t.b)]+r.default.unit.frac2hex(t.a):"#"+a.DEC2HEX[Math.round(t.r)]+a.DEC2HEX[Math.round(t.g)]+a.DEC2HEX[Math.round(t.b)]}};e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a=n(15);e.default=function(t,e,n,o){void 0===o&&(o=1);var s=i.default.set({h:r.default.channel.clamp.h(t),s:r.default.channel.clamp.s(e),l:r.default.channel.clamp.l(n),a:r.default.channel.clamp.a(o)});return a.default.stringify(s)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"a")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15);e.default=function(t){var e=i.default.parse(t),n=e.r,a=e.g,o=e.b,s=.2126*r.default.channel.toLinear(n)+.7152*r.default.channel.toLinear(a)+.0722*r.default.channel.toLinear(o);return r.default.lang.round(s)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(102);e.default=function(t){return r.default(t)>=.5}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"a",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"a",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(52);e.default=function(t,e){var n=r.default.parse(t),a={};for(var o in e)e[o]&&(a[o]=n[o]+e[o]);return i.default(t,a)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(51);e.default=function(t,e,n){void 0===n&&(n=50);var a=r.default.parse(t),o=a.r,s=a.g,c=a.b,u=a.a,l=r.default.parse(e),h=l.r,f=l.g,d=l.b,p=l.a,g=n/100,y=2*g-1,v=u-p,m=((y*v==-1?y:(y+v)/(1+y*v))+1)/2,b=1-m,x=o*m+h*b,_=s*m+f*b,k=c*m+d*b,w=u*g+p*(1-g);return i.default(x,_,k,w)}},function(t,e,n){var r=n(53),i=n(79),a=n(58),o=n(229),s=n(235),c=n(114),u=n(115),l=n(238),h=n(239),f=n(119),d=n(240),p=n(41),g=n(244),y=n(245),v=n(124),m=n(5),b=n(39),x=n(249),_=n(11),k=n(251),w=n(30),E={};E["[object Arguments]"]=E["[object Array]"]=E["[object ArrayBuffer]"]=E["[object DataView]"]=E["[object Boolean]"]=E["[object Date]"]=E["[object Float32Array]"]=E["[object Float64Array]"]=E["[object Int8Array]"]=E["[object Int16Array]"]=E["[object Int32Array]"]=E["[object Map]"]=E["[object Number]"]=E["[object Object]"]=E["[object RegExp]"]=E["[object Set]"]=E["[object String]"]=E["[object Symbol]"]=E["[object Uint8Array]"]=E["[object Uint8ClampedArray]"]=E["[object Uint16Array]"]=E["[object Uint32Array]"]=!0,E["[object Error]"]=E["[object Function]"]=E["[object WeakMap]"]=!1,t.exports=function t(e,n,T,C,A,S){var M,O=1&n,D=2&n,N=4&n;if(T&&(M=A?T(e,C,A,S):T(e)),void 0!==M)return M;if(!_(e))return e;var B=m(e);if(B){if(M=g(e),!O)return u(e,M)}else{var L=p(e),F="[object Function]"==L||"[object GeneratorFunction]"==L;if(b(e))return c(e,O);if("[object Object]"==L||"[object Arguments]"==L||F&&!A){if(M=D||F?{}:v(e),!O)return D?h(e,s(M,e)):l(e,o(M,e))}else{if(!E[L])return A?e:{};M=y(e,L,O)}}S||(S=new r);var P=S.get(e);if(P)return P;S.set(e,M),k(e)?e.forEach((function(r){M.add(t(r,n,T,r,e,S))})):x(e)&&e.forEach((function(r,i){M.set(i,t(r,n,T,i,e,S))}));var I=N?D?d:f:D?keysIn:w,j=B?void 0:I(e);return i(j||e,(function(r,i){j&&(r=e[i=r]),a(M,i,t(r,n,T,i,e,S))})),M}},function(t,e,n){(function(e){var n="object"==typeof e&&e&&e.Object===Object&&e;t.exports=n}).call(this,n(211))},function(t,e){var n=Function.prototype.toString;t.exports=function(t){if(null!=t){try{return n.call(t)}catch(t){}try{return t+""}catch(t){}}return""}},function(t,e,n){var r=n(33),i=function(){try{var t=r(Object,"defineProperty");return t({},"",{}),t}catch(t){}}();t.exports=i},function(t,e,n){var r=n(230),i=n(47),a=n(5),o=n(39),s=n(60),c=n(48),u=Object.prototype.hasOwnProperty;t.exports=function(t,e){var n=a(t),l=!n&&i(t),h=!n&&!l&&o(t),f=!n&&!l&&!h&&c(t),d=n||l||h||f,p=d?r(t.length,String):[],g=p.length;for(var y in t)!e&&!u.call(t,y)||d&&("length"==y||h&&("offset"==y||"parent"==y)||f&&("buffer"==y||"byteLength"==y||"byteOffset"==y)||s(y,g))||p.push(y);return p}},function(t,e){t.exports=function(t,e){return function(n){return t(e(n))}}},function(t,e,n){(function(t){var r=n(16),i=e&&!e.nodeType&&e,a=i&&"object"==typeof t&&t&&!t.nodeType&&t,o=a&&a.exports===i?r.Buffer:void 0,s=o?o.allocUnsafe:void 0;t.exports=function(t,e){if(e)return t.slice();var n=t.length,r=s?s(n):new t.constructor(n);return t.copy(r),r}}).call(this,n(7)(t))},function(t,e){t.exports=function(t,e){var n=-1,r=t.length;for(e||(e=Array(r));++nl))return!1;var f=c.get(t);if(f&&c.get(e))return f==e;var d=-1,p=!0,g=2&n?new r:void 0;for(c.set(t,e),c.set(e,t);++d0&&(a=c.removeMin(),(o=s[a]).distance!==Number.POSITIVE_INFINITY);)r(a).forEach(u);return s}(t,String(e),n||a,r||function(e){return t.outEdges(e)})};var a=r.constant(1)},function(t,e,n){var r=n(10);function i(){this._arr=[],this._keyIndices={}}t.exports=i,i.prototype.size=function(){return this._arr.length},i.prototype.keys=function(){return this._arr.map((function(t){return t.key}))},i.prototype.has=function(t){return r.has(this._keyIndices,t)},i.prototype.priority=function(t){var e=this._keyIndices[t];if(void 0!==e)return this._arr[e].priority},i.prototype.min=function(){if(0===this.size())throw new Error("Queue underflow");return this._arr[0].key},i.prototype.add=function(t,e){var n=this._keyIndices;if(t=String(t),!r.has(n,t)){var i=this._arr,a=i.length;return n[t]=a,i.push({key:t,priority:e}),this._decrease(a),!0}return!1},i.prototype.removeMin=function(){this._swap(0,this._arr.length-1);var t=this._arr.pop();return delete this._keyIndices[t.key],this._heapify(0),t.key},i.prototype.decrease=function(t,e){var n=this._keyIndices[t];if(e>this._arr[n].priority)throw new Error("New priority is greater than current priority. Key: "+t+" Old: "+this._arr[n].priority+" New: "+e);this._arr[n].priority=e,this._decrease(n)},i.prototype._heapify=function(t){var e=this._arr,n=2*t,r=n+1,i=t;n>1].priority2?e[2]:void 0;for(u&&a(e[0],e[1],u)&&(r=1);++n1&&o.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,c=Math.sqrt(o*o+s*s);return aMath.abs(o)*u?(s<0&&(u=-u),n=0===s?0:u*o/s,r=u):(o<0&&(c=-c),n=c,r=0===o?0:c*s/o);return{x:i+n,y:a+r}}},function(t,e,n){t.exports=function t(e){"use strict";var n=/^\0+/g,r=/[\0\r\f]/g,i=/: */g,a=/zoo|gra/,o=/([,: ])(transform)/g,s=/,+\s*(?![^(]*[)])/g,c=/ +\s*(?![^(]*[)])/g,u=/ *[\0] */g,l=/,\r+?/g,h=/([\t\r\n ])*\f?&/g,f=/:global\(((?:[^\(\)\[\]]*|\[.*\]|\([^\(\)]*\))*)\)/g,d=/\W+/g,p=/@(k\w+)\s*(\S*)\s*/,g=/::(place)/g,y=/:(read-only)/g,v=/\s+(?=[{\];=:>])/g,m=/([[}=:>])\s+/g,b=/(\{[^{]+?);(?=\})/g,x=/\s{2,}/g,_=/([^\(])(:+) */g,k=/[svh]\w+-[tblr]{2}/,w=/\(\s*(.*)\s*\)/g,E=/([\s\S]*?);/g,T=/-self|flex-/g,C=/[^]*?(:[rp][el]a[\w-]+)[^]*/,A=/stretch|:\s*\w+\-(?:conte|avail)/,S=/([^-])(image-set\()/,M="-webkit-",O="-moz-",D="-ms-",N=1,B=1,L=0,F=1,P=1,I=1,j=0,R=0,Y=0,z=[],U=[],$=0,W=null,H=0,V=1,G="",q="",X="";function Z(t,e,i,a,o){for(var s,c,l=0,h=0,f=0,d=0,v=0,m=0,b=0,x=0,k=0,E=0,T=0,C=0,A=0,S=0,O=0,D=0,j=0,U=0,W=0,K=i.length,it=K-1,at="",ot="",st="",ct="",ut="",lt="";O0&&(ot=ot.replace(r,"")),ot.trim().length>0)){switch(b){case 32:case 9:case 59:case 13:case 10:break;default:ot+=i.charAt(O)}b=59}if(1===j)switch(b){case 123:case 125:case 59:case 34:case 39:case 40:case 41:case 44:j=0;case 9:case 13:case 10:case 32:break;default:for(j=0,W=O,v=b,O--,b=59;W0&&(++O,b=v);case 123:W=K}}switch(b){case 123:for(v=(ot=ot.trim()).charCodeAt(0),T=1,W=++O;O0&&(ot=ot.replace(r,"")),m=ot.charCodeAt(1)){case 100:case 109:case 115:case 45:s=e;break;default:s=z}if(W=(st=Z(e,s,st,m,o+1)).length,Y>0&&0===W&&(W=ot.length),$>0&&(c=nt(3,st,s=J(z,ot,U),e,B,N,W,m,o,a),ot=s.join(""),void 0!==c&&0===(W=(st=c.trim()).length)&&(m=0,st="")),W>0)switch(m){case 115:ot=ot.replace(w,et);case 100:case 109:case 45:st=ot+"{"+st+"}";break;case 107:st=(ot=ot.replace(p,"$1 $2"+(V>0?G:"")))+"{"+st+"}",st=1===P||2===P&&tt("@"+st,3)?"@"+M+st+"@"+st:"@"+st;break;default:st=ot+st,112===a&&(ct+=st,st="")}else st="";break;default:st=Z(e,J(e,ot,U),st,a,o+1)}ut+=st,C=0,j=0,S=0,D=0,U=0,A=0,ot="",st="",b=i.charCodeAt(++O);break;case 125:case 59:if((W=(ot=(D>0?ot.replace(r,""):ot).trim()).length)>1)switch(0===S&&(45===(v=ot.charCodeAt(0))||v>96&&v<123)&&(W=(ot=ot.replace(" ",":")).length),$>0&&void 0!==(c=nt(1,ot,e,t,B,N,ct.length,a,o,a))&&0===(W=(ot=c.trim()).length)&&(ot="\0\0"),v=ot.charCodeAt(0),m=ot.charCodeAt(1),v){case 0:break;case 64:if(105===m||99===m){lt+=ot+i.charAt(O);break}default:if(58===ot.charCodeAt(W-1))break;ct+=Q(ot,v,m,ot.charCodeAt(2))}C=0,j=0,S=0,D=0,U=0,ot="",b=i.charCodeAt(++O)}}switch(b){case 13:case 10:if(h+d+f+l+R===0)switch(E){case 41:case 39:case 34:case 64:case 126:case 62:case 42:case 43:case 47:case 45:case 58:case 44:case 59:case 123:case 125:break;default:S>0&&(j=1)}47===h?h=0:F+C===0&&107!==a&&ot.length>0&&(D=1,ot+="\0"),$*H>0&&nt(0,ot,e,t,B,N,ct.length,a,o,a),N=1,B++;break;case 59:case 125:if(h+d+f+l===0){N++;break}default:switch(N++,at=i.charAt(O),b){case 9:case 32:if(d+l+h===0)switch(x){case 44:case 58:case 9:case 32:at="";break;default:32!==b&&(at=" ")}break;case 0:at="\\0";break;case 12:at="\\f";break;case 11:at="\\v";break;case 38:d+h+l===0&&F>0&&(U=1,D=1,at="\f"+at);break;case 108:if(d+h+l+L===0&&S>0)switch(O-S){case 2:112===x&&58===i.charCodeAt(O-3)&&(L=x);case 8:111===k&&(L=k)}break;case 58:d+h+l===0&&(S=O);break;case 44:h+f+d+l===0&&(D=1,at+="\r");break;case 34:case 39:0===h&&(d=d===b?0:0===d?b:d);break;case 91:d+h+f===0&&l++;break;case 93:d+h+f===0&&l--;break;case 41:d+h+l===0&&f--;break;case 40:if(d+h+l===0){if(0===C)switch(2*x+3*k){case 533:break;default:T=0,C=1}f++}break;case 64:h+f+d+l+S+A===0&&(A=1);break;case 42:case 47:if(d+l+f>0)break;switch(h){case 0:switch(2*b+3*i.charCodeAt(O+1)){case 235:h=47;break;case 220:W=O,h=42}break;case 42:47===b&&42===x&&W+2!==O&&(33===i.charCodeAt(W+2)&&(ct+=i.substring(W,O+1)),at="",h=0)}}if(0===h){if(F+d+l+A===0&&107!==a&&59!==b)switch(b){case 44:case 126:case 62:case 43:case 41:case 40:if(0===C){switch(x){case 9:case 32:case 10:case 13:at+="\0";break;default:at="\0"+at+(44===b?"":"\0")}D=1}else switch(b){case 40:S+7===O&&108===x&&(S=0),C=++T;break;case 41:0==(C=--T)&&(D=1,at+="\0")}break;case 9:case 32:switch(x){case 0:case 123:case 125:case 59:case 44:case 12:case 9:case 32:case 10:case 13:break;default:0===C&&(D=1,at+="\0")}}ot+=at,32!==b&&9!==b&&(E=b)}}k=x,x=b,O++}if(W=ct.length,Y>0&&0===W&&0===ut.length&&0===e[0].length==0&&(109!==a||1===e.length&&(F>0?q:X)===e[0])&&(W=e.join(",").length+2),W>0){if(s=0===F&&107!==a?function(t){for(var e,n,i=0,a=t.length,o=Array(a);i1)){if(f=c.charCodeAt(c.length-1),d=n.charCodeAt(0),e="",0!==l)switch(f){case 42:case 126:case 62:case 43:case 32:case 40:break;default:e=" "}switch(d){case 38:n=e+q;case 126:case 62:case 43:case 32:case 41:case 40:break;case 91:n=e+n+q;break;case 58:switch(2*n.charCodeAt(1)+3*n.charCodeAt(2)){case 530:if(I>0){n=e+n.substring(8,h-1);break}default:(l<1||s[l-1].length<1)&&(n=e+q+n)}break;case 44:e="";default:n=h>1&&n.indexOf(":")>0?e+n.replace(_,"$1"+q+"$2"):e+n+q}c+=n}o[i]=c.replace(r,"").trim()}return o}(e):e,$>0&&void 0!==(c=nt(2,ct,s,t,B,N,W,a,o,a))&&0===(ct=c).length)return lt+ct+ut;if(ct=s.join(",")+"{"+ct+"}",P*L!=0){switch(2!==P||tt(ct,2)||(L=0),L){case 111:ct=ct.replace(y,":-moz-$1")+ct;break;case 112:ct=ct.replace(g,"::-webkit-input-$1")+ct.replace(g,"::-moz-$1")+ct.replace(g,":-ms-input-$1")+ct}L=0}}return lt+ct+ut}function J(t,e,n){var r=e.trim().split(l),i=r,a=r.length,o=t.length;switch(o){case 0:case 1:for(var s=0,c=0===o?"":t[0]+" ";s0&&F>0)return i.replace(f,"$1").replace(h,"$1"+X);break;default:return t.trim()+i.replace(h,"$1"+t.trim())}default:if(n*F>0&&i.indexOf("\f")>0)return i.replace(h,(58===t.charCodeAt(0)?"":"$1")+t.trim())}return t+i}function Q(t,e,n,r){var u,l=0,h=t+";",f=2*e+3*n+4*r;if(944===f)return function(t){var e=t.length,n=t.indexOf(":",9)+1,r=t.substring(0,n).trim(),i=t.substring(n,e-1).trim();switch(t.charCodeAt(9)*V){case 0:break;case 45:if(110!==t.charCodeAt(10))break;default:var a=i.split((i="",s)),o=0;for(n=0,e=a.length;o64&&h<90||h>96&&h<123||95===h||45===h&&45!==u.charCodeAt(1)))switch(isNaN(parseFloat(u))+(-1!==u.indexOf("("))){case 1:switch(u){case"infinite":case"alternate":case"backwards":case"running":case"normal":case"forwards":case"both":case"none":case"linear":case"ease":case"ease-in":case"ease-out":case"ease-in-out":case"paused":case"reverse":case"alternate-reverse":case"inherit":case"initial":case"unset":case"step-start":case"step-end":break;default:u+=G}}l[n++]=u}i+=(0===o?"":",")+l.join(" ")}}return i=r+i+";",1===P||2===P&&tt(i,1)?M+i+i:i}(h);if(0===P||2===P&&!tt(h,1))return h;switch(f){case 1015:return 97===h.charCodeAt(10)?M+h+h:h;case 951:return 116===h.charCodeAt(3)?M+h+h:h;case 963:return 110===h.charCodeAt(5)?M+h+h:h;case 1009:if(100!==h.charCodeAt(4))break;case 969:case 942:return M+h+h;case 978:return M+h+O+h+h;case 1019:case 983:return M+h+O+h+D+h+h;case 883:return 45===h.charCodeAt(8)?M+h+h:h.indexOf("image-set(",11)>0?h.replace(S,"$1-webkit-$2")+h:h;case 932:if(45===h.charCodeAt(4))switch(h.charCodeAt(5)){case 103:return M+"box-"+h.replace("-grow","")+M+h+D+h.replace("grow","positive")+h;case 115:return M+h+D+h.replace("shrink","negative")+h;case 98:return M+h+D+h.replace("basis","preferred-size")+h}return M+h+D+h+h;case 964:return M+h+D+"flex-"+h+h;case 1023:if(99!==h.charCodeAt(8))break;return u=h.substring(h.indexOf(":",15)).replace("flex-","").replace("space-between","justify"),M+"box-pack"+u+M+h+D+"flex-pack"+u+h;case 1005:return a.test(h)?h.replace(i,":"+M)+h.replace(i,":"+O)+h:h;case 1e3:switch(l=(u=h.substring(13).trim()).indexOf("-")+1,u.charCodeAt(0)+u.charCodeAt(l)){case 226:u=h.replace(k,"tb");break;case 232:u=h.replace(k,"tb-rl");break;case 220:u=h.replace(k,"lr");break;default:return h}return M+h+D+u+h;case 1017:if(-1===h.indexOf("sticky",9))return h;case 975:switch(l=(h=t).length-10,f=(u=(33===h.charCodeAt(l)?h.substring(0,l):h).substring(t.indexOf(":",7)+1).trim()).charCodeAt(0)+(0|u.charCodeAt(7))){case 203:if(u.charCodeAt(8)<111)break;case 115:h=h.replace(u,M+u)+";"+h;break;case 207:case 102:h=h.replace(u,M+(f>102?"inline-":"")+"box")+";"+h.replace(u,M+u)+";"+h.replace(u,D+u+"box")+";"+h}return h+";";case 938:if(45===h.charCodeAt(5))switch(h.charCodeAt(6)){case 105:return u=h.replace("-items",""),M+h+M+"box-"+u+D+"flex-"+u+h;case 115:return M+h+D+"flex-item-"+h.replace(T,"")+h;default:return M+h+D+"flex-line-pack"+h.replace("align-content","").replace(T,"")+h}break;case 973:case 989:if(45!==h.charCodeAt(3)||122===h.charCodeAt(4))break;case 931:case 953:if(!0===A.test(t))return 115===(u=t.substring(t.indexOf(":")+1)).charCodeAt(0)?Q(t.replace("stretch","fill-available"),e,n,r).replace(":fill-available",":stretch"):h.replace(u,M+u)+h.replace(u,O+u.replace("fill-",""))+h;break;case 962:if(h=M+h+(102===h.charCodeAt(5)?D+h:"")+h,n+r===211&&105===h.charCodeAt(13)&&h.indexOf("transform",10)>0)return h.substring(0,h.indexOf(";",27)+1).replace(o,"$1-webkit-$2")+h}return h}function tt(t,e){var n=t.indexOf(1===e?":":"{"),r=t.substring(0,3!==e?n:10),i=t.substring(n+1,t.length-1);return W(2!==e?r:r.replace(C,"$1"),i,e)}function et(t,e){var n=Q(e,e.charCodeAt(0),e.charCodeAt(1),e.charCodeAt(2));return n!==e+";"?n.replace(E," or ($1)").substring(4):"("+e+")"}function nt(t,e,n,r,i,a,o,s,c,u){for(var l,h=0,f=e;h<$;++h)switch(l=U[h].call(at,t,f,n,r,i,a,o,s,c,u)){case void 0:case!1:case!0:case null:break;default:f=l}if(f!==e)return f}function rt(t,e,n,r){for(var i=e+1;i0&&(G=i.replace(d,91===a?"":"-")),a=1,1===F?X=i:q=i;var o,s=[X];$>0&&void 0!==(o=nt(-1,n,s,s,B,N,0,0,0,0))&&"string"==typeof o&&(n=o);var c=Z(z,s,n,0,0);return $>0&&void 0!==(o=nt(-2,c,s,s,B,N,c.length,0,0,0))&&"string"!=typeof(c=o)&&(a=0),G="",X="",q="",L=0,B=1,N=1,j*a==0?c:function(t){return t.replace(r,"").replace(v,"").replace(m,"$1").replace(b,"$1").replace(x," ")}(c)}return at.use=function t(e){switch(e){case void 0:case null:$=U.length=0;break;default:if("function"==typeof e)U[$++]=e;else if("object"==typeof e)for(var n=0,r=e.length;n=255?255:t<0?0:t},g:function(t){return t>=255?255:t<0?0:t},b:function(t){return t>=255?255:t<0?0:t},h:function(t){return t%360},s:function(t){return t>=100?100:t<0?0:t},l:function(t){return t>=100?100:t<0?0:t},a:function(t){return t>=1?1:t<0?0:t}},toLinear:function(t){var e=t/255;return t>.03928?Math.pow((e+.055)/1.055,2.4):e/12.92},hue2rgb:function(t,e,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+(e-t)*(2/3-n)*6:t},hsl2rgb:function(t,e){var n=t.h,i=t.s,a=t.l;if(100===i)return 2.55*a;n/=360,i/=100;var o=(a/=100)<.5?a*(1+i):a+i-a*i,s=2*a-o;switch(e){case"r":return 255*r.hue2rgb(s,o,n+1/3);case"g":return 255*r.hue2rgb(s,o,n);case"b":return 255*r.hue2rgb(s,o,n-1/3)}},rgb2hsl:function(t,e){var n=t.r,r=t.g,i=t.b;n/=255,r/=255,i/=255;var a=Math.max(n,r,i),o=Math.min(n,r,i),s=(a+o)/2;if("l"===e)return 100*s;if(a===o)return 0;var c=a-o;if("s"===e)return 100*(s>.5?c/(2-a-o):c/(a+o));switch(a){case n:return 60*((r-i)/c+(r1?e:"0"+e},dec2hex:function(t){var e=Math.round(t).toString(16);return e.length>1?e:"0"+e}};e.default=r},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(75),a=n(177),o=function(){function t(t,e){this.color=e,this.changed=!1,this.data=t,this.type=new a.default}return t.prototype.set=function(t,e){return this.color=e,this.changed=!1,this.data=t,this.type.type=i.TYPE.ALL,this},t.prototype._ensureHSL=function(){void 0===this.data.h&&(this.data.h=r.default.channel.rgb2hsl(this.data,"h")),void 0===this.data.s&&(this.data.s=r.default.channel.rgb2hsl(this.data,"s")),void 0===this.data.l&&(this.data.l=r.default.channel.rgb2hsl(this.data,"l"))},t.prototype._ensureRGB=function(){void 0===this.data.r&&(this.data.r=r.default.channel.hsl2rgb(this.data,"r")),void 0===this.data.g&&(this.data.g=r.default.channel.hsl2rgb(this.data,"g")),void 0===this.data.b&&(this.data.b=r.default.channel.hsl2rgb(this.data,"b"))},Object.defineProperty(t.prototype,"r",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.r?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"r")):this.data.r},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.r=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"g",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.g?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"g")):this.data.g},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.g=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"b",{get:function(){return this.type.is(i.TYPE.HSL)||void 0===this.data.b?(this._ensureHSL(),r.default.channel.hsl2rgb(this.data,"b")):this.data.b},set:function(t){this.type.set(i.TYPE.RGB),this.changed=!0,this.data.b=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"h",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.h?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"h")):this.data.h},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.h=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"s",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.s?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"s")):this.data.s},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.s=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"l",{get:function(){return this.type.is(i.TYPE.RGB)||void 0===this.data.l?(this._ensureRGB(),r.default.channel.rgb2hsl(this.data,"l")):this.data.l},set:function(t){this.type.set(i.TYPE.HSL),this.changed=!0,this.data.l=t},enumerable:!0,configurable:!0}),Object.defineProperty(t.prototype,"a",{get:function(){return this.data.a},set:function(t){this.changed=!0,this.data.a=t},enumerable:!0,configurable:!0}),t}();e.default=o},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(75),i=function(){function t(){this.type=r.TYPE.ALL}return t.prototype.get=function(){return this.type},t.prototype.set=function(t){if(this.type&&this.type!==t)throw new Error("Cannot change both RGB and HSL channels at the same time");this.type=t},t.prototype.reset=function(){this.type=r.TYPE.ALL},t.prototype.is=function(t){return this.type===t},t}();e.default=i},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i={};e.DEC2HEX=i;for(var a=0;a<=255;a++)i[a]=r.default.unit.dec2hex(a)},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(99),i={colors:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyanaqua:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",transparent:"#00000000",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},parse:function(t){t=t.toLowerCase();var e=i.colors[t];if(e)return r.default.parse(e)},stringify:function(t){var e=r.default.stringify(t);for(var n in i.colors)if(i.colors[n]===e)return n}};e.default=i},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a={re:/^rgba?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?)))?\s*?\)$/i,parse:function(t){var e=t.charCodeAt(0);if(114===e||82===e){var n=t.match(a.re);if(n){var o=n[1],s=n[2],c=n[3],u=n[4],l=n[5],h=n[6],f=n[7],d=n[8];return i.default.set({r:r.default.channel.clamp.r(s?2.55*parseFloat(o):parseFloat(o)),g:r.default.channel.clamp.g(u?2.55*parseFloat(c):parseFloat(c)),b:r.default.channel.clamp.b(h?2.55*parseFloat(l):parseFloat(l)),a:f?r.default.channel.clamp.a(d?parseFloat(f)/100:parseFloat(f)):1},t)}}},stringify:function(t){return t.a<1?"rgba("+r.default.lang.round(t.r)+", "+r.default.lang.round(t.g)+", "+r.default.lang.round(t.b)+", "+r.default.lang.round(t.a)+")":"rgb("+r.default.lang.round(t.r)+", "+r.default.lang.round(t.g)+", "+r.default.lang.round(t.b)+")"}};e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(45),a={re:/^hsla?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(?:deg|grad|rad|turn)?)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(%)?))?\s*?\)$/i,hueRe:/^(.+?)(deg|grad|rad|turn)$/i,_hue2deg:function(t){var e=t.match(a.hueRe);if(e){var n=e[1];switch(e[2]){case"grad":return r.default.channel.clamp.h(.9*parseFloat(n));case"rad":return r.default.channel.clamp.h(180*parseFloat(n)/Math.PI);case"turn":return r.default.channel.clamp.h(360*parseFloat(n))}}return r.default.channel.clamp.h(parseFloat(t))},parse:function(t){var e=t.charCodeAt(0);if(104===e||72===e){var n=t.match(a.re);if(n){var o=n[1],s=n[2],c=n[3],u=n[4],l=n[5];return i.default.set({h:a._hue2deg(o),s:r.default.channel.clamp.s(parseFloat(s)),l:r.default.channel.clamp.l(parseFloat(c)),a:u?r.default.channel.clamp.a(l?parseFloat(u)/100:parseFloat(u)):1},t)}}},stringify:function(t){return t.a<1?"hsla("+r.default.lang.round(t.h)+", "+r.default.lang.round(t.s)+"%, "+r.default.lang.round(t.l)+"%, "+t.a+")":"hsl("+r.default.lang.round(t.h)+", "+r.default.lang.round(t.s)+"%, "+r.default.lang.round(t.l)+"%)"}};e.default=a},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"r")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"g")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"b")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"h")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"s")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(29);e.default=function(t){return r.default(t,"l")}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(103);e.default=function(t){return!r.default(t)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15);e.default=function(t){try{return r.default.parse(t),!0}catch(t){return!1}}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"s",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"s",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"l",e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t,e){return r.default(t,"l",-e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(32);e.default=function(t){return r.default(t,"h",180)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(52);e.default=function(t){return r.default(t,{s:0})}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(15),i=n(107);e.default=function(t,e){void 0===e&&(e=100);var n=r.default.parse(t);return n.r=255-n.r,n.g=255-n.g,n.b=255-n.b,i.default(n,t,e)}},function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var r=n(9),i=n(15),a=n(106);e.default=function(t,e){var n,o,s,c=i.default.parse(t),u={};for(var l in e)u[l]=(n=c[l],o=e[l],s=r.default.channel.max[l],o>0?(s-n)*o/100:n*o/100);return a.default(t,u)}},function(t,e,n){t.exports={Graph:n(76),version:n(300)}},function(t,e,n){var r=n(108);t.exports=function(t){return r(t,4)}},function(t,e){t.exports=function(){this.__data__=[],this.size=0}},function(t,e,n){var r=n(55),i=Array.prototype.splice;t.exports=function(t){var e=this.__data__,n=r(e,t);return!(n<0)&&(n==e.length-1?e.pop():i.call(e,n,1),--this.size,!0)}},function(t,e,n){var r=n(55);t.exports=function(t){var e=this.__data__,n=r(e,t);return n<0?void 0:e[n][1]}},function(t,e,n){var r=n(55);t.exports=function(t){return r(this.__data__,t)>-1}},function(t,e,n){var r=n(55);t.exports=function(t,e){var n=this.__data__,i=r(n,t);return i<0?(++this.size,n.push([t,e])):n[i][1]=e,this}},function(t,e,n){var r=n(54);t.exports=function(){this.__data__=new r,this.size=0}},function(t,e){t.exports=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n}},function(t,e){t.exports=function(t){return this.__data__.get(t)}},function(t,e){t.exports=function(t){return this.__data__.has(t)}},function(t,e,n){var r=n(54),i=n(77),a=n(78);t.exports=function(t,e){var n=this.__data__;if(n instanceof r){var o=n.__data__;if(!i||o.length<199)return o.push([t,e]),this.size=++n.size,this;n=this.__data__=new a(o)}return n.set(t,e),this.size=n.size,this}},function(t,e,n){var r=n(37),i=n(214),a=n(11),o=n(110),s=/^\[object .+?Constructor\]$/,c=Function.prototype,u=Object.prototype,l=c.toString,h=u.hasOwnProperty,f=RegExp("^"+l.call(h).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$");t.exports=function(t){return!(!a(t)||i(t))&&(r(t)?f:s).test(o(t))}},function(t,e){var n;n=function(){return this}();try{n=n||new Function("return this")()}catch(t){"object"==typeof window&&(n=window)}t.exports=n},function(t,e,n){var r=n(38),i=Object.prototype,a=i.hasOwnProperty,o=i.toString,s=r?r.toStringTag:void 0;t.exports=function(t){var e=a.call(t,s),n=t[s];try{t[s]=void 0;var r=!0}catch(t){}var i=o.call(t);return r&&(e?t[s]=n:delete t[s]),i}},function(t,e){var n=Object.prototype.toString;t.exports=function(t){return n.call(t)}},function(t,e,n){var r,i=n(215),a=(r=/[^.]+$/.exec(i&&i.keys&&i.keys.IE_PROTO||""))?"Symbol(src)_1."+r:"";t.exports=function(t){return!!a&&a in t}},function(t,e,n){var r=n(16)["__core-js_shared__"];t.exports=r},function(t,e){t.exports=function(t,e){return null==t?void 0:t[e]}},function(t,e,n){var r=n(218),i=n(54),a=n(77);t.exports=function(){this.size=0,this.__data__={hash:new r,map:new(a||i),string:new r}}},function(t,e,n){var r=n(219),i=n(220),a=n(221),o=n(222),s=n(223);function c(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e0){if(++e>=800)return arguments[0]}else e=0;return t.apply(void 0,arguments)}}},function(t,e,n){var r=n(131),i=n(292),a=n(296),o=n(132),s=n(297),c=n(90);t.exports=function(t,e,n){var u=-1,l=i,h=t.length,f=!0,d=[],p=d;if(n)f=!1,l=a;else if(h>=200){var g=e?null:s(t);if(g)return c(g);f=!1,l=o,p=new r}else p=e?[]:d;t:for(;++u-1}},function(t,e,n){var r=n(145),i=n(294),a=n(295);t.exports=function(t,e,n){return e==e?a(t,e,n):r(t,i,n)}},function(t,e){t.exports=function(t){return t!=t}},function(t,e){t.exports=function(t,e,n){for(var r=n-1,i=t.length;++r1||1===e.length&&t.hasEdge(e[0],e[0])}))}},function(t,e,n){var r=n(10);t.exports=function(t,e,n){return function(t,e,n){var r={},i=t.nodes();return i.forEach((function(t){r[t]={},r[t][t]={distance:0},i.forEach((function(e){t!==e&&(r[t][e]={distance:Number.POSITIVE_INFINITY})})),n(t).forEach((function(n){var i=n.v===t?n.w:n.v,a=e(n);r[t][i]={distance:a,predecessor:t}}))})),i.forEach((function(t){var e=r[t];i.forEach((function(n){var a=r[n];i.forEach((function(n){var r=a[t],i=e[n],o=a[n],s=r.distance+i.distance;s0;){if(n=c.removeMin(),r.has(s,n))o.setEdge(n,s[n]);else{if(l)throw new Error("Input graph is not connected: "+t);l=!0}t.nodeEdges(n).forEach(u)}return o}},function(t,e,n){var r;try{r=n(3)}catch(t){}r||(r=window.graphlib),t.exports=r},function(t,e,n){"use strict";var r=n(4),i=n(345),a=n(348),o=n(349),s=n(8).normalizeRanks,c=n(351),u=n(8).removeEmptyRanks,l=n(352),h=n(353),f=n(354),d=n(355),p=n(364),g=n(8),y=n(17).Graph;t.exports=function(t,e){var n=e&&e.debugTiming?g.time:g.notime;n("layout",(function(){var e=n(" buildLayoutGraph",(function(){return function(t){var e=new y({multigraph:!0,compound:!0}),n=C(t.graph());return e.setGraph(r.merge({},m,T(n,v),r.pick(n,b))),r.forEach(t.nodes(),(function(n){var i=C(t.node(n));e.setNode(n,r.defaults(T(i,x),_)),e.setParent(n,t.parent(n))})),r.forEach(t.edges(),(function(n){var i=C(t.edge(n));e.setEdge(n,r.merge({},w,T(i,k),r.pick(i,E)))})),e}(t)}));n(" runLayout",(function(){!function(t,e){e(" makeSpaceForEdgeLabels",(function(){!function(t){var e=t.graph();e.ranksep/=2,r.forEach(t.edges(),(function(n){var r=t.edge(n);r.minlen*=2,"c"!==r.labelpos.toLowerCase()&&("TB"===e.rankdir||"BT"===e.rankdir?r.width+=r.labeloffset:r.height+=r.labeloffset)}))}(t)})),e(" removeSelfEdges",(function(){!function(t){r.forEach(t.edges(),(function(e){if(e.v===e.w){var n=t.node(e.v);n.selfEdges||(n.selfEdges=[]),n.selfEdges.push({e:e,label:t.edge(e)}),t.removeEdge(e)}}))}(t)})),e(" acyclic",(function(){i.run(t)})),e(" nestingGraph.run",(function(){l.run(t)})),e(" rank",(function(){o(g.asNonCompoundGraph(t))})),e(" injectEdgeLabelProxies",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.width&&n.height){var r=t.node(e.v),i={rank:(t.node(e.w).rank-r.rank)/2+r.rank,e:e};g.addDummyNode(t,"edge-proxy",i,"_ep")}}))}(t)})),e(" removeEmptyRanks",(function(){u(t)})),e(" nestingGraph.cleanup",(function(){l.cleanup(t)})),e(" normalizeRanks",(function(){s(t)})),e(" assignRankMinMax",(function(){!function(t){var e=0;r.forEach(t.nodes(),(function(n){var i=t.node(n);i.borderTop&&(i.minRank=t.node(i.borderTop).rank,i.maxRank=t.node(i.borderBottom).rank,e=r.max(e,i.maxRank))})),t.graph().maxRank=e}(t)})),e(" removeEdgeLabelProxies",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);"edge-proxy"===n.dummy&&(t.edge(n.e).labelRank=n.rank,t.removeNode(e))}))}(t)})),e(" normalize.run",(function(){a.run(t)})),e(" parentDummyChains",(function(){c(t)})),e(" addBorderSegments",(function(){h(t)})),e(" order",(function(){d(t)})),e(" insertSelfEdges",(function(){!function(t){var e=g.buildLayerMatrix(t);r.forEach(e,(function(e){var n=0;r.forEach(e,(function(e,i){var a=t.node(e);a.order=i+n,r.forEach(a.selfEdges,(function(e){g.addDummyNode(t,"selfedge",{width:e.label.width,height:e.label.height,rank:a.rank,order:i+ ++n,e:e.e,label:e.label},"_se")})),delete a.selfEdges}))}))}(t)})),e(" adjustCoordinateSystem",(function(){f.adjust(t)})),e(" position",(function(){p(t)})),e(" positionSelfEdges",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);if("selfedge"===n.dummy){var r=t.node(n.e.v),i=r.x+r.width/2,a=r.y,o=n.x-i,s=r.height/2;t.setEdge(n.e,n.label),t.removeNode(e),n.label.points=[{x:i+2*o/3,y:a-s},{x:i+5*o/6,y:a-s},{x:i+o,y:a},{x:i+5*o/6,y:a+s},{x:i+2*o/3,y:a+s}],n.label.x=n.x,n.label.y=n.y}}))}(t)})),e(" removeBorderNodes",(function(){!function(t){r.forEach(t.nodes(),(function(e){if(t.children(e).length){var n=t.node(e),i=t.node(n.borderTop),a=t.node(n.borderBottom),o=t.node(r.last(n.borderLeft)),s=t.node(r.last(n.borderRight));n.width=Math.abs(s.x-o.x),n.height=Math.abs(a.y-i.y),n.x=o.x+n.width/2,n.y=i.y+n.height/2}})),r.forEach(t.nodes(),(function(e){"border"===t.node(e).dummy&&t.removeNode(e)}))}(t)})),e(" normalize.undo",(function(){a.undo(t)})),e(" fixupEdgeLabelCoords",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(r.has(n,"x"))switch("l"!==n.labelpos&&"r"!==n.labelpos||(n.width-=n.labeloffset),n.labelpos){case"l":n.x-=n.width/2+n.labeloffset;break;case"r":n.x+=n.width/2+n.labeloffset}}))}(t)})),e(" undoCoordinateSystem",(function(){f.undo(t)})),e(" translateGraph",(function(){!function(t){var e=Number.POSITIVE_INFINITY,n=0,i=Number.POSITIVE_INFINITY,a=0,o=t.graph(),s=o.marginx||0,c=o.marginy||0;function u(t){var r=t.x,o=t.y,s=t.width,c=t.height;e=Math.min(e,r-s/2),n=Math.max(n,r+s/2),i=Math.min(i,o-c/2),a=Math.max(a,o+c/2)}r.forEach(t.nodes(),(function(e){u(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.has(n,"x")&&u(n)})),e-=s,i-=c,r.forEach(t.nodes(),(function(n){var r=t.node(n);r.x-=e,r.y-=i})),r.forEach(t.edges(),(function(n){var a=t.edge(n);r.forEach(a.points,(function(t){t.x-=e,t.y-=i})),r.has(a,"x")&&(a.x-=e),r.has(a,"y")&&(a.y-=i)})),o.width=n-e+s,o.height=a-i+c}(t)})),e(" assignNodeIntersects",(function(){!function(t){r.forEach(t.edges(),(function(e){var n,r,i=t.edge(e),a=t.node(e.v),o=t.node(e.w);i.points?(n=i.points[0],r=i.points[i.points.length-1]):(i.points=[],n=o,r=a),i.points.unshift(g.intersectRect(a,n)),i.points.push(g.intersectRect(o,r))}))}(t)})),e(" reversePoints",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);n.reversed&&n.points.reverse()}))}(t)})),e(" acyclic.undo",(function(){i.undo(t)}))}(e,n)})),n(" updateInputGraph",(function(){!function(t,e){r.forEach(t.nodes(),(function(n){var r=t.node(n),i=e.node(n);r&&(r.x=i.x,r.y=i.y,e.children(n).length&&(r.width=i.width,r.height=i.height))})),r.forEach(t.edges(),(function(n){var i=t.edge(n),a=e.edge(n);i.points=a.points,r.has(a,"x")&&(i.x=a.x,i.y=a.y)})),t.graph().width=e.graph().width,t.graph().height=e.graph().height}(t,e)}))}))};var v=["nodesep","edgesep","ranksep","marginx","marginy"],m={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},b=["acyclicer","ranker","rankdir","align"],x=["width","height"],_={width:0,height:0},k=["minlen","weight","width","height","labeloffset"],w={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},E=["labelpos"];function T(t,e){return r.mapValues(r.pick(t,e),Number)}function C(t){var e={};return r.forEach(t,(function(t,n){e[n.toLowerCase()]=t})),e}},function(t,e,n){var r=n(108);t.exports=function(t){return r(t,5)}},function(t,e,n){var r=n(315)(n(316));t.exports=r},function(t,e,n){var r=n(25),i=n(24),a=n(30);t.exports=function(t){return function(e,n,o){var s=Object(e);if(!i(e)){var c=r(n,3);e=a(e),n=function(t){return c(s[t],t,s)}}var u=t(e,n,o);return u>-1?s[c?e[u]:u]:void 0}}},function(t,e,n){var r=n(145),i=n(25),a=n(317),o=Math.max;t.exports=function(t,e,n){var s=null==t?0:t.length;if(!s)return-1;var c=null==n?0:a(n);return c<0&&(c=o(s+c,0)),r(t,i(e,3),c)}},function(t,e,n){var r=n(155);t.exports=function(t){var e=r(t),n=e%1;return e==e?n?e-n:e:0}},function(t,e,n){var r=n(11),i=n(42),a=/^\s+|\s+$/g,o=/^[-+]0x[0-9a-f]+$/i,s=/^0b[01]+$/i,c=/^0o[0-7]+$/i,u=parseInt;t.exports=function(t){if("number"==typeof t)return t;if(i(t))return NaN;if(r(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=r(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(a,"");var n=s.test(t);return n||c.test(t)?u(t.slice(2),n?2:8):o.test(t)?NaN:+t}},function(t,e,n){var r=n(89),i=n(127),a=n(40);t.exports=function(t,e){return null==t?t:r(t,i(e),a)}},function(t,e){t.exports=function(t){var e=null==t?0:t.length;return e?t[e-1]:void 0}},function(t,e,n){var r=n(59),i=n(88),a=n(25);t.exports=function(t,e){var n={};return e=a(e,3),i(t,(function(t,i,a){r(n,i,e(t,i,a))})),n}},function(t,e,n){var r=n(95),i=n(323),a=n(35);t.exports=function(t){return t&&t.length?r(t,a,i):void 0}},function(t,e){t.exports=function(t,e){return t>e}},function(t,e,n){var r=n(325),i=n(328)((function(t,e,n){r(t,e,n)}));t.exports=i},function(t,e,n){var r=n(53),i=n(157),a=n(89),o=n(326),s=n(11),c=n(40),u=n(159);t.exports=function t(e,n,l,h,f){e!==n&&a(n,(function(a,c){if(f||(f=new r),s(a))o(e,n,c,l,t,h,f);else{var d=h?h(u(e,c),a,c+"",e,n,f):void 0;void 0===d&&(d=a),i(e,c,d)}}),c)}},function(t,e,n){var r=n(157),i=n(114),a=n(123),o=n(115),s=n(124),c=n(47),u=n(5),l=n(146),h=n(39),f=n(37),d=n(11),p=n(158),g=n(48),y=n(159),v=n(327);t.exports=function(t,e,n,m,b,x,_){var k=y(t,n),w=y(e,n),E=_.get(w);if(E)r(t,n,E);else{var T=x?x(k,w,n+"",t,e,_):void 0,C=void 0===T;if(C){var A=u(w),S=!A&&h(w),M=!A&&!S&&g(w);T=w,A||S||M?u(k)?T=k:l(k)?T=o(k):S?(C=!1,T=i(w,!0)):M?(C=!1,T=a(w,!0)):T=[]:p(w)||c(w)?(T=k,c(k)?T=v(k):d(k)&&!f(k)||(T=s(w))):C=!1}C&&(_.set(w,T),b(T,w,m,x,_),_.delete(w)),r(t,n,T)}}},function(t,e,n){var r=n(46),i=n(40);t.exports=function(t){return r(t,i(t))}},function(t,e,n){var r=n(67),i=n(68);t.exports=function(t){return r((function(e,n){var r=-1,a=n.length,o=a>1?n[a-1]:void 0,s=a>2?n[2]:void 0;for(o=t.length>3&&"function"==typeof o?(a--,o):void 0,s&&i(n[0],n[1],s)&&(o=a<3?void 0:o,a=1),e=Object(e);++r1&&o(t,e[0],e[1])?e=[]:n>2&&o(e[0],e[1],e[2])&&(e=[e[0]]),i(t,r(e,1),[])}));t.exports=s},function(t,e,n){var r=n(66),i=n(25),a=n(141),o=n(340),s=n(61),c=n(341),u=n(35);t.exports=function(t,e,n){var l=-1;e=r(e.length?e:[u],s(i));var h=a(t,(function(t,n,i){return{criteria:r(e,(function(e){return e(t)})),index:++l,value:t}}));return o(h,(function(t,e){return c(t,e,n)}))}},function(t,e){t.exports=function(t,e){var n=t.length;for(t.sort(e);n--;)t[n]=t[n].value;return t}},function(t,e,n){var r=n(342);t.exports=function(t,e,n){for(var i=-1,a=t.criteria,o=e.criteria,s=a.length,c=n.length;++i=c?u:u*("desc"==n[i]?-1:1)}return t.index-e.index}},function(t,e,n){var r=n(42);t.exports=function(t,e){if(t!==e){var n=void 0!==t,i=null===t,a=t==t,o=r(t),s=void 0!==e,c=null===e,u=e==e,l=r(e);if(!c&&!l&&!o&&t>e||o&&s&&u&&!c&&!l||i&&s&&u||!n&&u||!a)return 1;if(!i&&!o&&!l&&t0;--c)if(r=e[c].dequeue()){i=i.concat(s(t,e,n,r,!0));break}}return i}(n.graph,n.buckets,n.zeroIdx);return r.flatten(r.map(u,(function(e){return t.outEdges(e.v,e.w)})),!0)};var o=r.constant(1);function s(t,e,n,i,a){var o=a?[]:void 0;return r.forEach(t.inEdges(i.v),(function(r){var i=t.edge(r),s=t.node(r.v);a&&o.push({v:r.v,w:r.w}),s.out-=i,c(e,n,s)})),r.forEach(t.outEdges(i.v),(function(r){var i=t.edge(r),a=r.w,o=t.node(a);o.in-=i,c(e,n,o)})),t.removeNode(i.v),o}function c(t,e,n){n.out?n.in?t[n.out-n.in+e].enqueue(n):t[t.length-1].enqueue(n):t[0].enqueue(n)}},function(t,e){function n(){var t={};t._next=t._prev=t,this._sentinel=t}function r(t){t._prev._next=t._next,t._next._prev=t._prev,delete t._next,delete t._prev}function i(t,e){if("_next"!==t&&"_prev"!==t)return e}t.exports=n,n.prototype.dequeue=function(){var t=this._sentinel,e=t._prev;if(e!==t)return r(e),e},n.prototype.enqueue=function(t){var e=this._sentinel;t._prev&&t._next&&r(t),t._next=e._next,e._next._prev=t,e._next=t,t._prev=e},n.prototype.toString=function(){for(var t=[],e=this._sentinel,n=e._prev;n!==e;)t.push(JSON.stringify(n,i)),n=n._prev;return"["+t.join(", ")+"]"}},function(t,e,n){"use strict";var r=n(4),i=n(8);t.exports={run:function(t){t.graph().dummyChains=[],r.forEach(t.edges(),(function(e){!function(t,e){var n,r,a,o=e.v,s=t.node(o).rank,c=e.w,u=t.node(c).rank,l=e.name,h=t.edge(e),f=h.labelRank;if(u===s+1)return;for(t.removeEdge(e),a=0,++s;sc.lim&&(u=c,l=!0);var h=r.filter(e.edges(),(function(e){return l===m(t,t.node(e.v),u)&&l!==m(t,t.node(e.w),u)}));return r.minBy(h,(function(t){return a(e,t)}))}function v(t,e,n,i){var a=n.v,o=n.w;t.removeEdge(a,o),t.setEdge(i.v,i.w,{}),d(t),h(t,e),function(t,e){var n=r.find(t.nodes(),(function(t){return!e.node(t).parent})),i=s(t,n);i=i.slice(1),r.forEach(i,(function(n){var r=t.node(n).parent,i=e.edge(n,r),a=!1;i||(i=e.edge(r,n),a=!0),e.node(n).rank=e.node(r).rank+(a?i.minlen:-i.minlen)}))}(t,e)}function m(t,e,n){return n.low<=e.lim&&e.lim<=n.lim}t.exports=l,l.initLowLimValues=d,l.initCutValues=h,l.calcCutValue=f,l.leaveEdge=g,l.enterEdge=y,l.exchangeEdges=v},function(t,e,n){var r=n(4);t.exports=function(t){var e=function(t){var e={},n=0;function i(a){var o=n;r.forEach(t.children(a),i),e[a]={low:o,lim:n++}}return r.forEach(t.children(),i),e}(t);r.forEach(t.graph().dummyChains,(function(n){for(var r=t.node(n),i=r.edgeObj,a=function(t,e,n,r){var i,a,o=[],s=[],c=Math.min(e[n].low,e[r].low),u=Math.max(e[n].lim,e[r].lim);i=n;do{i=t.parent(i),o.push(i)}while(i&&(e[i].low>c||u>e[i].lim));a=i,i=r;for(;(i=t.parent(i))!==a;)s.push(i);return{path:o.concat(s.reverse()),lca:a}}(t,e,i.v,i.w),o=a.path,s=a.lca,c=0,u=o[c],l=!0;n!==i.w;){if(r=t.node(n),l){for(;(u=o[c])!==s&&t.node(u).maxRank=2),s=l.buildLayerMatrix(t);var y=a(t,s);y0;)e%2&&(n+=c[e+1]),c[e=e-1>>1]+=t.weight;u+=t.weight*n}))),u}t.exports=function(t,e){for(var n=0,r=1;r=t.barycenter)&&function(t,e){var n=0,r=0;t.weight&&(n+=t.barycenter*t.weight,r+=t.weight);e.weight&&(n+=e.barycenter*e.weight,r+=e.weight);t.vs=e.vs.concat(t.vs),t.barycenter=n/r,t.weight=r,t.i=Math.min(e.i,t.i),e.merged=!0}(t,e)}}function i(e){return function(n){n.in.push(e),0==--n.indegree&&t.push(n)}}for(;t.length;){var a=t.pop();e.push(a),r.forEach(a.in.reverse(),n(a)),r.forEach(a.out,i(a))}return r.map(r.filter(e,(function(t){return!t.merged})),(function(t){return r.pick(t,["vs","i","barycenter","weight"])}))}(r.filter(n,(function(t){return!t.indegree})))}},function(t,e,n){var r=n(4),i=n(8);function a(t,e,n){for(var i;e.length&&(i=r.last(e)).i<=n;)e.pop(),t.push(i.vs),n++;return n}t.exports=function(t,e){var n=i.partition(t,(function(t){return r.has(t,"barycenter")})),o=n.lhs,s=r.sortBy(n.rhs,(function(t){return-t.i})),c=[],u=0,l=0,h=0;o.sort((f=!!e,function(t,e){return t.barycentere.barycenter?1:f?e.i-t.i:t.i-e.i})),h=a(c,s,h),r.forEach(o,(function(t){h+=t.vs.length,c.push(t.vs),u+=t.barycenter*t.weight,l+=t.weight,h=a(c,s,h)}));var f;var d={vs:r.flatten(c,!0)};l&&(d.barycenter=u/l,d.weight=l);return d}},function(t,e,n){var r=n(4),i=n(17).Graph;t.exports=function(t,e,n){var a=function(t){var e;for(;t.hasNode(e=r.uniqueId("_root")););return e}(t),o=new i({compound:!0}).setGraph({root:a}).setDefaultNodeLabel((function(e){return t.node(e)}));return r.forEach(t.nodes(),(function(i){var s=t.node(i),c=t.parent(i);(s.rank===e||s.minRank<=e&&e<=s.maxRank)&&(o.setNode(i),o.setParent(i,c||a),r.forEach(t[n](i),(function(e){var n=e.v===i?e.w:e.v,a=o.edge(n,i),s=r.isUndefined(a)?0:a.weight;o.setEdge(n,i,{weight:t.edge(e).weight+s})})),r.has(s,"minRank")&&o.setNode(i,{borderLeft:s.borderLeft[e],borderRight:s.borderRight[e]}))})),o}},function(t,e,n){var r=n(4);t.exports=function(t,e,n){var i,a={};r.forEach(n,(function(n){for(var r,o,s=t.parent(n);s;){if((r=t.parent(s))?(o=a[r],a[r]=s):(o=i,i=s),o&&o!==s)return void e.setEdge(o,s);s=r}}))}},function(t,e,n){"use strict";var r=n(4),i=n(8),a=n(365).positionX;t.exports=function(t){(function(t){var e=i.buildLayerMatrix(t),n=t.graph().ranksep,a=0;r.forEach(e,(function(e){var i=r.max(r.map(e,(function(e){return t.node(e).height})));r.forEach(e,(function(e){t.node(e).y=a+i/2})),a+=i+n}))})(t=i.asNonCompoundGraph(t)),r.forEach(a(t),(function(e,n){t.node(n).x=e}))}},function(t,e,n){"use strict";var r=n(4),i=n(17).Graph,a=n(8);function o(t,e){var n={};return r.reduce(e,(function(e,i){var a=0,o=0,s=e.length,u=r.last(i);return r.forEach(i,(function(e,l){var h=function(t,e){if(t.node(e).dummy)return r.find(t.predecessors(e),(function(e){return t.node(e).dummy}))}(t,e),f=h?t.node(h).order:s;(h||e===u)&&(r.forEach(i.slice(o,l+1),(function(e){r.forEach(t.predecessors(e),(function(r){var i=t.node(r),o=i.order;!(os)&&c(n,e,u)}))}))}return r.reduce(e,(function(e,n){var a,o=-1,s=0;return r.forEach(n,(function(r,c){if("border"===t.node(r).dummy){var u=t.predecessors(r);u.length&&(a=t.node(u[0]).order,i(n,s,c,o,a),s=c,o=a)}i(n,s,n.length,a,e.length)})),n})),n}function c(t,e,n){if(e>n){var r=e;e=n,n=r}var i=t[e];i||(t[e]=i={}),i[n]=!0}function u(t,e,n){if(e>n){var i=e;e=n,n=i}return r.has(t[e],n)}function l(t,e,n,i){var a={},o={},s={};return r.forEach(e,(function(t){r.forEach(t,(function(t,e){a[t]=t,o[t]=t,s[t]=e}))})),r.forEach(e,(function(t){var e=-1;r.forEach(t,(function(t){var c=i(t);if(c.length)for(var l=((c=r.sortBy(c,(function(t){return s[t]}))).length-1)/2,h=Math.floor(l),f=Math.ceil(l);h<=f;++h){var d=c[h];o[t]===t&&e0}t.exports=function(t,e,r,i){var a,o,s,c,u,l,h,f,d,p,g,y,v;if(a=e.y-t.y,s=t.x-e.x,u=e.x*t.y-t.x*e.y,d=a*r.x+s*r.y+u,p=a*i.x+s*i.y+u,0!==d&&0!==p&&n(d,p))return;if(o=i.y-r.y,c=r.x-i.x,l=i.x*r.y-r.x*i.y,h=o*t.x+c*t.y+l,f=o*e.x+c*e.y+l,0!==h&&0!==f&&n(h,f))return;if(0===(g=a*c-o*s))return;return y=Math.abs(g/2),{x:(v=s*l-c*u)<0?(v-y)/g:(v+y)/g,y:(v=o*u-a*l)<0?(v-y)/g:(v+y)/g}}},function(t,e,n){var r=n(43),i=n(31),a=n(153).layout;t.exports=function(){var t=n(371),e=n(374),i=n(375),u=n(376),l=n(377),h=n(378),f=n(379),d=n(380),p=n(381),g=function(n,g){!function(t){t.nodes().forEach((function(e){var n=t.node(e);r.has(n,"label")||t.children(e).length||(n.label=e),r.has(n,"paddingX")&&r.defaults(n,{paddingLeft:n.paddingX,paddingRight:n.paddingX}),r.has(n,"paddingY")&&r.defaults(n,{paddingTop:n.paddingY,paddingBottom:n.paddingY}),r.has(n,"padding")&&r.defaults(n,{paddingLeft:n.padding,paddingRight:n.padding,paddingTop:n.padding,paddingBottom:n.padding}),r.defaults(n,o),r.each(["paddingLeft","paddingRight","paddingTop","paddingBottom"],(function(t){n[t]=Number(n[t])})),r.has(n,"width")&&(n._prevWidth=n.width),r.has(n,"height")&&(n._prevHeight=n.height)})),t.edges().forEach((function(e){var n=t.edge(e);r.has(n,"label")||(n.label=""),r.defaults(n,s)}))}(g);var y=c(n,"output"),v=c(y,"clusters"),m=c(y,"edgePaths"),b=i(c(y,"edgeLabels"),g),x=t(c(y,"nodes"),g,d);a(g),l(x,g),h(b,g),u(m,g,p);var _=e(v,g);f(_,g),function(t){r.each(t.nodes(),(function(e){var n=t.node(e);r.has(n,"_prevWidth")?n.width=n._prevWidth:delete n.width,r.has(n,"_prevHeight")?n.height=n._prevHeight:delete n.height,delete n._prevWidth,delete n._prevHeight}))}(g)};return g.createNodes=function(e){return arguments.length?(t=e,g):t},g.createClusters=function(t){return arguments.length?(e=t,g):e},g.createEdgeLabels=function(t){return arguments.length?(i=t,g):i},g.createEdgePaths=function(t){return arguments.length?(u=t,g):u},g.shapes=function(t){return arguments.length?(d=t,g):d},g.arrows=function(t){return arguments.length?(p=t,g):p},g};var o={paddingLeft:10,paddingRight:10,paddingTop:10,paddingBottom:10,rx:0,ry:0,shape:"rect"},s={arrowhead:"normal",curve:i.curveLinear};function c(t,e){var n=t.select("g."+e);return n.empty()&&(n=t.append("g").attr("class",e)),n}},function(t,e,n){"use strict";var r=n(43),i=n(97),a=n(12),o=n(31);t.exports=function(t,e,n){var s,c=e.nodes().filter((function(t){return!a.isSubgraph(e,t)})),u=t.selectAll("g.node").data(c,(function(t){return t})).classed("update",!0);u.exit().remove(),u.enter().append("g").attr("class","node").style("opacity",0),(u=t.selectAll("g.node")).each((function(t){var s=e.node(t),c=o.select(this);a.applyClass(c,s.class,(c.classed("update")?"update ":"")+"node"),c.select("g.label").remove();var u=c.append("g").attr("class","label"),l=i(u,s),h=n[s.shape],f=r.pick(l.node().getBBox(),"width","height");s.elem=this,s.id&&c.attr("id",s.id),s.labelId&&u.attr("id",s.labelId),r.has(s,"width")&&(f.width=s.width),r.has(s,"height")&&(f.height=s.height),f.width+=s.paddingLeft+s.paddingRight,f.height+=s.paddingTop+s.paddingBottom,u.attr("transform","translate("+(s.paddingLeft-s.paddingRight)/2+","+(s.paddingTop-s.paddingBottom)/2+")");var d=o.select(this);d.select(".label-container").remove();var p=h(d,f,s).classed("label-container",!0);a.applyStyle(p,s.style);var g=p.node().getBBox();s.width=g.width,s.height=g.height})),s=u.exit?u.exit():u.selectAll(null);return a.applyTransition(s,e).style("opacity",0).remove(),u}},function(t,e,n){var r=n(12);t.exports=function(t,e){for(var n=t.append("text"),i=function(t){for(var e,n="",r=!1,i=0;i0&&void 0!==arguments[0]?arguments[0]:"fatal";isNaN(t)&&(t=t.toLowerCase(),void 0!==s[t]&&(t=s[t])),c.trace=function(){},c.debug=function(){},c.info=function(){},c.warn=function(){},c.error=function(){},c.fatal=function(){},t<=s.fatal&&(c.fatal=console.error?console.error.bind(console,l("FATAL"),"color: orange"):console.log.bind(console,"",l("FATAL"))),t<=s.error&&(c.error=console.error?console.error.bind(console,l("ERROR"),"color: orange"):console.log.bind(console,"",l("ERROR"))),t<=s.warn&&(c.warn=console.warn?console.warn.bind(console,l("WARN"),"color: orange"):console.log.bind(console,"",l("WARN"))),t<=s.info&&(c.info=console.info?console.info.bind(console,l("INFO"),"color: lightblue"):console.log.bind(console,"",l("INFO"))),t<=s.debug&&(c.debug=console.debug?console.debug.bind(console,l("DEBUG"),"color: lightgreen"):console.log.bind(console,"",l("DEBUG")))},l=function(t){var e=o()().format("ss.SSS");return"%c".concat(e," : ").concat(t," : ")},h=n(169),f=n.n(h),d=n(0),p=n(44),g=n(70),y=function(t){for(var e="",n=0;n>=0;){if(!((n=t.indexOf("=0)){e+=t,n=-1;break}e+=t.substr(0,n),(n=(t=t.substr(n+1)).indexOf("<\/script>"))>=0&&(n+=9,t=t.substr(n))}return e},v=//gi,m=function(t){return t.replace(v,"#br#")},b=function(t){return t.replace(/#br#/g,"
")},x={getRows:function(t){if(!t)return 1;var e=m(t);return(e=e.replace(/\\n/g,"#br#")).split("#br#")},sanitizeText:function(t,e){var n=t,r=!0;if(!e.flowchart||!1!==e.flowchart.htmlLabels&&"false"!==e.flowchart.htmlLabels||(r=!1),r){var i=e.securityLevel;"antiscript"===i?n=y(n):"loose"!==i&&(n=(n=(n=m(n)).replace(//g,">")).replace(/=/g,"="),n=b(n))}return n},hasBreaks:function(t){return//gi.test(t)},splitBreaks:function(t){return t.split(//gi)},lineBreakRegex:v,removeScript:y};function _(t,e){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:null;try{var n=new RegExp("[%]{2}(?![{]".concat(C.source,")(?=[}][%]{2}).*\n"),"ig");t=t.trim().replace(n,"").replace(/'/gm,'"'),c.debug("Detecting diagram directive".concat(null!==e?" type:"+e:""," based on the text:").concat(t));for(var r,i=[];null!==(r=T.exec(t));)if(r.index===T.lastIndex&&T.lastIndex++,r&&!e||e&&r[1]&&r[1].match(e)||e&&r[2]&&r[2].match(e)){var a=r[1]?r[1]:r[2],o=r[3]?r[3].trim():r[4]?JSON.parse(r[4].trim()):null;i.push({type:a,args:o})}return 0===i.length&&i.push({type:t,args:null}),1===i.length?i[0]:i}catch(n){return c.error("ERROR: ".concat(n.message," - Unable to parse directive").concat(null!==e?" type:"+e:""," based on the text:").concat(t)),{type:null,args:null}}},M=function(t){return t=t.replace(T,"").replace(A,"\n"),c.debug("Detecting diagram type based on the text "+t),t.match(/^\s*sequenceDiagram/)?"sequence":t.match(/^\s*gantt/)?"gantt":t.match(/^\s*classDiagram-v2/)?"classDiagram":t.match(/^\s*classDiagram/)?"class":t.match(/^\s*stateDiagram-v2/)?"stateDiagram":t.match(/^\s*stateDiagram/)?"state":t.match(/^\s*gitGraph/)?"git":t.match(/^\s*flowchart/)?"flowchart-v2":t.match(/^\s*info/)?"info":t.match(/^\s*pie/)?"pie":t.match(/^\s*erDiagram/)?"er":t.match(/^\s*journey/)?"journey":"flowchart"},O=function(t,e){var n={};return function(){for(var r=arguments.length,i=new Array(r),a=0;a"},n),x.lineBreakRegex.test(t))return t;var r=t.split(" "),i=[],a="";return r.forEach((function(t,o){var s=z("".concat(t," "),n),c=z(a,n);if(s>e){var u=Y(t,e,"-",n),l=u.hyphenatedStrings,h=u.remainingWord;i.push.apply(i,[a].concat(w(l))),a=h}else c+s>=e?(i.push(a),a=t):a=[a,t].filter(Boolean).join(" ");o+1===r.length&&i.push(a)})),i.filter((function(t){return""!==t})).join(n.joinWith)}),(function(t,e,n){return"".concat(t,"-").concat(e,"-").concat(n.fontSize,"-").concat(n.fontWeight,"-").concat(n.fontFamily,"-").concat(n.joinWith)})),Y=O((function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"-",r=arguments.length>3?arguments[3]:void 0;r=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",margin:0},r);var i=t.split(""),a=[],o="";return i.forEach((function(t,s){var c="".concat(o).concat(t);if(z(c,r)>=e){var u=s+1,l=i.length===u,h="".concat(c).concat(n);a.push(l?c:h),o=""}else o=c})),{hyphenatedStrings:a,remainingWord:o}}),(function(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"-",r=arguments.length>3?arguments[3]:void 0;return"".concat(t,"-").concat(e,"-").concat(n,"-").concat(r.fontSize,"-").concat(r.fontWeight,"-").concat(r.fontFamily)})),z=function(t,e){return e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial"},e),U(t,e).width},U=O((function(t,e){var n=e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial"},e),r=n.fontSize,i=n.fontFamily,a=n.fontWeight;if(!t)return{width:0,height:0};var o=["sans-serif",i],s=t.split(x.lineBreakRegex),c=[],u=Object(d.select)("body");if(!u.remove)return{width:0,height:0,lineHeight:0};for(var l=u.append("svg"),h=0,f=o;hc[1].height&&c[0].width>c[1].width&&c[0].lineHeight>c[1].lineHeight?0:1]}),(function(t,e){return"".concat(t,"-").concat(e.fontSize,"-").concat(e.fontWeight,"-").concat(e.fontFamily)})),$=function(t,e,n){var r=new Map;return r.set("height",t),n?(r.set("width","100%"),r.set("style","max-width: ".concat(e,"px;"))):r.set("width",e),r},W=function(t,e,n,r){!function(t,e){var n=!0,r=!1,i=void 0;try{for(var a,o=e[Symbol.iterator]();!(n=(a=o.next()).done);n=!0){var s=a.value;t.attr(s[0],s[1])}}catch(t){r=!0,i=t}finally{try{n||null==o.return||o.return()}finally{if(r)throw i}}}(t,$(e,n,r))},H={assignWithDepth:I,wrapLabel:R,calculateTextHeight:function(t,e){return e=Object.assign({fontSize:12,fontWeight:400,fontFamily:"Arial",margin:15},e),U(t,e).height},calculateTextWidth:z,calculateTextDimensions:U,calculateSvgSizeAttrs:$,configureSvgSize:W,detectInit:function(t){var e=S(t,/(?:init\b)|(?:initialize\b)/),n={};if(Array.isArray(e)){var r=e.map((function(t){return t.args}));n=I(n,w(r))}else n=e.args;if(n){var i=M(t);["config"].forEach((function(t){void 0!==n[t]&&("flowchart-v2"===i&&(i="flowchart"),n[i]=n[t],delete n[t])}))}return n},detectDirective:S,detectType:M,isSubstringInArray:function(t,e){for(var n=0;n=1&&(i={x:t.x,y:t.y}),a>0&&a<1&&(i={x:(1-a)*e.x+a*t.x,y:(1-a)*e.y+a*t.y})}}e=t})),i}(t)},calcCardinalityPosition:function(t,e,n){var r;c.info("our points",e),e[0]!==n&&(e=e.reverse()),e.forEach((function(t){N(t,r),r=t}));var i,a=25;r=void 0,e.forEach((function(t){if(r&&!i){var e=N(t,r);if(e=1&&(i={x:t.x,y:t.y}),n>0&&n<1&&(i={x:(1-n)*r.x+n*t.x,y:(1-n)*r.y+n*t.y})}}r=t}));var o=t?10:5,s=Math.atan2(e[0].y-i.y,e[0].x-i.x),u={x:0,y:0};return u.x=Math.sin(s)*o+(e[0].x+i.x)/2,u.y=-Math.cos(s)*o+(e[0].y+i.y)/2,u},calcTerminalLabelPosition:function(t,e,n){var r,i=JSON.parse(JSON.stringify(n));c.info("our points",i),"start_left"!==e&&"start_right"!==e&&(i=i.reverse()),i.forEach((function(t){N(t,r),r=t}));var a,o=25;r=void 0,i.forEach((function(t){if(r&&!a){var e=N(t,r);if(e=1&&(a={x:t.x,y:t.y}),n>0&&n<1&&(a={x:(1-n)*r.x+n*t.x,y:(1-n)*r.y+n*t.y})}}r=t}));var s=10,u=Math.atan2(i[0].y-a.y,i[0].x-a.x),l={x:0,y:0};return l.x=Math.sin(u)*s+(i[0].x+a.x)/2,l.y=-Math.cos(u)*s+(i[0].y+a.y)/2,"start_left"===e&&(l.x=Math.sin(u+Math.PI)*s+(i[0].x+a.x)/2,l.y=-Math.cos(u+Math.PI)*s+(i[0].y+a.y)/2),"end_right"===e&&(l.x=Math.sin(u-Math.PI)*s+(i[0].x+a.x)/2-5,l.y=-Math.cos(u-Math.PI)*s+(i[0].y+a.y)/2-5),"end_left"===e&&(l.x=Math.sin(u)*s+(i[0].x+a.x)/2-5,l.y=-Math.cos(u)*s+(i[0].y+a.y)/2-5),l},formatUrl:function(t,e){var n=t.trim();if(n)return"loose"!==e.securityLevel?Object(g.sanitizeUrl)(n):n},getStylesFromArray:B,generateId:F,random:P,memoize:O,runFunc:function(t){for(var e,n=t.split("."),r=n.length-1,i=n[r],a=window,o=0;o1?s-1:0),u=1;u=0&&(n=!0)})),n},qt=function(t,e){var n=[];return t.nodes.forEach((function(r,i){Gt(e,r)||n.push(t.nodes[i])})),{nodes:n}},Xt={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},defaultConfig:function(){return gt.flowchart},addVertex:function(t,e,n,r,i){var a,o=t;void 0!==o&&0!==o.trim().length&&(void 0===Dt[o]&&(Dt[o]={id:o,domId:"flowchart-"+o+"-"+Mt,styles:[],classes:[]}),Mt++,void 0!==e?(Ot=_t(),'"'===(a=x.sanitizeText(e.trim(),Ot))[0]&&'"'===a[a.length-1]&&(a=a.substring(1,a.length-1)),Dt[o].text=a):void 0===Dt[o].text&&(Dt[o].text=t),void 0!==n&&(Dt[o].type=n),null!=r&&r.forEach((function(t){Dt[o].styles.push(t)})),null!=i&&i.forEach((function(t){Dt[o].classes.push(t)})))},lookUpDomId:Yt,addLink:function(t,e,n,r){var i,a;for(i=0;i/)&&(At="LR"),At.match(/.*v/)&&(At="TB")},setClass:Ut,setTooltip:function(t,e){t.split(",").forEach((function(t){void 0!==e&&(Pt["gen-1"===St?Yt(t):t]=x.sanitizeText(e,Ot))}))},getTooltip:function(t){return Pt[t]},setClickEvent:function(t,e,n){t.split(",").forEach((function(t){!function(t,e,n){var r=Yt(t);if("loose"===_t().securityLevel&&void 0!==e){var i=[];if("string"==typeof n){i=n.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(var a=0;a=0)&&s.push(t))})),"gen-1"===St){c.warn("LOOKING UP");for(var l=0;l0&&function t(e,n){var r=Lt[n].nodes;if(!((Ht+=1)>2e3)){if(Vt[Ht]=n,Lt[n].id===e)return{result:!0,count:0};for(var i=0,a=1;i=0){var s=t(e,o);if(s.result)return{result:!0,count:a+s.count};a+=s.count}i+=1}return{result:!1,count:a}}}("none",Lt.length-1)},getSubGraphs:function(){return Lt},destructLink:function(t,e){var n,r=function(t){var e=t.trim(),n=e.slice(0,-1),r="arrow_open";switch(e.slice(-1)){case"x":r="arrow_cross","x"===e[0]&&(r="double_"+r,n=n.slice(1));break;case">":r="arrow_point","<"===e[0]&&(r="double_"+r,n=n.slice(1));break;case"o":r="arrow_circle","o"===e[0]&&(r="double_"+r,n=n.slice(1))}var i="normal",a=n.length-1;"="===n[0]&&(i="thick");var o=function(t,e){for(var n=e.length,r=0,i=0;in.height/2-a)){var o=a*a*(1-r*r/(i*i));0!=o&&(o=Math.sqrt(o)),o=a-o,t.y-n.y>0&&(o=-o),e.y+=o}return e},c}function de(t,e,n,r){return t.insert("polygon",":first-child").attr("points",r.map((function(t){return t.x+","+t.y})).join(" ")).attr("transform","translate("+-e/2+","+n/2+")")}var pe={addToRender:function(t){t.shapes().question=ne,t.shapes().hexagon=re,t.shapes().stadium=le,t.shapes().subroutine=he,t.shapes().cylinder=fe,t.shapes().rect_left_inv_arrow=ie,t.shapes().lean_right=ae,t.shapes().lean_left=oe,t.shapes().trapezoid=se,t.shapes().inv_trapezoid=ce,t.shapes().rect_right_inv_arrow=ue},addToRenderV2:function(t){t({question:ne}),t({hexagon:re}),t({stadium:le}),t({subroutine:he}),t({cylinder:fe}),t({rect_left_inv_arrow:ie}),t({lean_right:ae}),t({lean_left:oe}),t({trapezoid:se}),t({inv_trapezoid:ce}),t({rect_right_inv_arrow:ue})}},ge={},ye=function(t,e,n){var r=Object(d.select)('[id="'.concat(n,'"]'));Object.keys(t).forEach((function(n){var i=t[n],a="default";i.classes.length>0&&(a=i.classes.join(" "));var o,s=B(i.styles),u=void 0!==i.text?i.text:i.id;if(_t().flowchart.htmlLabels){var l={label:u.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"")}))};(o=ee()(r,l).node()).parentNode.removeChild(o)}else{var h=document.createElementNS("http://www.w3.org/2000/svg","text");h.setAttribute("style",s.labelStyle.replace("color:","fill:"));for(var f=u.split(x.lineBreakRegex),d=0;d').concat(a.text.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"")})),"")):(u.labelType="text",u.label=a.text.replace(x.lineBreakRegex,"\n"),void 0===a.style&&(u.style=u.style||"stroke: #333; stroke-width: 1.5px;fill:none"),u.labelStyle=u.labelStyle.replace("color:","fill:"))),u.id=o,u.class=s+" "+c,u.minlen=a.length||1,e.setEdge(Xt.lookUpDomId(a.start),Xt.lookUpDomId(a.end),u,i)}))},me=function(t){for(var e=Object.keys(t),n=0;n=0;h--)i=l[h],Xt.addVertex(i.id,i.title,"group",void 0,i.classes);var f=Xt.getVertices();c.warn("Get vertices",f);var p=Xt.getEdges(),g=0;for(g=l.length-1;g>=0;g--){i=l[g],Object(d.selectAll)("cluster").append("text");for(var y=0;y"),c.info("vertexText"+i),function(t){var e,n,r=Object(d.select)(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")),i=r.append("xhtml:div"),a=t.label,o=t.isNode?"nodeLabel":"edgeLabel";return i.html(''+a+""),e=i,(n=t.labelStyle)&&e.attr("style",n),i.style("display","inline-block"),i.style("white-space","nowrap"),i.attr("xmlns","http://www.w3.org/1999/xhtml"),r.node()}({isNode:r,label:i.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"")})),labelStyle:e.replace("fill:","color:")});var a=document.createElementNS("http://www.w3.org/2000/svg","text");a.setAttribute("style",e.replace("color:","fill:"));var o=[];o="string"==typeof i?i.split(/\\n|\n|/gi):Array.isArray(i)?i:[];for(var s=0;s0)t(a,n,r,i);else{var o=n.node(a);c.info("cp ",a," to ",i," with parent ",e),r.setNode(a,o),i!==n.parent(a)&&(c.warn("Setting parent",a,n.parent(a)),r.setParent(a,n.parent(a))),e!==i&&a!==e?(c.debug("Setting parent",a,e),r.setParent(a,e)):(c.info("In copy ",e,"root",i,"data",n.node(e),i),c.debug("Not Setting parent for node=",a,"cluster!==rootId",e!==i,"node!==clusterId",a!==e));var s=n.edges(a);c.debug("Copying Edges",s),s.forEach((function(t){c.info("Edge",t);var a=n.edge(t.v,t.w,t.name);c.info("Edge data",a,i);try{!function(t,e){return c.info("Decendants of ",e," is ",Oe[e]),c.info("Edge is ",t),t.v!==e&&(t.w!==e&&(Oe[e]?(c.info("Here "),Oe[e].indexOf(t.v)>=0||(!!Ne(t.v,e)||(!!Ne(t.w,e)||Oe[e].indexOf(t.w)>=0))):(c.debug("Tilt, ",e,",not in decendants"),!1)))}(t,i)?c.info("Skipping copy of edge ",t.v,"--\x3e",t.w," rootId: ",i," clusterId:",e):(c.info("Copying as ",t.v,t.w,a,t.name),r.setEdge(t.v,t.w,a,t.name),c.info("newGraph edges ",r.edges(),r.edge(r.edges()[0])))}catch(t){c.error(t)}}))}c.debug("Removing node",a),n.removeNode(a)}))},Le=function t(e,n){c.trace("Searching",e);var r=n.children(e);if(c.trace("Searching children of id ",e,r),r.length<1)return c.trace("This is a valid node",e),e;for(var i=0;i ",a),a}},Fe=function(t){return Me[t]&&Me[t].externalConnections&&Me[t]?Me[t].id:t},Pe=function(t,e){!t||e>10?c.debug("Opting out, no graph "):(c.debug("Opting in, graph "),t.nodes().forEach((function(e){t.children(e).length>0&&(c.warn("Cluster identified",e," Replacement id in edges: ",Le(e,t)),Oe[e]=function t(e,n){for(var r=n.children(e),i=[].concat(r),a=0;a0?(c.debug("Cluster identified",e,Oe),r.forEach((function(t){t.v!==e&&t.w!==e&&(Ne(t.v,e)^Ne(t.w,e)&&(c.warn("Edge: ",t," leaves cluster ",e),c.warn("Decendants of XXX ",e,": ",Oe[e]),Me[e].externalConnections=!0))}))):c.debug("Not a cluster ",e,Oe)})),t.edges().forEach((function(e){var n=t.edge(e);c.warn("Edge "+e.v+" -> "+e.w+": "+JSON.stringify(e)),c.warn("Edge "+e.v+" -> "+e.w+": "+JSON.stringify(t.edge(e)));var r=e.v,i=e.w;c.warn("Fix XXX",Me,"ids:",e.v,e.w,"Translateing: ",Me[e.v]," --- ",Me[e.w]),(Me[e.v]||Me[e.w])&&(c.warn("Fixing and trixing - removing XXX",e.v,e.w,e.name),r=Fe(e.v),i=Fe(e.w),t.removeEdge(e.v,e.w,e.name),r!==e.v&&(n.fromCluster=e.v),i!==e.w&&(n.toCluster=e.w),c.warn("Fix Replacing with XXX",r,i,e.name),t.setEdge(r,i,n,e.name))})),c.warn("Adjusted Graph",G.a.json.write(t)),Ie(t,0),c.trace(Me))},Ie=function t(e,n){if(c.warn("extractor - ",n,G.a.json.write(e),e.children("D")),n>10)c.error("Bailing out");else{for(var r=e.nodes(),i=!1,a=0;a0}if(i){c.debug("Nodes = ",r,n);for(var u=0;u0){c.warn("Cluster without external connections, without a parent and with children",l,n);var h=e.graph(),f=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:"TB"===h.rankdir?"LR":"TB",nodesep:50,ranksep:50,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}}));c.warn("Old graph before copy",G.a.json.write(e)),Be(l,e,f,l),e.setNode(l,{clusterNode:!0,id:l,clusterData:Me[l].clusterData,labelText:Me[l].labelText,graph:f}),c.warn("New graph after copy node: (",l,")",G.a.json.write(f)),c.debug("Old graph after copy",G.a.json.write(e))}else c.warn("Cluster ** ",l," **not meeting the criteria !externalConnections:",!Me[l].externalConnections," no parent: ",!e.parent(l)," children ",e.children(l)&&e.children(l).length>0,e.children("D"),n),c.debug(Me);else c.debug("Not a cluster",l,n)}r=e.nodes(),c.warn("New list of nodes",r);for(var d=0;d0}var $e=function(t,e,n,r){var i,a,o,s,c,u,l,h,f,d,p,g,y;if(i=e.y-t.y,o=t.x-e.x,c=e.x*t.y-t.x*e.y,f=i*n.x+o*n.y+c,d=i*r.x+o*r.y+c,!(0!==f&&0!==d&&Ue(f,d)||(a=r.y-n.y,s=n.x-r.x,u=r.x*n.y-n.x*r.y,l=a*t.x+s*t.y+u,h=a*e.x+s*e.y+u,0!==l&&0!==h&&Ue(l,h)||0==(p=i*s-a*o))))return g=Math.abs(p/2),{x:(y=o*u-s*c)<0?(y-g)/p:(y+g)/p,y:(y=a*c-i*u)<0?(y-g)/p:(y+g)/p}},We=function(t,e,n){var r=t.x,i=t.y,a=[],o=Number.POSITIVE_INFINITY,s=Number.POSITIVE_INFINITY;"function"==typeof e.forEach?e.forEach((function(t){o=Math.min(o,t.x),s=Math.min(s,t.y)})):(o=Math.min(o,e.x),s=Math.min(s,e.y));for(var c=r-t.width/2-o,u=i-t.height/2-s,l=0;l1&&a.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,c=Math.sqrt(o*o+s*s);return aMath.abs(o)*u?(s<0&&(u=-u),n=0===s?0:u*o/s,r=u):(o<0&&(c=-c),n=c,r=0===o?0:c*s/o),{x:i+n,y:a+r}},Ve={node:n.n(Re).a,circle:ze,ellipse:Ye,polygon:We,rect:He},Ge=function(t,e){var n=Ce(t,e,"node "+e.classes,!0),r=n.shapeSvg,i=n.bbox,a=n.halfPadding;c.info("Classes = ",e.classes);var o=r.insert("rect",":first-child");return o.attr("rx",e.rx).attr("ry",e.ry).attr("x",-i.width/2-a).attr("y",-i.height/2-a).attr("width",i.width+e.padding).attr("height",i.height+e.padding),Ae(e,o),e.intersect=function(t){return Ve.rect(e,t)},r};function qe(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e0){var r=t.split("~");n=r[0],e=r[1]}return{className:n,type:e}},tn=function(t){var e=Qe(t);void 0===Ze[e.className]&&(Ze[e.className]={id:e.className,type:e.type,cssClasses:[],methods:[],members:[],annotations:[],domId:"classid-"+e.className+"-"+Je},Je++)},en=function(t){for(var e=Object.keys(Ze),n=0;n>")?r.annotations.push(i.substring(2,i.length-2)):i.indexOf(")")>0?r.methods.push(i):i&&r.members.push(i)}},rn=function(t,e){t.split(",").forEach((function(t){var n=t;t[0].match(/\d/)&&(n="classid-"+n),void 0!==Ze[n]&&Ze[n].cssClasses.push(e)}))},an=function(t,e,n){var r=_t(),i=t,a=en(i);if("loose"===r.securityLevel&&void 0!==e&&void 0!==Ze[i]){var o=[];if("string"==typeof n){o=n.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);for(var s=0;s1&&a>i&&a<=t.length){var o="",s="",c=t.substring(0,1);c.match(/\w/)?s=t.substring(0,i).trim():(c.match(/\+|-|~|#/)&&(o=c),s=t.substring(1,i).trim());var u=t.substring(i+1,a),l=t.substring(a+1,1);n=yn(l),e=o+s+"("+gn(u.trim())+")",a<"".length&&""!==(r=t.substring(a+2).trim())&&(r=" : "+gn(r))}else e=gn(t);return{displayText:e,cssStyle:n}},pn=function(t,e,n,r){var i=ln(e),a=t.append("tspan").attr("x",r.padding).text(i.displayText);""!==i.cssStyle&&a.attr("style",i.cssStyle),n||a.attr("dy",r.textHeight)},gn=function t(e){var n=e;return-1!=e.indexOf("~")?t(n=(n=n.replace("~","<")).replace("~",">")):n},yn=function(t){switch(t){case"*":return"font-style:italic;";case"$":return"text-decoration:underline;";default:return""}},vn=function(t,e,n){c.info("Rendering class "+e);var r,i=e.id,a={id:i,label:e.id,width:0,height:0},o=t.append("g").attr("id",en(i)).attr("class","classGroup");r=e.link?o.append("svg:a").attr("xlink:href",e.link).attr("target",e.linkTarget).append("text").attr("y",n.textHeight+n.padding).attr("x",0):o.append("text").attr("y",n.textHeight+n.padding).attr("x",0);var s=!0;e.annotations.forEach((function(t){var e=r.append("tspan").text("«"+t+"»");s||e.attr("dy",n.textHeight),s=!1}));var u=e.id;void 0!==e.type&&""!==e.type&&(u+="<"+e.type+">");var l=r.append("tspan").text(u).attr("class","title");s||l.attr("dy",n.textHeight);var h=r.node().getBBox().height,f=o.append("line").attr("x1",0).attr("y1",n.padding+h+n.dividerMargin/2).attr("y2",n.padding+h+n.dividerMargin/2),d=o.append("text").attr("x",n.padding).attr("y",h+n.dividerMargin+n.textHeight).attr("fill","white").attr("class","classText");s=!0,e.members.forEach((function(t){pn(d,t,s,n),s=!1}));var p=d.node().getBBox(),g=o.append("line").attr("x1",0).attr("y1",n.padding+h+n.dividerMargin+p.height).attr("y2",n.padding+h+n.dividerMargin+p.height),y=o.append("text").attr("x",n.padding).attr("y",h+2*n.dividerMargin+p.height+n.textHeight).attr("fill","white").attr("class","classText");s=!0,e.methods.forEach((function(t){pn(y,t,s,n),s=!1}));var v=o.node().getBBox(),m=" ";e.cssClasses.length>0&&(m+=e.cssClasses.join(" "));var b=o.insert("rect",":first-child").attr("x",0).attr("y",0).attr("width",v.width+2*n.padding).attr("height",v.height+n.padding+.5*n.dividerMargin).attr("class",m).node().getBBox().width;return r.node().childNodes.forEach((function(t){t.setAttribute("x",(b-t.getBBox().width)/2)})),e.tooltip&&r.insert("title").text(e.tooltip),f.attr("x2",b),g.attr("x2",b),a.width=b,a.height=v.height+n.padding+.5*n.dividerMargin,a},mn=function(t,e,n,r){var i=function(t){switch(t){case on.AGGREGATION:return"aggregation";case on.EXTENSION:return"extension";case on.COMPOSITION:return"composition";case on.DEPENDENCY:return"dependency"}};e.points=e.points.filter((function(t){return!Number.isNaN(t.y)}));var a,o,s=e.points,u=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis),l=t.append("path").attr("d",u(s)).attr("id","edge"+un).attr("class","relation"),h="";r.arrowMarkerAbsolute&&(h=(h=(h=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),1==n.relation.lineType&&l.attr("class","relation dashed-line"),"none"!==n.relation.type1&&l.attr("marker-start","url("+h+"#"+i(n.relation.type1)+"Start)"),"none"!==n.relation.type2&&l.attr("marker-end","url("+h+"#"+i(n.relation.type2)+"End)");var f,p,g,y,v=e.points.length,m=H.calcLabelPosition(e.points);if(a=m.x,o=m.y,v%2!=0&&v>1){var b=H.calcCardinalityPosition("none"!==n.relation.type1,e.points,e.points[0]),x=H.calcCardinalityPosition("none"!==n.relation.type2,e.points,e.points[v-1]);c.debug("cardinality_1_point "+JSON.stringify(b)),c.debug("cardinality_2_point "+JSON.stringify(x)),f=b.x,p=b.y,g=x.x,y=x.y}if(void 0!==n.title){var _=t.append("g").attr("class","classLabel"),k=_.append("text").attr("class","label").attr("x",a).attr("y",o).attr("fill","red").attr("text-anchor","middle").text(n.title);window.label=k;var w=k.node().getBBox();_.insert("rect",":first-child").attr("class","box").attr("x",w.x-r.padding/2).attr("y",w.y-r.padding/2).attr("width",w.width+r.padding).attr("height",w.height+r.padding)}(c.info("Rendering relation "+JSON.stringify(n)),void 0!==n.relationTitle1&&"none"!==n.relationTitle1)&&t.append("g").attr("class","cardinality").append("text").attr("class","type1").attr("x",f).attr("y",p).attr("fill","black").attr("font-size","6").text(n.relationTitle1);void 0!==n.relationTitle2&&"none"!==n.relationTitle2&&t.append("g").attr("class","cardinality").append("text").attr("class","type2").attr("x",g).attr("y",y).attr("fill","black").attr("font-size","6").text(n.relationTitle2);un++},bn=function(t,e,n){var r=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),i=70,a=10;"LR"===n&&(i=10,a=70);var o=r.append("rect").style("stroke","black").style("fill","black").attr("x",-1*i/2).attr("y",-1*a/2).attr("width",i).attr("height",a).attr("class","fork-join");return Ae(e,o),e.height=e.height+e.padding/2,e.width=e.width+e.padding/2,e.intersect=function(t){return Ve.rect(e,t)},r},xn={question:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding+(i.height+e.padding),o=[{x:a/2,y:0},{x:a,y:-a/2},{x:a/2,y:-a},{x:0,y:-a/2}];c.info("Question main (Circle)");var s=Se(r,a,a,o);return Ae(e,s),e.intersect=function(t){return c.warn("Intersect called"),Ve.polygon(e,o,t)},r},rect:function(t,e){var n=Ce(t,e,"node "+e.classes,!0),r=n.shapeSvg,i=n.bbox,a=n.halfPadding;c.trace("Classes = ",e.classes);var o=r.insert("rect",":first-child");return o.attr("class","basic label-container").attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",-i.width/2-a).attr("y",-i.height/2-a).attr("width",i.width+e.padding).attr("height",i.height+e.padding),Ae(e,o),e.intersect=function(t){return Ve.rect(e,t)},r},rectWithTitle:function(t,e){var n;n=e.classes?"node "+e.classes:"node default";var r=t.insert("g").attr("class",n).attr("id",e.domId||e.id),i=r.insert("rect",":first-child"),a=r.insert("line"),o=r.insert("g").attr("class","label"),s=e.labelText.flat();c.info("Label text",s[0]);var u,l=o.node().appendChild(Te(s[0],e.labelStyle,!0,!0));if(_t().flowchart.htmlLabels){var h=l.children[0],f=Object(d.select)(l);u=h.getBoundingClientRect(),f.attr("width",u.width),f.attr("height",u.height)}c.info("Text 2",s);var p=s.slice(1,s.length),g=l.getBBox(),y=o.node().appendChild(Te(p.join("
"),e.labelStyle,!0,!0));if(_t().flowchart.htmlLabels){var v=y.children[0],m=Object(d.select)(y);u=v.getBoundingClientRect(),m.attr("width",u.width),m.attr("height",u.height)}var b=e.padding/2;return Object(d.select)(y).attr("transform","translate( "+(u.width>g.width?0:(g.width-u.width)/2)+", "+(g.height+b+5)+")"),Object(d.select)(l).attr("transform","translate( "+(u.widthe.height/2-s)){var i=s*s*(1-r*r/(o*o));0!=i&&(i=Math.sqrt(i)),i=s-i,t.y-e.y>0&&(i=-i),n.y+=i}return n},r},start:function(t,e){var n=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),r=n.insert("circle",":first-child");return r.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),Ae(e,r),e.intersect=function(t){return Ve.circle(e,7,t)},n},end:function(t,e){var n=t.insert("g").attr("class","node default").attr("id",e.domId||e.id),r=n.insert("circle",":first-child"),i=n.insert("circle",":first-child");return i.attr("class","state-start").attr("r",7).attr("width",14).attr("height",14),r.attr("class","state-end").attr("r",5).attr("width",10).attr("height",10),Ae(e,i),e.intersect=function(t){return Ve.circle(e,7,t)},n},note:Ge,subroutine:function(t,e){var n=Ce(t,e,void 0,!0),r=n.shapeSvg,i=n.bbox,a=i.width+e.padding,o=i.height+e.padding,s=Se(r,a,o,[{x:0,y:0},{x:a,y:0},{x:a,y:-o},{x:0,y:-o},{x:0,y:0},{x:-8,y:0},{x:a+8,y:0},{x:a+8,y:-o},{x:-8,y:-o},{x:-8,y:0}]);return Ae(e,s),e.intersect=function(t){return Ve.polygon(e,t)},r},fork:bn,join:bn,class_box:function(t,e){var n,r=e.padding/2;n=e.classes?"node "+e.classes:"node default";var i=t.insert("g").attr("class",n).attr("id",e.domId||e.id),a=i.insert("rect",":first-child"),o=i.insert("line"),s=i.insert("line"),c=0,u=4,l=i.insert("g").attr("class","label"),h=0,f=e.classData.annotations&&e.classData.annotations[0],p=e.classData.annotations[0]?"«"+e.classData.annotations[0]+"»":"",g=l.node().appendChild(Te(p,e.labelStyle,!0,!0)),y=g.getBBox();if(_t().flowchart.htmlLabels){var v=g.children[0],m=Object(d.select)(g);y=v.getBoundingClientRect(),m.attr("width",y.width),m.attr("height",y.height)}e.classData.annotations[0]&&(u+=y.height+4,c+=y.width);var b=e.classData.id;void 0!==e.classData.type&&""!==e.classData.type&&(b+="<"+e.classData.type+">");var x=l.node().appendChild(Te(b,e.labelStyle,!0,!0));Object(d.select)(x).attr("class","classTitle");var _=x.getBBox();if(_t().flowchart.htmlLabels){var k=x.children[0],w=Object(d.select)(x);_=k.getBoundingClientRect(),w.attr("width",_.width),w.attr("height",_.height)}u+=_.height+4,_.width>c&&(c=_.width);var E=[];e.classData.members.forEach((function(t){var n=ln(t).displayText,r=l.node().appendChild(Te(n,e.labelStyle,!0,!0)),i=r.getBBox();if(_t().flowchart.htmlLabels){var a=r.children[0],o=Object(d.select)(r);i=a.getBoundingClientRect(),o.attr("width",i.width),o.attr("height",i.height)}i.width>c&&(c=i.width),u+=i.height+4,E.push(r)})),u+=8;var T=[];if(e.classData.methods.forEach((function(t){var n=ln(t).displayText,r=l.node().appendChild(Te(n,e.labelStyle,!0,!0)),i=r.getBBox();if(_t().flowchart.htmlLabels){var a=r.children[0],o=Object(d.select)(r);i=a.getBoundingClientRect(),o.attr("width",i.width),o.attr("height",i.height)}i.width>c&&(c=i.width),u+=i.height+4,T.push(r)})),u+=8,f){var C=(c-y.width)/2;Object(d.select)(g).attr("transform","translate( "+(-1*c/2+C)+", "+-1*u/2+")"),h=y.height+4}var A=(c-_.width)/2;return Object(d.select)(x).attr("transform","translate( "+(-1*c/2+A)+", "+(-1*u/2+h)+")"),h+=_.height+4,o.attr("class","divider").attr("x1",-c/2-r).attr("x2",c/2+r).attr("y1",-u/2-r+8+h).attr("y2",-u/2-r+8+h),h+=8,E.forEach((function(t){Object(d.select)(t).attr("transform","translate( "+-c/2+", "+(-1*u/2+h+4)+")"),h+=_.height+4})),h+=8,s.attr("class","divider").attr("x1",-c/2-r).attr("x2",c/2+r).attr("y1",-u/2-r+8+h).attr("y2",-u/2-r+8+h),h+=8,T.forEach((function(t){Object(d.select)(t).attr("transform","translate( "+-c/2+", "+(-1*u/2+h)+")"),h+=_.height+4})),a.attr("class","outer title-state").attr("x",-c/2-r).attr("y",-u/2-r).attr("width",c+e.padding).attr("height",u+e.padding),Ae(e,a),e.intersect=function(t){return Ve.rect(e,t)},i}},_n={},kn=function(t){var e=_n[t.id];c.trace("Transforming node",t,"translate("+(t.x-t.width/2-5)+", "+(t.y-t.height/2-5)+")");t.clusterNode?e.attr("transform","translate("+(t.x-t.width/2-8)+", "+(t.y-t.height/2-8)+")"):e.attr("transform","translate("+t.x+", "+t.y+")")},wn={rect:function(t,e){c.trace("Creating subgraph rect for ",e.id,e);var n=t.insert("g").attr("class","cluster"+(e.class?" "+e.class:"")).attr("id",e.id),r=n.insert("rect",":first-child"),i=n.insert("g").attr("class","cluster-label"),a=i.node().appendChild(Te(e.labelText,e.labelStyle,void 0,!0)),o=a.getBBox();if(_t().flowchart.htmlLabels){var s=a.children[0],u=Object(d.select)(a);o=s.getBoundingClientRect(),u.attr("width",o.width),u.attr("height",o.height)}var l=0*e.padding,h=l/2;c.trace("Data ",e,JSON.stringify(e)),r.attr("style",e.style).attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h).attr("width",e.width+l).attr("height",e.height+l),i.attr("transform","translate("+(e.x-o.width/2)+", "+(e.y-e.height/2+e.padding/3)+")");var f=r.node().getBBox();return e.width=f.width,e.height=f.height,e.intersect=function(t){return He(e,t)},n},roundedWithTitle:function(t,e){var n=t.insert("g").attr("class",e.classes).attr("id",e.id),r=n.insert("rect",":first-child"),i=n.insert("g").attr("class","cluster-label"),a=n.append("rect"),o=i.node().appendChild(Te(e.labelText,e.labelStyle,void 0,!0)),s=o.getBBox();if(_t().flowchart.htmlLabels){var c=o.children[0],u=Object(d.select)(o);s=c.getBoundingClientRect(),u.attr("width",s.width),u.attr("height",s.height)}s=o.getBBox();var l=0*e.padding,h=l/2;r.attr("class","outer").attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h).attr("width",e.width+l).attr("height",e.height+l),a.attr("class","inner").attr("x",e.x-e.width/2-h).attr("y",e.y-e.height/2-h+s.height-1).attr("width",e.width+l).attr("height",e.height+l-s.height-3),i.attr("transform","translate("+(e.x-s.width/2)+", "+(e.y-e.height/2-e.padding/3+(_t().flowchart.htmlLabels?5:3))+")");var f=r.node().getBBox();return e.width=f.width,e.height=f.height,e.intersect=function(t){return He(e,t)},n},noteGroup:function(t,e){var n=t.insert("g").attr("class","note-cluster").attr("id",e.id),r=n.insert("rect",":first-child"),i=0*e.padding,a=i/2;r.attr("rx",e.rx).attr("ry",e.ry).attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2-a).attr("width",e.width+i).attr("height",e.height+i).attr("fill","none");var o=r.node().getBBox();return e.width=o.width,e.height=o.height,e.intersect=function(t){return He(e,t)},n},divider:function(t,e){var n=t.insert("g").attr("class",e.classes).attr("id",e.id),r=n.insert("rect",":first-child"),i=0*e.padding,a=i/2;r.attr("class","divider").attr("x",e.x-e.width/2-a).attr("y",e.y-e.height/2).attr("width",e.width+i).attr("height",e.height+i);var o=r.node().getBBox();return e.width=o.width,e.height=o.height,e.intersect=function(t){return He(e,t)},n}},En={},Tn={},Cn={},An=function(t,e){var n=t.x,r=t.y,i=Math.abs(e.x-n),a=Math.abs(e.y-r),o=t.width/2,s=t.height/2;return i>=o||a>=s},Sn=function(t,e,n){c.warn("intersection calc o:",e," i:",n,t);var r=t.x,i=t.y,a=Math.abs(r-n.x),o=t.width/2,s=n.xMath.abs(r-e.x)*u){var y=n.y0&&c.info("Recursive edges",n.edge(n.edges()[0]));var s=o.insert("g").attr("class","clusters"),u=o.insert("g").attr("class","edgePaths"),l=o.insert("g").attr("class","edgeLabels"),h=o.insert("g").attr("class","nodes");return n.nodes().forEach((function(e){var o=n.node(e);if(void 0!==i){var s=JSON.parse(JSON.stringify(i.clusterData));c.info("Setting data for cluster XXX (",e,") ",s,i),n.setNode(i.id,s),n.parent(e)||(c.warn("Setting parent",e,i.id),n.setParent(e,i.id,s))}if(c.info("(Insert) Node XXX"+e+": "+JSON.stringify(n.node(e))),o&&o.clusterNode){c.info("Cluster identified",e,o,n.node(e));var u=t(h,o.graph,r,n.node(e));Ae(o,u),function(t,e){_n[e.id]=t}(u,o),c.warn("Recursive render complete",u,o)}else n.children(e).length>0?(c.info("Cluster - the non recursive path XXX",e,o.id,o,n),c.info(Le(o.id,n)),Me[o.id]={id:Le(o.id,n),node:o}):(c.info("Node - the non recursive path",e,o.id,o),function(t,e,n){var r,i;e.link?(r=t.insert("svg:a").attr("xlink:href",e.link).attr("target",e.linkTarget||"_blank"),i=xn[e.shape](r,e,n)):r=i=xn[e.shape](t,e,n),e.tooltip&&i.attr("title",e.tooltip),e.class&&i.attr("class","node default "+e.class),_n[e.id]=r,e.haveCallback&&_n[e.id].attr("class",_n[e.id].attr("class")+" clickable")}(h,n.node(e),a))})),n.edges().forEach((function(t){var e=n.edge(t.v,t.w,t.name);c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(t)),c.info("Edge "+t.v+" -> "+t.w+": ",t," ",JSON.stringify(n.edge(t))),c.info("Fix",Me,"ids:",t.v,t.w,"Translateing: ",Me[t.v],Me[t.w]),function(t,e){var n=Te(e.label,e.labelStyle),r=t.insert("g").attr("class","edgeLabel"),i=r.insert("g").attr("class","label");i.node().appendChild(n);var a=n.getBBox();if(_t().flowchart.htmlLabels){var o=n.children[0],s=Object(d.select)(n);a=o.getBoundingClientRect(),s.attr("width",a.width),s.attr("height",a.height)}if(i.attr("transform","translate("+-a.width/2+", "+-a.height/2+")"),Tn[e.id]=r,e.width=a.width,e.height=a.height,e.startLabelLeft){var c=Te(e.startLabelLeft,e.labelStyle),u=t.insert("g").attr("class","edgeTerminals"),l=u.insert("g").attr("class","inner");l.node().appendChild(c);var h=c.getBBox();l.attr("transform","translate("+-h.width/2+", "+-h.height/2+")"),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].startLeft=u}if(e.startLabelRight){var f=Te(e.startLabelRight,e.labelStyle),p=t.insert("g").attr("class","edgeTerminals"),g=p.insert("g").attr("class","inner");p.node().appendChild(f),g.node().appendChild(f);var y=f.getBBox();g.attr("transform","translate("+-y.width/2+", "+-y.height/2+")"),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].startRight=p}if(e.endLabelLeft){var v=Te(e.endLabelLeft,e.labelStyle),m=t.insert("g").attr("class","edgeTerminals"),b=m.insert("g").attr("class","inner");b.node().appendChild(v);var x=v.getBBox();b.attr("transform","translate("+-x.width/2+", "+-x.height/2+")"),m.node().appendChild(v),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].endLeft=m}if(e.endLabelRight){var _=Te(e.endLabelRight,e.labelStyle),k=t.insert("g").attr("class","edgeTerminals"),w=k.insert("g").attr("class","inner");w.node().appendChild(_);var E=_.getBBox();w.attr("transform","translate("+-E.width/2+", "+-E.height/2+")"),k.node().appendChild(_),Cn[e.id]||(Cn[e.id]={}),Cn[e.id].endRight=k}}(l,e)})),n.edges().forEach((function(t){c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(t))})),c.info("#############################################"),c.info("### Layout ###"),c.info("#############################################"),c.info(n),ke.a.layout(n),c.info("Graph after layout:",G.a.json.write(n)),je(n).forEach((function(t){var e=n.node(t);c.info("Position "+t+": "+JSON.stringify(n.node(t))),c.info("Position "+t+": ("+e.x,","+e.y,") width: ",e.width," height: ",e.height),e&&e.clusterNode?kn(e):n.children(t).length>0?(!function(t,e){c.trace("Inserting cluster");var n=e.shape||"rect";En[e.id]=wn[n](t,e)}(s,e),Me[e.id].node=e):kn(e)})),n.edges().forEach((function(t){var e=n.edge(t);c.info("Edge "+t.v+" -> "+t.w+": "+JSON.stringify(e),e);var i=function(t,e,n,r,i,a){var o=n.points,s=!1,u=a.node(e.v),l=a.node(e.w);if(l.intersect&&u.intersect&&((o=o.slice(1,n.points.length-1)).unshift(u.intersect(o[0])),c.info("Last point",o[o.length-1],l,l.intersect(o[o.length-1])),o.push(l.intersect(o[o.length-1]))),n.toCluster){var h;c.trace("edge",n),c.trace("to cluster",r[n.toCluster]),o=[];var f=!1;n.points.forEach((function(t){var e=r[n.toCluster].node;if(An(e,t)||f)f||o.push(t);else{c.trace("inside",n.toCluster,t,h);var i=Sn(e,h,t),a=!1;o.forEach((function(t){a=a||t.x===i.x&&t.y===i.y})),o.find((function(t){return t.x===i.x&&t.y===i.y}))?c.warn("no intersect",i,o):o.push(i),f=!0}h=t})),s=!0}if(n.fromCluster){c.trace("edge",n),c.warn("from cluster",r[n.fromCluster]);for(var p,g=[],y=!1,v=o.length-1;v>=0;v--){var m=o[v],b=r[n.fromCluster].node;if(An(b,m)||y)c.trace("Outside point",m),y||g.unshift(m);else{c.warn("inside",n.fromCluster,m,b);var x=Sn(b,p,m);g.unshift(x),y=!0}p=m}o=g,s=!0}var _,k=o.filter((function(t){return!Number.isNaN(t.y)})),w=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis);switch(n.thickness){case"normal":_="edge-thickness-normal";break;case"thick":_="edge-thickness-thick";break;default:_=""}switch(n.pattern){case"solid":_+=" edge-pattern-solid";break;case"dotted":_+=" edge-pattern-dotted";break;case"dashed":_+=" edge-pattern-dashed"}var E=t.append("path").attr("d",w(k)).attr("id",n.id).attr("class"," "+_+(n.classes?" "+n.classes:"")).attr("style",n.style),T="";switch(_t().state.arrowMarkerAbsolute&&(T=(T=(T=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),c.info("arrowTypeStart",n.arrowTypeStart),c.info("arrowTypeEnd",n.arrowTypeEnd),n.arrowTypeStart){case"arrow_cross":E.attr("marker-start","url("+T+"#"+i+"-crossStart)");break;case"arrow_point":E.attr("marker-start","url("+T+"#"+i+"-pointStart)");break;case"arrow_barb":E.attr("marker-start","url("+T+"#"+i+"-barbStart)");break;case"arrow_circle":E.attr("marker-start","url("+T+"#"+i+"-circleStart)");break;case"aggregation":E.attr("marker-start","url("+T+"#"+i+"-aggregationStart)");break;case"extension":E.attr("marker-start","url("+T+"#"+i+"-extensionStart)");break;case"composition":E.attr("marker-start","url("+T+"#"+i+"-compositionStart)");break;case"dependency":E.attr("marker-start","url("+T+"#"+i+"-dependencyStart)")}switch(n.arrowTypeEnd){case"arrow_cross":E.attr("marker-end","url("+T+"#"+i+"-crossEnd)");break;case"arrow_point":E.attr("marker-end","url("+T+"#"+i+"-pointEnd)");break;case"arrow_barb":E.attr("marker-end","url("+T+"#"+i+"-barbEnd)");break;case"arrow_circle":E.attr("marker-end","url("+T+"#"+i+"-circleEnd)");break;case"aggregation":E.attr("marker-end","url("+T+"#"+i+"-aggregationEnd)");break;case"extension":E.attr("marker-end","url("+T+"#"+i+"-extensionEnd)");break;case"composition":E.attr("marker-end","url("+T+"#"+i+"-compositionEnd)");break;case"dependency":E.attr("marker-end","url("+T+"#"+i+"-dependencyEnd)")}var C={};return s&&(C.updatedPath=o),C.originalPath=n.points,C}(u,t,e,Me,r,n);!function(t,e){c.info("Moving label",t.id,t.label,Tn[t.id]);var n=e.updatedPath?e.updatedPath:e.originalPath;if(t.label){var r=Tn[t.id],i=t.x,a=t.y;if(n){var o=H.calcLabelPosition(n);c.info("Moving label from (",i,",",a,") to (",o.x,",",o.y,")")}r.attr("transform","translate("+i+", "+a+")")}if(t.startLabelLeft){var s=Cn[t.id].startLeft,u=t.x,l=t.y;if(n){var h=H.calcTerminalLabelPosition(0,"start_left",n);u=h.x,l=h.y}s.attr("transform","translate("+u+", "+l+")")}if(t.startLabelRight){var f=Cn[t.id].startRight,d=t.x,p=t.y;if(n){var g=H.calcTerminalLabelPosition(0,"start_right",n);d=g.x,p=g.y}f.attr("transform","translate("+d+", "+p+")")}if(t.endLabelLeft){var y=Cn[t.id].endLeft,v=t.x,m=t.y;if(n){var b=H.calcTerminalLabelPosition(0,"end_left",n);v=b.x,m=b.y}y.attr("transform","translate("+v+", "+m+")")}if(t.endLabelRight){var x=Cn[t.id].endRight,_=t.x,k=t.y;if(n){var w=H.calcTerminalLabelPosition(0,"end_right",n);_=w.x,k=w.y}x.attr("transform","translate("+_+", "+k+")")}}(e,i)})),o},On=function(t,e,n,r,i){Ee(t,n,r,i),_n={},Tn={},Cn={},En={},Oe={},De={},Me={},c.warn("Graph at first:",G.a.json.write(e)),Pe(e),c.warn("Graph after:",G.a.json.write(e)),Mn(t,e,r)},Dn={},Nn=function(t,e,n){var r=Object(d.select)('[id="'.concat(n,'"]'));Object.keys(t).forEach((function(n){var i=t[n],a="default";i.classes.length>0&&(a=i.classes.join(" "));var o,s=B(i.styles),u=void 0!==i.text?i.text:i.id;if(_t().flowchart.htmlLabels){var l={label:u.replace(/fa[lrsb]?:fa-[\w-]+/g,(function(t){return"")}))};(o=ee()(r,l).node()).parentNode.removeChild(o)}else{var h=document.createElementNS("http://www.w3.org/2000/svg","text");h.setAttribute("style",s.labelStyle.replace("color:","fill:"));for(var f=u.split(x.lineBreakRegex),d=0;d=0;h--)i=l[h],c.info("Subgraph - ",i),Xt.addVertex(i.id,i.title,"group",void 0,i.classes);var f=Xt.getVertices(),p=Xt.getEdges();c.info(p);var g=0;for(g=l.length-1;g>=0;g--){i=l[g],Object(d.selectAll)("cluster").append("text");for(var y=0;y0)switch(e.valign){case"top":case"start":s=function(){return Math.round(e.y+e.textMargin)};break;case"middle":case"center":s=function(){return Math.round(e.y+(n+r+e.textMargin)/2)};break;case"bottom":case"end":s=function(){return Math.round(e.y+(n+r+2*e.textMargin)-e.textMargin)}}if(void 0!==e.anchor&&void 0!==e.textMargin&&void 0!==e.width)switch(e.anchor){case"left":case"start":e.x=Math.round(e.x+e.textMargin),e.anchor="start",e.dominantBaseline="text-after-edge",e.alignmentBaseline="middle";break;case"middle":case"center":e.x=Math.round(e.x+e.width/2),e.anchor="middle",e.dominantBaseline="middle",e.alignmentBaseline="middle";break;case"right":case"end":e.x=Math.round(e.x+e.width-e.textMargin),e.anchor="end",e.dominantBaseline="text-before-edge",e.alignmentBaseline="middle"}for(var c=0;c0&&(r+=(l._groups||l)[0][0].getBBox().height,n=r),a.push(l)}return a},jn=function(t,e){var n,r,i,a,o,s=t.append("polygon");return s.attr("points",(n=e.x,r=e.y,i=e.width,a=e.height,n+","+r+" "+(n+i)+","+r+" "+(n+i)+","+(r+a-(o=7))+" "+(n+i-1.2*o)+","+(r+a)+" "+n+","+(r+a))),s.attr("class","labelBox"),e.y=e.y+e.height/2,In(t,e),s},Rn=-1,Yn=function(){return{x:0,y:0,fill:void 0,anchor:void 0,style:"#666",width:void 0,height:void 0,textMargin:0,rx:0,ry:0,tspan:!0,valign:void 0}},zn=function(){return{x:0,y:0,fill:"#EDF2AE",stroke:"#666",width:100,anchor:"start",height:100,rx:0,ry:0}},Un=function(){function t(t,e,n,i,a,o,s){r(e.append("text").attr("x",n+a/2).attr("y",i+o/2+5).style("text-anchor","middle").text(t),s)}function e(t,e,n,i,a,o,s,c){for(var u=c.actorFontSize,l=c.actorFontFamily,h=c.actorFontWeight,f=t.split(x.lineBreakRegex),d=0;d2&&void 0!==arguments[2]?arguments[2]:{text:void 0,wrap:void 0},r=arguments.length>3?arguments[3]:void 0;if(r===ir.ACTIVE_END){var i=er(t.actor);if(i<1){var a=new Error("Trying to inactivate an inactive participant ("+t.actor+")");throw a.hash={text:"->>-",token:"->>-",line:"1",loc:{first_line:1,last_line:1,first_column:1,last_column:1},expected:["'ACTIVE_PARTICIPANT'"]},a}}return qn.push({from:t,to:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,type:r}),!0},rr=function(){return Qn},ir={SOLID:0,DOTTED:1,NOTE:2,SOLID_CROSS:3,DOTTED_CROSS:4,SOLID_OPEN:5,DOTTED_OPEN:6,LOOP_START:10,LOOP_END:11,ALT_START:12,ALT_ELSE:13,ALT_END:14,OPT_START:15,OPT_END:16,ACTIVE_START:17,ACTIVE_END:18,PAR_START:19,PAR_AND:20,PAR_END:21,RECT_START:22,RECT_END:23},ar=function(t,e,n){var r={actor:t,placement:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap},i=[].concat(t,t);Xn.push(r),qn.push({from:i[0],to:i[1],message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,type:ir.NOTE,placement:e})},or=function(t){Zn=t.text,Jn=void 0===t.wrap&&rr()||!!t.wrap},sr={addActor:tr,addMessage:function(t,e,n,r){qn.push({from:t,to:e,message:n.text,wrap:void 0===n.wrap&&rr()||!!n.wrap,answer:r})},addSignal:nr,autoWrap:rr,setWrap:function(t){Qn=t},enableSequenceNumbers:function(){Kn=!0},showSequenceNumbers:function(){return Kn},getMessages:function(){return qn},getActors:function(){return Gn},getActor:function(t){return Gn[t]},getActorKeys:function(){return Object.keys(Gn)},getTitle:function(){return Zn},parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().sequence},getTitleWrapped:function(){return Jn},clear:function(){Gn={},qn=[]},parseMessage:function(t){var e=t.trim(),n={text:e.replace(/^[:]?(?:no)?wrap:/,"").trim(),wrap:null!==e.match(/^[:]?wrap:/)||null===e.match(/^[:]?nowrap:/)&&void 0};return c.debug("parseMessage:",n),n},LINETYPE:ir,ARROWTYPE:{FILLED:0,OPEN:1},PLACEMENT:{LEFTOF:0,RIGHTOF:1,OVER:2},addNote:ar,setTitle:or,apply:function t(e){if(e instanceof Array)e.forEach((function(e){t(e)}));else switch(e.type){case"addActor":tr(e.actor,e.actor,e.description);break;case"activeStart":case"activeEnd":nr(e.actor,void 0,void 0,e.signalType);break;case"addNote":ar(e.actor,e.placement,e.text);break;case"addMessage":nr(e.from,e.to,e.msg,e.signalType);break;case"loopStart":nr(void 0,void 0,e.loopText,e.signalType);break;case"loopEnd":nr(void 0,void 0,void 0,e.signalType);break;case"rectStart":nr(void 0,void 0,e.color,e.signalType);break;case"rectEnd":nr(void 0,void 0,void 0,e.signalType);break;case"optStart":nr(void 0,void 0,e.optText,e.signalType);break;case"optEnd":nr(void 0,void 0,void 0,e.signalType);break;case"altStart":case"else":nr(void 0,void 0,e.altText,e.signalType);break;case"altEnd":nr(void 0,void 0,void 0,e.signalType);break;case"setTitle":or(e.text);break;case"parStart":case"and":nr(void 0,void 0,e.parText,e.signalType);break;case"parEnd":nr(void 0,void 0,void 0,e.signalType)}}};Wn.parser.yy=sr;var cr={},ur={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],activations:[],models:{getHeight:function(){return Math.max.apply(null,0===this.actors.length?[0]:this.actors.map((function(t){return t.height||0})))+(0===this.loops.length?0:this.loops.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))+(0===this.messages.length?0:this.messages.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))+(0===this.notes.length?0:this.notes.map((function(t){return t.height||0})).reduce((function(t,e){return t+e})))},clear:function(){this.actors=[],this.loops=[],this.messages=[],this.notes=[]},addActor:function(t){this.actors.push(t)},addLoop:function(t){this.loops.push(t)},addMessage:function(t){this.messages.push(t)},addNote:function(t){this.notes.push(t)},lastActor:function(){return this.actors[this.actors.length-1]},lastLoop:function(){return this.loops[this.loops.length-1]},lastMessage:function(){return this.messages[this.messages.length-1]},lastNote:function(){return this.notes[this.notes.length-1]},actors:[],loops:[],messages:[],notes:[]},init:function(){this.sequenceItems=[],this.activations=[],this.models.clear(),this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0,pr(Wn.parser.yy.getConfig())},updateVal:function(t,e,n,r){void 0===t[e]?t[e]=n:t[e]=r(n,t[e])},updateBounds:function(t,e,n,r){var i=this,a=0;function o(o){return function(s){a++;var c=i.sequenceItems.length-a+1;i.updateVal(s,"starty",e-c*cr.boxMargin,Math.min),i.updateVal(s,"stopy",r+c*cr.boxMargin,Math.max),i.updateVal(ur.data,"startx",t-c*cr.boxMargin,Math.min),i.updateVal(ur.data,"stopx",n+c*cr.boxMargin,Math.max),"activation"!==o&&(i.updateVal(s,"startx",t-c*cr.boxMargin,Math.min),i.updateVal(s,"stopx",n+c*cr.boxMargin,Math.max),i.updateVal(ur.data,"starty",e-c*cr.boxMargin,Math.min),i.updateVal(ur.data,"stopy",r+c*cr.boxMargin,Math.max))}}this.sequenceItems.forEach(o()),this.activations.forEach(o("activation"))},insert:function(t,e,n,r){var i=Math.min(t,n),a=Math.max(t,n),o=Math.min(e,r),s=Math.max(e,r);this.updateVal(ur.data,"startx",i,Math.min),this.updateVal(ur.data,"starty",o,Math.min),this.updateVal(ur.data,"stopx",a,Math.max),this.updateVal(ur.data,"stopy",s,Math.max),this.updateBounds(i,o,a,s)},newActivation:function(t,e,n){var r=n[t.from.actor],i=gr(t.from.actor).length||0,a=r.x+r.width/2+(i-1)*cr.activationWidth/2;this.activations.push({startx:a,starty:this.verticalPos+2,stopx:a+cr.activationWidth,stopy:void 0,actor:t.from.actor,anchored:$n.anchorElement(e)})},endActivation:function(t){var e=this.activations.map((function(t){return t.actor})).lastIndexOf(t.from.actor);return this.activations.splice(e,1)[0]},createLoop:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{message:void 0,wrap:!1,width:void 0},e=arguments.length>1?arguments[1]:void 0;return{startx:void 0,starty:this.verticalPos,stopx:void 0,stopy:void 0,title:t.message,wrap:t.wrap,width:t.width,height:0,fill:e}},newLoop:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{message:void 0,wrap:!1,width:void 0},e=arguments.length>1?arguments[1]:void 0;this.sequenceItems.push(this.createLoop(t,e))},endLoop:function(){return this.sequenceItems.pop()},addSectionToLoop:function(t){var e=this.sequenceItems.pop();e.sections=e.sections||[],e.sectionTitles=e.sectionTitles||[],e.sections.push({y:ur.getVerticalPos(),height:0}),e.sectionTitles.push(t),this.sequenceItems.push(e)},bumpVerticalPos:function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=this.verticalPos},getVerticalPos:function(){return this.verticalPos},getBounds:function(){return{bounds:this.data,models:this.models}}},lr=function(t){return{fontFamily:t.messageFontFamily,fontSize:t.messageFontSize,fontWeight:t.messageFontWeight}},hr=function(t){return{fontFamily:t.noteFontFamily,fontSize:t.noteFontSize,fontWeight:t.noteFontWeight}},fr=function(t){return{fontFamily:t.actorFontFamily,fontSize:t.actorFontSize,fontWeight:t.actorFontWeight}},dr=function(t,e,n,r){for(var i=0,a=0,o=0;o0&&o.forEach((function(r){if(n=r,i.startx===i.stopx){var a=e[t.from],o=e[t.to];n.from=Math.min(a.x-i.width/2,a.x-a.width/2,n.from),n.to=Math.max(o.x+i.width/2,o.x+a.width/2,n.to),n.width=Math.max(n.width,Math.abs(n.to-n.from))-cr.labelBoxWidth}else n.from=Math.min(i.startx,n.from),n.to=Math.max(i.stopx,n.to),n.width=Math.max(n.width,i.width)-cr.labelBoxWidth})))})),ur.activations=[],c.debug("Loop type widths:",a),a},_r={bounds:ur,drawActors:dr,setConf:pr,draw:function(t,e){cr=_t().sequence,Wn.parser.yy.clear(),Wn.parser.yy.setWrap(cr.wrap),Wn.parser.parse(t+"\n"),ur.init(),c.debug("C:".concat(JSON.stringify(cr,null,2)));var n=Object(d.select)('[id="'.concat(e,'"]')),r=Wn.parser.yy.getActors(),i=Wn.parser.yy.getActorKeys(),a=Wn.parser.yy.getMessages(),o=Wn.parser.yy.getTitle(),s=mr(r,a);cr.height=br(r,s),dr(n,r,i,0);var u=xr(a,r,s);$n.insertArrowHead(n),$n.insertArrowCrossHead(n),$n.insertSequenceNumber(n);var l=1;a.forEach((function(t){var e,i,a;switch(t.type){case Wn.parser.yy.LINETYPE.NOTE:i=t.noteModel,function(t,e){ur.bumpVerticalPos(cr.boxMargin),e.height=cr.boxMargin,e.starty=ur.getVerticalPos();var n=$n.getNoteRect();n.x=e.startx,n.y=e.starty,n.width=e.width||cr.width,n.class="note";var r=t.append("g"),i=$n.drawRect(r,n),a=$n.getTextObj();a.x=e.startx,a.y=e.starty,a.width=n.width,a.dy="1em",a.text=e.message,a.class="noteText",a.fontFamily=cr.noteFontFamily,a.fontSize=cr.noteFontSize,a.fontWeight=cr.noteFontWeight,a.anchor=cr.noteAlign,a.textMargin=cr.noteMargin,a.valign=cr.noteAlign;var o=In(r,a),s=Math.round(o.map((function(t){return(t._groups||t)[0][0].getBBox().height})).reduce((function(t,e){return t+e})));i.attr("height",s+2*cr.noteMargin),e.height+=s+2*cr.noteMargin,ur.bumpVerticalPos(s+2*cr.noteMargin),e.stopy=e.starty+s+2*cr.noteMargin,e.stopx=e.startx+n.width,ur.insert(e.startx,e.starty,e.stopx,e.stopy),ur.models.addNote(e)}(n,i);break;case Wn.parser.yy.LINETYPE.ACTIVE_START:ur.newActivation(t,n,r);break;case Wn.parser.yy.LINETYPE.ACTIVE_END:!function(t,e){var r=ur.endActivation(t);r.starty+18>e&&(r.starty=e-6,e+=12),$n.drawActivation(n,r,e,cr,gr(t.from.actor).length),ur.insert(r.startx,e-10,r.stopx,e)}(t,ur.getVerticalPos());break;case Wn.parser.yy.LINETYPE.LOOP_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.LOOP_END:e=ur.endLoop(),$n.drawLoop(n,e,"loop",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.RECT_START:vr(u,t,cr.boxMargin,cr.boxMargin,(function(t){return ur.newLoop(void 0,t.message)}));break;case Wn.parser.yy.LINETYPE.RECT_END:e=ur.endLoop(),$n.drawBackgroundRect(n,e),ur.models.addLoop(e),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos());break;case Wn.parser.yy.LINETYPE.OPT_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.OPT_END:e=ur.endLoop(),$n.drawLoop(n,e,"opt",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.ALT_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.ALT_ELSE:vr(u,t,cr.boxMargin+cr.boxTextMargin,cr.boxMargin,(function(t){return ur.addSectionToLoop(t)}));break;case Wn.parser.yy.LINETYPE.ALT_END:e=ur.endLoop(),$n.drawLoop(n,e,"alt",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;case Wn.parser.yy.LINETYPE.PAR_START:vr(u,t,cr.boxMargin,cr.boxMargin+cr.boxTextMargin,(function(t){return ur.newLoop(t)}));break;case Wn.parser.yy.LINETYPE.PAR_AND:vr(u,t,cr.boxMargin+cr.boxTextMargin,cr.boxMargin,(function(t){return ur.addSectionToLoop(t)}));break;case Wn.parser.yy.LINETYPE.PAR_END:e=ur.endLoop(),$n.drawLoop(n,e,"par",cr),ur.bumpVerticalPos(e.stopy-ur.getVerticalPos()),ur.models.addLoop(e);break;default:try{(a=t.msgModel).starty=ur.getVerticalPos(),a.sequenceIndex=l,function(t,e){ur.bumpVerticalPos(10);var n=e.startx,r=e.stopx,i=e.starty,a=e.message,o=e.type,s=e.sequenceIndex,c=x.splitBreaks(a).length,u=H.calculateTextDimensions(a,lr(cr)),l=u.height/c;e.height+=l,ur.bumpVerticalPos(l);var h=$n.getTextObj();h.x=n,h.y=i+10,h.width=r-n,h.class="messageText",h.dy="1em",h.text=a,h.fontFamily=cr.messageFontFamily,h.fontSize=cr.messageFontSize,h.fontWeight=cr.messageFontWeight,h.anchor=cr.messageAlign,h.valign=cr.messageAlign,h.textMargin=cr.wrapPadding,h.tspan=!1,In(t,h);var f,d,p=u.height-10,g=u.width;if(n===r){d=ur.getVerticalPos()+p,cr.rightAngles?f=t.append("path").attr("d","M ".concat(n,",").concat(d," H ").concat(n+Math.max(cr.width/2,g/2)," V ").concat(d+25," H ").concat(n)):(p+=cr.boxMargin,d=ur.getVerticalPos()+p,f=t.append("path").attr("d","M "+n+","+d+" C "+(n+60)+","+(d-10)+" "+(n+60)+","+(d+30)+" "+n+","+(d+20))),p+=30;var y=Math.max(g/2,cr.width/2);ur.insert(n-y,ur.getVerticalPos()-10+p,r+y,ur.getVerticalPos()+30+p)}else p+=cr.boxMargin,d=ur.getVerticalPos()+p,(f=t.append("line")).attr("x1",n),f.attr("y1",d),f.attr("x2",r),f.attr("y2",d),ur.insert(n,d-10,r,d);o===Wn.parser.yy.LINETYPE.DOTTED||o===Wn.parser.yy.LINETYPE.DOTTED_CROSS||o===Wn.parser.yy.LINETYPE.DOTTED_OPEN?(f.style("stroke-dasharray","3, 3"),f.attr("class","messageLine1")):f.attr("class","messageLine0");var v="";cr.arrowMarkerAbsolute&&(v=(v=(v=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),f.attr("stroke-width",2),f.attr("stroke","none"),f.style("fill","none"),o!==Wn.parser.yy.LINETYPE.SOLID&&o!==Wn.parser.yy.LINETYPE.DOTTED||f.attr("marker-end","url("+v+"#arrowhead)"),o!==Wn.parser.yy.LINETYPE.SOLID_CROSS&&o!==Wn.parser.yy.LINETYPE.DOTTED_CROSS||f.attr("marker-end","url("+v+"#crosshead)"),(sr.showSequenceNumbers()||cr.showSequenceNumbers)&&(f.attr("marker-start","url("+v+"#sequencenumber)"),t.append("text").attr("x",n).attr("y",d+4).attr("font-family","sans-serif").attr("font-size","12px").attr("text-anchor","middle").attr("textLength","16px").attr("class","sequenceNumber").text(s)),ur.bumpVerticalPos(p),e.height+=p,e.stopy=e.starty+e.height,ur.insert(e.fromBounds,e.starty,e.toBounds,e.stopy)}(n,a),ur.models.addMessage(a)}catch(t){c.error("error while drawing message",t)}}[Wn.parser.yy.LINETYPE.SOLID_OPEN,Wn.parser.yy.LINETYPE.DOTTED_OPEN,Wn.parser.yy.LINETYPE.SOLID,Wn.parser.yy.LINETYPE.DOTTED,Wn.parser.yy.LINETYPE.SOLID_CROSS,Wn.parser.yy.LINETYPE.DOTTED_CROSS].includes(t.type)&&l++})),cr.mirrorActors&&(ur.bumpVerticalPos(2*cr.boxMargin),dr(n,r,i,ur.getVerticalPos()));var h=ur.getBounds().bounds;c.debug("For line height fix Querying: #"+e+" .actor-line"),Object(d.selectAll)("#"+e+" .actor-line").attr("y2",h.stopy);var f=h.stopy-h.starty+2*cr.diagramMarginY;cr.mirrorActors&&(f=f-cr.boxMargin+cr.bottomMarginAdj);var p=h.stopx-h.startx+2*cr.diagramMarginX;o&&n.append("text").text(o).attr("x",(h.stopx-h.startx)/2-2*cr.diagramMarginX).attr("y",-25),W(n,f,p,cr.useMaxWidth);var g=o?40:0;n.attr("viewBox",h.startx-cr.diagramMarginX+" -"+(cr.diagramMarginY+g)+" "+p+" "+(f+g)),c.debug("models:",ur.models)}},kr=n(27),wr=n.n(kr);function Er(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e=6&&n.indexOf("weekends")>=0||(n.indexOf(t.format("dddd").toLowerCase())>=0||n.indexOf(t.format(e.trim()))>=0)},Yr=function(t,e,n){if(n.length&&!t.manualEndTime){var r=o()(t.startTime,e,!0);r.add(1,"d");var i=o()(t.endTime,e,!0),a=zr(r,i,e,n);t.endTime=i.toDate(),t.renderEndTime=a}},zr=function(t,e,n,r){for(var i=!1,a=null;t<=e;)i||(a=e.toDate()),(i=Rr(t,n,r))&&e.add(1,"d"),t.add(1,"d");return a},Ur=function(t,e,n){n=n.trim();var r=/^after\s+([\d\w- ]+)/.exec(n.trim());if(null!==r){var i=null;if(r[1].split(" ").forEach((function(t){var e=Xr(t);void 0!==e&&(i?e.endTime>i.endTime&&(i=e):i=e)})),i)return i.endTime;var a=new Date;return a.setHours(0,0,0,0),a}var s=o()(n,e.trim(),!0);return s.isValid()?s.toDate():(c.debug("Invalid date:"+n),c.debug("With date format:"+e.trim()),new Date)},$r=function(t,e){if(null!==t)switch(t[2]){case"s":e.add(t[1],"seconds");break;case"m":e.add(t[1],"minutes");break;case"h":e.add(t[1],"hours");break;case"d":e.add(t[1],"days");break;case"w":e.add(t[1],"weeks")}return e.toDate()},Wr=function(t,e,n,r){r=r||!1,n=n.trim();var i=o()(n,e.trim(),!0);return i.isValid()?(r&&i.add(1,"d"),i.toDate()):$r(/^([\d]+)([wdhms])/.exec(n.trim()),o()(t))},Hr=0,Vr=function(t){return void 0===t?"task"+(Hr+=1):t},Gr=[],qr={},Xr=function(t){var e=qr[t];return Gr[e]},Zr=function(){for(var t=function(t){var e=Gr[t],n="";switch(Gr[t].raw.startTime.type){case"prevTaskEnd":var r=Xr(e.prevTaskId);e.startTime=r.endTime;break;case"getStartDate":(n=Ur(0,Ar,Gr[t].raw.startTime.startData))&&(Gr[t].startTime=n)}return Gr[t].startTime&&(Gr[t].endTime=Wr(Gr[t].startTime,Ar,Gr[t].raw.endTime.data,Ir),Gr[t].endTime&&(Gr[t].processed=!0,Gr[t].manualEndTime=o()(Gr[t].raw.endTime.data,"YYYY-MM-DD",!0).isValid(),Yr(Gr[t],Ar,Or))),Gr[t].processed},e=!0,n=0;nr?i=1:n0&&(e=t.classes.join(" "));for(var n=0,r=0;rn-e?n+a+1.5*ni.leftPadding>u?e+r-5:n+r+5:(n-e)/2+e+r})).attr("y",(function(t,r){return t.order*e+ni.barHeight/2+(ni.fontSize/2-2)+n})).attr("text-height",i).attr("class",(function(t){var e=o(t.startTime),n=o(t.endTime);t.milestone&&(n=e+i);var r=this.getBBox().width,a="";t.classes.length>0&&(a=t.classes.join(" "));for(var c=0,l=0;ln-e?n+r+1.5*ni.leftPadding>u?a+" taskTextOutsideLeft taskTextOutside"+c+" "+h:a+" taskTextOutsideRight taskTextOutside"+c+" "+h+" width-"+r:a+" taskText taskText"+c+" "+h+" width-"+r}))}(t,i,c,h,r,0,e),function(t,e){for(var n=[],r=0,i=0;i0&&a.setAttribute("dy","1em"),a.textContent=e[i],r.appendChild(a)}return r})).attr("x",10).attr("y",(function(i,a){if(!(a>0))return i[1]*t/2+e;for(var o=0;o "+t.w+": "+JSON.stringify(i.edge(t))),mn(r,i.edge(t),i.edge(t).relation,ci))}));var h=r.node().getBBox(),f=h.width+40,p=h.height+40;W(r,p,f,ci.useMaxWidth);var g="".concat(h.x-20," ").concat(h.y-20," ").concat(f," ").concat(p);c.debug("viewBox ".concat(g)),r.attr("viewBox",g)};ai.parser.yy=cn;var fi={dividerMargin:10,padding:5,textHeight:10},di=function(t){Object.keys(t).forEach((function(e){fi[e]=t[e]}))},pi=function(t,e){c.info("Drawing class"),cn.clear(),ai.parser.parse(t);var n=_t().flowchart;c.info("config:",n);var r=n.nodeSpacing||50,i=n.rankSpacing||50,a=new G.a.Graph({multigraph:!0,compound:!0}).setGraph({rankdir:"TD",nodesep:r,ranksep:i,marginx:8,marginy:8}).setDefaultEdgeLabel((function(){return{}})),o=cn.getClasses(),s=cn.getRelations();c.info(s),function(t,e){var n=Object.keys(t);c.info("keys:",n),c.info(t),n.forEach((function(n){var r=t[n],i="";r.cssClasses.length>0&&(i=i+" "+r.cssClasses.join(" "));var a={labelStyle:""},o=void 0!==r.text?r.text:r.id,s="";switch(r.type){case"class":s="class_box";break;default:s="class_box"}e.setNode(r.id,{labelStyle:a.labelStyle,shape:s,labelText:o,classData:r,rx:0,ry:0,class:i,style:a.style,id:r.id,domId:r.domId,haveCallback:r.haveCallback,link:r.link,width:"group"===r.type?500:void 0,type:r.type,padding:_t().flowchart.padding}),c.info("setNode",{labelStyle:a.labelStyle,shape:s,labelText:o,rx:0,ry:0,class:i,style:a.style,id:r.id,width:"group"===r.type?500:void 0,type:r.type,padding:_t().flowchart.padding})}))}(o,a),function(t,e){var n=0;t.forEach((function(r){n++;var i={classes:"relation"};i.pattern=1==r.relation.lineType?"dashed":"solid",i.id="id"+n,"arrow_open"===r.type?i.arrowhead="none":i.arrowhead="normal",c.info(i,r),i.startLabelRight="none"===r.relationTitle1?"":r.relationTitle1,i.endLabelLeft="none"===r.relationTitle2?"":r.relationTitle2,i.arrowTypeStart=gi(r.relation.type1),i.arrowTypeEnd=gi(r.relation.type2);var a="",o="";if(void 0!==r.style){var s=B(r.style);a=s.style,o=s.labelStyle}else a="fill:none";i.style=a,i.labelStyle=o,void 0!==r.interpolate?i.curve=D(r.interpolate,d.curveLinear):void 0!==t.defaultInterpolate?i.curve=D(t.defaultInterpolate,d.curveLinear):i.curve=D(fi.curve,d.curveLinear),r.text=r.title,void 0===r.text?void 0!==r.style&&(i.arrowheadStyle="fill: #333"):(i.arrowheadStyle="fill: #333",i.labelpos="c",_t().flowchart.htmlLabels,i.labelType="text",i.label=r.text.replace(x.lineBreakRegex,"\n"),void 0===r.style&&(i.style=i.style||"stroke: #333; stroke-width: 1.5px;fill:none"),i.labelStyle=i.labelStyle.replace("color:","fill:")),e.setEdge(r.id1,r.id2,i,n)}))}(s,a);var u=Object(d.select)('[id="'.concat(e,'"]'));u.attr("xmlns:xlink","http://www.w3.org/1999/xlink");var l=Object(d.select)("#"+e+" g");On(l,a,["aggregation","extension","composition","dependency"],"classDiagram",e);var h=u.node().getBBox(),f=h.width+16,p=h.height+16;if(c.debug("new ViewBox 0 0 ".concat(f," ").concat(p),"translate(".concat(8-a._label.marginx,", ").concat(8-a._label.marginy,")")),W(u,p,f,n.useMaxWidth),u.attr("viewBox","0 0 ".concat(f," ").concat(p)),u.select("g").attr("transform","translate(".concat(8-a._label.marginx,", ").concat(8-h.y,")")),!n.htmlLabels)for(var g=document.querySelectorAll('[id="'+e+'"] .edgeLabel .label'),y=0;y0&&o.length>0){var c={stmt:"state",id:F(),type:"divider",doc:mi(o)};i.push(mi(c)),n.doc=i}n.doc.forEach((function(e){return t(n,e,!0)}))}}({id:"root"},{id:"root",doc:bi},!0),{id:"root",doc:bi}},extract:function(t){var e;e=t.doc?t.doc:t,c.info(e),Ei(),c.info("Extract",e),e.forEach((function(t){"state"===t.stmt&&wi(t.id,t.type,t.doc,t.description,t.note),"relation"===t.stmt&&Ti(t.state1.id,t.state2.id,t.description)}))},trimColon:function(t){return t&&":"===t[0]?t.substr(1).trim():t.trim()}},Oi=n(22),Di=n.n(Oi),Ni={},Bi=function(t,e){Ni[t]=e},Li=function(t,e){var n=t.append("text").attr("x",2*_t().state.padding).attr("y",_t().state.textHeight+1.3*_t().state.padding).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.descriptions[0]).node().getBBox(),r=n.height,i=t.append("text").attr("x",_t().state.padding).attr("y",r+.4*_t().state.padding+_t().state.dividerMargin+_t().state.textHeight).attr("class","state-description"),a=!0,o=!0;e.descriptions.forEach((function(t){a||(!function(t,e,n){var r=t.append("tspan").attr("x",2*_t().state.padding).text(e);n||r.attr("dy",_t().state.textHeight)}(i,t,o),o=!1),a=!1}));var s=t.append("line").attr("x1",_t().state.padding).attr("y1",_t().state.padding+r+_t().state.dividerMargin/2).attr("y2",_t().state.padding+r+_t().state.dividerMargin/2).attr("class","descr-divider"),c=i.node().getBBox(),u=Math.max(c.width,n.width);return s.attr("x2",u+3*_t().state.padding),t.insert("rect",":first-child").attr("x",_t().state.padding).attr("y",_t().state.padding).attr("width",u+2*_t().state.padding).attr("height",c.height+r+2*_t().state.padding).attr("rx",_t().state.radius),t},Fi=function(t,e,n){var r,i=_t().state.padding,a=2*_t().state.padding,o=t.node().getBBox(),s=o.width,c=o.x,u=t.append("text").attr("x",0).attr("y",_t().state.titleShift).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.id),l=u.node().getBBox().width+a,h=Math.max(l,s);h===s&&(h+=a);var f=t.node().getBBox();e.doc,r=c-i,l>s&&(r=(s-h)/2+i),Math.abs(c-f.x)s&&(r=c-(l-s)/2);var d=1-_t().state.textHeight;return t.insert("rect",":first-child").attr("x",r).attr("y",d).attr("class",n?"alt-composit":"composit").attr("width",h).attr("height",f.height+_t().state.textHeight+_t().state.titleShift+1).attr("rx","0"),u.attr("x",r+i),l<=s&&u.attr("x",c+(h-a)/2-l/2+i),t.insert("rect",":first-child").attr("x",r).attr("y",_t().state.titleShift-_t().state.textHeight-_t().state.padding).attr("width",h).attr("height",3*_t().state.textHeight).attr("rx",_t().state.radius),t.insert("rect",":first-child").attr("x",r).attr("y",_t().state.titleShift-_t().state.textHeight-_t().state.padding).attr("width",h).attr("height",f.height+3+2*_t().state.textHeight).attr("rx",_t().state.radius),t},Pi=function(t,e){e.attr("class","state-note");var n=e.append("rect").attr("x",0).attr("y",_t().state.padding),r=function(t,e,n,r){var i=0,a=r.append("text");a.style("text-anchor","start"),a.attr("class","noteText");var o=t.replace(/\r\n/g,"
"),s=(o=o.replace(/\n/g,"
")).split(x.lineBreakRegex),c=1.25*_t().state.noteMargin,u=!0,l=!1,h=void 0;try{for(var f,d=s[Symbol.iterator]();!(u=(f=d.next()).done);u=!0){var p=f.value.trim();if(p.length>0){var g=a.append("tspan");if(g.text(p),0===c)c+=g.node().getBBox().height;i+=c,g.attr("x",e+_t().state.noteMargin),g.attr("y",n+i+1.25*_t().state.noteMargin)}}}catch(t){l=!0,h=t}finally{try{u||null==d.return||d.return()}finally{if(l)throw h}}return{textWidth:a.node().getBBox().width,textHeight:i}}(t,0,0,e.append("g")),i=r.textWidth,a=r.textHeight;return n.attr("height",a+2*_t().state.noteMargin),n.attr("width",i+2*_t().state.noteMargin),n},Ii=function(t,e){var n=e.id,r={id:n,label:e.id,width:0,height:0},i=t.append("g").attr("id",n).attr("class","stateGroup");"start"===e.type&&function(t){t.append("circle").attr("class","start-state").attr("r",_t().state.sizeUnit).attr("cx",_t().state.padding+_t().state.sizeUnit).attr("cy",_t().state.padding+_t().state.sizeUnit)}(i),"end"===e.type&&function(t){t.append("circle").attr("class","end-state-outer").attr("r",_t().state.sizeUnit+_t().state.miniPadding).attr("cx",_t().state.padding+_t().state.sizeUnit+_t().state.miniPadding).attr("cy",_t().state.padding+_t().state.sizeUnit+_t().state.miniPadding),t.append("circle").attr("class","end-state-inner").attr("r",_t().state.sizeUnit).attr("cx",_t().state.padding+_t().state.sizeUnit+2).attr("cy",_t().state.padding+_t().state.sizeUnit+2)}(i),"fork"!==e.type&&"join"!==e.type||function(t,e){var n=_t().state.forkWidth,r=_t().state.forkHeight;if(e.parentId){var i=n;n=r,r=i}t.append("rect").style("stroke","black").style("fill","black").attr("width",n).attr("height",r).attr("x",_t().state.padding).attr("y",_t().state.padding)}(i,e),"note"===e.type&&Pi(e.note.text,i),"divider"===e.type&&function(t){t.append("line").style("stroke","grey").style("stroke-dasharray","3").attr("x1",_t().state.textHeight).attr("class","divider").attr("x2",2*_t().state.textHeight).attr("y1",0).attr("y2",0)}(i),"default"===e.type&&0===e.descriptions.length&&function(t,e){var n=t.append("text").attr("x",2*_t().state.padding).attr("y",_t().state.textHeight+2*_t().state.padding).attr("font-size",_t().state.fontSize).attr("class","state-title").text(e.id),r=n.node().getBBox();t.insert("rect",":first-child").attr("x",_t().state.padding).attr("y",_t().state.padding).attr("width",r.width+2*_t().state.padding).attr("height",r.height+2*_t().state.padding).attr("rx",_t().state.radius)}(i,e),"default"===e.type&&e.descriptions.length>0&&Li(i,e);var a=i.node().getBBox();return r.width=a.width+2*_t().state.padding,r.height=a.height+2*_t().state.padding,Bi(n,r),r},ji=0;Oi.parser.yy=Mi;var Ri={},Yi=function t(e,n,r,i){var a,o=new G.a.Graph({compound:!0,multigraph:!0}),s=!0;for(a=0;a "+t.w+": "+JSON.stringify(o.edge(t))),function(t,e,n){e.points=e.points.filter((function(t){return!Number.isNaN(t.y)}));var r=e.points,i=Object(d.line)().x((function(t){return t.x})).y((function(t){return t.y})).curve(d.curveBasis),a=t.append("path").attr("d",i(r)).attr("id","edge"+ji).attr("class","transition"),o="";if(_t().state.arrowMarkerAbsolute&&(o=(o=(o=window.location.protocol+"//"+window.location.host+window.location.pathname+window.location.search).replace(/\(/g,"\\(")).replace(/\)/g,"\\)")),a.attr("marker-end","url("+o+"#"+function(t){switch(t){case Mi.relationType.AGGREGATION:return"aggregation";case Mi.relationType.EXTENSION:return"extension";case Mi.relationType.COMPOSITION:return"composition";case Mi.relationType.DEPENDENCY:return"dependency"}}(Mi.relationType.DEPENDENCY)+"End)"),void 0!==n.title){for(var s=t.append("g").attr("class","stateLabel"),u=H.calcLabelPosition(e.points),l=u.x,h=u.y,f=x.getRows(n.title),p=0,g=[],y=0,v=0,m=0;m<=f.length;m++){var b=s.append("text").attr("text-anchor","middle").text(f[m]).attr("x",l).attr("y",h+p),_=b.node().getBBox();if(y=Math.max(y,_.width),v=Math.min(v,_.x),c.info(_.x,l,h+p),0===p){var k=b.node().getBBox();p=k.height,c.info("Title height",p,h)}g.push(b)}var w=p*f.length;if(f.length>1){var E=(f.length-1)*p*.5;g.forEach((function(t,e){return t.attr("y",h+e*p-E)})),w=p*f.length}var T=s.node().getBBox();s.insert("rect",":first-child").attr("class","box").attr("x",l-y/2-_t().state.padding/2).attr("y",h-w/2-_t().state.padding/2-3.5).attr("width",y+_t().state.padding).attr("height",w+_t().state.padding),c.info(T)}ji++}(n,o.edge(t),o.edge(t).relation))})),w=k.getBBox();var E={id:r||"root",label:r||"root",width:0,height:0};return E.width=w.width+2*vi.padding,E.height=w.height+2*vi.padding,c.debug("Doc rendered",E,o),E},zi=function(){},Ui=function(t,e){vi=_t().state,Oi.parser.yy.clear(),Oi.parser.parse(t),c.debug("Rendering diagram "+t);var n=Object(d.select)("[id='".concat(e,"']"));n.append("defs").append("marker").attr("id","dependencyEnd").attr("refX",19).attr("refY",7).attr("markerWidth",20).attr("markerHeight",28).attr("orient","auto").append("path").attr("d","M 19,7 L9,13 L14,7 L9,1 Z"),new G.a.Graph({multigraph:!0,compound:!0,rankdir:"RL"}).setDefaultEdgeLabel((function(){return{}}));var r=Mi.getRootDoc();Yi(r,n,void 0,!1);var i=vi.padding,a=n.node().getBBox(),o=a.width+2*i,s=a.height+2*i;W(n,s,1.75*o,vi.useMaxWidth),n.attr("viewBox","".concat(a.x-vi.padding," ").concat(a.y-vi.padding," ")+o+" "+s)},$i={},Wi={},Hi=function(t,e,n,r){if("root"!==n.id){var i="rect";!0===n.start&&(i="start"),!1===n.start&&(i="end"),"default"!==n.type&&(i=n.type),Wi[n.id]||(Wi[n.id]={id:n.id,shape:i,description:n.id,classes:"statediagram-state"}),n.description&&(Array.isArray(Wi[n.id].description)?(Wi[n.id].shape="rectWithTitle",Wi[n.id].description.push(n.description)):Wi[n.id].description.length>0?(Wi[n.id].shape="rectWithTitle",Wi[n.id].description===n.id?Wi[n.id].description=[n.description]:Wi[n.id].description=[Wi[n.id].description,n.description]):(Wi[n.id].shape="rect",Wi[n.id].description=n.description)),!Wi[n.id].type&&n.doc&&(c.info("Setting cluser for ",n.id),Wi[n.id].type="group",Wi[n.id].shape="divider"===n.type?"divider":"roundedWithTitle",Wi[n.id].classes=Wi[n.id].classes+" "+(r?"statediagram-cluster statediagram-cluster-alt":"statediagram-cluster"));var a={labelStyle:"",shape:Wi[n.id].shape,labelText:Wi[n.id].description,classes:Wi[n.id].classes,style:"",id:n.id,domId:"state-"+n.id+"-"+Vi,type:Wi[n.id].type,padding:15};if(n.note){var o={labelStyle:"",shape:"note",labelText:n.note.text,classes:"statediagram-note",style:"",id:n.id+"----note",domId:"state-"+n.id+"----note-"+Vi,type:Wi[n.id].type,padding:15},s={labelStyle:"",shape:"noteGroup",labelText:n.note.text,classes:Wi[n.id].classes,style:"",id:n.id+"----parent",domId:"state-"+n.id+"----parent-"+Vi,type:"group",padding:0};Vi++,t.setNode(n.id+"----parent",s),t.setNode(o.id,o),t.setNode(n.id,a),t.setParent(n.id,n.id+"----parent"),t.setParent(o.id,n.id+"----parent");var u=n.id,l=o.id;"left of"===n.note.position&&(u=o.id,l=n.id),t.setEdge(u,l,{arrowhead:"none",arrowType:"",style:"fill:none",labelStyle:"",classes:"transition note-edge",arrowheadStyle:"fill: #333",labelpos:"c",labelType:"text",thickness:"normal"})}else t.setNode(n.id,a)}e&&"root"!==e.id&&(c.info("Setting node ",n.id," to be child of its parent ",e.id),t.setParent(n.id,e.id)),n.doc&&(c.info("Adding nodes children "),Gi(t,n,n.doc,!r))},Vi=0,Gi=function(t,e,n,r){Vi=0,c.trace("items",n),n.forEach((function(n){if("state"===n.stmt||"default"===n.stmt)Hi(t,e,n,r);else if("relation"===n.stmt){Hi(t,e,n.state1,r),Hi(t,e,n.state2,r);var i={id:"edge"+Vi,arrowhead:"normal",arrowTypeEnd:"arrow_barb",style:"fill:none",labelStyle:"",label:n.description,arrowheadStyle:"fill: #333",labelpos:"c",labelType:"text",thickness:"normal",classes:"transition"},a=n.state1.id,o=n.state2.id;t.setEdge(a,o,i,Vi),Vi++}}))},qi=function(t){for(var e=Object.keys(t),n=0;ne.seq?t:e}),t[0]),n="";t.forEach((function(t){n+=t===e?"\t*":"\t|"}));var r,i,a,o=[n,e.id,e.seq];for(var s in Ki)Ki[s]===e.id&&o.push(s);if(c.debug(o.join(" ")),Array.isArray(e.parent)){var u=Zi[e.parent[0]];aa(t,e,u),t.push(Zi[e.parent[1]])}else{if(null==e.parent)return;var l=Zi[e.parent];aa(t,e,l)}r=t,i=function(t){return t.id},a=Object.create(null),oa(t=r.reduce((function(t,e){var n=i(e);return a[n]||(a[n]=!0,t.push(e)),t}),[]))}var sa,ca=function(){var t=Object.keys(Zi).map((function(t){return Zi[t]}));return t.forEach((function(t){c.debug(t.id)})),t.sort((function(t,e){return e.seq-t.seq})),t},ua={setDirection:function(t){ta=t},setOptions:function(t){c.debug("options str",t),t=(t=t&&t.trim())||"{}";try{ia=JSON.parse(t)}catch(t){c.error("error while parsing gitGraph options",t.message)}},getOptions:function(){return ia},commit:function(t){var e={id:na(),message:t,seq:ea++,parent:null==Ji?null:Ji.id};Ji=e,Zi[e.id]=e,Ki[Qi]=e.id,c.debug("in pushCommit "+e.id)},branch:function(t){Ki[t]=null!=Ji?Ji.id:null,c.debug("in createBranch")},merge:function(t){var e=Zi[Ki[Qi]],n=Zi[Ki[t]];if(function(t,e){return t.seq>e.seq&&ra(e,t)}(e,n))c.debug("Already merged");else{if(ra(e,n))Ki[Qi]=Ki[t],Ji=Zi[Ki[Qi]];else{var r={id:na(),message:"merged branch "+t+" into "+Qi,seq:ea++,parent:[null==Ji?null:Ji.id,Ki[t]]};Ji=r,Zi[r.id]=r,Ki[Qi]=r.id}c.debug(Ki),c.debug("in mergeBranch")}},checkout:function(t){c.debug("in checkout");var e=Ki[Qi=t];Ji=Zi[e]},reset:function(t){c.debug("in reset",t);var e=t.split(":")[0],n=parseInt(t.split(":")[1]),r="HEAD"===e?Ji:Zi[Ki[e]];for(c.debug(r,n);n>0;)if(n--,!(r=Zi[r.parent])){var i="Critical error - unique parent commit not found during reset";throw c.error(i),i}Ji=r,Ki[Qi]=r.id},prettyPrint:function(){c.debug(Zi),oa([ca()[0]])},clear:function(){Zi={},Ki={master:Ji=null},Qi="master",ea=0},getBranchesAsObjArray:function(){var t=[];for(var e in Ki)t.push({name:e,commit:Zi[Ki[e]]});return t},getBranches:function(){return Ki},getCommits:function(){return Zi},getCommitsArray:ca,getCurrentBranch:function(){return Qi},getDirection:function(){return ta},getHead:function(){return Ji}},la=n(71),ha=n.n(la),fa={},da={nodeSpacing:150,nodeFillColor:"yellow",nodeStrokeWidth:2,nodeStrokeColor:"grey",lineStrokeWidth:4,branchOffset:50,lineColor:"grey",leftMargin:50,branchColors:["#442f74","#983351","#609732","#AA9A39"],nodeRadius:10,nodeLabel:{width:75,height:100,x:-25,y:0}},pa={};function ga(t,e,n,r){var i=D(r,d.curveBasis),a=da.branchColors[n%da.branchColors.length],o=Object(d.line)().x((function(t){return Math.round(t.x)})).y((function(t){return Math.round(t.y)})).curve(i);t.append("svg:path").attr("d",o(e)).style("stroke",a).style("stroke-width",da.lineStrokeWidth).style("fill","none")}function ya(t,e){e=e||t.node().getBBox();var n=t.node().getCTM();return{left:n.e+e.x*n.a,top:n.f+e.y*n.d,width:e.width,height:e.height}}function va(t,e,n,r,i){c.debug("svgDrawLineForCommits: ",e,n);var a=ya(t.select("#node-"+e+" circle")),o=ya(t.select("#node-"+n+" circle"));switch(r){case"LR":if(a.left-o.left>da.nodeSpacing){var s={x:a.left-da.nodeSpacing,y:o.top+o.height/2};ga(t,[s,{x:o.left+o.width,y:o.top+o.height/2}],i,"linear"),ga(t,[{x:a.left,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:s.y},s],i)}else ga(t,[{x:a.left,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:a.top+a.height/2},{x:a.left-da.nodeSpacing/2,y:o.top+o.height/2},{x:o.left+o.width,y:o.top+o.height/2}],i);break;case"BT":if(o.top-a.top>da.nodeSpacing){var u={x:o.left+o.width/2,y:a.top+a.height+da.nodeSpacing};ga(t,[u,{x:o.left+o.width/2,y:o.top}],i,"linear"),ga(t,[{x:a.left+a.width/2,y:a.top+a.height},{x:a.left+a.width/2,y:a.top+a.height+da.nodeSpacing/2},{x:o.left+o.width/2,y:u.y-da.nodeSpacing/2},u],i)}else ga(t,[{x:a.left+a.width/2,y:a.top+a.height},{x:a.left+a.width/2,y:a.top+da.nodeSpacing/2},{x:o.left+o.width/2,y:o.top-da.nodeSpacing/2},{x:o.left+o.width/2,y:o.top}],i)}}function ma(t,e){return t.select(e).node().cloneNode(!0)}function ba(t,e,n,r){var i,a=Object.keys(fa).length;if("string"==typeof e)do{if(i=fa[e],c.debug("in renderCommitHistory",i.id,i.seq),t.select("#node-"+e).size()>0)return;t.append((function(){return ma(t,"#def-commit")})).attr("class","commit").attr("id",(function(){return"node-"+i.id})).attr("transform",(function(){switch(r){case"LR":return"translate("+(i.seq*da.nodeSpacing+da.leftMargin)+", "+sa*da.branchOffset+")";case"BT":return"translate("+(sa*da.branchOffset+da.leftMargin)+", "+(a-i.seq)*da.nodeSpacing+")"}})).attr("fill",da.nodeFillColor).attr("stroke",da.nodeStrokeColor).attr("stroke-width",da.nodeStrokeWidth);var o=void 0;for(var s in n)if(n[s].commit===i){o=n[s];break}o&&(c.debug("found branch ",o.name),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","branch-label").text(o.name+", ")),t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-id").text(i.id),""!==i.message&&"BT"===r&&t.select("#node-"+i.id+" p").append("xhtml:span").attr("class","commit-msg").text(", "+i.message),e=i.parent}while(e&&fa[e]);Array.isArray(e)&&(c.debug("found merge commmit",e),ba(t,e[0],n,r),sa++,ba(t,e[1],n,r),sa--)}function xa(t,e,n,r){for(r=r||0;e.seq>0&&!e.lineDrawn;)"string"==typeof e.parent?(va(t,e.id,e.parent,n,r),e.lineDrawn=!0,e=fa[e.parent]):Array.isArray(e.parent)&&(va(t,e.id,e.parent[0],n,r),va(t,e.id,e.parent[1],n,r+1),xa(t,fa[e.parent[1]],n,r+1),e.lineDrawn=!0,e=fa[e.parent[0]])}var _a,ka=function(t){pa=t},wa=function(t,e,n){try{var r=ha.a.parser;r.yy=ua,r.yy.clear(),c.debug("in gitgraph renderer",t+"\n","id:",e,n),r.parse(t+"\n"),da=Object.assign(da,pa,ua.getOptions()),c.debug("effective options",da);var i=ua.getDirection();fa=ua.getCommits();var a=ua.getBranchesAsObjArray();"BT"===i&&(da.nodeLabel.x=a.length*da.branchOffset,da.nodeLabel.width="100%",da.nodeLabel.y=-2*da.nodeRadius);var o=Object(d.select)('[id="'.concat(e,'"]'));for(var s in function(t){t.append("defs").append("g").attr("id","def-commit").append("circle").attr("r",da.nodeRadius).attr("cx",0).attr("cy",0),t.select("#def-commit").append("foreignObject").attr("width",da.nodeLabel.width).attr("height",da.nodeLabel.height).attr("x",da.nodeLabel.x).attr("y",da.nodeLabel.y).attr("class","node-label").attr("requiredFeatures","http://www.w3.org/TR/SVG11/feature#Extensibility").append("p").html("")}(o),sa=1,a){var u=a[s];ba(o,u.commit.id,a,i),xa(o,u.commit,i),sa++}o.attr("height",(function(){return"BT"===i?Object.keys(fa).length*da.nodeSpacing:(a.length+1)*da.branchOffset}))}catch(t){c.error("Error while rendering gitgraph"),c.error(t.message)}},Ea="",Ta=!1,Ca={setMessage:function(t){c.debug("Setting message to: "+t),Ea=t},getMessage:function(){return Ea},setInfo:function(t){Ta=t},getInfo:function(){return Ta}},Aa=n(72),Sa=n.n(Aa),Ma={},Oa=function(t){Object.keys(t).forEach((function(e){Ma[e]=t[e]}))},Da=function(t,e,n){try{var r=Sa.a.parser;r.yy=Ca,c.debug("Renering info diagram\n"+t),r.parse(t),c.debug("Parsed info diagram");var i=Object(d.select)("#"+e);i.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size","32px").style("text-anchor","middle").text("v "+n),i.attr("height",100),i.attr("width",400)}catch(t){c.error("Error while rendering info diagram"),c.error(t.message)}},Na={},Ba=function(t){Object.keys(t).forEach((function(e){Na[e]=t[e]}))},La=function(t,e){try{c.debug("Renering svg for syntax error\n");var n=Object(d.select)("#"+t),r=n.append("g");r.append("path").attr("class","error-icon").attr("d","m411.313,123.313c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32-9.375,9.375-20.688-20.688c-12.484-12.5-32.766-12.5-45.25,0l-16,16c-1.261,1.261-2.304,2.648-3.31,4.051-21.739-8.561-45.324-13.426-70.065-13.426-105.867,0-192,86.133-192,192s86.133,192 192,192 192-86.133 192-192c0-24.741-4.864-48.327-13.426-70.065 1.402-1.007 2.79-2.049 4.051-3.31l16-16c12.5-12.492 12.5-32.758 0-45.25l-20.688-20.688 9.375-9.375 32.001-31.999zm-219.313,100.687c-52.938,0-96,43.063-96,96 0,8.836-7.164,16-16,16s-16-7.164-16-16c0-70.578 57.422-128 128-128 8.836,0 16,7.164 16,16s-7.164,16-16,16z"),r.append("path").attr("class","error-icon").attr("d","m459.02,148.98c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l16,16c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16.001-16z"),r.append("path").attr("class","error-icon").attr("d","m340.395,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16-16c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l15.999,16z"),r.append("path").attr("class","error-icon").attr("d","m400,64c8.844,0 16-7.164 16-16v-32c0-8.836-7.156-16-16-16-8.844,0-16,7.164-16,16v32c0,8.836 7.156,16 16,16z"),r.append("path").attr("class","error-icon").attr("d","m496,96.586h-32c-8.844,0-16,7.164-16,16 0,8.836 7.156,16 16,16h32c8.844,0 16-7.164 16-16 0-8.836-7.156-16-16-16z"),r.append("path").attr("class","error-icon").attr("d","m436.98,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688l32-32c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32c-6.251,6.25-6.251,16.375-0.001,22.625z"),r.append("text").attr("class","error-text").attr("x",1240).attr("y",250).attr("font-size","150px").style("text-anchor","middle").text("Syntax error in graph"),r.append("text").attr("class","error-text").attr("x",1050).attr("y",400).attr("font-size","100px").style("text-anchor","middle").text("mermaid version "+e),n.attr("height",100),n.attr("width",400),n.attr("viewBox","768 0 512 512")}catch(t){c.error("Error while rendering info diagram"),c.error(t.message)}},Fa={},Pa="",Ia={parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().pie},addSection:function(t,e){void 0===Fa[t]&&(Fa[t]=e,c.debug("Added new section :",t))},getSections:function(){return Fa},cleanupValue:function(t){return":"===t.substring(0,1)?(t=t.substring(1).trim(),Number(t.trim())):Number(t.trim())},clear:function(){Fa={},Pa=""},setTitle:function(t){Pa=t},getTitle:function(){return Pa}},ja=n(73),Ra=n.n(ja),Ya={},za=function(t){Object.keys(t).forEach((function(e){Ya[e]=t[e]}))},Ua=function(t,e){try{var n=Ra.a.parser;n.yy=Ia,c.debug("Rendering info diagram\n"+t),n.yy.clear(),n.parse(t),c.debug("Parsed info diagram");var r=document.getElementById(e);void 0===(_a=r.parentElement.offsetWidth)&&(_a=1200),void 0!==Ya.useWidth&&(_a=Ya.useWidth);var i=Object(d.select)("#"+e);W(i,450,_a,Ya.useMaxWidth),r.setAttribute("viewBox","0 0 "+_a+" 450");var a=Math.min(_a,450)/2-40,o=i.append("g").attr("transform","translate("+_a/2+",225)"),s=Ia.getSections(),u=0;Object.keys(s).forEach((function(t){u+=s[t]}));var l=Object(d.scaleOrdinal)().domain(s).range(d.schemeSet2),h=Object(d.pie)().value((function(t){return t.value}))(Object(d.entries)(s)),f=Object(d.arc)().innerRadius(0).outerRadius(a);o.selectAll("mySlices").data(h).enter().append("path").attr("d",f).attr("fill",(function(t){return l(t.data.key)})).attr("stroke","black").style("stroke-width","2px").style("opacity",.7),o.selectAll("mySlices").data(h).enter().append("text").text((function(t){return(t.data.value/u*100).toFixed(0)+"%"})).attr("transform",(function(t){return"translate("+f.centroid(t)+")"})).style("text-anchor","middle").attr("class","slice").style("font-size",17),o.append("text").text(n.yy.getTitle()).attr("x",0).attr("y",-200).attr("class","pieTitleText");var p=o.selectAll(".legend").data(l.domain()).enter().append("g").attr("class","legend").attr("transform",(function(t,e){return"translate(216,"+(22*e-22*l.domain().length/2)+")"}));p.append("rect").attr("width",18).attr("height",18).style("fill",l).style("stroke",l),p.append("text").attr("x",22).attr("y",14).text((function(t){return t}))}catch(t){c.error("Error while rendering info diagram"),c.error(t)}},$a={},Wa=[],Ha="",Va=function(t){return void 0===$a[t]&&($a[t]={attributes:[]},c.info("Added new entity :",t)),$a[t]},Ga={Cardinality:{ZERO_OR_ONE:"ZERO_OR_ONE",ZERO_OR_MORE:"ZERO_OR_MORE",ONE_OR_MORE:"ONE_OR_MORE",ONLY_ONE:"ONLY_ONE"},Identification:{NON_IDENTIFYING:"NON_IDENTIFYING",IDENTIFYING:"IDENTIFYING"},parseDirective:function(t,e,n){Go.parseDirective(this,t,e,n)},getConfig:function(){return _t().er},addEntity:Va,addAttributes:function(t,e){var n,r=Va(t);for(n=e.length-1;n>=0;n--)r.attributes.push(e[n]),c.debug("Added attribute ",e[n].attributeName)},getEntities:function(){return $a},addRelationship:function(t,e,n,r){var i={entityA:t,roleA:e,entityB:n,relSpec:r};Wa.push(i),c.debug("Added new relationship :",i)},getRelationships:function(){return Wa},clear:function(){$a={},Wa=[],Ha=""},setTitle:function(t){Ha=t},getTitle:function(){return Ha}},qa=n(74),Xa=n.n(qa),Za={ONLY_ONE_START:"ONLY_ONE_START",ONLY_ONE_END:"ONLY_ONE_END",ZERO_OR_ONE_START:"ZERO_OR_ONE_START",ZERO_OR_ONE_END:"ZERO_OR_ONE_END",ONE_OR_MORE_START:"ONE_OR_MORE_START",ONE_OR_MORE_END:"ONE_OR_MORE_END",ZERO_OR_MORE_START:"ZERO_OR_MORE_START",ZERO_OR_MORE_END:"ZERO_OR_MORE_END"},Ja=Za,Ka=function(t,e){var n;t.append("defs").append("marker").attr("id",Za.ONLY_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18 M15,0 L15,18"),t.append("defs").append("marker").attr("id",Za.ONLY_ONE_END).attr("refX",18).attr("refY",9).attr("markerWidth",18).attr("markerHeight",18).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,0 L3,18 M9,0 L9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_ONE_START).attr("refX",0).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",21).attr("cy",9).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M9,0 L9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_ONE_END).attr("refX",30).attr("refY",9).attr("markerWidth",30).attr("markerHeight",18).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",9).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,0 L21,18"),t.append("defs").append("marker").attr("id",Za.ONE_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q 18,0 36,18 Q 18,36 0,18 M42,9 L42,27"),t.append("defs").append("marker").attr("id",Za.ONE_OR_MORE_END).attr("refX",27).attr("refY",18).attr("markerWidth",45).attr("markerHeight",36).attr("orient","auto").append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M3,9 L3,27 M9,18 Q27,0 45,18 Q27,36 9,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_MORE_START).attr("refX",18).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",48).attr("cy",18).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M0,18 Q18,0 36,18 Q18,36 0,18"),(n=t.append("defs").append("marker").attr("id",Za.ZERO_OR_MORE_END).attr("refX",39).attr("refY",18).attr("markerWidth",57).attr("markerHeight",36).attr("orient","auto")).append("circle").attr("stroke",e.stroke).attr("fill","white").attr("cx",9).attr("cy",18).attr("r",6),n.append("path").attr("stroke",e.stroke).attr("fill","none").attr("d","M21,18 Q39,0 57,18 Q39,36 21,18")},Qa={},to=function(t,e,n){var r;return Object.keys(e).forEach((function(i){var a=t.append("g").attr("id",i);r=void 0===r?i:r;var o="entity-"+i,s=a.append("text").attr("class","er entityLabel").attr("id",o).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("style","font-family: "+_t().fontFamily+"; font-size: "+Qa.fontSize+"px").text(i),c=function(t,e,n){var r=Qa.entityPadding/3,i=Qa.entityPadding/3,a=.85*Qa.fontSize,o=e.node().getBBox(),s=[],c=0,u=0,l=o.height+2*r,h=1;n.forEach((function(n){var i="".concat(e.node().id,"-attr-").concat(h),o=t.append("text").attr("class","er entityLabel").attr("id","".concat(i,"-type")).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","left").attr("style","font-family: "+_t().fontFamily+"; font-size: "+a+"px").text(n.attributeType),f=t.append("text").attr("class","er entityLabel").attr("id","".concat(i,"-name")).attr("x",0).attr("y",0).attr("dominant-baseline","middle").attr("text-anchor","left").attr("style","font-family: "+_t().fontFamily+"; font-size: "+a+"px").text(n.attributeName);s.push({tn:o,nn:f});var d=o.node().getBBox(),p=f.node().getBBox();c=Math.max(c,d.width),u=Math.max(u,p.width),l+=Math.max(d.height,p.height)+2*r,h+=1}));var f={width:Math.max(Qa.minEntityWidth,Math.max(o.width+2*Qa.entityPadding,c+u+4*i)),height:n.length>0?l:Math.max(Qa.minEntityHeight,o.height+2*Qa.entityPadding)},d=Math.max(0,f.width-(c+u)-4*i);if(n.length>0){e.attr("transform","translate("+f.width/2+","+(r+o.height/2)+")");var p=o.height+2*r,g="attributeBoxOdd";s.forEach((function(e){var n=p+r+Math.max(e.tn.node().getBBox().height,e.nn.node().getBBox().height)/2;e.tn.attr("transform","translate("+i+","+n+")");var a=t.insert("rect","#"+e.tn.node().id).attr("class","er ".concat(g)).attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x",0).attr("y",p).attr("width",c+2*i+d/2).attr("height",e.tn.node().getBBox().height+2*r);e.nn.attr("transform","translate("+(parseFloat(a.attr("width"))+i)+","+n+")"),t.insert("rect","#"+e.nn.node().id).attr("class","er ".concat(g)).attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x","".concat(a.attr("x")+a.attr("width"))).attr("y",p).attr("width",u+2*i+d/2).attr("height",e.nn.node().getBBox().height+2*r),p+=Math.max(e.tn.node().getBBox().height,e.nn.node().getBBox().height)+2*r,g="attributeBoxOdd"==g?"attributeBoxEven":"attributeBoxOdd"}))}else f.height=Math.max(Qa.minEntityHeight,l),e.attr("transform","translate("+f.width/2+","+f.height/2+")");return f}(a,s,e[i].attributes),u=c.width,l=c.height,h=a.insert("rect","#"+o).attr("class","er entityBox").attr("fill",Qa.fill).attr("fill-opacity","100%").attr("stroke",Qa.stroke).attr("x",0).attr("y",0).attr("width",u).attr("height",l).node().getBBox();n.setNode(i,{width:h.width,height:h.height,shape:"rect",id:i})})),r},eo=function(t){return(t.entityA+t.roleA+t.entityB).replace(/\s/g,"")},no=0,ro=function(t){for(var e=Object.keys(t),n=0;n/gi," "),r=t.append("text");r.attr("x",e.x),r.attr("y",e.y),r.attr("class","legend"),r.style("text-anchor",e.anchor),void 0!==e.class&&r.attr("class",e.class);var i=r.append("tspan");return i.attr("x",e.x+2*e.textMargin),i.text(n),r},bo=-1,xo=function(){return{x:0,y:0,width:100,anchor:"start",height:100,rx:0,ry:0}},_o=function(){function t(t,e,n,i,a,o,s,c){r(e.append("text").attr("x",n+a/2).attr("y",i+o/2+5).style("font-color",c).style("text-anchor","middle").text(t),s)}function e(t,e,n,i,a,o,s,c,u){for(var l=c.taskFontSize,h=c.taskFontFamily,f=t.split(//gi),d=0;d3?function(t){var e=Object(d.arc)().startAngle(Math.PI/2).endAngle(Math.PI/2*3).innerRadius(7.5).outerRadius(15/2.2);t.append("path").attr("class","mouth").attr("d",e).attr("transform","translate("+o.cx+","+(o.cy+2)+")")}(s):o.score<3?function(t){var e=Object(d.arc)().startAngle(3*Math.PI/2).endAngle(Math.PI/2*5).innerRadius(7.5).outerRadius(15/2.2);t.append("path").attr("class","mouth").attr("d",e).attr("transform","translate("+o.cx+","+(o.cy+7)+")")}(s):function(t){t.append("line").attr("class","mouth").attr("stroke",2).attr("x1",o.cx-5).attr("y1",o.cy+7).attr("x2",o.cx+5).attr("y2",o.cy+7).attr("class","mouth").attr("stroke-width","1px").attr("stroke","#666")}(s);var c=xo();c.x=e.x,c.y=e.y,c.fill=e.fill,c.width=n.width,c.height=n.height,c.class="task task-type-"+e.num,c.rx=3,c.ry=3,yo(i,c);var u=e.x+14;e.people.forEach((function(t){var n=e.actors[t],r={cx:u,cy:e.y,r:7,fill:n,stroke:"#000",title:t};vo(i,r),u+=10})),_o(n)(e.task,i,c.x,c.y,c.width,c.height,{class:"task"},n,e.colour)},Co=function(t){t.append("defs").append("marker").attr("id","arrowhead").attr("refX",5).attr("refY",2).attr("markerWidth",6).attr("markerHeight",4).attr("orient","auto").append("path").attr("d","M 0,0 V 4 L6,2 Z")};ao.parser.yy=go;var Ao={leftMargin:150,diagramMarginX:50,diagramMarginY:20,taskMargin:50,width:150,height:50,taskFontSize:14,taskFontFamily:'"Open-Sans", "sans-serif"',boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,messageAlign:"center",bottomMarginAdj:1,activationWidth:10,textPlacement:"fo",actorColours:["#8FBC8F","#7CFC00","#00FFFF","#20B2AA","#B0E0E6","#FFFFE0"],sectionFills:["#191970","#8B008B","#4B0082","#2F4F4F","#800000","#8B4513","#00008B"],sectionColours:["#fff"]},So={};var Mo=Ao.leftMargin,Oo={data:{startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},verticalPos:0,sequenceItems:[],init:function(){this.sequenceItems=[],this.data={startx:void 0,stopx:void 0,starty:void 0,stopy:void 0},this.verticalPos=0},updateVal:function(t,e,n,r){void 0===t[e]?t[e]=n:t[e]=r(n,t[e])},updateBounds:function(t,e,n,r){var i,a=this,o=0;this.sequenceItems.forEach((function(s){o++;var c=a.sequenceItems.length-o+1;a.updateVal(s,"starty",e-c*Ao.boxMargin,Math.min),a.updateVal(s,"stopy",r+c*Ao.boxMargin,Math.max),a.updateVal(Oo.data,"startx",t-c*Ao.boxMargin,Math.min),a.updateVal(Oo.data,"stopx",n+c*Ao.boxMargin,Math.max),"activation"!==i&&(a.updateVal(s,"startx",t-c*Ao.boxMargin,Math.min),a.updateVal(s,"stopx",n+c*Ao.boxMargin,Math.max),a.updateVal(Oo.data,"starty",e-c*Ao.boxMargin,Math.min),a.updateVal(Oo.data,"stopy",r+c*Ao.boxMargin,Math.max))}))},insert:function(t,e,n,r){var i=Math.min(t,n),a=Math.max(t,n),o=Math.min(e,r),s=Math.max(e,r);this.updateVal(Oo.data,"startx",i,Math.min),this.updateVal(Oo.data,"starty",o,Math.min),this.updateVal(Oo.data,"stopx",a,Math.max),this.updateVal(Oo.data,"stopy",s,Math.max),this.updateBounds(i,o,a,s)},bumpVerticalPos:function(t){this.verticalPos=this.verticalPos+t,this.data.stopy=this.verticalPos},getVerticalPos:function(){return this.verticalPos},getBounds:function(){return this.data}},Do=Ao.sectionFills,No=Ao.sectionColours,Bo=function(t,e,n){for(var r="",i=n+(2*Ao.height+Ao.diagramMarginY),a=0,o="#CCC",s="black",c=0,u=0;u tspan {\n fill: ").concat(t.actorTextColor,";\n stroke: none;\n }\n\n .actor-line {\n stroke: ").concat(t.actorLineColor,";\n }\n\n .messageLine0 {\n stroke-width: 1.5;\n stroke-dasharray: none;\n stroke: ").concat(t.signalColor,";\n }\n\n .messageLine1 {\n stroke-width: 1.5;\n stroke-dasharray: 2, 2;\n stroke: ").concat(t.signalColor,";\n }\n\n #arrowhead path {\n fill: ").concat(t.signalColor,";\n stroke: ").concat(t.signalColor,";\n }\n\n .sequenceNumber {\n fill: ").concat(t.sequenceNumberColor,";\n }\n\n #sequencenumber {\n fill: ").concat(t.signalColor,";\n }\n\n #crosshead path {\n fill: ").concat(t.signalColor,";\n stroke: ").concat(t.signalColor,";\n }\n\n .messageText {\n fill: ").concat(t.signalTextColor,";\n stroke: ").concat(t.signalTextColor,";\n }\n\n .labelBox {\n stroke: ").concat(t.labelBoxBorderColor,";\n fill: ").concat(t.labelBoxBkgColor,";\n }\n\n .labelText, .labelText > tspan {\n fill: ").concat(t.labelTextColor,";\n stroke: none;\n }\n\n .loopText, .loopText > tspan {\n fill: ").concat(t.loopTextColor,";\n stroke: none;\n }\n\n .loopLine {\n stroke-width: 2px;\n stroke-dasharray: 2, 2;\n stroke: ").concat(t.labelBoxBorderColor,";\n fill: ").concat(t.labelBoxBorderColor,";\n }\n\n .note {\n //stroke: #decc93;\n stroke: ").concat(t.noteBorderColor,";\n fill: ").concat(t.noteBkgColor,";\n }\n\n .noteText, .noteText > tspan {\n fill: ").concat(t.noteTextColor,";\n stroke: none;\n }\n\n .activation0 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n\n .activation1 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n\n .activation2 {\n fill: ").concat(t.activationBkgColor,";\n stroke: ").concat(t.activationBorderColor,";\n }\n")},gantt:function(t){return'\n .mermaid-main-font {\n font-family: "trebuchet ms", verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n\n .section {\n stroke: none;\n opacity: 0.2;\n }\n\n .section0 {\n fill: '.concat(t.sectionBkgColor,";\n }\n\n .section2 {\n fill: ").concat(t.sectionBkgColor2,";\n }\n\n .section1,\n .section3 {\n fill: ").concat(t.altSectionBkgColor,";\n opacity: 0.2;\n }\n\n .sectionTitle0 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle1 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle2 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle3 {\n fill: ").concat(t.titleColor,";\n }\n\n .sectionTitle {\n text-anchor: start;\n font-size: 11px;\n text-height: 14px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n\n }\n\n\n /* Grid and axis */\n\n .grid .tick {\n stroke: ").concat(t.gridColor,";\n opacity: 0.8;\n shape-rendering: crispEdges;\n text {\n font-family: ").concat(t.fontFamily,";\n fill: ").concat(t.textColor,";\n }\n }\n\n .grid path {\n stroke-width: 0;\n }\n\n\n /* Today line */\n\n .today {\n fill: none;\n stroke: ").concat(t.todayLineColor,";\n stroke-width: 2px;\n }\n\n\n /* Task styling */\n\n /* Default task */\n\n .task {\n stroke-width: 2;\n }\n\n .taskText {\n text-anchor: middle;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n\n .taskText:not([font-size]) {\n font-size: 11px;\n }\n\n .taskTextOutsideRight {\n fill: ").concat(t.taskTextDarkColor,";\n text-anchor: start;\n font-size: 11px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n\n }\n\n .taskTextOutsideLeft {\n fill: ").concat(t.taskTextDarkColor,";\n text-anchor: end;\n font-size: 11px;\n }\n\n /* Special case clickable */\n .task.clickable {\n cursor: pointer;\n }\n .taskText.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n .taskTextOutsideLeft.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n .taskTextOutsideRight.clickable {\n cursor: pointer;\n fill: ").concat(t.taskTextClickableColor," !important;\n font-weight: bold;\n }\n\n /* Specific task settings for the sections*/\n\n .taskText0,\n .taskText1,\n .taskText2,\n .taskText3 {\n fill: ").concat(t.taskTextColor,";\n }\n\n .task0,\n .task1,\n .task2,\n .task3 {\n fill: ").concat(t.taskBkgColor,";\n stroke: ").concat(t.taskBorderColor,";\n }\n\n .taskTextOutside0,\n .taskTextOutside2\n {\n fill: ").concat(t.taskTextOutsideColor,";\n }\n\n .taskTextOutside1,\n .taskTextOutside3 {\n fill: ").concat(t.taskTextOutsideColor,";\n }\n\n\n /* Active task */\n\n .active0,\n .active1,\n .active2,\n .active3 {\n fill: ").concat(t.activeTaskBkgColor,";\n stroke: ").concat(t.activeTaskBorderColor,";\n }\n\n .activeText0,\n .activeText1,\n .activeText2,\n .activeText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n\n /* Completed task */\n\n .done0,\n .done1,\n .done2,\n .done3 {\n stroke: ").concat(t.doneTaskBorderColor,";\n fill: ").concat(t.doneTaskBkgColor,";\n stroke-width: 2;\n }\n\n .doneText0,\n .doneText1,\n .doneText2,\n .doneText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n\n /* Tasks on the critical line */\n\n .crit0,\n .crit1,\n .crit2,\n .crit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.critBkgColor,";\n stroke-width: 2;\n }\n\n .activeCrit0,\n .activeCrit1,\n .activeCrit2,\n .activeCrit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.activeTaskBkgColor,";\n stroke-width: 2;\n }\n\n .doneCrit0,\n .doneCrit1,\n .doneCrit2,\n .doneCrit3 {\n stroke: ").concat(t.critBorderColor,";\n fill: ").concat(t.doneTaskBkgColor,";\n stroke-width: 2;\n cursor: pointer;\n shape-rendering: crispEdges;\n }\n\n .milestone {\n transform: rotate(45deg) scale(0.8,0.8);\n }\n\n .milestoneText {\n font-style: italic;\n }\n .doneCritText0,\n .doneCritText1,\n .doneCritText2,\n .doneCritText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n .activeCritText0,\n .activeCritText1,\n .activeCritText2,\n .activeCritText3 {\n fill: ").concat(t.taskTextDarkColor," !important;\n }\n\n .titleText {\n text-anchor: middle;\n font-size: 18px;\n fill: ").concat(t.textColor," ;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n")},classDiagram:Po,"classDiagram-v2":Po,class:Po,stateDiagram:jo,state:jo,git:function(){return"\n .commit-id,\n .commit-msg,\n .branch-label {\n fill: lightgrey;\n color: lightgrey;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n }\n"},info:function(){return""},pie:function(t){return".pieTitleText {\n text-anchor: middle;\n font-size: 25px;\n fill: ".concat(t.taskTextDarkColor,";\n font-family: ").concat(t.fontFamily,";\n }\n .slice {\n font-family: ").concat(t.fontFamily,";\n fill: ").concat(t.textColor,";\n // fill: white;\n }\n .legend text {\n fill: ").concat(t.taskTextDarkColor,";\n font-family: ").concat(t.fontFamily,";\n font-size: 17px;\n }\n")},er:function(t){return"\n .entityBox {\n fill: ".concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n }\n\n .attributeBoxOdd {\n fill: #ffffff;\n stroke: ").concat(t.nodeBorder,";\n }\n\n .attributeBoxEven {\n fill: #f2f2f2;\n stroke: ").concat(t.nodeBorder,";\n }\n\n .relationshipLabelBox {\n fill: ").concat(t.tertiaryColor,";\n opacity: 0.7;\n background-color: ").concat(t.tertiaryColor,";\n rect {\n opacity: 0.5;\n }\n }\n\n .relationshipLine {\n stroke: ").concat(t.lineColor,";\n }\n")},journey:function(t){return".label {\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n color: ".concat(t.textColor,";\n }\n .mouth {\n stroke: #666;\n }\n\n line {\n stroke: ").concat(t.textColor,"\n }\n\n .legend {\n fill: ").concat(t.textColor,";\n }\n\n .label text {\n fill: #333;\n }\n .label {\n color: ").concat(t.textColor,"\n }\n\n .face {\n fill: #FFF8DC;\n stroke: #999;\n }\n\n .node rect,\n .node circle,\n .node ellipse,\n .node polygon,\n .node path {\n fill: ").concat(t.mainBkg,";\n stroke: ").concat(t.nodeBorder,";\n stroke-width: 1px;\n }\n\n .node .label {\n text-align: center;\n }\n .node.clickable {\n cursor: pointer;\n }\n\n .arrowheadPath {\n fill: ").concat(t.arrowheadColor,";\n }\n\n .edgePath .path {\n stroke: ").concat(t.lineColor,";\n stroke-width: 1.5px;\n }\n\n .flowchart-link {\n stroke: ").concat(t.lineColor,";\n fill: none;\n }\n\n .edgeLabel {\n background-color: ").concat(t.edgeLabelBackground,";\n rect {\n opacity: 0.5;\n }\n text-align: center;\n }\n\n .cluster rect {\n }\n\n .cluster text {\n fill: ").concat(t.titleColor,";\n }\n\n div.mermaidTooltip {\n position: absolute;\n text-align: center;\n max-width: 200px;\n padding: 2px;\n font-family: 'trebuchet ms', verdana, arial, sans-serif;\n font-family: var(--mermaid-font-family);\n font-size: 12px;\n background: ").concat(t.tertiaryColor,";\n border: 1px solid ").concat(t.border2,";\n border-radius: 2px;\n pointer-events: none;\n z-index: 100;\n }\n\n .task-type-0, .section-type-0 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType0):"",";\n }\n .task-type-1, .section-type-1 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType1):"",";\n }\n .task-type-2, .section-type-2 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType2):"",";\n }\n .task-type-3, .section-type-3 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType3):"",";\n }\n .task-type-4, .section-type-4 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType4):"",";\n }\n .task-type-5, .section-type-5 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType5):"",";\n }\n .task-type-6, .section-type-6 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType6):"",";\n }\n .task-type-7, .section-type-7 {\n ").concat(t.fillType0?"fill: ".concat(t.fillType7):"",";\n }\n")}},Yo=function(t,e,n){return" {\n font-family: ".concat(n.fontFamily,";\n font-size: ").concat(n.fontSize,";\n fill: ").concat(n.textColor,"\n }\n\n /* Classes common for multiple diagrams */\n\n .error-icon {\n fill: ").concat(n.errorBkgColor,";\n }\n .error-text {\n fill: ").concat(n.errorTextColor,";\n stroke: ").concat(n.errorTextColor,";\n }\n\n .edge-thickness-normal {\n stroke-width: 2px;\n }\n .edge-thickness-thick {\n stroke-width: 3.5px\n }\n .edge-pattern-solid {\n stroke-dasharray: 0;\n }\n\n .edge-pattern-dashed{\n stroke-dasharray: 3;\n }\n .edge-pattern-dotted {\n stroke-dasharray: 2;\n }\n\n .marker {\n fill: ").concat(n.lineColor,";\n }\n .marker.cross {\n stroke: ").concat(n.lineColor,";\n }\n\n svg {\n font-family: ").concat(n.fontFamily,";\n font-size: ").concat(n.fontSize,";\n }\n\n ").concat(Ro[t](n),"\n\n ").concat(e,"\n\n ").concat(t," { fill: apa;}\n")};function zo(t){return(zo="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var Uo={},$o=function(t,e,n){switch(c.debug("Directive type=".concat(e.type," with args:"),e.args),e.type){case"init":case"initialize":["config"].forEach((function(t){void 0!==e.args[t]&&("flowchart-v2"===n&&(n="flowchart"),e.args[n]=e.args[t],delete e.args[t])})),e.args,wt(e.args);break;case"wrap":case"nowrap":t&&t.setWrap&&t.setWrap("wrap"===e.type);break;default:c.warn("Unhandled directive: source: '%%{".concat(e.type,": ").concat(JSON.stringify(e.args?e.args:{}),"}%%"),e)}};function Wo(t){ka(t.git),me(t.flowchart),Ln(t.flowchart),void 0!==t.sequenceDiagram&&_r.setConf(I(t.sequence,t.sequenceDiagram)),_r.setConf(t.sequence),ri(t.gantt),li(t.class),zi(t.state),qi(t.state),Oa(t.class),za(t.class),ro(t.er),Lo(t.journey),Ba(t.class)}function Ho(){}var Vo=Object.freeze({render:function(t,e,n,r){Et();var i=e,a=H.detectInit(i);a&&wt(a);var o=_t();if(e.length>o.maxTextSize&&(i="graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa"),void 0!==r)r.innerHTML="",Object(d.select)(r).append("div").attr("id","d"+t).attr("style","font-family: "+o.fontFamily).append("svg").attr("id",t).attr("width","100%").attr("xmlns","http://www.w3.org/2000/svg").append("g");else{var s=document.getElementById(t);s&&s.remove();var u=document.querySelector("#d"+t);u&&u.remove(),Object(d.select)("body").append("div").attr("id","d"+t).append("svg").attr("id",t).attr("width","100%").attr("xmlns","http://www.w3.org/2000/svg").append("g")}window.txt=i,i=function(t){var e=t;return e=(e=(e=e.replace(/style.*:\S*#.*;/g,(function(t){return t.substring(0,t.length-1)}))).replace(/classDef.*:\S*#.*;/g,(function(t){return t.substring(0,t.length-1)}))).replace(/#\w+;/g,(function(t){var e=t.substring(1,t.length-1);return/^\+?\d+$/.test(e)?"fl°°"+e+"¶ß":"fl°"+e+"¶ß"}))}(i);var l=Object(d.select)("#d"+t).node(),h=H.detectType(i),g=l.firstChild,y=g.firstChild,v="";if(void 0!==o.themeCSS&&(v+="\n".concat(o.themeCSS)),void 0!==o.fontFamily&&(v+="\n:root { --mermaid-font-family: ".concat(o.fontFamily,"}")),void 0!==o.altFontFamily&&(v+="\n:root { --mermaid-alt-font-family: ".concat(o.altFontFamily,"}")),"flowchart"===h||"flowchart-v2"===h||"graph"===h){var m=be(i);for(var b in m)v+="\n.".concat(b," > * { ").concat(m[b].styles.join(" !important; ")," !important; }"),m[b].textStyles&&(v+="\n.".concat(b," tspan { ").concat(m[b].textStyles.join(" !important; ")," !important; }"))}var x=(new f.a)("#".concat(t),Yo(h,v,o.themeVariables)),_=document.createElement("style");_.innerHTML=x,g.insertBefore(_,y);try{switch(h){case"git":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,ka(o.git),wa(i,t,!1);break;case"flowchart":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,me(o.flowchart),xe(i,t,!1);break;case"flowchart-v2":o.flowchart.arrowMarkerAbsolute=o.arrowMarkerAbsolute,Ln(o.flowchart),Fn(i,t,!1);break;case"sequence":o.sequence.arrowMarkerAbsolute=o.arrowMarkerAbsolute,o.sequenceDiagram?(_r.setConf(Object.assign(o.sequence,o.sequenceDiagram)),console.error("`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.")):_r.setConf(o.sequence),_r.draw(i,t);break;case"gantt":o.gantt.arrowMarkerAbsolute=o.arrowMarkerAbsolute,ri(o.gantt),ii(i,t);break;case"class":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,li(o.class),hi(i,t);break;case"classDiagram":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,di(o.class),pi(i,t);break;case"state":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,zi(o.state),Ui(i,t);break;case"stateDiagram":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,qi(o.state),Xi(i,t);break;case"info":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,Oa(o.class),Da(i,t,p.version);break;case"pie":o.class.arrowMarkerAbsolute=o.arrowMarkerAbsolute,za(o.pie),Ua(i,t,p.version);break;case"er":ro(o.er),io(i,t,p.version);break;case"journey":Lo(o.journey),Fo(i,t,p.version)}}catch(e){throw La(t,p.version),e}Object(d.select)('[id="'.concat(t,'"]')).selectAll("foreignobject > *").attr("xmlns","http://www.w3.org/1999/xhtml");var k=Object(d.select)("#d"+t).node().innerHTML;if(c.debug("cnf.arrowMarkerAbsolute",o.arrowMarkerAbsolute),o.arrowMarkerAbsolute&&"false"!==o.arrowMarkerAbsolute||(k=k.replace(/marker-end="url\(.*?#/g,'marker-end="url(#',"g")),k=function(t){var e=t;return e=(e=(e=e.replace(/fl°°/g,(function(){return"&#"}))).replace(/fl°/g,(function(){return"&"}))).replace(/¶ß/g,(function(){return";"}))}(k),void 0!==n)switch(h){case"flowchart":case"flowchart-v2":n(k,Xt.bindFunctions);break;case"gantt":n(k,Qr.bindFunctions);break;case"class":case"classDiagram":n(k,cn.bindFunctions);break;default:n(k)}else c.debug("CB = undefined!");var w=Object(d.select)("#d"+t).node();return null!==w&&"function"==typeof w.remove&&Object(d.select)("#d"+t).node().remove(),k},parse:function(t){var e=H.detectInit(t);e&&c.debug("reinit ",e);var n,r=H.detectType(t);switch(c.debug("Type "+r),r){case"git":(n=ha.a).parser.yy=ua;break;case"flowchart":case"flowchart-v2":Xt.clear(),(n=Jt.a).parser.yy=Xt;break;case"sequence":(n=Hn.a).parser.yy=sr;break;case"gantt":(n=wr.a).parser.yy=Qr;break;case"class":case"classDiagram":(n=oi.a).parser.yy=cn;break;case"state":case"stateDiagram":(n=Di.a).parser.yy=Mi;break;case"info":c.debug("info info info"),(n=Sa.a).parser.yy=Ca;break;case"pie":c.debug("pie"),(n=Ra.a).parser.yy=Ia;break;case"er":c.debug("er"),(n=Xa.a).parser.yy=Ga;break;case"journey":c.debug("Journey"),(n=oo.a).parser.yy=go}return n.parser.yy.graphType=r,n.parser.yy.parseError=function(t,e){throw{str:t,hash:e}},n.parse(t),n},parseDirective:function(t,e,n,r){try{if(void 0!==e)switch(e=e.trim(),n){case"open_directive":Uo={};break;case"type_directive":Uo.type=e.toLowerCase();break;case"arg_directive":Uo.args=JSON.parse(e);break;case"close_directive":$o(t,Uo,r),Uo=null}}catch(t){c.error("Error while rendering sequenceDiagram directive: ".concat(e," jison context: ").concat(n)),c.error(t.message)}},initialize:function(t){t&&t.fontFamily&&(t.themeVariables&&t.themeVariables.fontFamily||(t.themeVariables={fontFamily:t.fontFamily})),dt=I({},t),t&&t.theme&&ht[t.theme]?t.themeVariables=ht[t.theme].getThemeVariables(t.themeVariables):t&&(t.themeVariables=ht.default.getThemeVariables(t.themeVariables));var e="object"===zo(t)?function(t){return yt=I({},gt),yt=I(yt,t),t.theme&&(yt.themeVariables=ht[t.theme].getThemeVariables(t.themeVariables)),mt=bt(yt,vt),yt}(t):xt();Wo(e),u(e.logLevel)},reinitialize:Ho,getConfig:_t,setConfig:function(t){return I(mt,t),_t()},getSiteConfig:xt,updateSiteConfig:function(t){return yt=I(yt,t),bt(yt,vt),yt},reset:function(){Et()},globalReset:function(){Et(),Wo(_t())},defaultConfig:gt});u(_t().logLevel),Et(_t());var Go=Vo,qo=function(){Xo.startOnLoad?Go.getConfig().startOnLoad&&Xo.init():void 0===Xo.startOnLoad&&(c.debug("In start, no config"),Go.getConfig().startOnLoad&&Xo.init())};"undefined"!=typeof document&& +/*! + * Wait for document loaded before starting the execution + */ +window.addEventListener("load",(function(){qo()}),!1);var Xo={startOnLoad:!0,htmlLabels:!0,mermaidAPI:Go,parse:Go.parse,render:Go.render,init:function(){var t,e,n=this,r=Go.getConfig();arguments.length>=2?( +/*! sequence config was passed as #1 */ +void 0!==arguments[0]&&(Xo.sequenceConfig=arguments[0]),t=arguments[1]):t=arguments[0],"function"==typeof arguments[arguments.length-1]?(e=arguments[arguments.length-1],c.debug("Callback function found")):void 0!==r.mermaid&&("function"==typeof r.mermaid.callback?(e=r.mermaid.callback,c.debug("Callback function found")):c.debug("No Callback function found")),t=void 0===t?document.querySelectorAll(".mermaid"):"string"==typeof t?document.querySelectorAll(t):t instanceof window.Node?[t]:t,c.debug("Start On Load before: "+Xo.startOnLoad),void 0!==Xo.startOnLoad&&(c.debug("Start On Load inner: "+Xo.startOnLoad),Go.updateSiteConfig({startOnLoad:Xo.startOnLoad})),void 0!==Xo.ganttConfig&&Go.updateSiteConfig({gantt:Xo.ganttConfig});for(var a,o=H.initIdGeneratior(r.deterministicIds,r.deterministicIDSeed).next,s=function(r){var s=t[r]; +/*! Check if previously processed */if(s.getAttribute("data-processed"))return"continue";s.setAttribute("data-processed",!0);var u="mermaid-".concat(o());a=i(a=s.innerHTML).trim().replace(//gi,"
");var l=H.detectInit(a);l&&c.debug("Detected early reinit: ",l);try{Go.render(u,a,(function(t,n){s.innerHTML=t,void 0!==e&&e(u),n&&n(s)}),s)}catch(t){c.warn("Syntax Error rendering"),c.warn(t),n.parseError&&n.parseError(t)}},u=0;u ⚠️ Here lay dragons: this codebase is still experimental, try at your own risk! + +## About the documentation + +The two main sections of this book are: + +- [Exploration](./explore): documents the process of exploring the design and implementation space for Anoma +- [Specifications](./specs): implementation independent technical specifications + +The Anoma user guide and networks documentation can be found at . + +### The source + +This book is written using [mdBook](https://rust-lang.github.io/mdBook/) with [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid) for diagrams, it currently lives in the [Anoma repo](https://github.com/anoma/anoma). + +To get started quickly, in the `docs` directory one can: + +```shell +# Install dependencies +make dev-deps + +# This will open the book in your default browser and rebuild on changes +make serve +``` + +The mermaid diagrams docs can be found at . + +[Contributions](https://github.com/anoma/anoma/issues) to the contents and the structure of this book (nothing is set in stone) should be made via pull requests. Code changes that diverge from the spec should also update this book. diff --git a/documentation/dev/src/SUMMARY.md b/documentation/dev/src/SUMMARY.md new file mode 100644 index 00000000000..f0d84c6afa0 --- /dev/null +++ b/documentation/dev/src/SUMMARY.md @@ -0,0 +1,63 @@ +# Summary + +- [Introduction](./README.md) +- [Exploration](./explore/README.md) + - [Design](./explore/design/README.md) + - [Overview](./explore/design/overview.md) + - [Gossip network](./explore/design/gossip.md) + - [Intent gossip](./explore/design/intent_gossip/intent_gossip.md) + - [Intent](./explore/design/intent_gossip/intent.md) + - [Topic](./explore/design/intent_gossip/topic.md) + - [Incentive](./explore/design/intent_gossip/incentive.md) + - [Matchmaker](./explore/design/intent_gossip/matchmaker.md) + - [Fungible token](./explore/design/intent_gossip/fungible_token.md) + - [Distributed key generation gossip](./explore/design/dkg.md) + - [The ledger](./explore/design/ledger.md) + - [Parameters](./explore/design/ledger/parameters.md) + - [Epochs](./explore/design/ledger/epochs.md) + - [Accounts](./explore/design/ledger/accounts.md) + - [Validity predicates](./explore/design/ledger/vp.md) + - [Transactions](./explore/design/ledger/tx.md) + - [WASM VM](./explore/design/ledger/wasm-vm.md) + - [Front-running prevention](./explore/design/ledger/front-running.md) + - [Fractal scaling](./explore/design/ledger/fractal-scaling.md) + - [Upgrade system](./explore/design/upgrade-system.md) + - [Storage](./explore/design/ledger/storage.md) + - [Data schema](./explore/design/ledger/storage/data-schema.md) + - [PoS integration](./explore/design/ledger/pos-integration.md) + - [Crypto primitives](./explore/design/crypto-primitives.md) + - [Actors](./explore/design/actors.md) + - [Proof of Stake system](./explore/design/pos.md) + - [Testnet setup](./explore/design/testnet-setup.md) + - [Testnet launch procedure](./explore/design/testnet-launch-procedure/README.md) + - [Prototypes](./explore/prototypes/README.md) + - [Base ledger](./explore/prototypes/base-ledger.md) + - [Gossip layer](./explore/prototypes/gossip-layer.md) + - [Libraries & Tools](./explore/libraries/README.md) + - [Cryptography]() + - [network](./explore/libraries/network.md) + - [Command-line interface](./explore/libraries/cli.md) + - [Database](./explore/libraries/db.md) + - [Logging](./explore/libraries/logging.md) + - [Networking]() + - [Packaging](./explore/libraries/packaging.md) + - [Serialization](./explore/libraries/serialization.md) + - [WASM runtime](./explore/libraries/wasm.md) + - [Error handling](./explore/libraries/errors.md) + - [Glossary](./explore/design/glossary.md) + - [Resources](./explore/resources/README.md) + - [IDE](./explore/resources/ide.md) +- [Specifications](./specs/README.md) + - [Overview](./specs/overview.md) + - [The ledger](./specs/ledger.md) + - [RPC](./specs/ledger/rpc.md) + - [Default transactions](./specs/ledger/default-transactions.md) + - [Default validity predicates](./specs/ledger/default-validity-predicates.md) + - [Trade system]() + - [Intent gossip system]() + - [Fractal scaling]() + - [Upgrade system]() + - [Crypto](./specs/crypto.md) + - [Encoding](./specs/encoding.md) +- [Archive](./archive/README.md) + - [Domain name addresses](./archive/domain-name-addresses.md) diff --git a/documentation/dev/src/archive/README.md b/documentation/dev/src/archive/README.md new file mode 100644 index 00000000000..415bb0a8b95 --- /dev/null +++ b/documentation/dev/src/archive/README.md @@ -0,0 +1,3 @@ +# Archive + +Deprecated pages archived for possible later re-use. diff --git a/documentation/dev/src/archive/domain-name-addresses.md b/documentation/dev/src/archive/domain-name-addresses.md new file mode 100644 index 00000000000..0b752202ae8 --- /dev/null +++ b/documentation/dev/src/archive/domain-name-addresses.md @@ -0,0 +1,21 @@ +# Domain name addresses + +The transparent addresses are similar to domain names and the ones used in e.g. [ENS as specified in EIP-137](https://eips.ethereum.org/EIPS/eip-137) and [account IDs in Near protocol](https://nomicon.io/DataStructures/Account.html). These are the addresses of accounts associated with dynamic storage sub-spaces, where the address of the account is the prefix key segment of its sub-space. + +A transparent address is a human-readable string very similar to a domain name, containing only alpha-numeric ASCII characters, hyphen (`-`) and full stop (`.`) as a separator between the "labels" of the address. The letter case is not significant and any upper case letters are converted to lower case. The last label of an address is said to be the top-level name and each predecessor segment is the sub-name of its successor. + +The length of an address must be at least 3 characters. For compatibility with a legacy DNS TXT record, we'll use syntax as defined in [RFC-1034 - section 3.5 DNS preferred name syntax](https://www.ietf.org/rfc/rfc1034.txt). That is, the upper limit is 255 characters and 63 for each label in an address (which should be sufficient anyway); and the label must not begin or end with hyphen (`-`) and must not begin with a digit. + +These addresses can be chosen by users who wish to [initialize a new account](#initializing-a-new-account), following these rules: + +- a new address must be initialized on-chain + - each sub-label must be authorized by the predecessor level address (e.g. initializing address `free.eth` must be authorized by `eth`, or `gives.free.eth` by `free.eth`, etc.) + - note that besides the address creation, each address level is considered to be a distinct address with its own dynamic storage sub-space and validity predicate. +- the top-level names under certain length (to be specified) cannot be initialized directly, they may be [auctioned like in ENS registrar as described in EIP-162](https://eips.ethereum.org/EIPS/eip-162). + - some top-level names may be reserved + +For convenience, the `anoma` top-level address is initially setup to allow initialization of any previously unused second-level address, e.g. `bob.anoma` (we may want to revise this before launch to e.g. auction the short ones, like with top-level names to make the process fairer). + +Like in ENS, the addresses are stored on chain by their hash, encoded with [bech32m](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) ([not yet adopted in Zcash](https://github.com/zcash/zips/issues/484)), which is an improved version of [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki). Likewise, this is for two reasons: +- help preserve privacy of addresses that were not revealed publicly and to prevent trivial enumeration of registered names (of course, you can still try to enumerate by hashes) +- using fixed-length string in the ledger simplifies gas accounting diff --git a/documentation/dev/src/explore/README.md b/documentation/dev/src/explore/README.md new file mode 100644 index 00000000000..fc5b5117cf2 --- /dev/null +++ b/documentation/dev/src/explore/README.md @@ -0,0 +1,5 @@ +# Exploration + +This section documents the process of exploring the design and implementation space for Anoma. Ideally, the captured information should provide an overview of the explored space and help to guide further decisions. + +The content of this section is more free-form. This is largely a cross-over of both the implementation details and the design of implementation-independent specifications. diff --git a/documentation/dev/src/explore/design/README.md b/documentation/dev/src/explore/design/README.md new file mode 100644 index 00000000000..ca7061372f5 --- /dev/null +++ b/documentation/dev/src/explore/design/README.md @@ -0,0 +1,3 @@ +# Design + +This section covers the exploration of the possible design directions of the involved components. diff --git a/documentation/dev/src/explore/design/actors.md b/documentation/dev/src/explore/design/actors.md new file mode 100644 index 00000000000..d742097db74 --- /dev/null +++ b/documentation/dev/src/explore/design/actors.md @@ -0,0 +1,41 @@ +# Actors and Incentives + +Anoma consists of various actors fulfilling various roles in the network. They are all incentivized to act for the good of the network. The native Anoma token `XAN` is used to settle transaction fees and pay for the incentives in Anoma. + +## Fees associated with a transaction + +Users of Anoma can + +- transfer private assets they hold to other users and +- barter assets with other users. + +Each transaction may be associated with the following fees, paid in `XAN`: + +- **Execution fees** to compensate for computing, storage and memory costs, charges at 2 stages: + - **initial fee (init_f)**: charged before the transaction is settled + - **post-execution fee (exe_f)**: charged after the settlement +- **Exchange fee (ex_f)**: a fee proportional to the value exchanged in a trade + +## Actors and their associated fees and responsibilities + +| Actor | Responsibilities | Incentives | Bond in escrow | May also be | +|---|---|---|---|---| +| User | Make offers or send transactions | Features of Anoma | X | Anyone | +| Signer | Generate key shards | portions of init_f, exe_f | ✓ | Validator | +| Validator | Validate | portions of init_f, exe_f |✓ | Signer | +| Submitter | Submit orders & pay init_f | successful orders get init_f back plus bonus | X | | +| Intent gossip operator | Signs and shares orders | portions of init_f, exe_f | X | | +| Market maker | Signs and broadcast orders | the difference between the ask and bid price | X | | +| Proposer | Proposes blocks | portions of init_f, exe_f | | Validator | + +Questions to explore: + +- How do we calculate the incentives? What are the equations for each actor? + +- How do we calculate the bond/reward for the signers and validators? + +- How do we ensure certain dual/multi agencies are allowed but not others? E.g., signers can be validators but we may not want them to be proposers because they may have knowledge of which transactions are encrypted. + +## Actors and fees flowchart + +![Summary](summary.png?raw=true) diff --git a/documentation/dev/src/explore/design/crypto-primitives.md b/documentation/dev/src/explore/design/crypto-primitives.md new file mode 100644 index 00000000000..81f68cb7917 --- /dev/null +++ b/documentation/dev/src/explore/design/crypto-primitives.md @@ -0,0 +1,7 @@ +# Crypto primitives + +[Tracking Issue](https://github.com/anoma/anoma/issues/39) + +--- + +This page should describe cryptography primitives that we might want to use, such as types of keys, hashing functions, etc. diff --git a/documentation/dev/src/explore/design/dkg.md b/documentation/dev/src/explore/design/dkg.md new file mode 100644 index 00000000000..204b89c63ca --- /dev/null +++ b/documentation/dev/src/explore/design/dkg.md @@ -0,0 +1,2 @@ +# Distributed key generation gossip +> ⚠️ This section is WIP. diff --git a/documentation/dev/src/explore/design/glossary.md b/documentation/dev/src/explore/design/glossary.md new file mode 100644 index 00000000000..7de04470916 --- /dev/null +++ b/documentation/dev/src/explore/design/glossary.md @@ -0,0 +1,17 @@ +# Glossary + +[comment]: <> (Each item in the list below has to be followed by 2 spaces with the description on the very next line) + +- **intent gossip** +The intent gossip network must maintain a mempool of intents and gossips them +via a p2p layer. Each intent gossip node maintains a list of interests that +describe what intents it is interested in. +- **intent** +An expression of intent describes a particular trade an account agrees to. +- **matchmaker** +The matchmaker tries to match intents together. For each match it crafts a valid +transaction and submits it to the base ledger. +- **validity predicate (VP)** +A [validity predicate](ledger/vp.html) is a piece of code +attached to an account that can accept or reject any state changes performed by +a transaction in its sub-space. diff --git a/documentation/dev/src/explore/design/gossip.md b/documentation/dev/src/explore/design/gossip.md new file mode 100644 index 00000000000..4879363d330 --- /dev/null +++ b/documentation/dev/src/explore/design/gossip.md @@ -0,0 +1,17 @@ +# The gossip network + +The gossip network runs in parallel to the ledger network and is used to +propagate off-chain information. The network is based on +[libp2p](https://libp2p.io/) , a peer to peer network system that is implemented +in different languages, has a large user base and an active development. It +allows us to readily implement a network to run our application. + +The gossip network is used to propagate messages of two different applications, +intents for the [intent gossip system](intent_gossip/intent_gossip.md), and message for distributed keys +generation application. + +## Flow diagram: High level overview + +![gossip process](./gossip_process.svg "gossip process") + +[Diagram on Excalidraw](https://excalidraw.com/#room=5d4a2a84ef52cf5f5f96,r4ghl40frJ9putMy-0vyOQ) diff --git a/documentation/dev/src/explore/design/gossip_process.excalidraw b/documentation/dev/src/explore/design/gossip_process.excalidraw new file mode 100644 index 00000000000..36842526939 --- /dev/null +++ b/documentation/dev/src/explore/design/gossip_process.excalidraw @@ -0,0 +1,966 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "ellipse", + "version": 603, + "versionNonce": 717195654, + "isDeleted": false, + "id": "9XNyo7y8QCSEp4yAFf5oy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1156.111111111111, + "y": 164.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 573.6666666666667, + "height": 372.55555555555554, + "seed": 821968881, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "arrow", + "version": 1450, + "versionNonce": 941173851, + "isDeleted": false, + "id": "lXNINuf2v3Bas7FefE3LH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1586.2054115693877, + "y": -370.2728566628903, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 246.73688630955917, + "height": 192.92404363452567, + "seed": 736805503, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "5xDzwVV0gDBlD-WL85LYy", + "focus": 0.18717224637187657, + "gap": 6.459472910604248 + }, + "endBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "focus": -0.23250849205562685, + "gap": 13.411672225924079 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -246.73688630955917, + 192.92404363452567 + ] + ] + }, + { + "type": "diamond", + "version": 278, + "versionNonce": 198810363, + "isDeleted": false, + "id": "5xDzwVV0gDBlD-WL85LYy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1565.6666666666665, + "y": -444.66666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 127, + "height": 100, + "seed": 563016927, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "lXNINuf2v3Bas7FefE3LH", + "RR6GEDZFWUssCdTAnrDqo" + ] + }, + { + "type": "text", + "version": 253, + "versionNonce": 1818093109, + "isDeleted": false, + "id": "HskYIBtFFYajWD-2Djn1f", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1605.1666666666665, + "y": -409.66666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 50, + "height": 26, + "seed": 1643469585, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "lXNINuf2v3Bas7FefE3LH" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "client", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "rectangle", + "version": 432, + "versionNonce": 232368667, + "isDeleted": false, + "id": "5cPSOu9KDeCIp3Nmd546m", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1176, + "y": -287.2222222222222, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 570.0000000000001, + "height": 356.2222222222222, + "seed": 1306655743, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "diamond", + "version": 627, + "versionNonce": 1842306310, + "isDeleted": false, + "id": "xxAsLeElpbNGHmw4QPxMz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1181.3333333333333, + "y": -207.55555555555554, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 209, + "height": 188, + "seed": 1389553521, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "B4iQSfzoi419GYzKlXw5m", + "MPfW6JEUQYoWrzHeI_NxD", + "z6DMUeY7qrwuosEZr9D8K", + "GJPwvquPv4Afa-iz06Txv", + "bleUDuct9nWGxJfhyl_J2", + "V6oy4uJc_J45aC2sNpHvE", + "lXNINuf2v3Bas7FefE3LH", + "OIBiMf6VpPETwJAGERKti", + "J8EUyGyYHRSW895zskWbp" + ] + }, + { + "type": "diamond", + "version": 604, + "versionNonce": 1476920774, + "isDeleted": false, + "id": "dSBMH7Bqm1h_eoLM_yqkZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1184.4444444444446, + "y": 264.99999999999994, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 307.888888888889, + "height": 215.66666666666666, + "seed": 1838896927, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "OIBiMf6VpPETwJAGERKti", + "MPfW6JEUQYoWrzHeI_NxD", + "FqT97u9kn8yPY-e9ZEmwX", + "3w6C3S5gFIBLSOSnHrL9p", + "J8EUyGyYHRSW895zskWbp" + ] + }, + { + "type": "text", + "version": 446, + "versionNonce": 2067771125, + "isDeleted": false, + "id": "FOQ3kxQ9jn8r8iZ6-5Kdn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1227.3333333333333, + "y": -149.55555555555551, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 121, + "height": 52, + "seed": 1118918783, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent \nbroadcaster", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 346, + "versionNonce": 94289307, + "isDeleted": false, + "id": "wNwxFCvE4WMQV8QLZiqxu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1272, + "y": 281.99999999999994, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 131, + "height": 78, + "seed": 197177745, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "OIBiMf6VpPETwJAGERKti", + "MPfW6JEUQYoWrzHeI_NxD", + "J8EUyGyYHRSW895zskWbp" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "intent\nbroadcaster \nnetwork", + "baseline": 70, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1548, + "versionNonce": 1059405510, + "isDeleted": false, + "id": "OIBiMf6VpPETwJAGERKti", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1340.8991464307837, + "y": 256.43457590166764, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 19.424658574333307, + "height": 274.53680436823555, + "seed": 413185503, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", + "focus": 0.06980420648639936, + "gap": 8.455707710143173 + }, + "endBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "focus": -0.27643465526659067, + "gap": 24.916176360598527 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -19.424658574333307, + -274.53680436823555 + ] + ] + }, + { + "type": "arrow", + "version": 1783, + "versionNonce": 702656213, + "isDeleted": false, + "id": "MPfW6JEUQYoWrzHeI_NxD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1225.7890453334985, + "y": -41.97976066319893, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 21.266468152933612, + "height": 381.0875685373966, + "seed": 1670492081, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "gap": 23.483962590379445, + "focus": 0.5312629502131418 + }, + "endBinding": { + "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", + "gap": 16.103707014847412, + "focus": -0.8818009263489358 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -21.266468152933612, + 381.0875685373966 + ] + ] + }, + { + "type": "text", + "version": 540, + "versionNonce": 1475775174, + "isDeleted": false, + "id": "InLHuKJiMfsKjnhDLER2S", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1224.6666666666667, + "y": 122.99999999999997, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 52, + "seed": 1075843217, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "validate \nmsg", + "baseline": 44, + "textAlign": "right", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1384, + "versionNonce": 280535450, + "isDeleted": false, + "id": "J8EUyGyYHRSW895zskWbp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1386.7368978905408, + "y": 286.51414709947557, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 19.54258711706757, + "height": 343.3666092756827, + "seed": 1500547647, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", + "focus": 0.34597438474774744, + "gap": 10.117038200934843 + }, + "endBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "focus": -0.7476913039884736, + "gap": 26.682463765500543 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -19.54258711706757, + -343.3666092756827 + ] + ] + }, + { + "type": "text", + "version": 452, + "versionNonce": 1029595482, + "isDeleted": false, + "id": "3a6V_ozlu_-z813t_fjbn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1391, + "y": 187.77777777777777, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 156, + "height": 36, + "seed": 15072497, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "libP2P logic", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "diamond", + "version": 365, + "versionNonce": 757518677, + "isDeleted": false, + "id": "hngD3gT8MxLbMtULxwILm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1204.6111111111113, + "y": -434.0000000000001, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 127, + "height": 100, + "seed": 662285233, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "lXNINuf2v3Bas7FefE3LH", + "V6oy4uJc_J45aC2sNpHvE" + ] + }, + { + "type": "text", + "version": 334, + "versionNonce": 979982971, + "isDeleted": false, + "id": "Tne4ggu_ZDCdoqKD1HVLY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1244.1111111111113, + "y": -402.0000000000001, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 50, + "height": 26, + "seed": 1728898015, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "lXNINuf2v3Bas7FefE3LH" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "client", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "arrow", + "version": 1682, + "versionNonce": 1212570555, + "isDeleted": false, + "id": "V6oy4uJc_J45aC2sNpHvE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1262.664151563201, + "y": -330.8400855354932, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.5819871518015134, + "height": 140.4581124311759, + "seed": 1528357951, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "hngD3gT8MxLbMtULxwILm", + "focus": 0.10105347446704833, + "gap": 5.8523740982304915 + }, + "endBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "focus": -0.18349209857314347, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.5819871518015134, + 140.4581124311759 + ] + ] + }, + { + "type": "text", + "version": 377, + "versionNonce": 1391350555, + "isDeleted": false, + "id": "q9mdbiPTZ5bMiHS1pP8Jd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1267.8333333333333, + "y": -327.77777777777777, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 26, + "seed": 280036991, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "send msg", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 138, + "versionNonce": 2055493659, + "isDeleted": false, + "id": "JdaaIi8F9pCIbQiuefyHi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1388.75, + "y": -206.72222222222223, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 180, + "height": 35, + "seed": 476397371, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 3, + "text": "Gossip Node", + "baseline": 28, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 419, + "versionNonce": 1035911669, + "isDeleted": false, + "id": "E1bibepZLZFj3VmWghmhk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1557.1388888888887, + "y": -324.94444444444554, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 26, + "seed": 1482472437, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "send msg", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "diamond", + "version": 629, + "versionNonce": 251582106, + "isDeleted": false, + "id": "JaiST4fQ2FlodcbDsHGRd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1525.611111111111, + "y": -178.99999999999997, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 209, + "height": 188, + "seed": 1161636571, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "B4iQSfzoi419GYzKlXw5m", + "MPfW6JEUQYoWrzHeI_NxD", + "z6DMUeY7qrwuosEZr9D8K", + "GJPwvquPv4Afa-iz06Txv", + "bleUDuct9nWGxJfhyl_J2", + "RR6GEDZFWUssCdTAnrDqo", + "yZ1sDbO9UxZw6Yw7d8BGk", + "sofyhfQ6yAERl8W7igqud", + "98jHSy6KgIgI-kigPhATL", + "Jj-F4u_NSjuW-tVWK6ErH" + ] + }, + { + "type": "text", + "version": 477, + "versionNonce": 320811829, + "isDeleted": false, + "id": "yE8obB-cq-eO9tpUS_PcA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1575.611111111111, + "y": -117.99999999999994, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 121, + "height": 52, + "seed": 413385813, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "DKG \nbroadcaster", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 115, + "versionNonce": 808640859, + "isDeleted": false, + "id": "RR6GEDZFWUssCdTAnrDqo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1660.0572555330295, + "y": -366.9575940330065, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 24.665187381169744, + "height": 183.91639038865497, + "seed": 45437883, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "5xDzwVV0gDBlD-WL85LYy", + "focus": -0.5447225569886008, + "gap": 1.5967808146620044 + }, + "endBinding": { + "elementId": "JaiST4fQ2FlodcbDsHGRd", + "focus": -0.07528645180716763, + "gap": 6.5362537154315845 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -24.665187381169744, + 183.91639038865497 + ] + ] + }, + { + "type": "diamond", + "version": 596, + "versionNonce": 1248600538, + "isDeleted": false, + "id": "sJFOeRzApKjnkoavWYSOm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1415.6666666666667, + "y": 270.6666666666667, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 292.8888888888888, + "height": 207.6666666666666, + "seed": 3834485, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "OIBiMf6VpPETwJAGERKti", + "MPfW6JEUQYoWrzHeI_NxD", + "FqT97u9kn8yPY-e9ZEmwX", + "3w6C3S5gFIBLSOSnHrL9p", + "J8EUyGyYHRSW895zskWbp", + "yZ1sDbO9UxZw6Yw7d8BGk", + "sofyhfQ6yAERl8W7igqud", + "98jHSy6KgIgI-kigPhATL", + "Jj-F4u_NSjuW-tVWK6ErH" + ] + }, + { + "type": "text", + "version": 354, + "versionNonce": 339994458, + "isDeleted": false, + "id": "ERwB6OfVxtsDLuZO1G4dn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1507.2222222222224, + "y": 292.6666666666667, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 131, + "height": 78, + "seed": 410831707, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "OIBiMf6VpPETwJAGERKti", + "MPfW6JEUQYoWrzHeI_NxD", + "yZ1sDbO9UxZw6Yw7d8BGk" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "DKG\nbroadcaster \nnetwork", + "baseline": 70, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 432, + "versionNonce": 855557126, + "isDeleted": false, + "id": "yZ1sDbO9UxZw6Yw7d8BGk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1613.2645903010791, + "y": 5.6816480400971585, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.37650849922488, + "height": 267.8739593512054, + "seed": 978571771, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "JaiST4fQ2FlodcbDsHGRd", + "focus": 0.056328322376417134, + "gap": 8.799318498256582 + }, + "endBinding": { + "elementId": "ERwB6OfVxtsDLuZO1G4dn", + "focus": 0.016268660508214034, + "gap": 19.11105927536414 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -32.37650849922488, + 267.8739593512054 + ] + ] + }, + { + "type": "arrow", + "version": 398, + "versionNonce": 2067988678, + "isDeleted": false, + "id": "Jj-F4u_NSjuW-tVWK6ErH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1623.0089473868802, + "y": 304.406818117378, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 33.75764899556157, + "height": 307.2241654227815, + "seed": 398843163, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "sJFOeRzApKjnkoavWYSOm", + "focus": 0.3632505836483008, + "gap": 7.699251414567215 + }, + "endBinding": { + "elementId": "JaiST4fQ2FlodcbDsHGRd", + "focus": -0.3414897328591685, + "gap": 9.04048516465393 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 33.75764899556157, + -307.2241654227815 + ] + ] + }, + { + "type": "text", + "version": 554, + "versionNonce": 720882453, + "isDeleted": false, + "id": "kXVNso4ItXnAMFArYHbYk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1486.1111111111115, + "y": 109.99999999999997, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 52, + "seed": 1662723835, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "validate \nmsg", + "baseline": 44, + "textAlign": "right", + "verticalAlign": "top" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/gossip_process.svg b/documentation/dev/src/explore/design/gossip_process.svg new file mode 100644 index 00000000000..32a046a1ac9 --- /dev/null +++ b/documentation/dev/src/explore/design/gossip_process.svg @@ -0,0 +1,16 @@ + + + + + + + clientIntent broadcasterintentbroadcaster networkvalidate msglibP2P logicclientsend msgGossip Nodesend msgDKG broadcasterDKGbroadcaster networkvalidate msg \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/example.excalidraw b/documentation/dev/src/explore/design/intent_gossip/example.excalidraw new file mode 100644 index 00000000000..876d8a453bf --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/example.excalidraw @@ -0,0 +1,2202 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "version": 686, + "versionNonce": 1681340216, + "isDeleted": false, + "id": "8-d43siu84Jr4CoHQ4O3h", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 87.4761904761906, + "y": 120.73809523809535, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 188, + "height": 120, + "seed": 454219117, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "- 400 < X < 1000 USD\n- rate USD to EUR: \n >= 1.5 EUR/USD\n- rate USD to CZK:\n >= 10 USD/CZK\n- in less than 20 mn", + "baseline": 116, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 196, + "versionNonce": 124057672, + "isDeleted": false, + "id": "pMCzM8kGspun9xkTr6k5d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 61.95238095238085, + "y": 83.64285714285725, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 94, + "height": 25, + "seed": 1950434275, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "- DATA: ", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 295, + "versionNonce": 1203474488, + "isDeleted": false, + "id": "2p7QI8-lBoSz4ic4BK3eT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 44.61904761904748, + "y": 43.0714285714287, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 284.3809523809525, + "height": 245.90476190476187, + "seed": 1582276557, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "oYCojvs3_Pz8ZGLg2mw3V", + "954BBr80PqUI9uJuWnFsM", + "uKTSmBiHZULyjH127gePI", + "Czhwb2CjT6al5-6rCgR97" + ] + }, + { + "type": "text", + "version": 156, + "versionNonce": 1336848200, + "isDeleted": false, + "id": "h9Y3DYl2lfuXh9QZLoKSa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 63.39285714285711, + "y": 255.0476190476184, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 152, + "height": 25, + "seed": 668258179, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "- timestamp T", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 448, + "versionNonce": 1263323448, + "isDeleted": false, + "id": "UiaZM4EJyss7ILeYlY9LS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 75.14285714285711, + "y": 118.35714285714295, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 247.28571428571422, + "height": 129.47619047619048, + "seed": 138681901, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "oYCojvs3_Pz8ZGLg2mw3V", + "bMCs-OrsKdLPqnA1PVwiv", + "954BBr80PqUI9uJuWnFsM" + ] + }, + { + "type": "text", + "version": 255, + "versionNonce": 1940489800, + "isDeleted": false, + "id": "MBhNEXU9Gx54BPICMfGTq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 66.14285714285711, + "y": 47.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 217, + "height": 52, + "seed": 607512355, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "oYCojvs3_Pz8ZGLg2mw3V", + "954BBr80PqUI9uJuWnFsM" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "intent from account A\n", + "baseline": 44, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1706, + "versionNonce": 441377336, + "isDeleted": false, + "id": "HBiEEEb8ounDRLLN9wXKf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 797.2148759236247, + "y": 300.28571428571433, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 82.58839012004557, + "height": 92.74093504094964, + "seed": 1181817966, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "UXXI91T9S5l9KHt7Wp0ZV", + "gap": 1, + "focus": -0.6432089438320445 + }, + "endBinding": { + "elementId": "Vva_ZJK0PzeOvHwKtR1hx", + "gap": 4.782874482859853, + "focus": 0.22336105149411092 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -82.58839012004557, + 92.74093504094964 + ] + ] + }, + { + "type": "arrow", + "version": 1822, + "versionNonce": 540114248, + "isDeleted": false, + "id": "oYCojvs3_Pz8ZGLg2mw3V", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 492.80297633357, + "y": 292.13629393845633, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 30.182646679681625, + "height": 92.45738907898789, + "seed": 1847628014, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "hf7oNcONr_HxLhhGRiJqE", + "gap": 10.279151081313444, + "focus": 0.38338854643484865 + }, + "endBinding": { + "elementId": "Vva_ZJK0PzeOvHwKtR1hx", + "gap": 13.215840792079547, + "focus": -0.011602561064372598 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 30.182646679681625, + 92.45738907898789 + ] + ] + }, + { + "type": "rectangle", + "version": 315, + "versionNonce": 1403378488, + "isDeleted": false, + "id": "UXXI91T9S5l9KHt7Wp0ZV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2.309523809529537, + "y": -69.04761904761892, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1041.3809523809525, + "height": 368.33333333333326, + "seed": 1810420256, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "oYCojvs3_Pz8ZGLg2mw3V", + "HBiEEEb8ounDRLLN9wXKf" + ] + }, + { + "type": "arrow", + "version": 941, + "versionNonce": 111318088, + "isDeleted": false, + "id": "Swcv4ZXJejaz5TYfHeDMu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 876.138834695055, + "y": -424.07142857142946, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 9.184895705945564, + "height": 459.29069763915254, + "seed": 936098418, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "usikQRBWwYKM-qybe0fQH", + "gap": 2, + "focus": -0.255388727353868 + }, + "endBinding": { + "elementId": "swYlyjktS4gIwsiQLSPjZ", + "gap": 9.066445217991259, + "focus": 0.15984810321549953 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -9.184895705945564, + 459.29069763915254 + ] + ] + }, + { + "type": "rectangle", + "version": 310, + "versionNonce": 431338552, + "isDeleted": false, + "id": "usikQRBWwYKM-qybe0fQH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 774.0119047619048, + "y": -530.0714285714295, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 164, + "height": 104, + "seed": 354374066, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Swcv4ZXJejaz5TYfHeDMu", + "ol2AB4uLuMEYj138srSeF" + ] + }, + { + "type": "rectangle", + "version": 312, + "versionNonce": 1289276232, + "isDeleted": false, + "id": "swYlyjktS4gIwsiQLSPjZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 700, + "y": 44.285714285714334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 282.8571428571426, + "height": 236, + "seed": 345326578, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "HBiEEEb8ounDRLLN9wXKf", + "Swcv4ZXJejaz5TYfHeDMu" + ] + }, + { + "type": "text", + "version": 268, + "versionNonce": 576875832, + "isDeleted": false, + "id": "wXWmmm8mpOqmxFuLw0x-V", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 798.0119047619048, + "y": -481.07142857142946, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 101, + "height": 26, + "seed": 992734510, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "account C", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 171, + "versionNonce": 2100306504, + "isDeleted": false, + "id": "XDW5IfPRFhlKsj3a1RbYD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 553.8333333333278, + "y": 327.61904761904776, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 127, + "height": 26, + "seed": 1564124704, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "fetch intents", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1247, + "versionNonce": 1192232504, + "isDeleted": false, + "id": "954BBr80PqUI9uJuWnFsM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 522.3067180843871, + "y": -423.5118681617779, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 5.583297287089863, + "height": 443.7930802552394, + "seed": 467951874, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "bDFsD9RIYPQQrHIWUfc_E", + "focus": 0.21113172608583025, + "gap": 6.8690842191745105 + }, + "endBinding": { + "elementId": "hf7oNcONr_HxLhhGRiJqE", + "focus": 0.002175851518750341, + "gap": 15.671168858919486 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -5.583297287089863, + 443.7930802552394 + ] + ] + }, + { + "type": "text", + "version": 399, + "versionNonce": 1988401480, + "isDeleted": false, + "id": "dYXMYP8IeZ5hPB8hDXUkx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 488.66666666666674, + "y": -491.04761904761915, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 99, + "height": 26, + "seed": 1024864898, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "account B", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 167, + "versionNonce": 1546793032, + "isDeleted": false, + "id": "Z7BWkND-NitKxr9Ih4Ie2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 389.80952380952374, + "y": 76.52380952380958, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 94, + "height": 25, + "seed": 469053826, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "- DATA: ", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 268, + "versionNonce": 1534974008, + "isDeleted": false, + "id": "hf7oNcONr_HxLhhGRiJqE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 372.47619047619037, + "y": 35.95238095238102, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 284.3809523809525, + "height": 245.90476190476187, + "seed": 25601394, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "oYCojvs3_Pz8ZGLg2mw3V", + "954BBr80PqUI9uJuWnFsM" + ] + }, + { + "type": "text", + "version": 459, + "versionNonce": 102033224, + "isDeleted": false, + "id": "oRS3euY4VEuk2SgUKpr1L", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 718.5714285714287, + "y": 138.8095238095238, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 188, + "height": 80, + "seed": 655184434, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "- 0 < X < 0.05 BTC \n- rate BTC to EUR: \n >= 0.00001 BTC/USD\n- in less than 1 mn", + "baseline": 76, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 182, + "versionNonce": 1065750840, + "isDeleted": false, + "id": "XrVgeTerY-7DS-ka0p0Em", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 716.5, + "y": 242.59523809523824, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 176, + "height": 25, + "seed": 1477693777, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "- timestamp T''", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 128, + "versionNonce": 1888009800, + "isDeleted": false, + "id": "d8WVAhFQ2zo-3HuEkCg6h", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 391.25, + "y": 247.9285714285707, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 164, + "height": 25, + "seed": 204593966, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "- timestamp T'", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 426, + "versionNonce": 1210001976, + "isDeleted": false, + "id": "Nkvl_WfjngjOTgW9Zbs2_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 390, + "y": 111.2380952380953, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 247.28571428571422, + "height": 129.47619047619048, + "seed": 1239365918, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "oYCojvs3_Pz8ZGLg2mw3V", + "bMCs-OrsKdLPqnA1PVwiv", + "954BBr80PqUI9uJuWnFsM" + ] + }, + { + "type": "rectangle", + "version": 337, + "versionNonce": 745125192, + "isDeleted": false, + "id": "LxE-VPfS6ezu0mmzjr8F1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 712.8571428571429, + "y": 130.2380952380953, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 237.2857142857145, + "height": 105.47619047619037, + "seed": 423857246, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 530, + "versionNonce": 640619320, + "isDeleted": false, + "id": "Hk5l9iiBugHCCPMbYtU63", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 12.624999999999886, + "y": 1537.291666666666, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 410, + "height": 250, + "seed": 212882431, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "if someone takes between\ntx.DATA.A.low to tx.DATA.A.high \ntx.DATA.A.output_asset \nfrom my account,\nit has to credit\nat least tx.DATA.A.min_rate \nthe amount in tx.DATA.A.input_asset\n and to be before\n (tx.DATA.A.timestamp\n + tx.DATA.A.TTL)", + "baseline": 245, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 645, + "versionNonce": 1326697544, + "isDeleted": false, + "id": "EygNVoDyqnn-JbFybEbGe", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 287.5238095238093, + "y": 660.2380952380953, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 281, + "height": 350, + "seed": 2012467634, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "- STATE FUNCTION : \n - 1/ src: A\n dest: C\n amount: 466.6 USD\n - 2/ src: C\n dest: B\n amount: 800 EUR\n - 3/ src: B \n dest: A\n amount: 0.017 BTC\n- DATA\n - 1/ intent A\n - 2/ intent B\n - 3/ intent C", + "baseline": 345, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 378, + "versionNonce": 1618836792, + "isDeleted": false, + "id": "0XL0H0k8apgsdhnJ1t5ab", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -3.9166666666667425, + "y": 1448.4999999999995, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 447.08333333333337, + "height": 377.9166666666665, + "seed": 124501362, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Zd243WE5eHC_WR8epiEAk", + "bMcnnlJo1LpwISJChgmKB", + "69EnjeqAfSHRFsprrsmiO", + "5qf0tF76bMQXA21Z0jkWa", + "cnhSr0ikLZ2glzJteomgZ", + "Jo906mQj2gE8MfU639rAz", + "ubXW8sd3hkMXVly_O_ZOm", + "8R2CEj2oEGuvX1dqJ8gCY" + ] + }, + { + "type": "arrow", + "version": 858, + "versionNonce": 2126699080, + "isDeleted": false, + "id": "Jo906mQj2gE8MfU639rAz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 468.4392508864622, + "y": 1386.951149425287, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 197.84511112482477, + "height": 37.33444576972829, + "seed": 1746017906, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "VvkwdNT_ReB6o1cn6jyZr", + "focus": -0.3762898352769947, + "gap": 2.034482758620584 + }, + "endBinding": { + "elementId": "0XL0H0k8apgsdhnJ1t5ab", + "focus": -0.8806478681490097, + "gap": 24.21440480498427 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -197.84511112482477, + 37.33444576972829 + ] + ] + }, + { + "type": "text", + "version": 241, + "versionNonce": 1174694472, + "isDeleted": false, + "id": "rDu3FdPNopgW6z9PCoHab", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 480.66666666666674, + "y": 1342.1666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 96, + "height": 26, + "seed": 1492486702, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "check VPs", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 357, + "versionNonce": 2070927672, + "isDeleted": false, + "id": "TsHuKC9fQpBlBOzMikQ_z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 305.83333333333326, + "y": 1874.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 442.4999999999999, + "height": 382.91666666666646, + "seed": 1984398894, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "y1ij4QN5do9jfy8-88ASN", + "MmzmsyscAAK-oBSzk0-T_", + "tjHofHebcS4-4X76vqi0M" + ] + }, + { + "type": "arrow", + "version": 1601, + "versionNonce": 108809784, + "isDeleted": false, + "id": "GN1g4nPawR3Tizbo7OUxq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 438.53788770476547, + "y": 517.4917358318999, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1.8992073358202788, + "height": 90.2497092278901, + "seed": 471491378, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": null, + "endBinding": { + "elementId": "VEHFhWJtyqbM7CEJvy1OY", + "gap": 6.496650178305457, + "focus": -0.2974579993705736 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.8992073358202788, + 90.2497092278901 + ] + ] + }, + { + "type": "rectangle", + "version": 371, + "versionNonce": 1915086408, + "isDeleted": false, + "id": "VEHFhWJtyqbM7CEJvy1OY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 268, + "y": 606.2380952380953, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 471.19047619047615, + "height": 412.00000000000006, + "seed": 465481138, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "GN1g4nPawR3Tizbo7OUxq", + "9m0lD6nkNLEEuDZhOAFaq" + ] + }, + { + "type": "rectangle", + "version": 541, + "versionNonce": 793102136, + "isDeleted": false, + "id": "Vva_ZJK0PzeOvHwKtR1hx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 193.7142857142859, + "y": 397.8095238095238, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 721.2857142857143, + "height": 138.52380952380955, + "seed": 926229486, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "oYCojvs3_Pz8ZGLg2mw3V", + "HBiEEEb8ounDRLLN9wXKf", + "GN1g4nPawR3Tizbo7OUxq", + "Czhwb2CjT6al5-6rCgR97" + ] + }, + { + "type": "text", + "version": 295, + "versionNonce": 642213960, + "isDeleted": false, + "id": "1nPK7swUFkn2ZECAVz_c2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 466.71428571428567, + "y": 399.7142857142857, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 201, + "height": 46, + "seed": 985887602, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "GN1g4nPawR3Tizbo7OUxq", + "oYCojvs3_Pz8ZGLg2mw3V", + "HBiEEEb8ounDRLLN9wXKf" + ], + "fontSize": 36, + "fontFamily": 1, + "text": "matchmaker", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 227, + "versionNonce": 1383604280, + "isDeleted": false, + "id": "-G2iBkHpCw0y563oTofms", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 394, + "y": 40.21428571428572, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 218, + "height": 52, + "seed": 1376390450, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "oYCojvs3_Pz8ZGLg2mw3V", + "954BBr80PqUI9uJuWnFsM" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "intent from account B\n", + "baseline": 44, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 161, + "versionNonce": 1220747080, + "isDeleted": false, + "id": "HA5fUOkkjum0HgfnMlIBG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 733, + "y": 60.95238095238096, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 220, + "height": 26, + "seed": 1992833394, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "intent from account C", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 153, + "versionNonce": 1878481208, + "isDeleted": false, + "id": "nBttw1DAeptrX2lVgvsY7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 720.7142857142857, + "y": 95.95238095238096, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 82, + "height": 25, + "seed": 1251151454, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "- DATA:", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 896, + "versionNonce": 407405112, + "isDeleted": false, + "id": "9m0lD6nkNLEEuDZhOAFaq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 508.568121346975, + "y": 1021.1321908520348, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.1429313226068984, + "height": 312.2365395554883, + "seed": 1155072110, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "VEHFhWJtyqbM7CEJvy1OY", + "focus": -0.01207639830586925, + "gap": 2.894095613939328 + }, + "endBinding": { + "elementId": "VvkwdNT_ReB6o1cn6jyZr", + "focus": -0.14754250955099754, + "gap": 2.381269592476542 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 3.1429313226068984, + 312.2365395554883 + ] + ] + }, + { + "type": "text", + "version": 299, + "versionNonce": 580155464, + "isDeleted": false, + "id": "hiykXkLOlmLVWGgrQKtH8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 532.9999999999999, + "y": 1144.9999999999998, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 265, + "height": 26, + "seed": 2146825074, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "send transaction to ledger", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 212, + "versionNonce": 1497632824, + "isDeleted": false, + "id": "7Ng3PQpN63cbVPHR8aHrn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 46.66666666666663, + "y": 1475.7499999999995, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 51, + "height": 26, + "seed": 983757230, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "A VP:", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 271, + "versionNonce": 1492004680, + "isDeleted": false, + "id": "AuP1LD1e9BIzr9fnDJADl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 324.58333333333326, + "y": 1904.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 52, + "height": 26, + "seed": 1592796398, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "B VP:", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 309, + "versionNonce": 1344396616, + "isDeleted": false, + "id": "VvkwdNT_ReB6o1cn6jyZr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 433.16666666666674, + "y": 1335.7499999999998, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 185, + "height": 49.166666666666735, + "seed": 1653182898, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9m0lD6nkNLEEuDZhOAFaq", + "Jo906mQj2gE8MfU639rAz", + "tjHofHebcS4-4X76vqi0M", + "o8vZWSYePDHs1bN3foTMP" + ] + }, + { + "type": "arrow", + "version": 981, + "versionNonce": 846653512, + "isDeleted": false, + "id": "tjHofHebcS4-4X76vqi0M", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 513.8848064154693, + "y": 1386.951149425287, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.1447670720162932, + "height": 467.54798651113333, + "seed": 572567090, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "VvkwdNT_ReB6o1cn6jyZr", + "focus": 0.12853479300213336, + "gap": 2.034482758620584 + }, + "endBinding": { + "elementId": "TsHuKC9fQpBlBOzMikQ_z", + "focus": -0.045390860814372345, + "gap": 20.250864063579456 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.1447670720162932, + 467.54798651113333 + ] + ] + }, + { + "type": "text", + "version": 349, + "versionNonce": 540359496, + "isDeleted": false, + "id": "0L6p1qM6u_tVfVgCK_FDY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 333.14285714285734, + "y": 623.5238095238096, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 307, + "height": 24, + "seed": 2092627950, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "TRANSACTION from matchmaker ", + "baseline": 17, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 643, + "versionNonce": 356842808, + "isDeleted": false, + "id": "PcxdOS9NrJhMOgUW0faBx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -165.8333333333394, + "y": -331.6666666666668, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1361.333333333333, + "height": 1437.3809523809525, + "seed": 2083480096, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 218, + "versionNonce": 192844360, + "isDeleted": false, + "id": "A6kNrvnIcbKPUZ5GodYoX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 353.2142857142801, + "y": -291.6666666666665, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 364, + "height": 92, + "seed": 389688288, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 36, + "fontFamily": 1, + "text": "Intent broadcaster \nnetwork", + "baseline": 78, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 158, + "versionNonce": 1595971128, + "isDeleted": false, + "id": "nVSCWf4TYONMAlK6r7v7u", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 607.1666666666612, + "y": -49.71428571428561, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 193, + "height": 36, + "seed": 1611512800, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "global mempool", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 635, + "versionNonce": 293931336, + "isDeleted": false, + "id": "HVqr4I6oVLiB-7Smz4WBL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 264.6904761904701, + "y": 450.04761904761926, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 584, + "height": 78, + "seed": 644801056, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "GN1g4nPawR3Tizbo7OUxq" + ], + "fontSize": 19.896103896103885, + "fontFamily": 1, + "text": "The matchmacker chooses values for the transaction that\n he thinks will be accepted by the ledger (the validity\npredicate of each account).", + "baseline": 70, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 452, + "versionNonce": 2052606792, + "isDeleted": false, + "id": "0ilp60kgFqvYZ-R-LlZ_8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -241.6666666666722, + "y": 1238.6666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1547.3333333333326, + "height": 1465.0000000000002, + "seed": 312942560, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 369, + "versionNonce": 781905208, + "isDeleted": false, + "id": "gwJeBcdO_DYmasftBcHif", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 537.8333333333277, + "y": 1261.1666666666665, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 258, + "height": 46, + "seed": 721258016, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 36, + "fontFamily": 1, + "text": "ledger network", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 683, + "versionNonce": 1321181768, + "isDeleted": false, + "id": "jU8TxFoAubCE7Tws_-GtR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 321.8333333333276, + "y": 1954.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 410, + "height": 250, + "seed": 1682625504, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "if someone takes between\ntx.DATA.B.amount \ntx.DATA.B.output_asset \nfrom my account,\nit has to credit\nat least tx.DATA.B.min_rate \nthe amount in tx.DATA.B.input_asset\n and to be before\n (tx.DATA.B.timestamp \n + tx.DATA.C.TTL)", + "baseline": 245, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1227, + "versionNonce": 385840712, + "isDeleted": false, + "id": "MmzmsyscAAK-oBSzk0-T_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 537.0941616654786, + "y": 2275.9127064518443, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0.8399583165244167, + "height": 211.42062688148872, + "seed": 331186144, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "TsHuKC9fQpBlBOzMikQ_z", + "focus": -0.041338956392506655, + "gap": 18.246039785178027 + }, + "endBinding": { + "elementId": "OLiV9Yr0v-YQNJbhTqcuZ", + "focus": 0.664082386729967, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.8399583165244167, + 211.42062688148872 + ] + ] + }, + { + "type": "text", + "version": 379, + "versionNonce": 884140088, + "isDeleted": false, + "id": "OLiV9Yr0v-YQNJbhTqcuZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 508.83333333332746, + "y": 2488.333333333333, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 35, + "height": 25, + "seed": 258550752, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "cnhSr0ikLZ2glzJteomgZ", + "MmzmsyscAAK-oBSzk0-T_", + "r3K74Ft73zYbYmn37KM_a", + "8R2CEj2oEGuvX1dqJ8gCY", + "xfJTcfqB9FHSZl9nnr-SE" + ], + "fontSize": 20, + "fontFamily": 3, + "text": "...", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 172, + "versionNonce": 1265089080, + "isDeleted": false, + "id": "bnfip1RTsFRJNftw4HckK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 470.8809523809465, + "y": 574.2857142857144, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 175, + "height": 26, + "seed": 111981536, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "craft transaction", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 370, + "versionNonce": 1269966152, + "isDeleted": false, + "id": "bDFsD9RIYPQQrHIWUfc_E", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 458.49999999999466, + "y": -534.3809523809524, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 164, + "height": 104, + "seed": 1802037792, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Swcv4ZXJejaz5TYfHeDMu", + "ol2AB4uLuMEYj138srSeF", + "954BBr80PqUI9uJuWnFsM" + ] + }, + { + "type": "text", + "version": 336, + "versionNonce": 467907384, + "isDeleted": false, + "id": "xzml5UDbVyEJKPO3if06i", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 816.5273034941135, + "y": -372.08003798021025, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 43, + "height": 26, + "seed": 806763701, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "send", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 385, + "versionNonce": 1271807048, + "isDeleted": false, + "id": "X0SNHo6dQnl8irYcF3Ubf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 529.5273034941135, + "y": -385.08003798021025, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 43, + "height": 26, + "seed": 207870843, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "send", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 448, + "versionNonce": 1282968632, + "isDeleted": false, + "id": "TVJpZzxoPMIinWFy_aK1i", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 212.9761904761957, + "y": -498.9599810098948, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 98, + "height": 52, + "seed": 505781699, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "account A\n", + "baseline": 44, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 415, + "versionNonce": 1045394248, + "isDeleted": false, + "id": "E7FV6AEnvxE7fX_r3_TzW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 182.80952380952374, + "y": -542.2933143432281, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 164, + "height": 104, + "seed": 1994281965, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Swcv4ZXJejaz5TYfHeDMu", + "ol2AB4uLuMEYj138srSeF", + "954BBr80PqUI9uJuWnFsM", + "uKTSmBiHZULyjH127gePI" + ] + }, + { + "type": "text", + "version": 429, + "versionNonce": 654984504, + "isDeleted": false, + "id": "l3RrMlLKlU9laipGh-WIb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 253.8368273036425, + "y": -392.9923999424859, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 43, + "height": 26, + "seed": 735243619, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "send", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 247, + "versionNonce": 1502726728, + "isDeleted": false, + "id": "uKTSmBiHZULyjH127gePI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 254.78140471240346, + "y": -433.642857142857, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 18.07520040340512, + "height": 446.8986715332853, + "seed": 1473743181, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "E7FV6AEnvxE7fX_r3_TzW", + "focus": 0.14647529307932466, + "gap": 4.650457200371079 + }, + "endBinding": { + "elementId": "2p7QI8-lBoSz4ic4BK3eT", + "focus": 0.6266906023232607, + "gap": 29.815614181000413 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 18.07520040340512, + 446.8986715332853 + ] + ] + }, + { + "type": "arrow", + "version": 214, + "versionNonce": 419225144, + "isDeleted": false, + "id": "Czhwb2CjT6al5-6rCgR97", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 251.88178381509215, + "y": 291.02380952380963, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 52.803167627033986, + "height": 104.42857142857144, + "seed": 1533710979, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "2p7QI8-lBoSz4ic4BK3eT", + "gap": 2.047619047619051, + "focus": 0.017984625714246562 + }, + "endBinding": { + "elementId": "Vva_ZJK0PzeOvHwKtR1hx", + "gap": 2.3571428571426907, + "focus": -0.5394946080216643 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 52.803167627033986, + 104.42857142857144 + ] + ] + }, + { + "type": "text", + "version": 439, + "versionNonce": 1127503672, + "isDeleted": false, + "id": "mgtimTjmkfFageE4a_ALC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 401.95238095238096, + "y": 117.14285714285734, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 188, + "height": 80, + "seed": 1741274855, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "- 800 EUR\n- rate EUR to BTC: \n >= 47058 EUR/BTC\n- in less than 10 mn", + "baseline": 76, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 349, + "versionNonce": 859410232, + "isDeleted": false, + "id": "n8qBdtjdYuoZicswHAM8Z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 605.3690476190475, + "y": 1444.017857142857, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 442.4999999999999, + "height": 382.91666666666646, + "seed": 1102897255, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "tjHofHebcS4-4X76vqi0M", + "y1ij4QN5do9jfy8-88ASN", + "MmzmsyscAAK-oBSzk0-T_", + "o8vZWSYePDHs1bN3foTMP", + "r3K74Ft73zYbYmn37KM_a", + "xfJTcfqB9FHSZl9nnr-SE" + ] + }, + { + "type": "text", + "version": 268, + "versionNonce": 554150456, + "isDeleted": false, + "id": "kEAF_yIT_4cO4uX63467_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 624.1190476190475, + "y": 1473.767857142857, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 54, + "height": 26, + "seed": 1833762185, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "C VP:", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 691, + "versionNonce": 340342088, + "isDeleted": false, + "id": "LgtU9c20p3iojrteT7TOy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 621.3690476190416, + "y": 1523.767857142857, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 410, + "height": 250, + "seed": 405945223, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "if someone takes between\ntx.DATA.C.low to tx.DATA.C. high \ntx.DATA.C.output_asset \nfrom my account,\nit has to credit\nat least tx.DATA.C.min_rate \nthe amount in tx.DATA.C.input_asset\n and to be before\n (tx.DATA.C.timestamp\n + tx.DATA.C.TTL)", + "baseline": 245, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 341, + "versionNonce": 1631355976, + "isDeleted": false, + "id": "o8vZWSYePDHs1bN3foTMP", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 622.3113514729953, + "y": 1387.3092905570172, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 131.8668164720766, + "height": 50.16689991917315, + "seed": 1895758345, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "VvkwdNT_ReB6o1cn6jyZr", + "focus": -0.16380407789917548, + "gap": 4.1446848063285415 + }, + "endBinding": { + "elementId": "n8qBdtjdYuoZicswHAM8Z", + "focus": 0.6183687597387931, + "gap": 6.5416666666667425 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 131.8668164720766, + 50.16689991917315 + ] + ] + }, + { + "id": "8R2CEj2oEGuvX1dqJ8gCY", + "type": "arrow", + "x": 166.28016609367018, + "y": 1833.6428571428573, + "width": 333.2772811594371, + "height": 669.3349011418622, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 973478472, + "version": 390, + "versionNonce": 235803208, + "isDeleted": false, + "boundElementIds": null, + "points": [ + [ + 0, + 0 + ], + [ + 61.005548192043705, + 588.333333333333 + ], + [ + 333.2772811594371, + 669.3349011418622 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "0XL0H0k8apgsdhnJ1t5ab", + "focus": 0.3030726950488866, + "gap": 7.226190476191277 + }, + "endBinding": { + "elementId": "OLiV9Yr0v-YQNJbhTqcuZ", + "focus": -0.5710015327583038, + "gap": 9.275886080220175 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "version": 540, + "versionNonce": 1649680456, + "isDeleted": false, + "id": "xfJTcfqB9FHSZl9nnr-SE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 875.4506366524696, + "y": 1845.3095238095234, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 316.498255700089, + "height": 653.299399447023, + "seed": 253988936, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "n8qBdtjdYuoZicswHAM8Z", + "focus": -0.2859002709985485, + "gap": 18.374999999999773 + }, + "endBinding": { + "elementId": "OLiV9Yr0v-YQNJbhTqcuZ", + "focus": 0.4400244271235751, + "gap": 15.119047619053163 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -53.16492236675572, + 571.6666666666665 + ], + [ + -316.498255700089, + 653.299399447023 + ] + ] + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/example.svg b/documentation/dev/src/explore/design/intent_gossip/example.svg new file mode 100644 index 00000000000..9a5a48a38c5 --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/example.svg @@ -0,0 +1,16 @@ + + + + + + + - 400 < X < 1000 USD- rate USD to EUR: >= 1.5 EUR/USD- rate USD to CZK: >= 10 USD/CZK- in less than 20 mn- DATA: - timestamp Tintent from account Aaccount Cfetch intentsaccount B- DATA: - 0 < X < 0.05 BTC - rate BTC to EUR: >= 0.00001 BTC/USD- in less than 1 mn- timestamp T''- timestamp T'if someone takes betweentx.DATA.A.low to tx.DATA.A.high tx.DATA.A.output_asset from my account,it has to creditat least tx.DATA.A.min_rate the amount in tx.DATA.A.input_asset and to be before (tx.DATA.A.timestamp + tx.DATA.A.TTL)- STATE FUNCTION : - 1/ src: A dest: C amount: 466.6 USD - 2/ src: C dest: B amount: 800 EUR - 3/ src: B dest: A amount: 0.017 BTC- DATA - 1/ intent A - 2/ intent B - 3/ intent Ccheck VPsmatchmakerintent from account Bintent from account C- DATA:send transaction to ledgerA VP:B VP:TRANSACTION from matchmaker Intent broadcaster networkglobal mempoolThe matchmacker chooses values for the transaction that he thinks will be accepted by the ledger (the validitypredicate of each account).ledger networkif someone takes betweentx.DATA.B.amount tx.DATA.B.output_asset from my account,it has to creditat least tx.DATA.B.min_rate the amount in tx.DATA.B.input_asset and to be before (tx.DATA.B.timestamp + tx.DATA.C.TTL)...craft transactionsendsendaccount Asend- 800 EUR- rate EUR to BTC: >= 47058 EUR/BTC- in less than 10 mnC VP:if someone takes betweentx.DATA.C.low to tx.DATA.C. high tx.DATA.C.output_asset from my account,it has to creditat least tx.DATA.C.min_rate the amount in tx.DATA.C.input_asset and to be before (tx.DATA.C.timestamp + tx.DATA.C.TTL) \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/fungible_token.md b/documentation/dev/src/explore/design/intent_gossip/fungible_token.md new file mode 100644 index 00000000000..43a34c5c94f --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/fungible_token.md @@ -0,0 +1,35 @@ +# Fungible token encoding and template + +The Heliax team implemented an intent encoding, a filter program template, and a +matchmaker program template that can be used to exchange fungible tokens between +any number of participants. + +## Intent encoding +The intent encoding allows the expression of a desire to participate in an asset +exchange. The encoding is defined as follows : + +```protobuf +message FungibleToken { + string address = 1; + string token_sell = 2; + int64 max_sell = 3; + int64 rate_min = 4; + string token_buy = 5; + int64 min_buy = 6; + google.protobuf.Timestamp expire = 7; +} +``` + +## Matchmaker program + +The filter program attempts to decode the intent and if successful, checks +that it's not yet expired and that the account address has enough funds for the +intended token to be sold. + +The main program can match intents for exchanging assets. It does that by +creating a graph from all intents. When a cycle is found then it removes all +intents from that cycle of the mempool and crafts a transaction based on all the +removed intents. + +![matchmaker](matchmaker_graph.svg) +[excalidraw link](https://excalidraw.com/#room=1db86ba6d5f0ccb7447c,2vvRd4X2Y3HDWHihJmy9zw) diff --git a/documentation/dev/src/explore/design/intent_gossip/gossip_process.excalidraw b/documentation/dev/src/explore/design/intent_gossip/gossip_process.excalidraw new file mode 100644 index 00000000000..36842526939 --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/gossip_process.excalidraw @@ -0,0 +1,966 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "ellipse", + "version": 603, + "versionNonce": 717195654, + "isDeleted": false, + "id": "9XNyo7y8QCSEp4yAFf5oy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1156.111111111111, + "y": 164.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 573.6666666666667, + "height": 372.55555555555554, + "seed": 821968881, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "arrow", + "version": 1450, + "versionNonce": 941173851, + "isDeleted": false, + "id": "lXNINuf2v3Bas7FefE3LH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1586.2054115693877, + "y": -370.2728566628903, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 246.73688630955917, + "height": 192.92404363452567, + "seed": 736805503, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "5xDzwVV0gDBlD-WL85LYy", + "focus": 0.18717224637187657, + "gap": 6.459472910604248 + }, + "endBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "focus": -0.23250849205562685, + "gap": 13.411672225924079 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -246.73688630955917, + 192.92404363452567 + ] + ] + }, + { + "type": "diamond", + "version": 278, + "versionNonce": 198810363, + "isDeleted": false, + "id": "5xDzwVV0gDBlD-WL85LYy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1565.6666666666665, + "y": -444.66666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 127, + "height": 100, + "seed": 563016927, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "lXNINuf2v3Bas7FefE3LH", + "RR6GEDZFWUssCdTAnrDqo" + ] + }, + { + "type": "text", + "version": 253, + "versionNonce": 1818093109, + "isDeleted": false, + "id": "HskYIBtFFYajWD-2Djn1f", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1605.1666666666665, + "y": -409.66666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 50, + "height": 26, + "seed": 1643469585, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "lXNINuf2v3Bas7FefE3LH" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "client", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "rectangle", + "version": 432, + "versionNonce": 232368667, + "isDeleted": false, + "id": "5cPSOu9KDeCIp3Nmd546m", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1176, + "y": -287.2222222222222, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 570.0000000000001, + "height": 356.2222222222222, + "seed": 1306655743, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "diamond", + "version": 627, + "versionNonce": 1842306310, + "isDeleted": false, + "id": "xxAsLeElpbNGHmw4QPxMz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1181.3333333333333, + "y": -207.55555555555554, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 209, + "height": 188, + "seed": 1389553521, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "B4iQSfzoi419GYzKlXw5m", + "MPfW6JEUQYoWrzHeI_NxD", + "z6DMUeY7qrwuosEZr9D8K", + "GJPwvquPv4Afa-iz06Txv", + "bleUDuct9nWGxJfhyl_J2", + "V6oy4uJc_J45aC2sNpHvE", + "lXNINuf2v3Bas7FefE3LH", + "OIBiMf6VpPETwJAGERKti", + "J8EUyGyYHRSW895zskWbp" + ] + }, + { + "type": "diamond", + "version": 604, + "versionNonce": 1476920774, + "isDeleted": false, + "id": "dSBMH7Bqm1h_eoLM_yqkZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1184.4444444444446, + "y": 264.99999999999994, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 307.888888888889, + "height": 215.66666666666666, + "seed": 1838896927, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "OIBiMf6VpPETwJAGERKti", + "MPfW6JEUQYoWrzHeI_NxD", + "FqT97u9kn8yPY-e9ZEmwX", + "3w6C3S5gFIBLSOSnHrL9p", + "J8EUyGyYHRSW895zskWbp" + ] + }, + { + "type": "text", + "version": 446, + "versionNonce": 2067771125, + "isDeleted": false, + "id": "FOQ3kxQ9jn8r8iZ6-5Kdn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1227.3333333333333, + "y": -149.55555555555551, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 121, + "height": 52, + "seed": 1118918783, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent \nbroadcaster", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 346, + "versionNonce": 94289307, + "isDeleted": false, + "id": "wNwxFCvE4WMQV8QLZiqxu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1272, + "y": 281.99999999999994, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 131, + "height": 78, + "seed": 197177745, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "OIBiMf6VpPETwJAGERKti", + "MPfW6JEUQYoWrzHeI_NxD", + "J8EUyGyYHRSW895zskWbp" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "intent\nbroadcaster \nnetwork", + "baseline": 70, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1548, + "versionNonce": 1059405510, + "isDeleted": false, + "id": "OIBiMf6VpPETwJAGERKti", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1340.8991464307837, + "y": 256.43457590166764, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 19.424658574333307, + "height": 274.53680436823555, + "seed": 413185503, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", + "focus": 0.06980420648639936, + "gap": 8.455707710143173 + }, + "endBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "focus": -0.27643465526659067, + "gap": 24.916176360598527 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -19.424658574333307, + -274.53680436823555 + ] + ] + }, + { + "type": "arrow", + "version": 1783, + "versionNonce": 702656213, + "isDeleted": false, + "id": "MPfW6JEUQYoWrzHeI_NxD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1225.7890453334985, + "y": -41.97976066319893, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 21.266468152933612, + "height": 381.0875685373966, + "seed": 1670492081, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "gap": 23.483962590379445, + "focus": 0.5312629502131418 + }, + "endBinding": { + "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", + "gap": 16.103707014847412, + "focus": -0.8818009263489358 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -21.266468152933612, + 381.0875685373966 + ] + ] + }, + { + "type": "text", + "version": 540, + "versionNonce": 1475775174, + "isDeleted": false, + "id": "InLHuKJiMfsKjnhDLER2S", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1224.6666666666667, + "y": 122.99999999999997, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 52, + "seed": 1075843217, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "validate \nmsg", + "baseline": 44, + "textAlign": "right", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1384, + "versionNonce": 280535450, + "isDeleted": false, + "id": "J8EUyGyYHRSW895zskWbp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1386.7368978905408, + "y": 286.51414709947557, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 19.54258711706757, + "height": 343.3666092756827, + "seed": 1500547647, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", + "focus": 0.34597438474774744, + "gap": 10.117038200934843 + }, + "endBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "focus": -0.7476913039884736, + "gap": 26.682463765500543 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -19.54258711706757, + -343.3666092756827 + ] + ] + }, + { + "type": "text", + "version": 452, + "versionNonce": 1029595482, + "isDeleted": false, + "id": "3a6V_ozlu_-z813t_fjbn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1391, + "y": 187.77777777777777, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 156, + "height": 36, + "seed": 15072497, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "libP2P logic", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "diamond", + "version": 365, + "versionNonce": 757518677, + "isDeleted": false, + "id": "hngD3gT8MxLbMtULxwILm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1204.6111111111113, + "y": -434.0000000000001, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 127, + "height": 100, + "seed": 662285233, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "lXNINuf2v3Bas7FefE3LH", + "V6oy4uJc_J45aC2sNpHvE" + ] + }, + { + "type": "text", + "version": 334, + "versionNonce": 979982971, + "isDeleted": false, + "id": "Tne4ggu_ZDCdoqKD1HVLY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1244.1111111111113, + "y": -402.0000000000001, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 50, + "height": 26, + "seed": 1728898015, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "lXNINuf2v3Bas7FefE3LH" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "client", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "arrow", + "version": 1682, + "versionNonce": 1212570555, + "isDeleted": false, + "id": "V6oy4uJc_J45aC2sNpHvE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1262.664151563201, + "y": -330.8400855354932, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.5819871518015134, + "height": 140.4581124311759, + "seed": 1528357951, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "hngD3gT8MxLbMtULxwILm", + "focus": 0.10105347446704833, + "gap": 5.8523740982304915 + }, + "endBinding": { + "elementId": "xxAsLeElpbNGHmw4QPxMz", + "focus": -0.18349209857314347, + "gap": 1 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.5819871518015134, + 140.4581124311759 + ] + ] + }, + { + "type": "text", + "version": 377, + "versionNonce": 1391350555, + "isDeleted": false, + "id": "q9mdbiPTZ5bMiHS1pP8Jd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1267.8333333333333, + "y": -327.77777777777777, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 26, + "seed": 280036991, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "send msg", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 138, + "versionNonce": 2055493659, + "isDeleted": false, + "id": "JdaaIi8F9pCIbQiuefyHi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1388.75, + "y": -206.72222222222223, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 180, + "height": 35, + "seed": 476397371, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 3, + "text": "Gossip Node", + "baseline": 28, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 419, + "versionNonce": 1035911669, + "isDeleted": false, + "id": "E1bibepZLZFj3VmWghmhk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1557.1388888888887, + "y": -324.94444444444554, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 26, + "seed": 1482472437, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "send msg", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "diamond", + "version": 629, + "versionNonce": 251582106, + "isDeleted": false, + "id": "JaiST4fQ2FlodcbDsHGRd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1525.611111111111, + "y": -178.99999999999997, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 209, + "height": 188, + "seed": 1161636571, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "B4iQSfzoi419GYzKlXw5m", + "MPfW6JEUQYoWrzHeI_NxD", + "z6DMUeY7qrwuosEZr9D8K", + "GJPwvquPv4Afa-iz06Txv", + "bleUDuct9nWGxJfhyl_J2", + "RR6GEDZFWUssCdTAnrDqo", + "yZ1sDbO9UxZw6Yw7d8BGk", + "sofyhfQ6yAERl8W7igqud", + "98jHSy6KgIgI-kigPhATL", + "Jj-F4u_NSjuW-tVWK6ErH" + ] + }, + { + "type": "text", + "version": 477, + "versionNonce": 320811829, + "isDeleted": false, + "id": "yE8obB-cq-eO9tpUS_PcA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1575.611111111111, + "y": -117.99999999999994, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 121, + "height": 52, + "seed": 413385813, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "DKG \nbroadcaster", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 115, + "versionNonce": 808640859, + "isDeleted": false, + "id": "RR6GEDZFWUssCdTAnrDqo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1660.0572555330295, + "y": -366.9575940330065, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 24.665187381169744, + "height": 183.91639038865497, + "seed": 45437883, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "5xDzwVV0gDBlD-WL85LYy", + "focus": -0.5447225569886008, + "gap": 1.5967808146620044 + }, + "endBinding": { + "elementId": "JaiST4fQ2FlodcbDsHGRd", + "focus": -0.07528645180716763, + "gap": 6.5362537154315845 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -24.665187381169744, + 183.91639038865497 + ] + ] + }, + { + "type": "diamond", + "version": 596, + "versionNonce": 1248600538, + "isDeleted": false, + "id": "sJFOeRzApKjnkoavWYSOm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1415.6666666666667, + "y": 270.6666666666667, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 292.8888888888888, + "height": 207.6666666666666, + "seed": 3834485, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "OIBiMf6VpPETwJAGERKti", + "MPfW6JEUQYoWrzHeI_NxD", + "FqT97u9kn8yPY-e9ZEmwX", + "3w6C3S5gFIBLSOSnHrL9p", + "J8EUyGyYHRSW895zskWbp", + "yZ1sDbO9UxZw6Yw7d8BGk", + "sofyhfQ6yAERl8W7igqud", + "98jHSy6KgIgI-kigPhATL", + "Jj-F4u_NSjuW-tVWK6ErH" + ] + }, + { + "type": "text", + "version": 354, + "versionNonce": 339994458, + "isDeleted": false, + "id": "ERwB6OfVxtsDLuZO1G4dn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1507.2222222222224, + "y": 292.6666666666667, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 131, + "height": 78, + "seed": 410831707, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "OIBiMf6VpPETwJAGERKti", + "MPfW6JEUQYoWrzHeI_NxD", + "yZ1sDbO9UxZw6Yw7d8BGk" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "DKG\nbroadcaster \nnetwork", + "baseline": 70, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 432, + "versionNonce": 855557126, + "isDeleted": false, + "id": "yZ1sDbO9UxZw6Yw7d8BGk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1613.2645903010791, + "y": 5.6816480400971585, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.37650849922488, + "height": 267.8739593512054, + "seed": 978571771, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "JaiST4fQ2FlodcbDsHGRd", + "focus": 0.056328322376417134, + "gap": 8.799318498256582 + }, + "endBinding": { + "elementId": "ERwB6OfVxtsDLuZO1G4dn", + "focus": 0.016268660508214034, + "gap": 19.11105927536414 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -32.37650849922488, + 267.8739593512054 + ] + ] + }, + { + "type": "arrow", + "version": 398, + "versionNonce": 2067988678, + "isDeleted": false, + "id": "Jj-F4u_NSjuW-tVWK6ErH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1623.0089473868802, + "y": 304.406818117378, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 33.75764899556157, + "height": 307.2241654227815, + "seed": 398843163, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "sJFOeRzApKjnkoavWYSOm", + "focus": 0.3632505836483008, + "gap": 7.699251414567215 + }, + "endBinding": { + "elementId": "JaiST4fQ2FlodcbDsHGRd", + "focus": -0.3414897328591685, + "gap": 9.04048516465393 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 33.75764899556157, + -307.2241654227815 + ] + ] + }, + { + "type": "text", + "version": 554, + "versionNonce": 720882453, + "isDeleted": false, + "id": "kXVNso4ItXnAMFArYHbYk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1486.1111111111115, + "y": 109.99999999999997, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 52, + "seed": 1662723835, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "validate \nmsg", + "baseline": 44, + "textAlign": "right", + "verticalAlign": "top" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/gossip_process.svg b/documentation/dev/src/explore/design/intent_gossip/gossip_process.svg new file mode 100644 index 00000000000..9129d9eea7d --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/gossip_process.svg @@ -0,0 +1,16 @@ + + + + + + + clientIntent broadcasterintentbroadcaster networkvalidate msglibP2P magicclientsend msgGossip Nodesend msgDKG broadcasterDKGbroadcaster networkvalidate msg \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/incentive.md b/documentation/dev/src/explore/design/intent_gossip/incentive.md new file mode 100644 index 00000000000..51905d457f2 --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/incentive.md @@ -0,0 +1,9 @@ +# Incentive + +[Tracking Issue](https://github.com/anoma/anoma/issues/37) + +--- + +TODO +- describe incentive function +- describe logic to ensure matchmaker can't cheat intent gossip service diff --git a/documentation/dev/src/explore/design/intent_gossip/intent.md b/documentation/dev/src/explore/design/intent_gossip/intent.md new file mode 100644 index 00000000000..62bffedbe0c --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/intent.md @@ -0,0 +1,28 @@ +# Intents + +An intent is a way of expressing a user's desire. It is defined as arbitrary +data and an optional address for a schema. The data is as arbitrary as possible +to allow the users to express any sort of intent. It could range from defining a +selling order for a specific token to offering piano lessons or even proposing a +green tax for shoes’ manufacturers. + +An intent is written using an encoding, or data schema. The encoding exists +either on-chain or off-chain. It must be known by users that want to express +similar intents. It also must be understood by some matchmaker. Otherwise, it +possibly won’t be matched. The user can define its own schema and inform either +off-chain or on-chain. Having it on-chain allows it to easily share it with other +participants. Please refer to [data schema](./../ledger/storage/data-schema.md) for more +information about the usage of on-chain schema. + +--- + +There is only a single intent type that is composed of arbitrary data and a +possible schema definition. + +```rust +struct Intent { + schema: Option, + data: Vec, + timestamp: Timestamp +} +``` diff --git a/documentation/dev/src/explore/design/intent_gossip/intent_gossip.md b/documentation/dev/src/explore/design/intent_gossip/intent_gossip.md new file mode 100644 index 00000000000..f9342a01810 --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/intent_gossip.md @@ -0,0 +1,51 @@ +# Intent gossip network + +The intent gossip network enables counterparty discovery for bartering. The +users can express any sort of intents that might be matched and transformed into +a transaction that fulfills the intents on the Anoma ledger. + +An [intent](./intent.md) describes the desire of a user, from asset exchange to a +green tax percent for selling shoes. These intents are picked up by a matchmaker +that composes them into transactions to send to the ledger network. A matchmaker +is optionally included in the intent gossip node. + +Each node connects to a specified intent gossip network, either a public or a +private one. Anyone can create their own network where they decide all aspects +of it: which type of intents is propagated, which nodes can participate, the +matchmaker logic, etc. It is possible, for example, to run the intent gossip system +over bluetooth to have it off-line. + +An intent gossip node is a peer in the intent gossip network that has the role +of propagating intents to all other connected nodes. + +The network uses the +[gossipsub](https://github.com/libp2p/specs/tree/512accdd81e35480911499cea14e7d7ea019f71b/pubsub/gossipsub) +network behaviour. This system aggregates nodes around topics of interest. Each +node subscribes to a set of topics and connects to other nodes that are also +subscribed to the same topics. A topic defines a sub-network for a defined +interest, e.g. “asset_exchange”. see +[gossipsub](https://github.com/libp2p/specs/tree/512accdd81e35480911499cea14e7d7ea019f71b/pubsub/gossipsub) +for more information on the network topology. + +Each node has an incentive to propagate intents and will obtain a small portion +of the fees if the intent is settled. (TODO: update when logic is found) See +[incentive](./incentive.md) for more information. + +### Flow diagram: asset exchange + +This example shows three intents matched together by the intent gossip network. +These three intents express user desires to exchange assets. + +![intent gossip and ledger network +interaction](./example.svg "intent gossip network") +[Diagram on Excalidraw](https://excalidraw.com/#room=257e44f4b4b5867bf541,XDEKyGVIpqCrfq55bRqKug) + +# Flow diagram: life cycle of intent and global process + +This diagram shows the process flow for intents, from users expressing their +desire to the ledger executing the validity predicate to check the crafted +transaction. + +![intent life cycle](./intent_life_cycle.svg "intent life +cycle") +[Diagram on Excalidraw](https://excalidraw.com/#room=7ac107b3757c64049003,cdMInfvdLtjaGWSZWEKrhw) diff --git a/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.excalidraw b/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.excalidraw new file mode 100644 index 00000000000..83b9eced853 --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.excalidraw @@ -0,0 +1,1686 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "version": 588, + "versionNonce": 594523952, + "isDeleted": false, + "id": "1u_2wBZI6b8qbCpHVN6MP", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -298.6003582724815, + "y": 1338.2754110612868, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 85.4456709210074, + "height": 21.78026905829601, + "seed": 71443719, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16.75405312176615, + "fontFamily": 1, + "text": "add intent", + "baseline": 14.78026905829601, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 454, + "versionNonce": 786627024, + "isDeleted": false, + "id": "2Wc5lEY411C5Gl9LSu5pY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -410.91401048972233, + "y": 1360.7731689088182, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 88, + "height": 18, + "seed": 1334554569, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "yk9tRH45JFldlow2BsTQc", + "IfZHk6DCJHzdGQSleRzVY", + "3M7y36XaiPpkZdYFa6HL0" + ], + "fontSize": 13.677130044843086, + "fontFamily": 3, + "text": "filter.wasm", + "baseline": 14, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 2279, + "versionNonce": 1948716336, + "isDeleted": false, + "id": "3M7y36XaiPpkZdYFa6HL0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -305.31404973574377, + "y": 1369.1513491711225, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 105.04325514724303, + "height": 0.1273972078367933, + "seed": 94578119, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "MA9plWdw54jKf3dPF231d", + "focus": 0.11381802003862611, + "gap": 4.5029253777944405 + }, + "endBinding": { + "elementId": "J3PIpIxWftxjM5b3onaa_", + "focus": -1.823336288291325, + "gap": 9.453089222814356 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 105.04325514724303, + 0.1273972078367933 + ] + ] + }, + { + "type": "rectangle", + "version": 628, + "versionNonce": 1926825424, + "isDeleted": false, + "id": "jTa_ZQSjZQ_9wK0AxfkKq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -200.54779225354866, + "y": 1340.590433482808, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 125.3736920777284, + "height": 59.495515695067446, + "seed": 1482668551, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Q4yHCPe8MjqsFcGPSqmAZ", + "3M7y36XaiPpkZdYFa6HL0", + "9rmYOU060-nUE-om6NWFg", + "dAdWOUfpjNuXJyfO475fP" + ] + }, + { + "type": "rectangle", + "version": 504, + "versionNonce": 1614695216, + "isDeleted": false, + "id": "MA9plWdw54jKf3dPF231d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -416.8923363492138, + "y": 1350.6808669656184, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 107.07536123567559, + "height": 33.48318385650231, + "seed": 186308295, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "IolhdtZMlJt05RE7PSeqH", + "3M7y36XaiPpkZdYFa6HL0" + ] + }, + { + "type": "arrow", + "version": 1314, + "versionNonce": 44918064, + "isDeleted": false, + "id": "IolhdtZMlJt05RE7PSeqH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -297.62941465804636, + "y": 1305.067079818005, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 35.4136526564796, + "height": 41.1911597898536, + "seed": 580979783, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "vMWskj1YJao4q8Szni_9g", + "focus": 0.2739809180363499, + "gap": 3.4310135063774396 + }, + "endBinding": { + "elementId": "MA9plWdw54jKf3dPF231d", + "focus": 0.17835547552991876, + "gap": 4.422627357759893 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -35.4136526564796, + 41.1911597898536 + ] + ] + }, + { + "type": "text", + "version": 630, + "versionNonce": 701297456, + "isDeleted": false, + "id": "J3PIpIxWftxjM5b3onaa_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -193.70922723112722, + "y": 1346.0612855007478, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 112.83632286995548, + "height": 16.412556053811695, + "seed": 856985703, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rmYOU060-nUE-om6NWFg", + "Q4yHCPe8MjqsFcGPSqmAZ", + "MOjksoKslfp16-Y701zlH", + "3M7y36XaiPpkZdYFa6HL0" + ], + "fontSize": 13.67713004484308, + "fontFamily": 2, + "text": "matchmaker.wasm", + "baseline": 11.412556053811695, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 629, + "versionNonce": 2084073754, + "isDeleted": false, + "id": "AfmJxEIuWj5_62161GQ01", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -326.2723811922641, + "y": 1440.665545590435, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 176.69394618834113, + "height": 56.203662182361796, + "seed": 337914215, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rmYOU060-nUE-om6NWFg", + "mlM1pmVM-052hahaRXJQR", + "ttC9xH3eOGzQ_k5DLzmBu" + ] + }, + { + "type": "text", + "version": 642, + "versionNonce": 1534794758, + "isDeleted": false, + "id": "TzqXubskSAjo2ZuvkxkCC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -293.94129001139663, + "y": 1468.2503736920773, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 112, + "height": 18, + "seed": 647873609, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "ttC9xH3eOGzQ_k5DLzmBu", + "9rmYOU060-nUE-om6NWFg" + ], + "fontSize": 13.67713004484309, + "fontFamily": 3, + "text": "tx.wawm + data", + "baseline": 14, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 2292, + "versionNonce": 1408723920, + "isDeleted": false, + "id": "9rmYOU060-nUE-om6NWFg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -147.6356968299097, + "y": 1408.7716634080846, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 66.68074390630872, + "height": 23.556734178462648, + "seed": 798702697, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "jTa_ZQSjZQ_9wK0AxfkKq", + "gap": 8.685714230208873, + "focus": -0.6781834884379211 + }, + "endBinding": { + "elementId": "AfmJxEIuWj5_62161GQ01", + "gap": 8.337148003887705, + "focus": -0.47373449663638795 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -66.68074390630872, + 23.556734178462648 + ] + ] + }, + { + "type": "text", + "version": 507, + "versionNonce": 260021712, + "isDeleted": false, + "id": "He6AuKTKQxKmk5B38hWHj", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -176.69016893516272, + "y": 1368.6692825112111, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 75.90807174887914, + "height": 17.780269058296007, + "seed": 836533545, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 13.677130044843084, + "fontFamily": 1, + "text": "craft data", + "baseline": 11.780269058296007, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1796, + "versionNonce": 1407883056, + "isDeleted": false, + "id": "ttC9xH3eOGzQ_k5DLzmBu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -245.0771506636562, + "y": 1508.714667199035, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 0.6776858655143201, + "height": 347.40968967814, + "seed": 187206441, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "AfmJxEIuWj5_62161GQ01", + "gap": 11.845459426238218, + "focus": 0.08178190315132251 + }, + "endBinding": { + "elementId": "SehQ2WycVyCQCB-DbZfI1", + "gap": 3.963620241223044, + "focus": -0.07279835372456127 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.6776858655143201, + 347.40968967814 + ] + ] + }, + { + "type": "ellipse", + "version": 410, + "versionNonce": 51621680, + "isDeleted": false, + "id": "SehQ2WycVyCQCB-DbZfI1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -329.5040702804547, + "y": 1859.9240159441954, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 183.84491778774336, + "height": 121.63153961136064, + "seed": 1448762345, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "ttC9xH3eOGzQ_k5DLzmBu" + ] + }, + { + "type": "text", + "version": 374, + "versionNonce": 1588809350, + "isDeleted": false, + "id": "zERziwpui3gX_VflZl-6H", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -294.7738759605737, + "y": 1914.3163926258123, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 102.57847533632317, + "height": 17.780269058296007, + "seed": 1122418569, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 13.677130044843079, + "fontFamily": 1, + "text": "Ledger network", + "baseline": 11.780269058296007, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 349, + "versionNonce": 996390352, + "isDeleted": false, + "id": "95Wz9icO-OmtvwP5kKKfo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -231.3882257363585, + "y": 1743.2290732436484, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 29.405829596412616, + "height": 17.780269058296007, + "seed": 1438218537, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 13.677130044843071, + "fontFamily": 1, + "text": "send", + "baseline": 11.780269058296007, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 236, + "versionNonce": 2057635802, + "isDeleted": false, + "id": "1o37i1BlUHSA_I1CYSHoq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -369.98077680761, + "y": 1306.2222222222215, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 43.986547085201835, + "height": 24.30835496813786, + "seed": 365688905, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "IolhdtZMlJt05RE7PSeqH" + ], + "fontSize": 18.520651404295513, + "fontFamily": 1, + "text": "apply", + "baseline": 17.30835496813786, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1479, + "versionNonce": 442628400, + "isDeleted": false, + "id": "dAdWOUfpjNuXJyfO475fP", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -92.3069277739813, + "y": 1403.7229079845479, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 9.276809968310843, + "height": 353.05286013400246, + "seed": 203795395, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "jTa_ZQSjZQ_9wK0AxfkKq", + "focus": -0.7039148076260963, + "gap": 3.636958806672169 + }, + "endBinding": { + "elementId": "xA7xWASKaJXAE1SQjNFQ2", + "focus": 0.2211669756965918, + "gap": 14.492622357918698 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 9.276809968310843, + 353.05286013400246 + ] + ] + }, + { + "type": "ellipse", + "version": 494, + "versionNonce": 12155910, + "isDeleted": false, + "id": "xA7xWASKaJXAE1SQjNFQ2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -144.08927207417597, + "y": 1770.7329347284497, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 101.89461883408097, + "height": 60.17937219730958, + "seed": 833498093, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "dAdWOUfpjNuXJyfO475fP" + ] + }, + { + "type": "text", + "version": 413, + "versionNonce": 1448457242, + "isDeleted": false, + "id": "Mh7caG46B4Id7Rt9mZVK0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -78.91214202933338, + "y": 1692.8943697060292, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 90, + "height": 19, + "seed": 1753782275, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "dAdWOUfpjNuXJyfO475fP" + ], + "fontSize": 13.677130044843079, + "fontFamily": 1, + "text": "read storage", + "baseline": 13, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 411, + "versionNonce": 706916998, + "isDeleted": false, + "id": "3Nb-sNCg1mpGbUpGqPC6x", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -127.67671602036424, + "y": 1788.5132037867468, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 75.22421524663697, + "height": 17.780269058296007, + "seed": 1638966797, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 13.677130044843079, + "fontFamily": 1, + "text": "ledger node", + "baseline": 11.780269058296007, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 162, + "versionNonce": 137567834, + "isDeleted": false, + "id": "vMWskj1YJao4q8Szni_9g", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -294.2777777777778, + "y": 1243.3333333333333, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 61, + "seed": 1736645018, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "IolhdtZMlJt05RE7PSeqH", + "MODmRH_Ai_h7eca-aYe9A", + "mHbM0I-2cMJHDQh1moe77" + ] + }, + { + "type": "diamond", + "version": 389, + "versionNonce": 416087686, + "isDeleted": false, + "id": "mBiwqFRC0vttSdLSnsU30", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -445.2777777777778, + "y": 1092.6666666666667, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 406.66666666666674, + "height": 525.6666666666669, + "seed": 476615706, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rmYOU060-nUE-om6NWFg", + "dAdWOUfpjNuXJyfO475fP" + ] + }, + { + "type": "ellipse", + "version": 352, + "versionNonce": 1356395824, + "isDeleted": false, + "id": "JwMrnB618nsHGBBcf92Nk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -763.2777777777765, + "y": 874.9999999999998, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 763.3333333333335, + "height": 772.6666666666669, + "seed": 2052937478, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "ttC9xH3eOGzQ_k5DLzmBu" + ] + }, + { + "type": "text", + "version": 170, + "versionNonce": 1019110864, + "isDeleted": false, + "id": "tKzpFkSzFkhgxKS4-AVGh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -502.944444444443, + "y": 895.6666666666666, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 190, + "height": 52, + "seed": 1397120794, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "node connected to \ntopic X", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 443, + "versionNonce": 988924208, + "isDeleted": false, + "id": "jLWO7OrtG_BOPVeKMiwwN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -473.44444444444264, + "y": 870.3333333333328, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 738.6666666666667, + "height": 779.0000000000005, + "seed": 1658325702, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "ttC9xH3eOGzQ_k5DLzmBu" + ] + }, + { + "type": "text", + "version": 46, + "versionNonce": 39391066, + "isDeleted": false, + "id": "GyEOsmqW1Zbdi7mCavkOe", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -302.44444444444275, + "y": 1444, + "strokeColor": "#1864ab", + "backgroundColor": "transparent", + "width": 134, + "height": 21, + "seed": 734861702, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "wrap transaction", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 364, + "versionNonce": 770571728, + "isDeleted": false, + "id": "MODmRH_Ai_h7eca-aYe9A", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -479.5404756662694, + "y": 1107.858463582872, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 182.87979526759636, + "height": 175.27063591985802, + "seed": 1217671632, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "KwgaEOt8AQFLzGPvhESKA", + "focus": -0.46643591145185553, + "gap": 6.200069247495527 + }, + "endBinding": { + "elementId": "vMWskj1YJao4q8Szni_9g", + "focus": -0.7361561089573266, + "gap": 2.3829026208952087 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 182.87979526759636, + 175.27063591985802 + ] + ] + }, + { + "type": "diamond", + "version": 778, + "versionNonce": 595069744, + "isDeleted": false, + "id": "1aiocLnc3sR_lZQoFdB-y", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -704.777777777776, + "y": 1239.6666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 234.6666666666666, + "height": 309.6666666666667, + "seed": 344958598, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rmYOU060-nUE-om6NWFg", + "dAdWOUfpjNuXJyfO475fP", + "MODmRH_Ai_h7eca-aYe9A" + ] + }, + { + "type": "text", + "version": 1103, + "versionNonce": 2017820624, + "isDeleted": false, + "id": "50lEI8L3qiqHPIwEeJjSw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -633.4444444444428, + "y": 1277.4999999999993, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 42, + "seed": 2055761114, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "intent\ngossip node", + "baseline": 36, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 196, + "versionNonce": 951002416, + "isDeleted": false, + "id": "zoKUQOZpsx3bSrQmlje2Y", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -518.4444444444428, + "y": 1209.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 13, + "height": 21, + "seed": 1744336198, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "...", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 235, + "versionNonce": 73640752, + "isDeleted": false, + "id": "l4h4jtqh4lQ5ChrRu_d_W", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -534.9444444444428, + "y": 1211, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 45, + "height": 24.000000000000004, + "seed": 1561834950, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "NbiyFfs9_Zj9QLzFL8qHm", + "sZ8QrMhPhFBK7IsremLwb", + "B6B0y6Mtp0UrJDdAOrL4y" + ] + }, + { + "type": "arrow", + "version": 1093, + "versionNonce": 1896727504, + "isDeleted": false, + "id": "B6B0y6Mtp0UrJDdAOrL4y", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -548.3104597282063, + "y": 1368.7777417215675, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 24.520420568205054, + "height": 124.27733365833865, + "seed": 733305606, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "kfmygtSSHmjQGgRHYzhpm", + "focus": 0.25664643293730066, + "gap": 10.318970816658975 + }, + "endBinding": { + "elementId": "l4h4jtqh4lQ5ChrRu_d_W", + "focus": 0.285651344839639, + "gap": 9.50040806322886 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 24.520420568205054, + -124.27733365833865 + ] + ] + }, + { + "type": "arrow", + "version": 732, + "versionNonce": 138725840, + "isDeleted": false, + "id": "sZ8QrMhPhFBK7IsremLwb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -519.0071422289418, + "y": 1208, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.665799248315011, + "height": 60.764495548789455, + "seed": 1445654746, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "l4h4jtqh4lQ5ChrRu_d_W", + "focus": -0.2310245718703419, + "gap": 3 + }, + "endBinding": { + "elementId": "KwgaEOt8AQFLzGPvhESKA", + "focus": -0.06849615152086622, + "gap": 6.783169732481319 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -4.665799248315011, + -60.764495548789455 + ] + ] + }, + { + "type": "text", + "version": 854, + "versionNonce": 557904688, + "isDeleted": false, + "id": "y-BT1d_fUjOriE9fXDJL5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -600.8876146788973, + "y": 1384.7289755351683, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 72, + "height": 57, + "seed": 277391514, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 14.403669724770648, + "fontFamily": 1, + "text": "gossipsub \nintent\nmempool", + "baseline": 51, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 371, + "versionNonce": 434353968, + "isDeleted": false, + "id": "kfmygtSSHmjQGgRHYzhpm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -611.9444444444428, + "y": 1379.0967125382265, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 61, + "seed": 405227014, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "IolhdtZMlJt05RE7PSeqH", + "MODmRH_Ai_h7eca-aYe9A", + "B6B0y6Mtp0UrJDdAOrL4y", + "jNcNZ79jIqf8USX6XWVfL" + ] + }, + { + "type": "diamond", + "version": 51, + "versionNonce": 1683064986, + "isDeleted": false, + "id": "SHqk2AS-Tf7dKA-jlraJy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -642.9444444444428, + "y": 1809, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 126, + "height": 138, + "seed": 111487686, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "jNcNZ79jIqf8USX6XWVfL" + ] + }, + { + "type": "text", + "version": 61, + "versionNonce": 1776051718, + "isDeleted": false, + "id": "4KWmIOGgA881gCSC5JHPR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -605.4444444444428, + "y": 1867.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 45, + "height": 21, + "seed": 1914582042, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "Client", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 583, + "versionNonce": 1443824080, + "isDeleted": false, + "id": "jNcNZ79jIqf8USX6XWVfL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -579.5237129692832, + "y": 1804.7181399798692, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 5.784969773188777, + "height": 354.7181399798692, + "seed": 338257542, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "SHqk2AS-Tf7dKA-jlraJy", + "focus": -0.012292118582791034, + "gap": 4.302480703755165 + }, + "endBinding": { + "elementId": "kfmygtSSHmjQGgRHYzhpm", + "focus": 0.09507177161013172, + "gap": 9.903287461773516 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 5.784969773188777, + -354.7181399798692 + ] + ] + }, + { + "type": "text", + "version": 75, + "versionNonce": 1051571526, + "isDeleted": false, + "id": "2KyLbsA0pYg-9FlG-TXOS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -550.9444444444428, + "y": 1763.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 21, + "seed": 436422490, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "send intent", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 134, + "versionNonce": 1446470608, + "isDeleted": false, + "id": "_iAmT5vekaBL78IeOELHk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -616.4444444444428, + "y": 1474.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 29, + "height": 21, + "seed": 472650778, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "add", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "diamond", + "version": 649, + "versionNonce": 2130147290, + "isDeleted": false, + "id": "zO0p-uresSJTDL1fIuJzV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -425.7777777777761, + "y": 1051.6666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 135.66666666666674, + "height": 118.66666666666676, + "seed": 112661018, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rmYOU060-nUE-om6NWFg", + "dAdWOUfpjNuXJyfO475fP", + "MODmRH_Ai_h7eca-aYe9A", + "NbiyFfs9_Zj9QLzFL8qHm", + "sZ8QrMhPhFBK7IsremLwb", + "mHbM0I-2cMJHDQh1moe77", + "ixAHiLZKgPdknMrEKL8E0" + ] + }, + { + "type": "arrow", + "version": 273, + "versionNonce": 2086180304, + "isDeleted": false, + "id": "mHbM0I-2cMJHDQh1moe77", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -349.6653716829509, + "y": 1166.225689032409, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 50.06401310624267, + "height": 82.14785148842725, + "seed": 1712918342, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "zO0p-uresSJTDL1fIuJzV", + "focus": 0.3763418967402116, + "gap": 2.3589356079478208 + }, + "endBinding": { + "elementId": "vMWskj1YJao4q8Szni_9g", + "focus": -0.5326929819465109, + "gap": 5.323580798930379 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 50.06401310624267, + 82.14785148842725 + ] + ] + }, + { + "type": "diamond", + "version": 680, + "versionNonce": 992396592, + "isDeleted": false, + "id": "KwgaEOt8AQFLzGPvhESKA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -600.7777777777761, + "y": 1027.6666666666667, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 135.66666666666674, + "height": 118.66666666666676, + "seed": 1672626970, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rmYOU060-nUE-om6NWFg", + "dAdWOUfpjNuXJyfO475fP", + "MODmRH_Ai_h7eca-aYe9A", + "NbiyFfs9_Zj9QLzFL8qHm", + "sZ8QrMhPhFBK7IsremLwb", + "mHbM0I-2cMJHDQh1moe77" + ] + }, + { + "type": "diamond", + "version": 103, + "versionNonce": 1529867034, + "isDeleted": false, + "id": "5G2DYWMgIW2-Eyu0jHu5M", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -333.94444444444275, + "y": 634.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 126, + "height": 138, + "seed": 856516186, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "jNcNZ79jIqf8USX6XWVfL", + "ixAHiLZKgPdknMrEKL8E0" + ] + }, + { + "type": "text", + "version": 111, + "versionNonce": 1461469830, + "isDeleted": false, + "id": "3n5h8veTQO-lECAC8FTbh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -296.44444444444275, + "y": 693.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 45, + "height": 21, + "seed": 1537623110, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "Client", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 175, + "versionNonce": 288331846, + "isDeleted": false, + "id": "m_iuhNYFucnv5uwttXCPa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -281.94444444444275, + "y": 819.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 21, + "seed": 1275300634, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "send intent", + "baseline": 15, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 79, + "versionNonce": 1108019504, + "isDeleted": false, + "id": "ixAHiLZKgPdknMrEKL8E0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -269.94444444444275, + "y": 777, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 84, + "height": 274, + "seed": 1390808730, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "5G2DYWMgIW2-Eyu0jHu5M", + "focus": -0.37232070443749277, + "gap": 3.604130291018784 + }, + "endBinding": { + "elementId": "zO0p-uresSJTDL1fIuJzV", + "focus": -0.21219892752739253, + "gap": 3.135288841647885 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -84, + 274 + ] + ] + }, + { + "type": "text", + "version": 1136, + "versionNonce": 408089904, + "isDeleted": false, + "id": "Wj0MHJ1dtfAoF25CFCBfi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -576.9444444444428, + "y": 1060.2499999999998, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 42, + "seed": 960837584, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "intent\ngossip node", + "baseline": 36, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 1149, + "versionNonce": 1606676272, + "isDeleted": false, + "id": "TXaHYoLoaZlRQaR766TLH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -400.94444444444275, + "y": 1083.2500000000002, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 42, + "seed": 1165581104, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "intent\ngossip node", + "baseline": 36, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 1227, + "versionNonce": 1197055440, + "isDeleted": false, + "id": "9hZsHGB4M9tFJZIfi6Ao_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -305.94444444444275, + "y": 1134.2499999999998, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 128, + "height": 63, + "seed": 1645861840, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "intent\ngossip node\nwith matchmaker", + "baseline": 57, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 210, + "versionNonce": 346021168, + "isDeleted": false, + "id": "26K0-MZ5xxu4N_zB_HZRa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -164.94444444444275, + "y": 890.2499999999999, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 190, + "height": 52, + "seed": 1266445616, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "node connected to \ntopic Y", + "baseline": 44, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 913, + "versionNonce": 915376944, + "isDeleted": false, + "id": "ENHebue9f6lfx-kznKF5G", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": -285.94444444444275, + "y": 1243.7500000000002, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 72, + "height": 57, + "seed": 2113192912, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 14.403669724770648, + "fontFamily": 1, + "text": "gossipsub \nintent\nmempool", + "baseline": 51, + "textAlign": "left", + "verticalAlign": "top" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.svg b/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.svg new file mode 100644 index 00000000000..7295dd3431a --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.svg @@ -0,0 +1,16 @@ + + + + + + + add intentfilter.wasmmatchmaker.wasmtx.wawm + datacraft dataLedger networksendapplyread storageledger nodenode connected to topic Xwrap transactionintentgossip node...gossipsub intentmempoolClientsend intentaddClientsend intentintentgossip nodeintentgossip nodeintentgossip nodewith matchmakernode connected to topic Ygossipsub intentmempool \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker.md b/documentation/dev/src/explore/design/intent_gossip/matchmaker.md new file mode 100644 index 00000000000..ce2cace5bdf --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/matchmaker.md @@ -0,0 +1,80 @@ +# Matchmaker + +The matchmaker is a specific actor in the intent gossip network that tries to +match intents together. When intents are matched together, the matchmaker crafts +a transaction from them and sends it to the ledger network. + +A matchmaker is an intent gossip node started with additional parameters: a +ledger address and a list of sub-matchmakers. A sub-matchmaker is defined with a +topics list, a main program path, a filter program path, and a transaction code. + +The main and filter programs are wasm compiled code. Each has a defined +entrypoint and their own set of environment functions that they can call. + +When the matchmaker receives a new intent from the network it calls the +corresponding sub-matchmaker, the one that has the intent’s topic in their +topics list. A sub-matchmaker first checks if the intent is accepted by the +filter, before adding it to that sub-matchmaker database. Then the main program +is called with the intent and current state. + +## Sub-matchmaker topics' list + +A sub-matchmaker is defined to work with only a subset of encoding. Each intent +propagated to the corresponding topic will be process by this sub-matchmaker. + +Having a topics list instead of a unique topic allows a matchmaker to match +intents from different encodings. For example, when an updated version of an +encoding is out, the matchmaker could match intents from both versions if they +don’t diverge too much. + +## Sub-matchmaker database and state (name TBD) + +Each sub-matchmaker has a database and an arbitrary state. + +The database contains intents received by the node from the topics list that +passed the filter. + +The state is arbitrary data that is managed by the main program. That state is +given to all calls in the main program. + +The database is persistent but the state is not. When a node is started the +state is recovered by giving all intents from the database to the main program. +The invariant that the current state is equal to the state if the node is +restarted is not enforced and is the responsibility of the main program. + +## Filter program + +The filter is an optional wasm program given in parameters. This filter is used +to check each intent received by that sub-matchmaker. If it's not defined, +intents are directly passed to the main program. + + The entrypoint `filter_intent` takes an intent and returns a boolean. The +filter has the ability to query the state of the ledger for any given key. + +## Main program + +The main program is a mandatory wasm program given in parameters. The main +program must match together intents. + +The main program entrypoint `match_intent` takes the current state, a new intent +data and its id. The main program also has the ability to query the state of the +ledger. It also has functions `remove` and `get` to interact with the matchmaker +mempool. When a main matchmaker program finds a match it sends a transaction to +the ledger composed of the code template given in the matchmaker parameter and +the data given to this function. Finally the matchmaker must update its state so +the next run will have up to date values. + +The main program is called on two specific occasion; when intent gossip node is +started, on all intent from database and whenever a new intent is received from +the p2p network and the RPC endpoint, if enabled. + +## Transaction + +The transaction code given in parameters is used when the main program matches a +group of intents. The main program returns arbitrary data that is attached to +the transaction which is then injected into a ledger node. + +## Flow diagram: Matchmaker process +![matchmaker process](./matchmaker_process.svg "matchmaker process") + +[excalidraw link](https://excalidraw.com/#room=92b291c13cfab8fb22a4,OvHfWIrL0jeDzPI-EFZMaw) diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.excalidraw b/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.excalidraw new file mode 100644 index 00000000000..8d2d29fc95b --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.excalidraw @@ -0,0 +1,1648 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "ellipse", + "version": 304, + "versionNonce": 732978925, + "isDeleted": false, + "id": "0kpaYctW0QC41GkaU0Fnm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 788, + "y": 448, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 63.000000000000014, + "seed": 1594329571, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "YebzbhwZPZJG7VeGK6buO", + "x5FlGy9vo-0WFWlvTLxRB" + ] + }, + { + "type": "text", + "version": 66, + "versionNonce": 1832587875, + "isDeleted": false, + "id": "VGy1X1kAYasab67GT_3Vp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 815, + "y": 464, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 26, + "seed": 1391569133, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent A", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 357, + "versionNonce": 1055790499, + "isDeleted": false, + "id": "ipca-2U6Ichp5KRWeBGiU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1019.5, + "y": 553.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 63.000000000000014, + "seed": 961566285, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "YebzbhwZPZJG7VeGK6buO", + "jrzBVVdi5EqY0mYYzI-qC", + "vUyB6nADBS51AWp7XW9EQ" + ] + }, + { + "type": "text", + "version": 103, + "versionNonce": 2128726541, + "isDeleted": false, + "id": "sh9VeUsrJu8zyMqC4wTTn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1046.5, + "y": 569.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 26, + "seed": 54575363, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent B", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 443, + "versionNonce": 813787459, + "isDeleted": false, + "id": "mWM2JyFPRcVz5-LPE35xW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 912.5, + "y": 728.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 63.000000000000014, + "seed": 1522729677, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "jrzBVVdi5EqY0mYYzI-qC", + "jL-ZuiaA7Yepo4Ood8hh4" + ] + }, + { + "type": "text", + "version": 189, + "versionNonce": 563544173, + "isDeleted": false, + "id": "mkyAaMiHp_aflaPNTUS4h", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 939.5, + "y": 744.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 86, + "height": 26, + "seed": 1761418371, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent C", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 602, + "versionNonce": 665405667, + "isDeleted": false, + "id": "8Kq9uOVih2T18r283qOW_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 625.8084194993482, + "y": 740.257842502465, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 143.34456240942814, + "height": 63.15180022233556, + "seed": 1558060547, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "jL-ZuiaA7Yepo4Ood8hh4", + "hKuG4FbIRTd9ZuXgsPwJH", + "B-ARwwElkcqpEnbbGinI1" + ] + }, + { + "type": "text", + "version": 336, + "versionNonce": 2031512269, + "isDeleted": false, + "id": "jPnOeECE_YTxUJl_u56s4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 652.8734767374922, + "y": 756.2963949398833, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 89.21444793314066, + "height": 26.062647710805145, + "seed": 2115827629, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20.048190546773174, + "fontFamily": 1, + "text": "Intent D", + "baseline": 18.062647710805145, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 596, + "versionNonce": 1193323555, + "isDeleted": false, + "id": "eYMWKEiFVFlYbjiPgu_c4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1300.5, + "y": 552.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 63.000000000000014, + "seed": 1785759373, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "vUyB6nADBS51AWp7XW9EQ", + "Tz4cdqtGreDIsYuudDs1x" + ] + }, + { + "type": "text", + "version": 347, + "versionNonce": 967433101, + "isDeleted": false, + "id": "rHC_PRTjElJ0lMKCObfnb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1327.5, + "y": 568.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 87, + "height": 26, + "seed": 397063363, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent E", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 652, + "versionNonce": 590894061, + "isDeleted": false, + "id": "1MaFQq1C6f_KmXrCju5x4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1084.5, + "y": 873.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 63.000000000000014, + "seed": 1794271469, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "ZVBhabk31rzDP8O9ZI07b", + "wkpAIY8WXU4_DhUYO1aB9" + ] + }, + { + "type": "text", + "version": 393, + "versionNonce": 1012982253, + "isDeleted": false, + "id": "DLt_4JJLkufDMzrR-McEC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1111.5, + "y": 889.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 85, + "height": 26, + "seed": 1115072611, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent F", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 39, + "versionNonce": 1611153251, + "isDeleted": false, + "id": "YebzbhwZPZJG7VeGK6buO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 933, + "y": 485, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 96, + "height": 76, + "seed": 1699820525, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "0kpaYctW0QC41GkaU0Fnm", + "focus": -0.8133458878104184, + "gap": 2.90910614687202 + }, + "endBinding": { + "elementId": "ipca-2U6Ichp5KRWeBGiU", + "focus": -0.38721567770808374, + "gap": 6.84867663170747 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 96, + 76 + ] + ] + }, + { + "type": "arrow", + "version": 42, + "versionNonce": 858266701, + "isDeleted": false, + "id": "jrzBVVdi5EqY0mYYzI-qC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1080, + "y": 624, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 40, + "height": 103, + "seed": 1343642477, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "ipca-2U6Ichp5KRWeBGiU", + "focus": -0.05715042949410481, + "gap": 7.857427983505644 + }, + "endBinding": { + "elementId": "mWM2JyFPRcVz5-LPE35xW", + "focus": 0.5953281273765297, + "gap": 12.01223971985118 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -40, + 103 + ] + ] + }, + { + "type": "arrow", + "version": 257, + "versionNonce": 137386755, + "isDeleted": false, + "id": "jL-ZuiaA7Yepo4Ood8hh4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 909.4582871700171, + "y": 767.0701354272285, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 133.9560247707435, + "height": 0.9487028688702139, + "seed": 520169987, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "mWM2JyFPRcVz5-LPE35xW", + "focus": -0.21904362566296393, + "gap": 4.520486328322136 + }, + "endBinding": { + "elementId": "8Kq9uOVih2T18r283qOW_", + "focus": -0.10330406547410168, + "gap": 6.7389193162100725 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -133.9560247707435, + 0.9487028688702139 + ] + ] + }, + { + "type": "arrow", + "version": 39, + "versionNonce": 366545581, + "isDeleted": false, + "id": "vUyB6nADBS51AWp7XW9EQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1165, + "y": 592, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 9, + "seed": 1771476173, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "ipca-2U6Ichp5KRWeBGiU", + "focus": 0.381367937500731, + "gap": 3.8982105359317814 + }, + "endBinding": { + "elementId": "eYMWKEiFVFlYbjiPgu_c4", + "focus": 0.2019864602059321, + "gap": 6.524513889047455 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 129, + -9 + ] + ] + }, + { + "type": "arrow", + "version": 58, + "versionNonce": 403964579, + "isDeleted": false, + "id": "x5FlGy9vo-0WFWlvTLxRB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 630.6120629006252, + "y": 483.8482760775462, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 151.38793709937477, + "height": 3.1517239224538116, + "seed": 625380429, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": null, + "endBinding": { + "elementId": "0kpaYctW0QC41GkaU0Fnm", + "focus": -0.28899374373966724, + "gap": 7.345010020824645 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 151.38793709937477, + 3.1517239224538116 + ] + ] + }, + { + "type": "arrow", + "version": 257, + "versionNonce": 677492291, + "isDeleted": false, + "id": "B-ARwwElkcqpEnbbGinI1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 620.2972122415217, + "y": 767.8259116200478, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 105.25300037055923, + "height": 3.00722858201598, + "seed": 1445768771, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "8Kq9uOVih2T18r283qOW_", + "focus": 0.19635383025160105, + "gap": 5.918038308672706 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -105.25300037055923, + 3.00722858201598 + ] + ] + }, + { + "type": "arrow", + "version": 96, + "versionNonce": 1349387117, + "isDeleted": false, + "id": "Tz4cdqtGreDIsYuudDs1x", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1367, + "y": 622, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3, + "height": 106, + "seed": 1860965379, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "eYMWKEiFVFlYbjiPgu_c4", + "focus": 0.054884241524747655, + "gap": 6.574103558846954 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3, + 106 + ] + ] + }, + { + "type": "arrow", + "version": 376, + "versionNonce": 1294704067, + "isDeleted": false, + "id": "ZVBhabk31rzDP8O9ZI07b", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.670616178776941, + "x": 959.5453473034307, + "y": 783.1973748150889, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 7.812955781063806, + "height": 238.3078431598483, + "seed": 447610179, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": null, + "endBinding": { + "elementId": "1MaFQq1C6f_KmXrCju5x4", + "focus": 0.1315115545982045, + "gap": 10.746558995595109 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -7.812955781063806, + 238.3078431598483 + ] + ] + }, + { + "type": "arrow", + "version": 246, + "versionNonce": 1817294669, + "isDeleted": false, + "id": "wkpAIY8WXU4_DhUYO1aB9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1263.104260686975, + "y": 902.4835510585458, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 110.99401075709227, + "height": 3.577325516939027, + "seed": 891868547, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "e1dMmNfjngeSZQw9Q_6bR", + "focus": 1.9182906115878682, + "gap": 15.4835510585458 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 110.99401075709227, + 3.577325516939027 + ] + ] + }, + { + "type": "text", + "version": 60, + "versionNonce": 1709523331, + "isDeleted": false, + "id": "Y_SmrpIdN0Bw5qAuumOQq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 988, + "y": 482, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 78, + "height": 26, + "seed": 1670115203, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "0.1 BTC", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 84, + "versionNonce": 1194052653, + "isDeleted": false, + "id": "wBU3MwS8Tev8OyGNWq1F6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1083.5, + "y": 663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 101, + "height": 26, + "seed": 1534790019, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "1000 XTZ", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 176, + "versionNonce": 1801984291, + "isDeleted": false, + "id": "oPeSCDN2RaQLZlMs4lwHW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 786.3493583868772, + "y": 791.9373522891948, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 147, + "height": 26, + "seed": 1663457027, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20.04819054677318, + "fontFamily": 1, + "text": "Give 2.3k ADA", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 258, + "versionNonce": 35546349, + "isDeleted": false, + "id": "ECtAtkhrh6wCcSpTFeh6_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 488.2084241147941, + "y": 793.8867522150837, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 192, + "height": 26, + "seed": 812495341, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20.048190546773178, + "fontFamily": 1, + "text": "Give max 3.5k EUR", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 208, + "versionNonce": 18377827, + "isDeleted": false, + "id": "9ciMh63J68g-vSTAGOaJH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 609.5, + "y": 437, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 171, + "height": 26, + "seed": 1942912995, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Want min 3k USD", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 111, + "versionNonce": 1985072557, + "isDeleted": false, + "id": "o0sItaNu9BXqe7GFjkfGK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1185.5, + "y": 533, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 75, + "height": 26, + "seed": 1598303939, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "1.3 ETH", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 158, + "versionNonce": 717025187, + "isDeleted": false, + "id": "eY178tj8K8fiCB25oqtHg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1382, + "y": 647, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 198, + "height": 26, + "seed": 650001315, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Give max 15K DOGE", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 316, + "versionNonce": 181323661, + "isDeleted": false, + "id": "IsXt4dgTY7mgH6itytb74", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 872.5, + "y": 859, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 165, + "height": 26, + "seed": 403581795, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Want min 3 DOT", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 210, + "versionNonce": 2078831715, + "isDeleted": false, + "id": "e1dMmNfjngeSZQw9Q_6bR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1244, + "y": 861, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 198, + "height": 26, + "seed": 842009251, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wkpAIY8WXU4_DhUYO1aB9" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "Give max 0.20 BNB", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 197, + "versionNonce": 495152091, + "isDeleted": false, + "id": "wti3vXhEzdwXVYSSC_Yg4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 919.75, + "y": 1297.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 573.6995798319325, + "height": 340.49999999999994, + "seed": 1080165805, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 194, + "versionNonce": 2141066069, + "isDeleted": false, + "id": "NJGQKI0Bu-BSAQWAGkvs0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1049.9411764705899, + "y": 1313.487394957982, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 301.8718487394957, + "height": 37.19747899159662, + "seed": 1527490595, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28.61344537815124, + "fontFamily": 1, + "text": "Crafted transaction ", + "baseline": 26.19747899159662, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 402, + "versionNonce": 1602173051, + "isDeleted": false, + "id": "VZPj77ZO7kpLohrkLcsaq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 946.9327731092449, + "y": 1363.5609243697459, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 380.55882352941154, + "height": 260.3823529411763, + "seed": 245832803, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28.613445378151237, + "fontFamily": 1, + "text": "- transfers: \n - A -- 0.1 BTC --> B\n - B -- 1k XTZ --> C\n - C -- 2.3k ADA --> D\n - D -- 3.3k EUR --> G\n - G -- 3.9 USD --> A\n- Intent A, B, C, D, G", + "baseline": 249.3823529411763, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "line", + "version": 131, + "versionNonce": 282971573, + "isDeleted": false, + "id": "dDR0rbGDKpRDhHVnEbj1j", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 423.7733438086325, + "y": 1030.9922277551025, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1217.4922266087865, + "height": 0.6666331686519698, + "seed": 1295070381, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 1217.4922266087865, + -0.6666331686519698 + ] + ] + }, + { + "type": "ellipse", + "version": 756, + "versionNonce": 1167558477, + "isDeleted": false, + "id": "Y1Mwn-VLiFFNqjTIzDdGi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 971.395787942603, + "y": 1084.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 63.000000000000014, + "seed": 803447789, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wn9QHNTxewgA6oWR_jL0I", + "HtZRI-FNYskH4xD-NSUP7" + ] + }, + { + "type": "text", + "version": 491, + "versionNonce": 1766483021, + "isDeleted": false, + "id": "F7Vff6Hn-nwKWlHga6GEW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 998.395787942603, + "y": 1100.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 87, + "height": 26, + "seed": 1821023587, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent E", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 602, + "versionNonce": 1139487939, + "isDeleted": false, + "id": "wn9QHNTxewgA6oWR_jL0I", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 838.2662361154958, + "y": 1115.6051125581707, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 127.08332287156463, + "height": 3.5588919032202284, + "seed": 1515770947, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "GhbNF28zPBdmlEAk4sj4B", + "focus": 0.24968415527183857, + "gap": 10.417486034576314 + }, + "endBinding": { + "elementId": "Y1Mwn-VLiFFNqjTIzDdGi", + "focus": 0.20198646020593047, + "gap": 6.48230359390098 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 127.08332287156463, + -3.5588919032202284 + ] + ] + }, + { + "type": "arrow", + "version": 537, + "versionNonce": 1389208675, + "isDeleted": false, + "id": "HtZRI-FNYskH4xD-NSUP7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1119.895787942603, + "y": 1112.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 158.02664733117444, + "height": 1.6298954558747027, + "seed": 1000247245, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "Y1Mwn-VLiFFNqjTIzDdGi", + "focus": -0.1521545689608934, + "gap": 5.9061722485449195 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 158.02664733117444, + 1.6298954558747027 + ] + ] + }, + { + "type": "text", + "version": 284, + "versionNonce": 2018617933, + "isDeleted": false, + "id": "N9A9cOEizfeHChb2jpWZz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 853.395787942603, + "y": 1068.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 75, + "height": 26, + "seed": 1106624845, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "1.3 ETH", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 319, + "versionNonce": 1565357325, + "isDeleted": false, + "id": "zd6CqXgDZCINde5uj4yN1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1109.895787942603, + "y": 1068.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 198, + "height": 26, + "seed": 1379632643, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Give max 15K DOGE", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 488, + "versionNonce": 519207363, + "isDeleted": false, + "id": "GhbNF28zPBdmlEAk4sj4B", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 685.5, + "y": 1078.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 63.000000000000014, + "seed": 1312450381, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "YebzbhwZPZJG7VeGK6buO", + "jrzBVVdi5EqY0mYYzI-qC", + "vUyB6nADBS51AWp7XW9EQ", + "wn9QHNTxewgA6oWR_jL0I", + "KdMOyTpK2hvtrVnL6DNUk" + ] + }, + { + "type": "text", + "version": 229, + "versionNonce": 1476996579, + "isDeleted": false, + "id": "WJ9wOt80gJsxizloGLxfL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 712.5, + "y": 1094.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 88, + "height": 26, + "seed": 377241603, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent B", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 765, + "versionNonce": 949274637, + "isDeleted": false, + "id": "nADlxrdq_B6PfDfo-HQQG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 666.2871222634944, + "y": 1200.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 63.000000000000014, + "seed": 1679328365, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Tv4Heajn0ixll9tSaFYAV", + "gSSopQezSVkjkU58H15H_" + ] + }, + { + "type": "text", + "version": 502, + "versionNonce": 720585923, + "isDeleted": false, + "id": "mX16fM9A-0VWPNNJUyNpS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 693.2871222634944, + "y": 1216.75, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 85, + "height": 26, + "seed": 1794245859, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Intent F", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 622, + "versionNonce": 935737251, + "isDeleted": false, + "id": "Tv4Heajn0ixll9tSaFYAV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.670616178776941, + "x": 541.3324695669255, + "y": 1110.447374815089, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 7.812955781063806, + "height": 238.3078431598483, + "seed": 2026335949, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": null, + "endBinding": { + "elementId": "nADlxrdq_B6PfDfo-HQQG", + "focus": 0.07219246840230681, + "gap": 9.727127096910678 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -7.812955781063806, + 238.3078431598483 + ] + ] + }, + { + "type": "arrow", + "version": 492, + "versionNonce": 1577405251, + "isDeleted": false, + "id": "gSSopQezSVkjkU58H15H_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 844.8913829504695, + "y": 1229.7335510585458, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 110.99401075709227, + "height": 3.577325516939027, + "seed": 1824878723, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "2ga4PRzeyXVKa442XCN2Y", + "focus": 1.9182906115878662, + "gap": 15.4835510585458 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 110.99401075709227, + 3.577325516939027 + ] + ] + }, + { + "type": "text", + "version": 434, + "versionNonce": 1228568397, + "isDeleted": false, + "id": "g4OJ4hTt8EHD2ib5qCNue", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 454.2871222634972, + "y": 1186.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 165, + "height": 26, + "seed": 1089080621, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Want min 3 DOT", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 323, + "versionNonce": 1748647533, + "isDeleted": false, + "id": "2ga4PRzeyXVKa442XCN2Y", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 825.7871222634944, + "y": 1188.25, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 198, + "height": 26, + "seed": 610953251, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "gSSopQezSVkjkU58H15H_" + ], + "fontSize": 20, + "fontFamily": 1, + "text": "Give max 0.20 BNB", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 658, + "versionNonce": 1450766733, + "isDeleted": false, + "id": "KdMOyTpK2hvtrVnL6DNUk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 4.670616178776941, + "x": 559.906477890532, + "y": 993.9852887975298, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 7.812955781063806, + "height": 238.3078431598483, + "seed": 398907139, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": null, + "endBinding": { + "elementId": "GhbNF28zPBdmlEAk4sj4B", + "focus": -0.05450706234283051, + "gap": 10.753265054089042 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -7.812955781063806, + 238.3078431598483 + ] + ] + }, + { + "type": "text", + "version": 497, + "versionNonce": 1079658179, + "isDeleted": false, + "id": "aM2wnnRNQ8hdsJNJ-zvH6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 458.36113058710646, + "y": 1069.7879139824408, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 194, + "height": 26, + "seed": 1308118701, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "Want min 0.09 BTC", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "top" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.png b/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.png new file mode 100644 index 0000000000000000000000000000000000000000..ab284b0e70511ec0e8e40a0aa5c2bd6c4ed2ec12 GIT binary patch literal 156552 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:HJZpaSnO`Nr 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 + + + + + + Intent AIntent BIntent CIntent DIntent EIntent F0.1 BTC1000 XTZGive 2.3k ADAGive max 3.5k EURWant min 3k USD1.3 ETHGive max 15K DOGEWant min 3 DOTGive max 0.20 BNBCrafted transaction - transfers: - A -- 0.1 BTC --> B - B -- 1k XTZ --> C - C -- 2.3k ADA --> D - D -- 3.3k EUR --> G - G -- 3.9 USD --> A- Intent A, B, C, D, GIntent E1.3 ETHGive max 15K DOGEIntent BIntent FWant min 3 DOTGive max 0.20 BNBWant min 0.09 BTC \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.excalidraw b/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.excalidraw new file mode 100644 index 00000000000..dcb48040f97 --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.excalidraw @@ -0,0 +1,1883 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "version": 250, + "versionNonce": 477786094, + "isDeleted": false, + "id": "-fWwS7rt9LYqEFNzDaOFL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1334.071428571429, + "y": 1108.4285714285713, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 115, + "height": 70, + "seed": 1676859603, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "yBtnqGclgLrHk5_Ql7cuO" + ], + "fontSize": 28, + "fontFamily": 3, + "text": "send_tx\n(data)", + "baseline": 63, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 178, + "versionNonce": 1385647602, + "isDeleted": false, + "id": "-AUKbp8i4Y0O6x-LjVldd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 297, + "y": 132, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1587.8571428571427, + "height": 1546, + "seed": 2049426387, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 102, + "versionNonce": 1069695534, + "isDeleted": false, + "id": "8SDYqin0B7kut0s8YSY5Z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 870, + "y": 178, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 355, + "height": 46, + "seed": 1891888509, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 36, + "fontFamily": 1, + "text": "Matchmaker process", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 666, + "versionNonce": 1176289202, + "isDeleted": false, + "id": "9rcJtdHxvs9duQVcmN6pg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1018.4827638040854, + "y": -498.6392023935267, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 221.76726681910736, + "height": 784.1206838750081, + "seed": 395336627, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "u-ME94nrrlTlc6E3cTspG", + "focus": 0.5186114360714434, + "gap": 14.860797606473284 + }, + "endBinding": { + "elementId": "20SyJveLAUi7KoBBp_oDS", + "focus": 0.14947349178376995, + "gap": 4.518518518518533 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -221.76726681910736, + 784.1206838750081 + ] + ] + }, + { + "type": "rectangle", + "version": 296, + "versionNonce": 1188416622, + "isDeleted": false, + "id": "20SyJveLAUi7KoBBp_oDS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 670, + "y": 290, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 201, + "height": 61, + "seed": 1788635837, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rcJtdHxvs9duQVcmN6pg", + "EV9g4uNJem7yTF0HdehQG", + "coek_IDR9PRlSqn_xuBfH" + ] + }, + { + "type": "text", + "version": 126, + "versionNonce": 328071538, + "isDeleted": false, + "id": "u-ME94nrrlTlc6E3cTspG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 983, + "y": -549.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 197, + "height": 36, + "seed": 1618827037, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rcJtdHxvs9duQVcmN6pg", + "MQZgoJ0QZeyWyyOlwsTJ7", + "wX2vQSP79rzqU32fALRHb" + ], + "fontSize": 28, + "fontFamily": 1, + "text": "gossip mempool", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 194, + "versionNonce": 796790446, + "isDeleted": false, + "id": "CcRIOeocsp5wp09s5Cald", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 683, + "y": 298.5, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 176, + "height": 36, + "seed": 1367059613, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "Filter_1.wasm", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 343, + "versionNonce": 439483186, + "isDeleted": false, + "id": "q87Hkm0Sw3bVVd3Wg6HND", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 996.5, + "y": 276.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 201, + "height": 61, + "seed": 903521971, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rcJtdHxvs9duQVcmN6pg", + "wX2vQSP79rzqU32fALRHb" + ] + }, + { + "type": "rectangle", + "version": 386, + "versionNonce": 1449396462, + "isDeleted": false, + "id": "pF6B18evRJlwUi2JcmQxA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1352.5, + "y": 275.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 201, + "height": 61, + "seed": 2098314525, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "9rcJtdHxvs9duQVcmN6pg", + "MQZgoJ0QZeyWyyOlwsTJ7", + "quXd_TqwuN2CmoyI_vXkX", + "tQlObdUcJ4e-eO1KrOatP", + "AFE8t2QPl0MtqNC64Ktcv" + ] + }, + { + "type": "text", + "version": 268, + "versionNonce": 946849010, + "isDeleted": false, + "id": "dLtlrcEEya8xcYfwZhZia", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1365, + "y": 287, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 185, + "height": 36, + "seed": 2000393875, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "quXd_TqwuN2CmoyI_vXkX" + ], + "fontSize": 28, + "fontFamily": 1, + "text": "Filter_n.wasm", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 160, + "versionNonce": 454081326, + "isDeleted": false, + "id": "B5qXjcTYw3uMpWIOVJW6r", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1077.5, + "y": 289, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 23, + "height": 36, + "seed": 1953378803, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "...", + "baseline": 25, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "arrow", + "version": 452, + "versionNonce": 1741575858, + "isDeleted": false, + "id": "MQZgoJ0QZeyWyyOlwsTJ7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1154.9140472935462, + "y": -501.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 207.63095361581804, + "height": 762.5, + "seed": 52989821, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "u-ME94nrrlTlc6E3cTspG", + "focus": -0.63125, + "gap": 12 + }, + "endBinding": { + "elementId": "pF6B18evRJlwUi2JcmQxA", + "focus": -0.718727980147667, + "gap": 14.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 207.63095361581804, + 762.5 + ] + ] + }, + { + "type": "arrow", + "version": 398, + "versionNonce": 65873262, + "isDeleted": false, + "id": "wX2vQSP79rzqU32fALRHb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1089.7329110152689, + "y": -503.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0.6477652019841571, + "height": 777.5, + "seed": 858158013, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "u-ME94nrrlTlc6E3cTspG", + "focus": -0.08333333333333333, + "gap": 10 + }, + "endBinding": { + "elementId": "q87Hkm0Sw3bVVd3Wg6HND", + "focus": -0.06557377049180328, + "gap": 2.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.6477652019841571, + 777.5 + ] + ] + }, + { + "type": "rectangle", + "version": 251, + "versionNonce": 1492430962, + "isDeleted": false, + "id": "Qby63QOrxFMccQD3d2c92", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 584.4285714285714, + "y": 892.8571428571428, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 305, + "height": 129.8571428571429, + "seed": 906153683, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "EV9g4uNJem7yTF0HdehQG", + "yBtnqGclgLrHk5_Ql7cuO", + "Wb12KbhuRcEHH5IclCfjz" + ] + }, + { + "type": "text", + "version": 175, + "versionNonce": 1648510894, + "isDeleted": false, + "id": "EU8SONkJD9g8GszHushxg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 613.4285714285714, + "y": 905.3571428571428, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 264, + "height": 34, + "seed": 711796221, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 2, + "text": "matchmaker_1.wasm", + "baseline": 24, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 882, + "versionNonce": 262787634, + "isDeleted": false, + "id": "EV9g4uNJem7yTF0HdehQG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 739.6354774988769, + "y": 363.8196459125622, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.694775060113102, + "height": 59.792753760100425, + "seed": 589900403, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "20SyJveLAUi7KoBBp_oDS", + "focus": 0.32213015232123915, + "gap": 12.81964591256218 + }, + "endBinding": { + "elementId": "NUXnLdbMG_fQSEDQtu2Ik", + "focus": -0.4632754266251567, + "gap": 9.81617175590884 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.694775060113102, + 59.792753760100425 + ] + ] + }, + { + "type": "rectangle", + "version": 313, + "versionNonce": 1983455726, + "isDeleted": false, + "id": "02X_kZ6WrMz4xBI8cgnGg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1349.9285714285716, + "y": 894.7857142857143, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 305, + "height": 148.42857142857147, + "seed": 1614285181, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "EV9g4uNJem7yTF0HdehQG", + "quXd_TqwuN2CmoyI_vXkX", + "Q1Ij1-SrX5G6sFDtWRjZD" + ] + }, + { + "type": "text", + "version": 233, + "versionNonce": 2127870962, + "isDeleted": false, + "id": "zwDG2KxyuTcJmWoH5zXjV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1378.9285714285716, + "y": 907.2857142857143, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 264, + "height": 34, + "seed": 1715804371, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 2, + "text": "matchmaker_n.wasm", + "baseline": 24, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 260, + "versionNonce": 612134322, + "isDeleted": false, + "id": "qFSA3c0L_Ds7pnNR9vyEk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 988.4285714285716, + "y": 1605.7142857142858, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 221.00000000000006, + "height": 55, + "seed": 1486037725, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "KdQIFFwY0Q-VCyIjJLjqc", + "RuPyouXclWRd1e2Onhfm4", + "tqejUpWw7UdRQgeKJx1Qs", + "jfK56lesWGBAsc29YGxXx" + ] + }, + { + "type": "text", + "version": 175, + "versionNonce": 1108344430, + "isDeleted": false, + "id": "CWmX8zj_7xHE5WtGuwcr9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1025.4285714285716, + "y": 1615.2142857142858, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 159, + "height": 36, + "seed": 1565496083, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "Tx Channel", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 173, + "versionNonce": 1909000873, + "isDeleted": false, + "id": "NUXnLdbMG_fQSEDQtu2Ik", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 717.9285714285712, + "y": 433.42857142857144, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 97, + "height": 36, + "seed": 335380755, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "EV9g4uNJem7yTF0HdehQG", + "Wb12KbhuRcEHH5IclCfjz", + "AzA1FaOgPEa4cFOKIb_XI" + ], + "fontSize": 28, + "fontFamily": 1, + "text": "is valid", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 323, + "versionNonce": 1795041586, + "isDeleted": false, + "id": "QmaxQnRFTdOUnNNcMPy7X", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 524.4999999999995, + "y": 1261.9285714285713, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 537.8571428571428, + "height": 67, + "seed": 1680157853, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "EV9g4uNJem7yTF0HdehQG", + "RuPyouXclWRd1e2Onhfm4", + "yBtnqGclgLrHk5_Ql7cuO", + "tqejUpWw7UdRQgeKJx1Qs" + ] + }, + { + "type": "text", + "version": 333, + "versionNonce": 1782168302, + "isDeleted": false, + "id": "NUM8zAtLsxNcBlfW4Jj7Q", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 541.4999999999995, + "y": 1278.4285714285713, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 492, + "height": 35, + "seed": 1032271795, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "yBtnqGclgLrHk5_Ql7cuO" + ], + "fontSize": 28, + "fontFamily": 3, + "text": "tx_wrapper(data:Vec) -> Tx", + "baseline": 28, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 836, + "versionNonce": 998650610, + "isDeleted": false, + "id": "yBtnqGclgLrHk5_Ql7cuO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 745.9085294063941, + "y": 1042.550408585807, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0.5420749370830436, + "height": 221.1355590894077, + "seed": 1278179069, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "Qby63QOrxFMccQD3d2c92", + "focus": -0.05746246854310238, + "gap": 19.836122871521297 + }, + "endBinding": { + "elementId": "NUM8zAtLsxNcBlfW4Jj7Q", + "focus": -0.16651713537544344, + "gap": 14.742603753356661 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 0.5420749370830436, + 221.1355590894077 + ] + ] + }, + { + "type": "rectangle", + "version": 405, + "versionNonce": 1298148654, + "isDeleted": false, + "id": "ZJr3NtMIhOWJfBQVo_6Fv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1195.7857142857133, + "y": 1253.6428571428569, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 534.9999999999999, + "height": 67, + "seed": 422192253, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "EV9g4uNJem7yTF0HdehQG", + "yBtnqGclgLrHk5_Ql7cuO", + "KdQIFFwY0Q-VCyIjJLjqc", + "jfK56lesWGBAsc29YGxXx" + ] + }, + { + "type": "text", + "version": 372, + "versionNonce": 1508312242, + "isDeleted": false, + "id": "HCJ2WIORw1W3_N60QE3F-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1224.7857142857133, + "y": 1266.1428571428569, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 492, + "height": 35, + "seed": 22425043, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Q1Ij1-SrX5G6sFDtWRjZD" + ], + "fontSize": 28, + "fontFamily": 3, + "text": "tx_wrapper(data:Vec) -> Tx", + "baseline": 28, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 280, + "versionNonce": 533175922, + "isDeleted": false, + "id": "-Bpweaa_yHbmmzQ8Fbx5I", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 786.7142857142856, + "y": 1113.2142857142856, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 115, + "height": 70, + "seed": 194669533, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "yBtnqGclgLrHk5_Ql7cuO" + ], + "fontSize": 28, + "fontFamily": 3, + "text": "send_tx\n(data)", + "baseline": 63, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 170, + "versionNonce": 1490893230, + "isDeleted": false, + "id": "W9zv5kY3tnQEBVzDDO4N6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 600.1428571428569, + "y": 947.142857142857, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 220, + "height": 72, + "seed": 876039485, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "tries to match\nasset exchange", + "baseline": 61, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 233, + "versionNonce": 435093554, + "isDeleted": false, + "id": "6hdBGIrkrZ5cxGEvo-GL2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1366.1428571428569, + "y": 957.7142857142857, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 203, + "height": 72, + "seed": 1851337181, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Q1Ij1-SrX5G6sFDtWRjZD" + ], + "fontSize": 28, + "fontFamily": 1, + "text": "tries to match\nbid for NFT", + "baseline": 61, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 735, + "versionNonce": 10867694, + "isDeleted": false, + "id": "Q1Ij1-SrX5G6sFDtWRjZD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1475.9494785236807, + "y": 1045.8571428571422, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0.4267578776036771, + "height": 208.23402295192489, + "seed": 1632703357, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "6hdBGIrkrZ5cxGEvo-GL2", + "focus": -0.08283125635170244, + "gap": 16.14285714285637 + }, + "endBinding": { + "elementId": "HCJ2WIORw1W3_N60QE3F-", + "focus": 0.019007158541393378, + "gap": 12.051691333789677 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.4267578776036771, + 208.23402295192489 + ] + ] + }, + { + "type": "text", + "version": 192, + "versionNonce": 444084334, + "isDeleted": false, + "id": "bQlxAsWkDvgA9E8xnJtCS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 929.2857142857142, + "y": 1434.2857142857142, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 57, + "height": 36, + "seed": 288110269, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "push", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 240, + "versionNonce": 213112178, + "isDeleted": false, + "id": "oCUz5xIu6AGXHaNP05Lz-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1232.7857142857142, + "y": 1418.2857142857142, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 57, + "height": 36, + "seed": 18964637, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "push", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "diamond", + "version": 171, + "versionNonce": 125953897, + "isDeleted": false, + "id": "K_xcNPiBXE-cXJS9zVPXW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1359.2857142857142, + "y": 1675.6428571428573, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 530, + "height": 522.8571428571429, + "seed": 1923266557, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 353, + "versionNonce": 394354599, + "isDeleted": false, + "id": "-b3XUoKQR2HkQtxmLNJ8P", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1460.7857142857144, + "y": 1770.2857142857142, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 341, + "height": 340, + "seed": 307253395, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 2, + "text": "intent type \n\n\n- Asset Exchange V1,... ,Vn\n\n- NFT Bid V1, ....,Vm\n\n- Proposal tax rate\n\n- ...", + "baseline": 330, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 321, + "versionNonce": 1380748402, + "isDeleted": false, + "id": "tqejUpWw7UdRQgeKJx1Qs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 737.3862781852455, + "y": 1337.5000000000002, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 361.4616837725865, + "height": 256, + "seed": 519759858, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "QmaxQnRFTdOUnNNcMPy7X", + "focus": 0.36506890309214235, + "gap": 8.571428571428896 + }, + "endBinding": { + "elementId": "qFSA3c0L_Ds7pnNR9vyEk", + "focus": 0.3749733951794426, + "gap": 12.214285714285552 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 361.4616837725865, + 256 + ] + ] + }, + { + "type": "arrow", + "version": 309, + "versionNonce": 767591918, + "isDeleted": false, + "id": "jfK56lesWGBAsc29YGxXx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1464.4663097459384, + "y": 1327.5000000000002, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 304.9230901746971, + "height": 266.0000000000002, + "seed": 1704766194, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "ZJr3NtMIhOWJfBQVo_6Fv", + "focus": -0.15595141865445492, + "gap": 6.857142857143344 + }, + "endBinding": { + "elementId": "qFSA3c0L_Ds7pnNR9vyEk", + "focus": 0.10624312511016634, + "gap": 12.214285714285552 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -304.9230901746971, + 266.0000000000002 + ] + ] + }, + { + "type": "diamond", + "version": 308, + "versionNonce": 1298647335, + "isDeleted": false, + "id": "NgADSumoY4-dLkgWi5N4Z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 897.9285714285716, + "y": 455.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 425, + "height": 386.99999999999994, + "seed": 335028978, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "EV9g4uNJem7yTF0HdehQG", + "AzA1FaOgPEa4cFOKIb_XI" + ] + }, + { + "type": "text", + "version": 217, + "versionNonce": 1471444327, + "isDeleted": false, + "id": "MvTHemdF09EZ6oXZkE1jc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1018.9285714285716, + "y": 531.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 170, + "height": 46, + "seed": 724442674, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 36, + "fontFamily": 1, + "text": "database", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 757, + "versionNonce": 1293338825, + "isDeleted": false, + "id": "AzA1FaOgPEa4cFOKIb_XI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 820.3073244931843, + "y": 471.5118365287101, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 108.81143098291295, + "height": 175.18267082955458, + "seed": 360610866, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "NUXnLdbMG_fQSEDQtu2Ik", + "gap": 5.768100034460303, + "focus": -0.6938097555601055 + }, + "endBinding": { + "elementId": "18SWFQMli2cPThvdOpuoo", + "gap": 7.798248129571567, + "focus": -0.6635324351244143 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 108.81143098291295, + 175.18267082955458 + ] + ] + }, + { + "type": "arrow", + "version": 30, + "versionNonce": 1985069938, + "isDeleted": false, + "id": "coek_IDR9PRlSqn_xuBfH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 668.9285714285716, + "y": 354.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 68, + "height": 62, + "seed": 1014756974, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "20SyJveLAUi7KoBBp_oDS", + "focus": 0.48309968177517726, + "gap": 3.1071428571428896 + }, + "endBinding": { + "elementId": "7tbyhZRSlrMMeTwNPcbDl", + "focus": -0.14152410575427682, + "gap": 6 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -68, + 62 + ] + ] + }, + { + "type": "text", + "version": 27, + "versionNonce": 590603438, + "isDeleted": false, + "id": "7tbyhZRSlrMMeTwNPcbDl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 540.9285714285716, + "y": 422.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 74, + "height": 46, + "seed": 1767538158, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "coek_IDR9PRlSqn_xuBfH" + ], + "fontSize": 36, + "fontFamily": 1, + "text": "drop", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 32, + "versionNonce": 1173128498, + "isDeleted": false, + "id": "Wb12KbhuRcEHH5IclCfjz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 774.9285714285716, + "y": 474.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 8, + "height": 406, + "seed": 939628210, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "NUXnLdbMG_fQSEDQtu2Ik", + "focus": -0.1831322697972629, + "gap": 4.678571428571445 + }, + "endBinding": { + "elementId": "Qby63QOrxFMccQD3d2c92", + "focus": 0.1851313721138299, + "gap": 12.749999999999886 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -8, + 406 + ] + ] + }, + { + "type": "text", + "version": 13, + "versionNonce": 1089412846, + "isDeleted": false, + "id": "jEi03IUAXxK1nj2Y42B7G", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 800.9285714285716, + "y": 624.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 53, + "height": 46, + "seed": 665121458, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 36, + "fontFamily": 1, + "text": "run", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 26, + "versionNonce": 1615400690, + "isDeleted": false, + "id": "lI0I06cVAOlfanFTA8Ffo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 612.9285714285716, + "y": 574.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 65, + "height": 46, + "seed": 752657582, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 36, + "fontFamily": 1, + "text": "add", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 976, + "versionNonce": 1550157735, + "isDeleted": false, + "id": "tQlObdUcJ4e-eO1KrOatP", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1459.3837617831516, + "y": 349.05652363076683, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1.1844667472269066, + "height": 55.55587604189577, + "seed": 1629950318, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "pF6B18evRJlwUi2JcmQxA", + "focus": -0.0721870437439987, + "gap": 12.556523630766833 + }, + "endBinding": { + "elementId": "i-tn-ZhFtCTs742XKlQ71", + "focus": -0.29298735309248414, + "gap": 9.81617175590884 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.1844667472269066, + 55.55587604189577 + ] + ] + }, + { + "type": "text", + "version": 209, + "versionNonce": 435052722, + "isDeleted": false, + "id": "i-tn-ZhFtCTs742XKlQ71", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1423.428571428571, + "y": 414.42857142857144, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 97, + "height": 36, + "seed": 1973388402, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "tQlObdUcJ4e-eO1KrOatP", + "ho8uhQaYopUXmE-cOb5Oy", + "-aa5R0cDAH1OKloselZA3" + ], + "fontSize": 28, + "fontFamily": 1, + "text": "is valid", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 801, + "versionNonce": 1290356583, + "isDeleted": false, + "id": "ho8uhQaYopUXmE-cOb5Oy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1438.8508824450746, + "y": 460.6001539378572, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.01481739098062, + "height": 177.3752734807801, + "seed": 652742126, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "i-tn-ZhFtCTs742XKlQ71", + "gap": 10.171582509285766, + "focus": 0.08985472450032943 + }, + "endBinding": { + "elementId": "zBxeCe2V0Axk5Bu9Z7zb_", + "focus": 0.10695154534385387, + "gap": 3.009196397424489 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -160.01481739098062, + 177.3752734807801 + ] + ] + }, + { + "type": "arrow", + "version": 309, + "versionNonce": 214916146, + "isDeleted": false, + "id": "AFE8t2QPl0MtqNC64Ktcv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1553.1148808294615, + "y": 349.7631332623532, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 91.75609240578751, + "height": 126.39460265738433, + "seed": 635080690, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "pF6B18evRJlwUi2JcmQxA", + "focus": -0.5572753230194984, + "gap": 13.263133262353222 + }, + "endBinding": { + "elementId": "SW06q84zYJLMiyPVkQ1xY", + "focus": 0.049091461638458705, + "gap": 8.949406937405342 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 91.75609240578751, + 126.39460265738433 + ] + ] + }, + { + "type": "text", + "version": 129, + "versionNonce": 1871263726, + "isDeleted": false, + "id": "SW06q84zYJLMiyPVkQ1xY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1628.4285714285716, + "y": 485.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 74, + "height": 46, + "seed": 650911790, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "AFE8t2QPl0MtqNC64Ktcv" + ], + "fontSize": 36, + "fontFamily": 1, + "text": "drop", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 116, + "versionNonce": 314115570, + "isDeleted": false, + "id": "-aa5R0cDAH1OKloselZA3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1480.4285714285716, + "y": 455.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 8, + "height": 406, + "seed": 329761202, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "i-tn-ZhFtCTs742XKlQ71", + "focus": -0.18313226979726524, + "gap": 4.678571428571445 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -8, + 406 + ] + ] + }, + { + "type": "text", + "version": 44, + "versionNonce": 1438124590, + "isDeleted": false, + "id": "cl7vkyLcyLB5BfJMeXjQw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1506.4285714285716, + "y": 605.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 53, + "height": 46, + "seed": 754182766, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 36, + "fontFamily": 1, + "text": "run", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 200, + "versionNonce": 155678642, + "isDeleted": false, + "id": "DF_4GrKBdi7IsRviOYf09", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1306.4285714285716, + "y": 487.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 65, + "height": 46, + "seed": 1013496690, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 36, + "fontFamily": 1, + "text": "add", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "diamond", + "version": 500, + "versionNonce": 1653880585, + "isDeleted": false, + "id": "18SWFQMli2cPThvdOpuoo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 921.4285714285716, + "y": 614.6071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 110, + "height": 99.00000000000001, + "seed": 1872566985, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "tQlObdUcJ4e-eO1KrOatP", + "ho8uhQaYopUXmE-cOb5Oy", + "AzA1FaOgPEa4cFOKIb_XI" + ] + }, + { + "id": "bvMzoIc6JDXbDVYJF9Ibg", + "type": "text", + "x": 941.9285714285716, + "y": 654.6071428571429, + "width": 76, + "height": 26, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1926098473, + "version": 27, + "versionNonce": 1791601191, + "isDeleted": false, + "boundElementIds": null, + "text": "table 1 ", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "r0pIVYRB3ZOSkyTYj3h2g", + "type": "text", + "x": 1212.9285714285716, + "y": 649.1071428571429, + "width": 70, + "height": 26, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1775138089, + "version": 27, + "versionNonce": 1051956679, + "isDeleted": false, + "boundElementIds": null, + "text": "table n", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 18 + }, + { + "type": "diamond", + "version": 524, + "versionNonce": 1186891401, + "isDeleted": false, + "id": "zBxeCe2V0Axk5Bu9Z7zb_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1193.9285714285716, + "y": 615.1071428571429, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 110, + "height": 99.00000000000001, + "seed": 748894505, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "tQlObdUcJ4e-eO1KrOatP", + "ho8uhQaYopUXmE-cOb5Oy", + "AzA1FaOgPEa4cFOKIb_XI" + ] + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.svg b/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.svg new file mode 100644 index 00000000000..226afae23bb --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.svg @@ -0,0 +1,16 @@ + + + + + + + send_tx(data)Matchmaker processgossip mempoolFilter_1.wasmFilter_n.wasm...matchmaker_1.wasmmatchmaker_n.wasmTx Channelis validtx_wrapper(data:Vec<u8>) -> Txtx_wrapper(data:Vec<u8>) -> Txsend_tx(data)tries to matchasset exchangetries to matchbid for NFTpushpushintent type - Asset Exchange V1,... ,Vn- NFT Bid V1, ....,Vm- Proposal tax rate- ...databasedroprunaddis validdroprunaddtable 1 table n \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/topic.md b/documentation/dev/src/explore/design/intent_gossip/topic.md new file mode 100644 index 00000000000..08a6465d073 --- /dev/null +++ b/documentation/dev/src/explore/design/intent_gossip/topic.md @@ -0,0 +1,11 @@ +# Topic + +A topic is string and an encoding that describes this sub-network. In a topic +all intents use the exact same encoding. That encoding is known by matchmakers +so it can decode them to find matches. Whenever a node subscribes to a new topic +it informs all connected nodes and each of them propagate it. With this it’s +easy to create new topics in the intent gossip network and inform others. + +Other nodes can choose to subscribe to a new topic with the help of a +filter. This filter is defined as a combination of a whitelist, a regex +expression, and a maximum limit. diff --git a/documentation/dev/src/explore/design/ledger.md b/documentation/dev/src/explore/design/ledger.md new file mode 100644 index 00000000000..3c3fd8ce253 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger.md @@ -0,0 +1,80 @@ +# The ledger + +The ledger depends on [Tendermint node](https://github.com/tendermint/tendermint). Running the Anoma node will also initialize and run Tendermint node. Anoma communicates with Tendermint via the ABCI. + +## Overview + +The following diagram illustrates the current boundaries between the async and blocking code. + +![ledger threads](ledger/ledger_threads.svg "ledger threads") +[Diagram on Excalidraw](https://excalidraw.com/#room=952eca1f17ac3c7b5cee,ReXYfqLLleTjVnSQM9zrjw) + +## Threads config + +Configuration for threads usage can be changed via environment variables: + +- `ANOMA_TOKIO_THREADS`: Defaults to 1/2 logical cores +- `ANOMA_RAYON_THREADS`: Defaults to 1/2 logical cores. +- `ANOMA_ROCKSDB_COMPACTION_THREADS`: Defauls to 1/4 logical core. RocksDB also uses 1 more background thread for flushing. + +## Tendermint ABCI + +We are using the Tendermint state-machine replication engine via ABCI. It provides many useful things, such as a BFT consensus protocol, P2P layer with peer exchange, block sync and mempool layer. + +Useful resources: +- Tendermint ABCI +- Tendermint RPC reference +- Awesome collection + +Rust ABCI implementations: +- + - the future update planned for this crate is to add async support + - longer term the goal is to be able to [seamlessly switch from Go Tendermint + to Rust Tendermint](https://github.com/informalsystems/tendermint-rs/issues/29#issuecomment-672444401) + - includes RPC and light-client libraries +- + - async support +- + - deprecated in favor of informalsystems/tendermint-rs + +### ABCI Integration + +The ledger wraps the Tendermint node inside the Anoma node. The Tendermint node +communicates with the Anoma shell via four layers as illustrated below. + +```mermaid +flowchart LR + C[Client] --- R + subgraph Anoma Node + S((Anoma Shell)) + subgraph Tendermint ABCI + R[RPC] === T{Tendermint} + T --- TC[Consensus] + T --- TM[Mempool] + T --- TQ[Query] + T --- TS[Snapshot] + end + TC --- S + TM --- S + TQ --- S + TS --- S + end +``` + +The *consensus* connection allows the shell to: +- initialize genesis on start-up +- begin a block +- apply a transaction(s) in a block +- end a block +- commit a block + +The *mempool* connection asks the shell to validate transactions before they get +stored in the mempool and broadcasted to peers. The mempool will signify that +the transaction is either new, when it has not been validated before, or to be +re-checked when it has been validated at some previous level. + +The *query* connection is used for: +- the Tendermint node asks the last known state from the shell to determine if it needs to replay any blocks +- relay client queries for some state at a given path to the shell + +The *snapshot* connection is used to serve state sync snapshots for other nodes and/or restore state sync snapshots to a local node being bootstrapped. diff --git a/documentation/dev/src/explore/design/ledger/accounts.md b/documentation/dev/src/explore/design/ledger/accounts.md new file mode 100644 index 00000000000..3b14a086a8a --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/accounts.md @@ -0,0 +1,54 @@ +# Accounts + +[Tracking Issue](https://github.com/anoma/anoma/issues/45) + +--- + +There's only a single account type. Each account is associated with: + +- a unique [transparent address](../../../specs/ledger.html#transparent-addresses) +- a [validity predicate](./vp.md) +- [dynamic storage sub-space](#dynamic-storage-sub-space) + +## Shielded addresses + +Similar to [Zcash Sapling protocol payment addresses and keys (section 3.1)](https://raw.githubusercontent.com/zcash/zips/master/protocol/protocol.pdf), users can generate spending keys for private payments. A shielded payment address, incoming viewing key and full viewing key are derived from a spending key. In a private payment, a shielded payment address is hashed with a diversifier into a diversified transmission key. When a different diversifier function is chosen for different transactions, it prevents the transmission key from being matched across the transactions. + +The encoding of the shielded addresses, spending and viewing keys is not yet decided, but for consistency we'll probably use a the same schema with different prefixes for anything that can use an identifier. + +- TODO consider using a schema similar to the [unified addresses proposed in Zcash](https://github.com/zcash/zips/issues/482), that are designed to unify the payment addresses across different versions by encoding a typecode and the length of the payment address together with it. This may be especially useful for the protocol upgrade system and fractal scaling system. + +## Dynamic storage sub-space + +Each account can have an associated dynamic account state in the storage. This +state may be comprised of keys of the built-in supported types and values of arbitrary user bytes. + +The dynamic storage sub-space could be a unix filesystem-like tree under the +account's address key-space with `read, write, delete, has_key, iter_prefix` +(and maybe a few other convenience functions for hash-maps, hash-sets, optional values, etc.) functions parameterized with the the account's address. + +In addition, the storage sub-space would provide: + +- a public type/trait for storage keys and key segments: + - this should allow to turn types to storage key segments, key segments back to types + - combine key segments into keys + - can be extended with custom types in the code in a transaction +- a public type/trait for storage values: + - values need to implement encoding traits, e.g. `BorshSerialize, BorshDeserialize` + - this allows composition of types as specified for [Borsh](https://borsh.io) + - the Merkle tree hashing function should hash values from the encoded bytes of this trait (the encoded value may be cached, because we update the Merkle tree in-memory before we commit the finalized block to the DB) +- functions to get the size of a key and an encoded value (for storage fees) +- the updates to account storage should be immediately visible to the transaction that performed the updates + - validity predicate modifications have to be handled a little differently - + the old validity predicate should be run to check that the new validity + predicate (and other state changes included in the transaction) is valid + +## Initializing a new account + +A new account can be initialized on-chain with a transaction: + +- anything be written into its storage (initial parameter) +- a validity predicate has to be provided (we can have a default out-of-band) +- at minimum, accounts need to be enumerated on chain, this could be done with an address or a counter + +A newly created account should be validated by all the VPs triggered by the transaction, i.e. it should be included in the set of changed keys passed to each VP. If the VPs are not interested in the newly created account, they can choose to ignore it. diff --git a/documentation/dev/src/explore/design/ledger/epochs.md b/documentation/dev/src/explore/design/ledger/epochs.md new file mode 100644 index 00000000000..daa26f965d3 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/epochs.md @@ -0,0 +1,15 @@ +# Epochs + +An epoch is a range of blocks whose length is determined by the [epoch duration protocol parameter](./parameters.md#epoch-duration): minimum epoch duration and minimum number of blocks in an epoch. They are identified by consecutive natural numbers starting at 0. + +We store the current epoch in global storage and the epoch of each block in the block storage. We also store the minimum height and minimum time of a first block in the next epoch in global storage, so that changes to the epoch duration protocol parameter don't affect the current epoch, but rather apply from the following epoch. Note that protocol parameters changes may themselves be delayed. + +The first epoch (ID 0) starts on the genesis block. The next epoch minimum start time is set to the genesis time configured for the chain + minimum duration and the next epoch minimum height is set to the height of the genesis block (typically 1) + minimum number of blocks. + +On each block `BeginBlock` Tendermint call, we check if the current epoch is finished, in which case we move on to the next epoch. An epoch is finished when both the minimum number of blocks and minimum duration of an epoch have been created from the first block of a current epoch. When a new epoch starts, the next epoch minimum height is set to the block's height + minimum number of blocks and minimum start time time is set to block's time from the block header + minimum duration. + +## Predecessor blocks epochs + +We store the epoch ranges of predecessor blocks. This is used for example for to look-up the epoch from an evidence of validators that acted maliciously (which includes block height and block time) for PoS system. For the PoS system, in block at height `h`, we only need to know values from Tendermint `max(h - consensus_params.evidence.max_age_num_blocks, 0)`, which is set to `100000` by default. + +The predecessor epochs are stored in the block storage. We update this structure on every new epoch and trim any epochs that ended more than `max_age_num_blocks` ago. diff --git a/documentation/dev/src/explore/design/ledger/fractal-scaling.md b/documentation/dev/src/explore/design/ledger/fractal-scaling.md new file mode 100644 index 00000000000..d3da491b474 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/fractal-scaling.md @@ -0,0 +1,5 @@ +# Fractal scaling + +[Tracking Issue](https://github.com/anoma/anoma/issues/41) + +--- diff --git a/documentation/dev/src/explore/design/ledger/front-running.md b/documentation/dev/src/explore/design/ledger/front-running.md new file mode 100644 index 00000000000..67f37bc8219 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/front-running.md @@ -0,0 +1,7 @@ +# Front-running prevention + +[Tracking Issue](https://github.com/anoma/anoma/issues/42) + +--- + +This page should describe how DKG can be integrated for front-running prevention. diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md new file mode 100644 index 00000000000..d518b99ae8f --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -0,0 +1,104 @@ +# Governance + +Anoma 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 Anoma 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, Anoma relies an off chain signaling mechanism to agree on a common strategy. + +## Governance & Treasury 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 + +Also, it introduces some protocol parameters: +- `min_proposal_fund` +- `max_proposal_code_size` +- `min_proposal_period` +- `max_proposal_content_size` +- `min_proposal_grace_epochs` +- `max_proposal_fund_transfer` + +## On-chain proposals + +On-chain proposals are created under the `governance_address` storage space and, by default, this storage space is initialized with following storage keys: +``` +/$GovernanceAddress/counter: u64 +/$GovernanceAddress/min_proposal_fund: u64 +/$GovernanceAddress/max_proposal_code_size: u64 +/$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: +``` +/$GovernanceAddress/proposal/$id/content : Vec +/$GovernanceAddress/proposal/$id/author : Address +/$GovernanceAddress/proposal/$id/startEpoch: Epoch +/$GovernanceAddress/proposal/$id/endEpoch: Epoch +/$GovernanceAddress/proposal/$id/graceEpoch: Epoch +/$GovernanceAddress/proposal/$id/proposalCode: Option> +/$GovernanceAddress/proposal/$id/funds: u64 +``` + +and follow these rules: +- `$id` must be equal to `counter + 1`. +- `startEpoch` must: + - 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` +- `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. +- `funds` must be equal to `min_proposal_fund` and should be moved to the `governance_address`. +- `content` should follow the `Anoma 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. + +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`. + +Proposal can be submitted by any address as long as the above rules are respected. Votes can be casted only by active validators and delegator (at epoch `startEpoch` or less). +Moreover, validator can vote only during the first 2/3 of the voting period (from `startEpoch` and 2/3 of `endEpoch` - `startEpoch`). + +The preferred content template (`Anoma Improvement Proposal schema`) is the following: + +```json +{ + "title": "", + "authors": " ", + "discussions-to": "", + "created": "", + "license": "", + "abstract": "", + "motivation": "", + "details": " - optional field", +} +``` + +In order to vote a proposal, a transaction should modify the following storage key: +``` +/$GovernanceAddress/proposal/$id/vote/$validator_address/$voter_address: ProposalVote +``` + +where ProposalVote is a borsh encoded string containing either `yay` or `nay`, `$validator_address` is the delegation validator address and the `$voter_address` is the address of who is voting. A voter can be cast for each delegation. + +Vote is valid if it follow this rules: +- vote can be sent only by validator or delegators +- validator can vote only during the first 2/3 of the total voting period, delegator can vote for the whole voting period + +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. + + +## Off-chain proposal + +In case where its not possibile to run a proposal online (for example, when the chain is halted), an offline mechanism can be used. +The ledger offers the possibility to create and sign proposal which are verified against a specific chain epoch. + + + + diff --git a/documentation/dev/src/explore/design/ledger/ledger_threads.excalidraw b/documentation/dev/src/explore/design/ledger/ledger_threads.excalidraw new file mode 100644 index 00000000000..819629ee55d --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/ledger_threads.excalidraw @@ -0,0 +1,1340 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "version": 515, + "versionNonce": 1491685727, + "isDeleted": false, + "id": "pd_XdsJCvYgXqlJNpQD8q", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -310.9761904761901, + "y": 1403.8928571428573, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 911, + "height": 41, + "seed": 1316411295, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "updated": 1638281507048, + "fontSize": 20, + "fontFamily": 2, + "text": "- async main with async sub-processes and ctrl + c handler\n- a dedicated OS thread for RocksDB with a sequential loop over channel with messages from the shell", + "baseline": 36, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 330, + "versionNonce": 1245219103, + "isDeleted": false, + "id": "VmJo9ip-_Nb-VTVZEw8Kr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 78.02380952381029, + "y": 1599.3571428571431, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 163, + "height": 146, + "seed": 1806613471, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "Airq4-IKmbHT7aCAW3smZ", + "FRo3C6f6zW4iBBh1q8jaK", + "Aqac15Quru7DFq1J1zJ75", + "zNgYoohABcyhwzIbYb-Lo", + "I713YnaMZhOtFyLmXEF6C", + "ygicdvODl0-UPGPM9mOad", + "VR2l08AG-i7-7iT2EAl8J", + "DrJMvlZMVg268rjjsHD7B", + "Bh3r3CcvEfm1bVHZqeK1a" + ], + "updated": 1638283266585 + }, + { + "type": "text", + "version": 531, + "versionNonce": 1044522961, + "isDeleted": false, + "id": "80Zu63hFbcM_lvy25aCJr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -111.92857142857068, + "y": 1656.9523809523816, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 171, + "height": 17, + "seed": 2022880657, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "updated": 1638281507048, + "fontSize": 16, + "fontFamily": 2, + "text": "tokio worker thread pool", + "baseline": 13, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 56, + "versionNonce": 1546762687, + "isDeleted": false, + "id": "0VaGRuLD2GMiFpIQiPqMR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 141.35714285714357, + "y": 1651.8571428571431, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 43, + "height": 21, + "seed": 390719487, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wDh62TNPODaTXftcfo__h", + "FRo3C6f6zW4iBBh1q8jaK" + ], + "updated": 1638281507048, + "fontSize": 20, + "fontFamily": 2, + "text": "main", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 178, + "versionNonce": 704264639, + "isDeleted": false, + "id": "kKhMt_jqXFdZVfYVS52F1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 377.52380952381037, + "y": 1661.857142857143, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 94, + "height": 21, + "seed": 1218960241, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wDh62TNPODaTXftcfo__h", + "FRo3C6f6zW4iBBh1q8jaK", + "kV0NNyq4-xWHwvvcCPDyL", + "xaHJ5tNfkGKeq6e--abG0", + "I713YnaMZhOtFyLmXEF6C", + "UJiFTL3VeXcVlCGjdLbBU", + "-C7l5eC8Dm4YoMORDUQGW" + ], + "updated": 1638283282004, + "fontSize": 20, + "fontFamily": 2, + "text": "tendermint", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 643, + "versionNonce": 1815876767, + "isDeleted": false, + "id": "p66i1EgzUhPqrGZEygxGo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 414.35714285714363, + "y": 1726.309523809524, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 47, + "height": 21, + "seed": 1525321759, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wDh62TNPODaTXftcfo__h", + "FRo3C6f6zW4iBBh1q8jaK", + "kV0NNyq4-xWHwvvcCPDyL", + "xaHJ5tNfkGKeq6e--abG0", + "VR2l08AG-i7-7iT2EAl8J", + "SBaWRnKQiQocECulcwV4k", + "ffbOz_H2zSWPzpThk5_fq" + ], + "updated": 1638283279100, + "fontSize": 20, + "fontFamily": 2, + "text": "ABCI", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 255, + "versionNonce": 1768335409, + "isDeleted": false, + "id": "I713YnaMZhOtFyLmXEF6C", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 244.78461330949256, + "y": 1664.8194537417958, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 129.52276720017073, + "height": 15.533897768680845, + "seed": 182273361, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638283271486, + "startBinding": { + "elementId": "VmJo9ip-_Nb-VTVZEw8Kr", + "focus": -0.2411681420070903, + "gap": 4.170455686749136 + }, + "endBinding": { + "elementId": "kKhMt_jqXFdZVfYVS52F1", + "focus": -0.8687447328303558, + "gap": 3.2164290141470815 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 129.52276720017073, + 15.533897768680845 + ] + ] + }, + { + "type": "text", + "version": 235, + "versionNonce": 708334705, + "isDeleted": false, + "id": "01cRBcLYn8LXrrOMiIrB2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 5.939884494803849, + "x": 199.52380952381043, + "y": 1551.8571428571431, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 119, + "height": 21, + "seed": 276902975, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "I713YnaMZhOtFyLmXEF6C", + "DrJMvlZMVg268rjjsHD7B" + ], + "updated": 1638283263136, + "fontSize": 20, + "fontFamily": 2, + "text": "async futures", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1111, + "versionNonce": 1702628721, + "isDeleted": false, + "id": "VR2l08AG-i7-7iT2EAl8J", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 238.33212400494563, + "y": 1703.1980835703648, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 161.19522982640007, + "height": 31.032708996170186, + "seed": 445281073, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638281507048, + "startBinding": { + "elementId": "VmJo9ip-_Nb-VTVZEw8Kr", + "focus": 0.2086363906376382, + "gap": 4.419948117124861 + }, + "endBinding": { + "elementId": "p66i1EgzUhPqrGZEygxGo", + "focus": -0.3195111398796351, + "gap": 14.829789025797936 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 161.19522982640007, + 31.032708996170186 + ] + ] + }, + { + "type": "arrow", + "version": 826, + "versionNonce": 2051497041, + "isDeleted": false, + "id": "DrJMvlZMVg268rjjsHD7B", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 221.51578459884615, + "y": 1610.8100470351471, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 127.92316248352705, + "height": 54.95644848553911, + "seed": 2081875217, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638283266585, + "startBinding": { + "elementId": "VmJo9ip-_Nb-VTVZEw8Kr", + "focus": -0.4312502886203979, + "gap": 10.37056842119496 + }, + "endBinding": { + "elementId": "z3DZNuUiMC7mSDWrwEmHF", + "focus": 0.35415681298384605, + "gap": 9.084862441436712 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 127.92316248352705, + -54.95644848553911 + ] + ] + }, + { + "type": "text", + "version": 368, + "versionNonce": 900205887, + "isDeleted": false, + "id": "z3DZNuUiMC7mSDWrwEmHF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 358.5238095238099, + "y": 1526.8571428571431, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 132, + "height": 21, + "seed": 989469425, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wDh62TNPODaTXftcfo__h", + "FRo3C6f6zW4iBBh1q8jaK", + "kV0NNyq4-xWHwvvcCPDyL", + "DrJMvlZMVg268rjjsHD7B" + ], + "updated": 1638283266585, + "fontSize": 20, + "fontFamily": 2, + "text": "ctrl + c handler", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 375, + "versionNonce": 1451934303, + "isDeleted": false, + "id": "xle96qVuOvkE6VADHzlni", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -63.54761904761864, + "y": 2002.3333333333335, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 281, + "height": 21, + "seed": 494799007, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "xX0Sjp4d5xmMxZueHitLy", + "ygicdvODl0-UPGPM9mOad", + "iGXEcqMfe32IhFFoybdOg", + "SBaWRnKQiQocECulcwV4k", + "cKyMJJsCmpWKOTWNSqDMM", + "be-hOvlUqvUr4DyAypVOA" + ], + "updated": 1638281507048, + "fontSize": 20, + "fontFamily": 2, + "text": "shell loop over channel receiver", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 667, + "versionNonce": 1044403985, + "isDeleted": false, + "id": "ygicdvODl0-UPGPM9mOad", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 114.0925803512514, + "y": 1739.752193937748, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 133.51443718807064, + "height": 247.74780606225272, + "seed": 129733841, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638281507048, + "startBinding": { + "elementId": "VmJo9ip-_Nb-VTVZEw8Kr", + "focus": 0.10559015514879938, + "gap": 5.893085487515066 + }, + "endBinding": { + "elementId": "xle96qVuOvkE6VADHzlni", + "focus": -0.7527898136437603, + "gap": 14.833333333332803 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -133.51443718807064, + 247.74780606225272 + ] + ] + }, + { + "type": "text", + "version": 558, + "versionNonce": 98639487, + "isDeleted": false, + "id": "ZGLhGr9pwJBpxl80_p2qt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 5.262346732480442, + "x": -35.254492783874916, + "y": 1807.9987063687224, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 154, + "height": 21, + "seed": 319921343, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "ygicdvODl0-UPGPM9mOad" + ], + "updated": 1638281507048, + "fontSize": 20, + "fontFamily": 2, + "text": "spawn OS thread", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1621, + "versionNonce": 240580255, + "isDeleted": false, + "id": "SBaWRnKQiQocECulcwV4k", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 409.6368150642577, + "y": 1752.9709154297034, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 298.640755194554, + "height": 237.6580025907415, + "seed": 358460639, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638281507048, + "startBinding": { + "elementId": "p66i1EgzUhPqrGZEygxGo", + "focus": 0.21883904748132793, + "gap": 7.3710819795555835 + }, + "endBinding": { + "elementId": "xle96qVuOvkE6VADHzlni", + "focus": 0.039960091713429936, + "gap": 11.704415312888614 + }, + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -298.640755194554, + 237.6580025907415 + ] + ] + }, + { + "type": "text", + "version": 851, + "versionNonce": 1605404369, + "isDeleted": false, + "id": "Hc47nGW_gtOiXtmx_puAw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 279.04761904761995, + "y": 1856.380952380953, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 191, + "height": 21, + "seed": 977753233, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "xX0Sjp4d5xmMxZueHitLy", + "SBaWRnKQiQocECulcwV4k", + "iGXEcqMfe32IhFFoybdOg" + ], + "updated": 1638281507048, + "fontSize": 20, + "fontFamily": 2, + "text": "ABCI service channel", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 439, + "versionNonce": 379374271, + "isDeleted": false, + "id": "LSahqK9R8i6hSOFV5OmIF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 298.1904761904764, + "y": 2085.5000000000005, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 176, + "height": 158, + "seed": 1878178047, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "zNgYoohABcyhwzIbYb-Lo", + "xX0Sjp4d5xmMxZueHitLy", + "cKyMJJsCmpWKOTWNSqDMM", + "935hGv7CJb6ABYlB1w7Un", + "OOFLVlfvhKVR1Is2xCi1q" + ], + "updated": 1638281507048 + }, + { + "type": "text", + "version": 616, + "versionNonce": 1182368945, + "isDeleted": false, + "id": "XDidT01qaUV1Gznzbw6VR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 311.97619047619105, + "y": 2254.5714285714294, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 177, + "height": 17, + "seed": 1215339121, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "updated": 1638281507048, + "fontSize": 16, + "fontFamily": 2, + "text": "rayon worker thread pool", + "baseline": 13, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1276, + "versionNonce": 458431199, + "isDeleted": false, + "id": "cKyMJJsCmpWKOTWNSqDMM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 106.85462331104353, + "y": 2033.6964929459075, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 196.50126412469146, + "height": 93.86693191348036, + "seed": 676413727, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638281507048, + "startBinding": { + "elementId": "xle96qVuOvkE6VADHzlni", + "focus": 0.08460522257970042, + "gap": 10.363159612574037 + }, + "endBinding": { + "elementId": "LSahqK9R8i6hSOFV5OmIF", + "focus": -0.02942051669513393, + "gap": 4.391855423725104 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 196.50126412469146, + 93.86693191348036 + ] + ] + }, + { + "type": "text", + "version": 580, + "versionNonce": 822765311, + "isDeleted": false, + "id": "rRG2raWKW7GyhL34UFhlV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 343.3809523809524, + "y": 2144.476190476191, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 98, + "height": 41, + "seed": 1072284991, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wDh62TNPODaTXftcfo__h", + "FRo3C6f6zW4iBBh1q8jaK", + "kV0NNyq4-xWHwvvcCPDyL", + "xaHJ5tNfkGKeq6e--abG0" + ], + "updated": 1638281507048, + "fontSize": 20, + "fontFamily": 2, + "text": "VPs\nparallel iter", + "baseline": 36, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 306, + "versionNonce": 2074851441, + "isDeleted": false, + "id": "b50SmACnvUidkzXVx04HS", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -339.26190476190436, + "y": 1730.1428571428578, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 319, + "height": 62, + "seed": 907855199, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "updated": 1638282845956, + "fontSize": 20, + "fontFamily": 2, + "text": "number of threads configurable\nwith `ANOMA_TOKIO_THREADS`, \nby default `num_cpus::get() / 2`", + "baseline": 57, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 419, + "versionNonce": 1754525407, + "isDeleted": false, + "id": "-jwLDhFONkUnH8p6dVCpU", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 269.88095238095275, + "y": 2283.380952380953, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 413, + "height": 62, + "seed": 1526989841, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "updated": 1638282841096, + "fontSize": 20, + "fontFamily": 2, + "text": "number of threads configurable\nwith `ANOMA_RAYON_THREADS`, by default\n`num_cpus::get() / 2`", + "baseline": 57, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 713, + "versionNonce": 1325527761, + "isDeleted": false, + "id": "bLAc_B_EwGVAebjgfW-GM", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 695.2619047619047, + "y": 2167.3333333333344, + "strokeColor": "#364fc7", + "backgroundColor": "transparent", + "width": 635, + "height": 62, + "seed": 1245364607, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "updated": 1638282834114, + "fontSize": 20, + "fontFamily": 2, + "text": "RocksDB uses threads internally\n- one background thread for flush and 1/4 logical cores for compaction \n (configurable with `ANOMA_ROCKSDB_COMPACTION_THREADS`)`", + "baseline": 57, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "HKxtedzENwDDoYQRW85X4", + "type": "text", + "x": -340.1666666666664, + "y": 1328.6666666666665, + "width": 272, + "height": 29, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 781497873, + "version": 85, + "versionNonce": 655172657, + "isDeleted": false, + "boundElementIds": null, + "updated": 1638281507048, + "text": "Ledger threads usage", + "fontSize": 28, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 22 + }, + { + "id": "SX_rkUsCtuv-LdKRlIwFx", + "type": "rectangle", + "x": -358.4999999999998, + "y": 1504.9999999999998, + "width": 978.3333333333333, + "height": 298.3333333333337, + "angle": 0, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 647348369, + "version": 302, + "versionNonce": 1627937745, + "isDeleted": false, + "boundElementIds": null, + "updated": 1638283137559 + }, + { + "id": "kxQSCVcgQAsdNI3rXp9ER", + "type": "text", + "x": -350.1666666666663, + "y": 1508.9999999999998, + "width": 74, + "height": 36, + "angle": 0, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1948605009, + "version": 215, + "versionNonce": 1981838193, + "isDeleted": false, + "boundElementIds": null, + "updated": 1638283035428, + "text": "async", + "fontSize": 28, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 25 + }, + { + "type": "text", + "version": 277, + "versionNonce": 2053364255, + "isDeleted": false, + "id": "TV2bEmL-2y11hAOT8IMOk", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -177.33333333333292, + "y": 1918.6666666666663, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 98, + "height": 71, + "seed": 91868159, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "updated": 1638283038347, + "fontSize": 28, + "fontFamily": 1, + "text": "blocking\n", + "baseline": 61, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 486, + "versionNonce": 423922673, + "isDeleted": false, + "id": "t7FMzpG22hf9K9b8Hgx5u", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -190.99999999999977, + "y": 1910.8333333333323, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 1556.666666666667, + "height": 466.6666666666668, + "seed": 1098773535, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "updated": 1638281507049 + }, + { + "id": "YmOdBqI72YlYOC_tFyiBt", + "type": "ellipse", + "x": 611.5000000000001, + "y": 2019.999999999999, + "width": 155.0000000000002, + "height": 131.66666666666697, + "angle": 0, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 726287967, + "version": 142, + "versionNonce": 1947150239, + "isDeleted": false, + "boundElementIds": [ + "be-hOvlUqvUr4DyAypVOA", + "OOFLVlfvhKVR1Is2xCi1q" + ], + "updated": 1638281507049 + }, + { + "type": "text", + "version": 602, + "versionNonce": 1972364753, + "isDeleted": false, + "id": "ebWanxdnHMSCjVXAJ1sWn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 654.166666666667, + "y": 2079.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 83, + "height": 21, + "seed": 1386786559, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wDh62TNPODaTXftcfo__h", + "FRo3C6f6zW4iBBh1q8jaK", + "kV0NNyq4-xWHwvvcCPDyL", + "xaHJ5tNfkGKeq6e--abG0" + ], + "updated": 1638281507049, + "fontSize": 20, + "fontFamily": 2, + "text": "RocksDB", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1807, + "versionNonce": 1478941631, + "isDeleted": false, + "id": "be-hOvlUqvUr4DyAypVOA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 227.48704426394386, + "y": 2009.5043320379616, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 401.3592448054459, + "height": 32.65800259074172, + "seed": 1701426353, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638281507049, + "startBinding": { + "elementId": "xle96qVuOvkE6VADHzlni", + "focus": -0.7102674913486405, + "gap": 10.034663311562497 + }, + "endBinding": { + "elementId": "YmOdBqI72YlYOC_tFyiBt", + "focus": 0.5863245840872268, + "gap": 1.5114834147282323 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 401.3592448054459, + 32.65800259074172 + ] + ] + }, + { + "type": "arrow", + "version": 1882, + "versionNonce": 1192559537, + "isDeleted": false, + "id": "OOFLVlfvhKVR1Is2xCi1q", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 480.82037759727757, + "y": 2152.004332037962, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 131.35924480544588, + "height": 49.008664075925026, + "seed": 1636785279, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638281507049, + "startBinding": { + "elementId": "LSahqK9R8i6hSOFV5OmIF", + "focus": 0.2666217822310704, + "gap": 7.629048621732977 + }, + "endBinding": { + "elementId": "YmOdBqI72YlYOC_tFyiBt", + "focus": 0.15991624090733284, + "gap": 1.9092717911904202 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 131.35924480544588, + -49.008664075925026 + ] + ] + }, + { + "type": "text", + "version": 275, + "versionNonce": 1596631519, + "isDeleted": false, + "id": "yeegCSHnOaDfKfuSekj9E", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 389.5000000000003, + "y": 1589.4999999999995, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 46, + "height": 21, + "seed": 673712433, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "wDh62TNPODaTXftcfo__h", + "FRo3C6f6zW4iBBh1q8jaK", + "kV0NNyq4-xWHwvvcCPDyL", + "xaHJ5tNfkGKeq6e--abG0", + "I713YnaMZhOtFyLmXEF6C", + "UJiFTL3VeXcVlCGjdLbBU", + "Bh3r3CcvEfm1bVHZqeK1a", + "-C7l5eC8Dm4YoMORDUQGW", + "ffbOz_H2zSWPzpThk5_fq" + ], + "updated": 1638283282004, + "fontSize": 20, + "fontFamily": 2, + "text": "abort", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 409, + "versionNonce": 1855755007, + "isDeleted": false, + "id": "Bh3r3CcvEfm1bVHZqeK1a", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 231.67423860056107, + "y": 1623.025565469656, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 143.44099325300573, + "height": 27.283924274329593, + "seed": 1950715775, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "updated": 1638283202172, + "startBinding": { + "elementId": "VmJo9ip-_Nb-VTVZEw8Kr", + "focus": -0.47713874101051523, + "gap": 8.880495302337962 + }, + "endBinding": { + "elementId": "yeegCSHnOaDfKfuSekj9E", + "focus": 0.7643325901211494, + "gap": 14.384768146433487 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 143.44099325300573, + -27.283924274329593 + ] + ] + }, + { + "id": "-C7l5eC8Dm4YoMORDUQGW", + "type": "arrow", + "x": 484.8333333333335, + "y": 1678.2689363402262, + "width": 74.99999999999994, + "height": 58.632102220163915, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 495614865, + "version": 224, + "versionNonce": 892241329, + "isDeleted": false, + "boundElementIds": null, + "updated": 1638283282004, + "points": [ + [ + 0, + -13.985083726697901 + ], + [ + 35, + -72.61718594686181 + ], + [ + -39.99999999999994, + -72.40614231244754 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "kKhMt_jqXFdZVfYVS52F1", + "focus": 1.0417199777977608, + "gap": 13.309523809523114 + }, + "endBinding": { + "elementId": "yeegCSHnOaDfKfuSekj9E", + "focus": 0.5635527734305699, + "gap": 9.333333333333258 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "ffbOz_H2zSWPzpThk5_fq", + "type": "arrow", + "x": 466.50000000000017, + "y": 1756.1135929634597, + "width": 140.58726302441448, + "height": 170.55307370320688, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 669428511, + "version": 271, + "versionNonce": 347359441, + "isDeleted": false, + "boundElementIds": null, + "updated": 1638283279100, + "points": [ + [ + 0, + -24.446926296793116 + ], + [ + 115.00000000000006, + -44.85498639803167 + ], + [ + 53.333333333333314, + -195 + ], + [ + -25.587263024414426, + -157.16590320205424 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "p66i1EgzUhPqrGZEygxGo", + "focus": -0.004080057248600112, + "gap": 5.142857142856542 + }, + "endBinding": { + "elementId": "yeegCSHnOaDfKfuSekj9E", + "focus": 0.5838779200338639, + "gap": 5.41273697558546 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/ledger/ledger_threads.svg b/documentation/dev/src/explore/design/ledger/ledger_threads.svg new file mode 100644 index 00000000000..506f18a16e4 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/ledger_threads.svg @@ -0,0 +1,16 @@ + + + + + + + - async main with async sub-processes and ctrl + c handler- a dedicated OS thread for RocksDB with a sequential loop over channel with messages from the shelltokio worker thread poolmaintendermintABCIasync futuresctrl + c handlershell loop over channel receiverspawn OS threadABCI service channelrayon worker thread poolVPsparallel iternumber of threads configurablewith `ANOMA_TOKIO_THREADS`, by default `num_cpus::get() / 2`number of threads configurablewith `ANOMA_RAYON_THREADS`, by default`num_cpus::get() / 2`RocksDB uses threads internally- one background thread for flush and 1/4 logical cores for compaction (configurable with `ANOMA_ROCKSDB_COMPACTION_THREADS`)`Ledger threads usageasyncblockingRocksDBabort \ No newline at end of file diff --git a/documentation/dev/src/explore/design/ledger/parameters.md b/documentation/dev/src/explore/design/ledger/parameters.md new file mode 100644 index 00000000000..b10fecd1448 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/parameters.md @@ -0,0 +1,13 @@ +# Parameters + +The parameters are used to dynamically control certain variables in the protocol. They are implemented as an internal address with a native VP. The current values are written into and read from the block storage in the parameters account's sub-space. + +Initial parameters for a chain are set in the genesis configuration. On chain, these can be changed by 2/3 of voting power (specifics are TBA). + +## Epoch duration + +The parameters for [epoch](./epochs.md) duration are: + +- Minimum number of blocks in an epoch +- Minimum duration of an epoch +- Maximum expected time per block \ No newline at end of file diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md new file mode 100644 index 00000000000..bf6b0f69522 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -0,0 +1,245 @@ +# PoS integration + +The [PoS system](../pos.md) is integrated into Anoma ledger at 3 different layers: +- base ledger that performs genesis initialization, validator set updates on new epoch and applies slashes when they are received from ABCI +- an account with an internal address and a [native VP](vp.md#native-vps) that validates any changes applied by transactions to the PoS account state +- transaction WASMs to perform various PoS actions, also available as a library code for custom made transactions + +The `votes_per_token` PoS system parameter must be chosen to satisfy the [Tendermint requirement](https://github.com/tendermint/spec/blob/60395941214439339cc60040944c67893b5f8145/spec/abci/apps.md#validator-updates) of `MaxTotalVotingPower = MaxInt64 / 8`. + +All [the data relevant to the PoS system](../pos.md#storage) are stored under the PoS account's storage sub-space, with the following key schema (the PoS address prefix is omitted for clarity): + +- `params` (required): the system parameters +- for any validator, all the following fields are required: + - `validator/{validator_address}/consensus_key` + - `validator/{validator_address}/state` + - `validator/{validator_address}/total_deltas` + - `validator/{validator_address}/voting_power` +- `slash/{validator_address}` (optional): a list of slashes, where each record contains epoch and slash rate +- `bond/{bond_source}/{bond_validator} (optional)` +- `unbond/{unbond_source}/{unbond_validator} (optional)` +- `validator_set (required)` +- `total_voting_power (required)` + +- standard validator metadata (these are regular storage values, not epoched data): + - `validator/{validator_address}/staking_reward_address` (required): an address that should receive staking rewards + - `validator/{validator_address}/address_raw_hash` (required): raw hash of validator's address associated with the address is used for look-up of validator address from a raw hash + - TBA (e.g. alias, website, description, delegation commission rate, etc.) + +Only XAN tokens can be staked in bonds. The tokens being staked (bonds and unbonds amounts) are kept in the PoS account under `{xan_address}/balance/{pos_address}` until they are withdrawn. + +## Initialization + +The PoS system is initialized via the shell on chain initialization. The genesis validator set is given in the genesis configuration. On genesis initialization, all the epoched data is set to be immediately active for the current (the very first) epoch. + +## Staking rewards and transaction fees + +Staking rewards for validators are rewarded in Tendermint's method `BeginBlock` in the base ledger. A validator must specify a `validator/{validator_address}/staking_reward_address` for its rewards to be credited to this address. + +To a validator who proposed a block (`block.header.proposer_address`), the system rewards tokens based on the `block_proposer_reward` PoS parameter and each validator that voted on a block (`block.last_commit_info.validator` who `signed_last_block`) receives `block_vote_reward`. + +All the fees that are charged in a transaction execution (DKG transaction wrapper fee and transactions applied in a block) are transferred into a fee pool, which is another special account controlled by the PoS module. Note that the fee pool account may contain tokens other than the staking token XAN. + +- TODO describe the fee pool, related to , and + +## Transactions + +The transactions are assumed to be applied in epoch `n`. Any transaction that modifies [epoched data](../pos.md#epoched-data) updates the structure as described in [epoched data storage](../pos.md#storage). + +For slashing tokens, we implement a [PoS slash pool account](vp.md#pos-slash-pool-vp). Slashed tokens should be credited to this account and, for now, no tokens can be be debited by anyone. + +### Validator transactions + +The validator transactions are assumed to be applied with an account address `validator_address`. + +- `become_validator(consensus_key, staking_reward_address)`: + - creates a record in `validator/{validator_address}/consensus_key` in epoch `n + pipeline_length` + - creates a record in `validator/{validator_address}/staking_reward_address` + - sets `validator/{validator_address}/state` for to `pending` in the current epoch and `candidate` in epoch `n + pipeline_length` +- `deactivate`: + - sets `validator/{validator_address}/state` for to `inactive` in epoch `n + pipeline_length` +- `reactivate`: + - sets `validator/{validator_address}/state` for to `pending` in the current epoch and `candidate` in epoch `n + pipeline_length` +- `self_bond(amount)`: + - let `bond = read(bond/{validator_address}/{validator_address}/delta)` + - if `bond` exist, update it with the new bond amount in epoch `n + pipeline_length` + - else, create a new record with bond amount in epoch `n + pipeline_length` + - debit the token `amount` from the `validator_address` and credit it to the PoS account + - add the `amount` to `validator/{validator_address}/total_deltas` in epoch `n + pipeline_length` + - update the `validator/{validator_address}/voting_power` in epoch `n + pipeline_length` + - update the `total_voting_power` in epoch `n + pipeline_length` + - update `validator_set` in epoch `n + pipeline_length` +- `unbond(amount)`: + - let `bond = read(bond/{validator_address}/{validator_address}/delta)` + - if `bond` doesn't exist, panic + - let `pre_unbond = read(unbond/{validator_address}/{validator_address}/delta)` + - if `total(bond) - total(pre_unbond) < amount`, panic + - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch) until whole `amount` is decremented + - for each decremented `bond` value write a new `unbond` with the key set to the epoch of the source value + - decrement the `amount` from `validator/{validator_address}/total_deltas` in epoch `n + unbonding_length` + - update the `validator/{validator_address}/voting_power` in epoch `n + unbonding_length` + - update the `total_voting_power` in epoch `n + unbonding_length` + - update `validator_set` in epoch `n + unbonding_length` +- `withdraw_unbonds`: + - let `unbond = read(unbond/{validator_address}/{validator_address}/delta)` + - if `unbond` doesn't exist, panic + - if no `unbond` value is found for epochs <= `n`, panic + - for each `((bond_start, bond_end), amount) in unbond where unbond.epoch <= n`: + - let `amount_after_slash = amount` + - for each `slash in read(slash/{validator_address})`: + - if `bond_start <= slash.epoch && slash.epoch <= bond_end)`, `amount_after_slash *= (10_000 - slash.rate) / 10_000` + - credit the `amount_after_slash` to the `validator_address` and debit the whole `amount` (before slash, if any) from the PoS account + - burn the slashed tokens (`amount - amount_after_slash`), if not zero +- `change_consensus_key`: + - creates a record in `validator/{validator_address}/consensus_key` in epoch `n + pipeline_length` + +For `self_bond`, `unbond`, `withdraw_unbonds`, `become_validator` and `change_consensus_key` the transaction must be signed with the validator's public key. Additionally, for `become_validator` and `change_consensus_key` we must attach a signature with the validator's consensus key to verify its ownership. Note that for `self_bond`, signature verification is also performed because there are tokens debited from the validator's account. + +### Delegator transactions + +The delegator transactions are assumed to be applied with an account address `delegator_address`. + +- `delegate(validator_address, amount)`: + - let `bond = read(bond/{delegator_address}/{validator_address}/delta)` + - if `bond` exist, update it with the new bond amount in epoch `n + pipeline_length` + - else, create a new record with bond amount in epoch `n + pipeline_length` + - debit the token `amount` from the `delegator_address` and credit it to the PoS account + - add the `amount` to `validator/{validator_address}/total_deltas` in epoch `n + pipeline_length` + - update the `validator/{validator_address}/voting_power` in epoch `n + pipeline_length` + - update the `total_voting_power` in epoch `n + pipeline_length` + - update `validator_set` in epoch `n + pipeline_length` +- `undelegate(validator_address, amount)`: + - let `bond = read(bond/{delegator_address}/{validator_address}/delta)` + - if `bond` doesn't exist, panic + - let `pre_unbond = read(unbond/{delegator_address}/{validator_address}/delta)` + - if `total(bond) - total(pre_unbond) < amount`, panic + - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch) until whole `amount` is decremented + - for each decremented `bond` value write a new `unbond` with the key set to the epoch of the source value + - decrement the `amount` from `validator/{validator_address}/total_deltas` in epoch `n + unbonding_length` + - update the `validator/{validator_address}/voting_power` in epoch `n + unbonding_length` + - update the `total_voting_power` in epoch `n + unbonding_length` + - update `validator_set` in epoch `n + unbonding_length` +- `redelegate(src_validator_address, dest_validator_address, amount)`: + - `undelegate(src_validator_address, amount)` + - `delegate(dest_validator_address, amount)` but set in epoch `n + unbonding_length` instead of `n + pipeline_length` +- `withdraw_unbonds`: + - for each `validator_address in iter_prefix(unbond/{delegator_address})`: + - let `unbond = read(unbond/{validator_address}/{validator_address}/delta)` + - if no `unbond` value is found for epochs <= `n`, `continue` to the next `validator_address` + - for each `((bond_start, bond_end), amount)` in epochs <= `n`: + - let `amount_after_slash = amount` + - for each `slash in read(slash/{validator_address})`: + - if `bond_start <= slash.epoch && slash.epoch <= bond_end)`, `amount_after_slash *= (10_000 - slash.rate) / 10_000` + - credit the `amount_after_slash` to the `delegator_address` and debit the whole `amount` (before slash, if any) from the PoS account + - burn the slashed tokens (`amount - amount_after_slash`), if not zero + +For `delegate`, `undelegate`, `redelegate` and `withdraw_unbonds` the transaction must be signed with the delegator's public key. Note that for `delegate`, signature verification is also performed because there are tokens debited from the delegator's account. + +## Slashing + +Evidence for byzantine behaviour is received from Tendermint ABCI on `BeginBlock`. For each evidence: + +- append the `evidence` into `slash/{evidence.validator_address}` +- calculate the slashed amount from deltas in and before the `evidence.epoch` in `validator/{validator_address}/total_deltas` for the `evidence.validator_address` and the slash rate +- deduct the slashed amount from the `validator/{validator_address}/total_deltas` at `pipeline_length` offset +- update the `validator/{validator_address}/voting_power` for the `evidence.validator_address` in and after epoch `n + pipeline_length` +- update the `total_voting_power` in and after epoch `n + pipeline_length` +- update `validator_set` in and after epoch `n + pipeline_length` + +## Validity predicate + +In the following description, "pre-state" is the state prior to transaction execution and "post-state" is the state posterior to it. + +Any changes to PoS epoched data are checked to update the structure as described in [epoched data storage](../pos.md#storage). + +Because some key changes are expected to relate to others, the VP also accumulates some values that are checked for validity after key specific logic: +- `balance_delta: token::Change` +- `bond_delta: HashMap` +- `unbond_delta: HashMap` +- `total_deltas: HashMap` +- `total_stake_by_epoch: HashMap>` +- `expected_voting_power_by_epoch: HashMap>`: calculated from the validator's total deltas +- `expected_total_voting_power_delta_by_epoch: HashMap`: calculated from the validator's total deltas +- `voting_power_by_epoch: HashMap>` +- `validator_set_pre: Option>` +- `validator_set_post: Option>` +- `total_voting_power_delta_by_epoch: HashMap` +- `new_validators: HashMap` + +The accumulators are initialized to their default values (empty hash maps and hash set). The data keyed by address are using the validator addresses. + +For any updated epoched data, the `last_update` field must be set to the current epoch. + +The validity predicate triggers a validation logic based on the storage keys modified by a transaction: + +- `validator/{validator_address}/consensus_key`: + ```rust,ignore + match (pre_state, post_state) { + (None, Some(post)) => { + // - check that all other required validator fields have been initialized + // - check that the `state` sub-key for this validator address has been set + // correctly, i.e. the value should be initialized at `pipeline_length` offset + // - insert into or update `new_validators` accumulator + }, + (Some(pre), Some(post)) => { + // - check that the new consensus key is different from the old consensus + // key and that it has been set correctly, i.e. the value can only be changed at `pipeline_length` offset + }, + _ => false, + } + ``` +- `validator/{validator_address}/state`: + ```rust,ignore + match (pre_state, post_state) { + (None, Some(post)) => { + // - check that all other required validator fields have been initialized + // - check that the `post` state is set correctly: + // - the state should be set to `pending` in the current epoch and `candidate` at pipeline offset + // - insert into or update `new_validators` accumulator + }, + (Some(pre), Some(post)) => { + // - check that a validator has been correctly deactivated or reactivated + // - the `state` should only be changed at `pipeline_length` offset + // - if the `state` becomes `inactive`, it must have been `pending` or `candidate` + // - if the `state` becomes `pending`, it must have been `inactive` + // - if the `state` becomes `candidate`, it must have been `pending` or `inactive` + }, + _ => false, + } + ``` +- `validator/{validator_address}/total_deltas`: + - find the difference between the pre-state and post-state values and add it to the `total_deltas` accumulator and update `total_stake_by_epoch`, `expected_voting_power_by_epoch` and `expected_total_voting_power_delta_by_epoch` +- `validator/{validator_address}/voting_power`: + - find the difference between the pre-state and post-state value and insert it into the `voting_power_by_epoch` accumulator +- `bond/{bond_source}/{bond_validator}/delta`: + - for each difference between the post-state and pre-state values: + - if the difference is not in epoch `n` or `n + pipeline_length`, panic + - find slashes for the `bond_validator`, if any, and apply them to the delta value + - add it to the `bond_delta` accumulator +- `unbond/{unbond_source}/{unbond_validator}/deltas`: + - for each difference between the post-state and pre-state values: + - if the difference is not in epoch `n` or `n + unboding_length`, panic + - find slashes for the `bond_validator`, if any, and apply them to the delta value + - add it to the `unbond_delta` accumulator +- `validator_set`: + - set the accumulators `validator_set_pre` and `validator_set_post` +- `total_voting_power`: + - find the difference between the post-state and pre-state + - add it to the `total_voting_power_delta_by_epoch` accumulator +- PoS account's balance: + - find the difference between the post-state and pre-state + - add it to the `balance_delta` accumulator + +No other storage key changes are permitted by the VP. + +After the storage keys iteration, we check the accumulators: + +- For each `total_deltas`, there must be the same delta value in `bond_delta`. +- For each `bond_delta`, there must be validator's change in `total_deltas`. +- Check that all positive `unbond_delta` also have a `total_deltas` update. Negative unbond delta is from withdrawing, which removes tokens from unbond, but doesn't affect total deltas. +- Check validator sets updates against validator total stakes. +- Check voting power changes against validator total stakes. +- Check expected voting power changes against `voting_power_by_epoch`. +- Check expected total voting power change against `total_voting_power_delta_by_epoch`. +- Check that the sum of bonds and unbonds deltas is equal to the balance delta. +- Check that all the new validators have their required fields set and that they have been added to the validator set diff --git a/documentation/dev/src/explore/design/ledger/storage.md b/documentation/dev/src/explore/design/ledger/storage.md new file mode 100644 index 00000000000..7186dd00a35 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/storage.md @@ -0,0 +1,146 @@ +# Storage + +By default, we persist all the historical data for the predecessor blocks to be able to replay the whole chain and to be able to support Tendermint's rollback command (that allows to rollback the state to the predecessor block, which is useful recovering from a corrupt state). For values that change on every block, we can simply prefix their storage key with the block height. + +However, for the accounts storage it is reasonable to expect that in each block only a small subset of the data will be updated, so we can avoid persisting values that haven't changed from the predecessor block. To achieve that: + +- The latest value is written into and read from its storage key without any height prefix +- If the previous value is overwritten or deleted at block height `n`, we store the diff (old and new value) under `n` prefix (the height at which it's been changed from this value) + +Note that when there are multiple updates of a value with the same storage key in the same block, only the last value will be persisted to the block. + +The block's mutable metadata is permanently in-memory and batch written to DB once a block is finalized. + +```mermaid +graph LR + subgraph "in-memory" + LN[level n] + end + subgraph "DB (each level is immutable once written)" + LN .-> LNL[level n - 1] + LNL ===== L0[level 0] + end +``` + +The accounts storage data are written and read directly to/from the DB and the DB layer manages its cache. + +## In-memory (mutable state) + +The current state is stored in a Sparse Merkle tree. The layout of data in memory should be flexible to allow to optimize throughput. For example, the values of key/value pairs may better stored in a sequence outside of the tree structure. Furthermore, it maybe be better to have the data sorted in memory. This may be possible by decoupling the merkle tree structure from the data and the key/value pairs, as illustrated below. + +```mermaid +graph TD + subgraph storage + subgraph sparse merkle tree + B[branches as paths segments in hashes of keys] .-> L[leaves as a hashes of values] + end + subgraph columns + KV[dictionaries of key/value pairs] + end + end +``` + +It may be advantageous if the data columns keys are not hashed to preserve ordering. + +## DB (immutable state) + +The immutable state doesn't have the same requirements as the mutable. This means that a different data structures or memory layout may perform better (subject to benchmarks). The state trees in the immutable blocks should take advantage of its properties for optimization. For example, it can save storage space by sharing common data and/or delta compression. + +It's very likely that different settings for immutable storage will be provided in future, similar to e.g. [Tezos history modes](https://tezos.gitlab.io/user/history_modes.html). + +## Benchmarks + +We'd like to have easily reproducible benchmarks for the whole database integration that should be filled over time with pre-generated realistic data. This should enable us to tune and compare different hashing functions, backends, data structures, memory layouts, etc. + +### Criteria +- in-memory + - writes (insert, update, delete) + - possibly also concurrent writes, pending on the approach taken for concurrent transaction execution + - reads + - proof generation (inclusion, non-inclusion) +- DB (lower priority) + - writes in batched mode + - reads + - proof generation (inclusion, non-inclusion) + +## DB backends + +The considered options for a DB backend are given in [Libraries & Tools / Database page](../../libraries/db.md). + +### RocksDB + +A committed block is not immediately persisted on RocksDB. When the block is committed, a set of key-value pairs which compose the block is written to the memtable on RocksDB. For the efficient sequential write, a flush is executed to persist the data on the memtable to the disk as a file when the size of the memtable is getting big (the threshold is one of the tuning parameters). + +We can disable write-ahead log(WAL) which protects these data on the memtable from a crash by persisting the write logs to the disk. Disabling WAL helps reduce the write amplification. That's because WAL isn't required for Anoma because other nodes have the block. The blocks which have not been persisted to the disk by flush can be recovered even if an Anoma node crashes. + +## Implementation + +### `storage` module + +This is the main interface for interacting with storage in Anoma. + +This module and its sub-modules should implement the in-memory storage (and/or a cache layer) with Merkle tree (however, the interface should be agnostic to the choice of vector commitment scheme or whether or not there even is one, we may want non-Merklised storage) and the persistent DB. + +The in-memory storage holds chain's metadata and current block's storage. + +Its public API should allow/provide: +- get the Merkle root and Merkle tree proofs +- read-only storage API for ledger's metadata to be accessible for transactions' code, VPs and the RPC + - with public types of all the stored metadata +- unless specified otherwise, read the state from the current block + +An API made visible only to the shell module (e.g. `pub ( in SimplePath )` - https://doc.rust-lang.org/reference/visibility-and-privacy.html) should allow the shell to: +- load state from DB for latest persisted block or initialize a new storage if none found +- begin a new block +- within a block: + - transaction can modify [account sub-space](accounts.md#dynamic-storage-sub-space) + - the function that modify storage (e.g. `write` and `delete`) have to guarantee to also update the Merkle tree + - store each applied transaction and its result +- end the current block +- commit the current block (persist to storage) + +### `storage/merkle_tree` module +It consists of one Sparse Merkle Tree (base tree) and multiple Sparse Merkle Trees (subtrees). The base tree stores the store type and the root of each subtree as a key-value pair. Each subtree has the hashed key-value pairs for each data. + +```mermaid +graph TD + subgraph "Merkle tree" + subgraph "Base tree" + R[Root] --> I0 + R --> I1 + I0 --> L0[Subtree 0] + I0 --> L1[Subtree 1] + I1 --> L2[Subtree 2] + I1 --> L3[Subtree 3] + end + subgraph "Subtree 1" + L1 --> SR[Subroot] + SR --> SI0 + SR --> SI1 + SI0 --> SI00 + SI0 --> SI01 + SI1 --> SI10 + SI1 --> SI11 + SI00 --> SL000 + SI00 --> SL001 + SI01 --> SL010 + SI01 --> SL011 + SI10 --> SL100 + SI10 --> SL101 + SI11 --> SL110 + SI11 --> SL111 + end + end +``` + +The first segment of a [DB key](#db-keys) is used as a key in the base tree and the sub key (without the first segment) specifies the leaf of the subtree. + +A proof of the key-value pair in the Merkle tree should be made of two proofs for the base tree and the subtree. Merkle root is the root of the base tree. In the proof verification, the sub root is calculated with the subtree's proof at first. Then, the root is calculated with the base tree's proof and the calculated sub root as a value, and the calculated root is compared with the Merkle root. + +### `storage/db` module + +The persistent DB implementation (e.g. RocksDB). + +### DB keys + +The DB keys are composed of key segments. A key segment can be an `Address` which starts with `#` (there can be multiple addresses involved in a key) or any user defined non-empty utf-8 string (maybe limited to only alphanumerical characters). Also, `/` and `?` are reserved. `/` is used as a separator for segments. `?` is reserved for a validity predicate and the key segment `?` can be specified only by the specific API. \ No newline at end of file diff --git a/documentation/dev/src/explore/design/ledger/storage/data-schema.md b/documentation/dev/src/explore/design/ledger/storage/data-schema.md new file mode 100644 index 00000000000..792f9119686 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/storage/data-schema.md @@ -0,0 +1,93 @@ +# Data schema + +At high level, all the data in the [accounts' dynamic +sub-spaces](../accounts.md#dynamic-storage-sub-space) is just keys associated with +arbitrary bytes and intents are just wrapper around arbitrary data. To help the +processes that read and write this data (transactions, validity predicates, +matchmaker) interpret it and implement interesting functionality on top it, the +ledger could provide a way to describe the schema of the data. + +For storage data encoding, we're currently using the borsh library, which +provides a way to derive schema for data that can describe its structure in a +very generic way that can easily be consumed in different data-exchange formats +such as JSON. In Rust code, the data can be composed with Rust native ADTs +(`struct` and `enum`) and basic collection structures (fixed and dynamic sized +array, hash map, hash set). Borsh already has a decent coverage of different +implementations in e.g. JS and TypeScript, JVM based languages and Go, which +we'll hopefully be able to support in wasm in near future too. + +Note that the borsh data schema would not be forced upon the users as they can +still build and use custom data with arbitrary encoding. + +A naive implementation could add optional `schema` field to each stored key. To +reduce redundancy, there could be some "built-in" schemas and/or specific +storage space for commonly used data schema definitions. Storage fees apply, but +perhaps they can be split between all the users, so some commonly used data +schema may be almost free. + +A single address in the ledger is define with all schema. A specific schema can +be looked up with a key in its subspace. The schema variable is not yet +implemented and the definition might change to something more appropiate. + +## Schema derived library code + +### account example +Let's start with an example, in which some users want to deploy a +multi-signature account to some shared asset. They create a transaction, which +would initialize a new account with an address `shared-savings` and write into +its storage sub-space the initial funds for the account and data under the key +`"multisig"` with the following definition: + +```rust +#[derive(Schema)] +struct MultiSig { + threshold: u64, + counter: u64, + keys: Vec, +} +``` + +When the transaction is applied, the data is stored together with a reference to +the derived data schema, e.g.: + +```json +{ + "MultiSig": { + "struct": { + "named_fields": { + "threshold": "u64", + "counter": "u64", + "keys": { + "sequence": "PublicKey" + } + } + } + } +} +``` + +Now any transaction that wants to interact with this account can look-up and use its data schema. We can also use this information to display values read from storage from e.g. RPC or indexer. + +What's more, when the data has schema attached on-chain, with borsh we have bijective mapping between the data definitions and their schemas. We can use this nice property to generate code for data definitions back from the schema in any language supported by borsh and that we'll able to support in wasm. + +We can take this a step further and even generate some code for data access on top of our wasm environment functions to lift the burden of encoding/decoding data from storage. For our example, from the key `"multisig"`, in Rust we can generate this code: + +```rust +fn read_multisig() -> MultiSig; +fn write_multisig(MultiSig); +fn with_multisig(FnMut(MultiSig) -> MultiSig); +``` + +Which can be imported like regular library code in a transaction and arbitrarily extended by the users. Similarly, the schema could be used to derive some code for validity predicates and intents. + +We can generate the code on demand (e.g. we could allow to query a node to generate library code for some given accounts for a given language), but we could also provide some helpers for e.g. foundation's or validator's node to optionally automatically publish generated code via git for all the accounts in the current state. In Rust, using this library could look like this: + +```rust +// load the account(s) code where the identifier is the account's address. +use anoma_accounts::SharedSavings; + +fn transaction(...) { + let multisig = SharedSavings::read_multisig(); + ... +} +``` diff --git a/documentation/dev/src/explore/design/ledger/tx.md b/documentation/dev/src/explore/design/ledger/tx.md new file mode 100644 index 00000000000..c4c3344b2b1 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/tx.md @@ -0,0 +1,88 @@ +# Transactions + +[Tracking Issue](https://github.com/anoma/anoma/issues/43) + +--- + +There is only a single general transaction (tx) type: + +```rust +struct Transaction { + // A wasm module with a required entrypoint + code: Vec + // Optional arbitrary data + data: Option>, + // A timestamp of when the transaction was created + timestamp: Timestamp, + gas_limit: TODO, +} +``` + +The tx allows to include arbitrary `data`, e.g zero-knowledge proofs and/or arbitrary nonce bytes to obfuscate the tx's minimum encoded size that may be used to derive some information about the tx. + +TODO once we have DKG, we will probably want to have some kind of a wrapper transaction with submission fees, payer and signature + +## Tx life cycle + +```mermaid +flowchart TD + subgraph Node + I[Initialize chain] --> Begin + Begin[Begin block] --> Poll + Poll[Poll mempool queue] --> Apply + Apply[Apply txs] --> End + End[End block] --> Commit[Commit block] + Commit --> Begin + Commit --> Flush + subgraph Mempool + Validate --> V{is valid?} + V -->|Yes| Add[Add to local queue] + V -->|No| Fail[Drop tx] + Flush -->|Re-validate txs not included in this block| V + end + end + subgraph Client + Submit[Submit tx] --> Validate + end +``` + +New txs are injected by the client via mempool. Before including a tx in a local mempool queue, some cheap validation may be performed. Once a tx is included in a mempool queue, it will be gossiped with the peers and may be included in a block by the block proposer. Any txs that are left in the queue after flush will be subject to re-validation before being included again. + +The order of applying transactions within a block is fixed by the block proposer in [the front-running prevention protocol](front-running.md). + +TODO we might want to randomize the tx order after DKG protocol is completed + +### Block application + +Within a block, each tx is applied sequentially in three steps: + +```mermaid +flowchart TD + B[Begin block] --> N{Has next tx and within block gas limit?} + N --> |Yes|E + N -----> |No|EB[End block] + E[Exec tx code] -->|"∀ accounts with modified storage"| VP[Run validity predicates in parallel] + VP --> A{all accept} + A --> |No|R[Reject tx] + A --> |Yes|C[Commit tx and state changes] + R --> N + C --> N + +``` + +## Tx execution + +The code is allowed to read and write anything from [accounts' sub-spaces](./accounts.md#dynamic-storage-sub-space) and to [initialize new accounts](./accounts.md#initializing-a-new-account). Other data that is not in an account's subspace is read-only, e.g. chain and block metadata, account addresses and potentially keys. + +In addition to the verifiers specified in a transaction, each account whose sub-space has been modified by the tx triggers its VP. + +For [internal addresses](accounts.md#internal-transparent-addresses), we invoke their module's native VP interface directly. For other addresses, we look-up validity predicates WASM to be executed from storage. + +The VPs are then given the prior and posterior state from the account's sub-space together with the tx to decide if it accepts the tx's state modifications. + +Within a single tx the execution of the validity predicates will be parallelized and thus the fee for VPs execution would their maximum value (plus some portion of the fees for each of the other parallelized VPs - nothing should be "free"). Once any of the VPs rejects the modifications, execution is aborted, the transaction is rejected and state changes discarded. If all the VPs accept the modifications, the transaction is successful and modifications are committed to storage as the input of the next tx. + +The transaction's API should make it possible to transfer tokens to a hash of a public key that is not revealed. This could be done by having a "deposit" account from which the key's owner can claim the deposited funds. + +Should some type of token prefer not to allow to receive tokens without recipient's approval, a token account can implement logic to decline the received tokens. + diff --git a/documentation/dev/src/explore/design/ledger/vp.md b/documentation/dev/src/explore/design/ledger/vp.md new file mode 100644 index 00000000000..7cf939f331c --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/vp.md @@ -0,0 +1,53 @@ +# Validity predicates + +[Tracking Issue](https://github.com/anoma/anoma/issues/44) + +--- + +Each [account](accounts.md) is associated with exactly one validity predicate (VP). + +Conceptually, a VP is a function from the transaction's data and the storage state prior and posterior to a transaction execution returning a boolean value. A transaction may modify any data in the [accounts' dynamic storage sub-space](accounts.md#dynamic-storage-sub-space). Upon [transaction execution](tx.md#tx-execution), the VPs associated with the accounts whose storage has been modified are invoked to verify the transaction. If any of them reject the transaction, all of its storage modifications are discarded. + +There are some native VPs for [internal transparent addresses](accounts.md#internal-transparent-addresses) that are built into the ledger. All the other VPs are implemented as [WASM programs](wasm-vm.md). One can build a custom VP using the [VP template](https://github.com/anoma/anoma/tree/master/wasm/vp_template) or use one of the pre-defined VPs. + +The VPs must implement the following interface that will be invoked by the protocol: + +```rust +fn validate_tx( + // Data of the transaction that triggered this VP call + tx_data: Vec, + // Address of this VP + addr: Address, + // Storage keys that have been modified by the transation, relevant to this VP + keys_changed: BTreeSet, + // Set of all the addresses whose VP was triggered by the transaction + verifiers: BTreeSet

, +) -> bool; +``` + +The host functions available to call from inside the VP code can be found in [docs generated from code](https://dev.anoma.net/master/rustdoc/anoma_vm_env/imports/vp/index.html#functions). + +## Native VPs + +The native VPs follow the same interface as WASM VPs and rules for how they are [triggered by a transaction](tx.md#tx-execution). They can also call the same host functions as those provided in [WASM VPs environment](wasm-vm.md#vps-environment) and must also account any computation for gas usage. + +### PoS slash pool VP + +The Proof-of-Stake slash pool is a simple account with a native VP which can receive slashed tokens, but no token can ever be withdrawn from it by anyone at this point. + +## Fungible token VP + +The [fungible token VP](https://github.com/anoma/anoma/tree/master/wasm/wasm_source) allows to associate accounts balances of a specific token under its account. + +For illustration, users `Albert` and `Bertha` might hold some amount of token with the address `XAN`. Their balances would be stored in the `XAN`'s storage sub-space under the storage keys `@XAN/balance/@Albert` and `@XAN/balance/@Bertha`, respectively. When `Albert` or `Bertha` attempt to transact with their `XAN` tokens, its validity predicate would be triggered to check: + +- the total supply of `XAN` token is preserved (i.e. inputs = outputs) +- the senders (users whose balance has been deducted) are checked that their validity predicate has also been triggered + +Note that the fungible token VP doesn't need to know whether any of involved users accepted or rejected the transaction, because if any of the involved users rejects it, the whole transaction will be rejected. + +## User VP + +The [user VP](https://github.com/anoma/anoma/blob/master/wasm/wasm_source/src/vp_user.rs) currently provides a signature verification against a public key for sending tokens as prescribed by the fungible token VP. In this VP, a transfer of tokens doesn't have to be authorized by the receiving party. + +It also allows arbitrary storage modifications to the user's sub-space to be performed by a transaction that has been signed by the secret key corresponding to the user's public key stored on-chain. This functionality also allows one to update their own validity predicate. diff --git a/documentation/dev/src/explore/design/ledger/wasm-vm.md b/documentation/dev/src/explore/design/ledger/wasm-vm.md new file mode 100644 index 00000000000..3964caf80e8 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/wasm-vm.md @@ -0,0 +1,125 @@ +# WASM VM + +A wasm virtual machine will be used for [validity predicates](./vp.md) and [transactions code](./tx.md). + +The VM should provide: +- an interface for compiling from higher-level languages to wasm (initially only Rust) +- a wasm compiler, unless we use [an interpreted runtime](../../libraries/wasm.md) +- provide and inject [environments for higher-level languages for VPs and transactions](#wasm-environment) +- pre-process wasm modules + - check & sanitize modules + - inject gas metering + - inject stack height metering +- a runner for VPs and transaction code +- encode/decode wasm for transfer & storage +- [manage runtime memory](#wasm-memory) +- wasm development helpers +- helpers to estimate gas usage +- VM and environment versioning + +## Resources + +- [WebAssembly Specifications](https://webassembly.github.io/spec/) +- [wasmer examples](https://docs.wasmer.io/integrations/examples) +- [The WebAssembly Binary Toolkit](https://github.com/webassembly/wabt/) + - bunch of useful wasm tools (e.g. `wasm2wat` to convert from wasm binary to human-readable wat format) +- [Rust wasm WG](https://github.com/rustwasm/team) and [wasm book](https://rustwasm.github.io/book/introduction.html) (some sections are JS specific) +- [A practical guide to WebAssembly memory](https://radu-matei.com/blog/practical-guide-to-wasm-memory/) modulo JS specific details +- [Learn X in Y minutes Where X=WebAssembly](https://learnxinyminutes.com/docs/wasm/) + + +## Wasm environment + +The wasm environment will most likely be libraries that provide APIs for the wasm modules. + +### Common environment + +The common environment of VPs and transactions APIs: + +- math & crypto +- logging +- panics/aborts +- gas metering +- storage read-only API +- context API (chain metadata such as block height) + +The accounts sub-space storage is described under [accounts' dynamic storage sub-space](./accounts.md#dynamic-storage-sub-space). + +### VPs environment + +Because VPs are stateless, everything that is exposed in the VPs environment should be read-only: + +- storage API to account sub-space the [storage write log](#storage-write-log) +- transaction API + +### Transactions environment + +- storage write access for all public state via the [storage write log](#storage-write-log) + +Some exceptions as to what can be written are given under [transaction execution](./tx.md#tx-execution). + + +## Wasm memory + +The wasm memory allows to share data bi-directionally between the host (Rust shell) and the guest (wasm) through a [wasm linear memory instance](https://webassembly.github.io/spec/core/exec/runtime.html#syntax-meminst). + +Because [wasm currently only supports basic types](https://webassembly.github.io/spec/core/syntax/types.html), we need to choose how to represent more sophisticated data in memory. + +The options on how the data can be passed through the memory are: +- using ["C" structures](https://doc.rust-lang.org/nomicon/other-reprs.html#reprc) (probably too invasive because everything in memory would have to use C repr) +- (de)serializing the data with some encoding (JSON, binary, ...?) +- currently very unstable: [WebIDL](https://developer.mozilla.org/en-US/docs/Glossary/WebIDL) / [Interface Types](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) / [Reference types](https://github.com/WebAssembly/reference-types) + +The choice should allow for easy usage in wasm for users (e.g. in Rust a bindgen macro on data structures, similar to [wasm_bindgen used for JS <-> wasm](https://github.com/rustwasm/wasm-bindgen)). + +Related [wasmer issue](https://github.com/wasmerio/wasmer/issues/315). + +We're currently using borsh for storage serialization, which is also a good option for wasm memory. +- it's easy for users (can be derived) +- because borsh encoding is safe and consistent, the encoded bytes can also be used for Merkle tree hashing +- good performance, although it's not clear at this point if that may be negligible anyway + +### The data + +The data being passed between the host and the guest in the order of the execution: + +- For transactions: + - host-to-guest: pass tx.data to tx.code call + - guest-to-host: parameters of environment functions calls, including storage modifications (pending on storage API) + - host-to-guest: return results for host calls +- For validity predicates: + - host-to-guest: pass tx.data, prior and posterior account storage sub-space state and/or storage modifications (i.e. a write log) for the account + - guest-to-host: parameters of environment function calls + - host-to-guest: return results for host calls + - ~~guest-to-host~~: the VP result (`bool`) can be passed directly from the call + +### Storage write log + +The storage write log gathers any storage updates (`write`/`delete`s) performed by transactions. For each transaction, the write log changes must be accepted by all the validity predicates that were triggered by these changes. + +A validity predicate can read its prior state directly from storage as it is not changed by the transaction directly. For the posterior state, we first try to look-up the keys in the write log to try to find a new value if the key has been modified or deleted. If the key is not present in the write log, it means that the value has not changed and we can read it from storage. + +The write log of each transaction included in a block and accepted by VPs is accumulated into the block write log. Once the block is committed, we apply the storage changes from the block write log to the persistent storage. + +![write log](./wasm-vm/storage-write-log.svg "storage write log") +[Diagram on Excalidraw](https://excalidraw.com/new#room=333e1db689b083669c80,Y0i8yhvIAZCFICs753CSuA) + +## Gas metering + +The two main options for implementing gas metering within wasm using wasmer are: +- a [gas metering middleware included in wasmer](https://github.com/wasmerio/wasmer/tree/72d47336cc1461d63baa2322b38c4cb5f67bb72a/lib/middlewares). +- + +Both of these allow us to assign a gas cost for each wasm operation. + +`wasmer` gas middleware is more recent, so probably more risky. It injects the gas metering code into the wasm code, which is more efficient than host calls to a gas meter. + +`pwasm-utils` divides the wasm code into metered blocks. It performs host call with the gas cost of each block before it is executed. The gas metering injection is linear to the code size. + +The `pwasm-utils` seems like a safer option to begin with (and we'll probably need to use it for [stack height metering](#stack-height-metering) too). We can look into switching to `wasmer` middleware at later point. + +## Stack height metering + +For safety, we need to limit the stack height in wasm code. Similarly to gas metering, we can also use `wasmer` middleware or `pwasm-utils`. + +We have to use `pwasm-utils`, because `wasmer`'s stack limiter is currently non-deterministic (platform specific). This is to be fixed in this PR: . diff --git a/documentation/dev/src/explore/design/ledger/wasm-vm/storage-write-log.excalidraw b/documentation/dev/src/explore/design/ledger/wasm-vm/storage-write-log.excalidraw new file mode 100644 index 00000000000..c9fc0eb5e42 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/wasm-vm/storage-write-log.excalidraw @@ -0,0 +1,2194 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "version": 249, + "versionNonce": 1349029114, + "isDeleted": false, + "id": "QZDxznfmnnPUJUIgeuSXh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1303, + "y": 645.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 213, + "height": 26, + "seed": 1482810554, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "current block storage", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 309, + "versionNonce": 837703590, + "isDeleted": false, + "id": "V9SouvzuSlJK9NBpLw-Lu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1282, + "y": 601, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 261, + "height": 152, + "seed": 1467983226, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "hUA84yk4qfMCZG_gRU50z", + "D5zvn3BUfPBp5UmhfH-R6", + "eYxCi8UKcn5uAWz3t_8mk", + "dkBfRJuKw60zXeKHUFpx7", + "eaup2MZzC_fxXF-rrJcQL", + "pAAuF8rBfvJVRZOI-su_9", + "4K7cklh56YvUmwWJnvyMn" + ] + }, + { + "type": "rectangle", + "version": 132, + "versionNonce": 1820384698, + "isDeleted": false, + "id": "1qijVCNE7PVbjyfSBXz7x", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 419, + "y": 343, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 186.00000000000003, + "height": 89, + "seed": 59124154, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "d9oF2lqBkYEPA2mhaoxx1", + "3DaZweLcKBTYk46SAiGnF", + "v6ToHf-twsNDUsIyBovKd", + "hUA84yk4qfMCZG_gRU50z", + "TaACFAGwqruq3j9gT7vCO", + "bZqG2HKWxNREQ7T_hO4g6", + "GBk9xqSLQuWPibzKJhodn", + "b8DDDjU-Sm7_Kd9ymWfaa" + ] + }, + { + "type": "text", + "version": 59, + "versionNonce": 1053848294, + "isDeleted": false, + "id": "UpYqf8dA_2EzFYxosHbTQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 430, + "y": 375, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160, + "height": 20, + "seed": 966406310, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "v6ToHf-twsNDUsIyBovKd" + ], + "fontSize": 16, + "fontFamily": 3, + "text": "apply transaction", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 256, + "versionNonce": 1276917562, + "isDeleted": false, + "id": "__A77YQzon7uaQw6WLbXT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 412, + "y": 944.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 186.00000000000003, + "height": 89, + "seed": 1869390118, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "pEl0DEXOohm3dPPO6t0_x", + "AUeSz0iQoeFeO4ux6Fh0G", + "MCAwfJNVZfn_l-DJyt7B7", + "fp5_BqnGg98c0yKJzRcYE", + "oHae4DgxREq35ry9VS7-m" + ] + }, + { + "type": "text", + "version": 59, + "versionNonce": 12926970, + "isDeleted": false, + "id": "IgWkRriPRAVGdf4ONSzab", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 451, + "y": 260, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 129, + "height": 25, + "seed": 859678842, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "d9oF2lqBkYEPA2mhaoxx1" + ], + "fontSize": 20, + "fontFamily": 3, + "text": "begin block", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 231, + "versionNonce": 1227510950, + "isDeleted": false, + "id": "d9oF2lqBkYEPA2mhaoxx1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 511.7057128734241, + "y": 303, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 5.554969500967559, + "height": 39, + "seed": 595645882, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "zVu269gdQqC4-Z9Hujhtc", + "focus": -0.08125628303367922, + "gap": 1 + }, + "endBinding": { + "elementId": "1qijVCNE7PVbjyfSBXz7x", + "focus": -0.12412177985948478, + "gap": 1 + }, + "points": [ + [ + 0, + 0 + ], + [ + -5.554969500967559, + 39 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 122, + "versionNonce": 1541212538, + "isDeleted": false, + "id": "jza0ceFstQW-YWoG6OQuI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 447, + "y": 966, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 105, + "height": 25, + "seed": 950213882, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "AUeSz0iQoeFeO4ux6Fh0G" + ], + "fontSize": 20, + "fontFamily": 3, + "text": "end block", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 267, + "versionNonce": 672714923, + "isDeleted": false, + "id": "iYO2n7hMiKxoHgmIIyceE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 834.6666666666667, + "y": 188.66666666666652, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 384.00000000000006, + "height": 826.3333333333335, + "seed": 1588804794, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "B4hqeI5QKuwc_ZKQ90Cs6", + "fp5_BqnGg98c0yKJzRcYE", + "QHFY7aCNAn9lT-m8c_PHq", + "tMCdNkMMAZBekJGu2L7GR", + "7wSUMENT531su3n6Iow_Q", + "dkBfRJuKw60zXeKHUFpx7", + "Tq0MwFDWLgdxVHK7MbdAZ", + "b8DDDjU-Sm7_Kd9ymWfaa", + "7ppiLydiNHgqsK20RKRCk", + "9wOVQkDmBFA-8h8sXkUyj" + ] + }, + { + "type": "text", + "version": 131, + "versionNonce": 640891685, + "isDeleted": false, + "id": "g1KRxQ25BwJR6IMDL9wx6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 854.6666666666667, + "y": 210.33333333333331, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 118, + "height": 36, + "seed": 150594662, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 28, + "fontFamily": 1, + "text": "write log", + "baseline": 25, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 751, + "versionNonce": 1500643627, + "isDeleted": false, + "id": "v6ToHf-twsNDUsIyBovKd", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 624.5, + "y": 417.6554282830066, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 268.8023255813953, + "height": 17.072981304704626, + "seed": 2057969574, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "1qijVCNE7PVbjyfSBXz7x", + "gap": 19.500000000000007, + "focus": 0.45575397392109807 + }, + "endBinding": { + "elementId": "GvawR-j7WrctsjHTQx6y-", + "gap": 12.697674418604656, + "focus": 0.5445206473336952 + }, + "points": [ + [ + 0, + 0 + ], + [ + 268.8023255813953, + 17.072981304704626 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 146, + "versionNonce": 671076774, + "isDeleted": false, + "id": "OSTKDbfaiP68QofnvRZzT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 623, + "y": 368, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 188, + "height": 20, + "seed": 1409193638, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "read & write storage", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 439, + "versionNonce": 210391493, + "isDeleted": false, + "id": "S-FHHu4Wq-0IIIQU9qTRD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 849.3333333333335, + "y": 338.00000000000006, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 357, + "height": 59, + "seed": 1045438310, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "For each transaction store a hash map \nfrom DB keys to values of \n`Update(value) | Delete`", + "baseline": 55, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 316, + "versionNonce": 694541626, + "isDeleted": false, + "id": "9iE7-Xt5uU99Dq8OR70FT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 924.6666666666666, + "y": 560.6666666666666, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 155, + "height": 68, + "seed": 2023349862, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "QHFY7aCNAn9lT-m8c_PHq", + "dkBfRJuKw60zXeKHUFpx7", + "D5zvn3BUfPBp5UmhfH-R6" + ], + "fontSize": 13.948717948717952, + "fontFamily": 3, + "text": "try to find in the \nwrite log first,\nif no entry found,\nread storage", + "baseline": 65, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 695, + "versionNonce": 1851599718, + "isDeleted": false, + "id": "D5zvn3BUfPBp5UmhfH-R6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1109.2498432736227, + "y": 599.0041137810159, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 171.30552397389124, + "height": 12.776928288053455, + "seed": 1454445862, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "UdhUrYmgxC05m-H6pBEVT", + "focus": 0.05559216571754019, + "gap": 4.916509940288961 + }, + "endBinding": { + "elementId": "V9SouvzuSlJK9NBpLw-Lu", + "focus": 0.6459299538936937, + "gap": 1.4446327524860862 + }, + "points": [ + [ + 0, + 0 + ], + [ + 171.30552397389124, + 12.776928288053455 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 138, + "versionNonce": 476534458, + "isDeleted": false, + "id": "gW1KXDYTam9sFP9t3H7zs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 392, + "y": 567, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 166, + "height": 73, + "seed": 1582554298, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "z_yLKRxOC9fRqAuU0gapI", + "dkBfRJuKw60zXeKHUFpx7", + "66wNRVaMBQ_LuSsbzKxuz", + "gbwPr1c5T09O00WkQQ-Q5" + ] + }, + { + "type": "text", + "version": 43, + "versionNonce": 1097468390, + "isDeleted": false, + "id": "lzhN5VgZhOe5LWp4bpH7x", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 443, + "y": 590, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 82, + "height": 25, + "seed": 1820850534, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "run VPs", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 320, + "versionNonce": 1770024826, + "isDeleted": false, + "id": "TaACFAGwqruq3j9gT7vCO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 504.2862949189084, + "y": 433, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.4160869119018002, + "height": 26.06302729025481, + "seed": 1006966374, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "1qijVCNE7PVbjyfSBXz7x", + "focus": -0.008823761658798604, + "gap": 1 + }, + "endBinding": { + "elementId": "NuGlGaRuYWd7MpH1YbSpO", + "focus": -0.07657894358195909, + "gap": 4.936972709745191 + }, + "points": [ + [ + 0, + 0 + ], + [ + -2.4160869119018002, + 26.06302729025481 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "version": 675, + "versionNonce": 48593413, + "isDeleted": false, + "id": "z_yLKRxOC9fRqAuU0gapI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 471.17515729282275, + "y": 648.6109585800341, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1.7747959460477887, + "height": 33.171299759019576, + "seed": 1290697530, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "gW1KXDYTam9sFP9t3H7zs", + "gap": 8.610958580034094, + "focus": 0.07338129663473107 + }, + "endBinding": { + "elementId": "YSMPWeP5aBKhZYlx6tYkk", + "gap": 11.62499519211975, + "focus": -0.0006202187392369463 + }, + "points": [ + [ + 0, + 0 + ], + [ + 1.7747959460477887, + 33.171299759019576 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "diamond", + "version": 163, + "versionNonce": 1988834123, + "isDeleted": false, + "id": "YSMPWeP5aBKhZYlx6tYkk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 408, + "y": 693, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 136.00000000000003, + "height": 90.00000000000003, + "seed": 549577786, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "QHFY7aCNAn9lT-m8c_PHq", + "z_yLKRxOC9fRqAuU0gapI", + "tMCdNkMMAZBekJGu2L7GR", + "e_XAGm7U_62kJBVB0LwwS", + "9wOVQkDmBFA-8h8sXkUyj" + ] + }, + { + "type": "arrow", + "version": 1113, + "versionNonce": 1948111045, + "isDeleted": false, + "id": "QHFY7aCNAn9lT-m8c_PHq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 549.1768379545191, + "y": 744.0766248064365, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 265.1790177344675, + "height": 10.925022761954438, + "seed": 1109141606, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "YSMPWeP5aBKhZYlx6tYkk", + "gap": 7.982795265165528, + "focus": 0.19941886735023753 + }, + "endBinding": { + "elementId": "iYO2n7hMiKxoHgmIIyceE", + "gap": 20.31081097768015, + "focus": -0.2896844776924461 + }, + "points": [ + [ + 0, + 0 + ], + [ + 265.1790177344675, + -10.925022761954438 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 158, + "versionNonce": 892414394, + "isDeleted": false, + "id": "JioJP53WPdSLFwR7f998s", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 423.5, + "y": 725, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 103, + "height": 20, + "seed": 2018171962, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "QHFY7aCNAn9lT-m8c_PHq" + ], + "fontSize": 16, + "fontFamily": 3, + "text": "any reject?", + "baseline": 16, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 377, + "versionNonce": 740660901, + "isDeleted": false, + "id": "NfrHkeDj-lzeKgSVsyR5L", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 546.3333333333331, + "y": 757.0000000000001, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 250, + "height": 50, + "seed": 778432314, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "z_yLKRxOC9fRqAuU0gapI" + ], + "fontSize": 13.333333333333332, + "fontFamily": 3, + "text": "yes - clear current tx write log\nno - merge current tx write log \nto block write log", + "baseline": 46, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 383, + "versionNonce": 673745771, + "isDeleted": false, + "id": "e_XAGm7U_62kJBVB0LwwS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 477.7799139738019, + "y": 784.819294955471, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 8.9855334080533, + "height": 23.652787900168505, + "seed": 1312295482, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "YSMPWeP5aBKhZYlx6tYkk", + "focus": 0.23538850078685178, + "gap": 2.4994442635820278 + }, + "endBinding": { + "elementId": "lgZeJc37pIP2-kkWn1vyx", + "focus": 0.3122449582090152, + "gap": 5.494234114668458 + }, + "points": [ + [ + 0, + 0 + ], + [ + 8.9855334080533, + 23.652787900168505 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 191, + "versionNonce": 1838081382, + "isDeleted": false, + "id": "TP8ombONfZMcHJ7OZd0kZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 372, + "y": 827, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 35, + "height": 25, + "seed": 2057277734, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "yes", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 347, + "versionNonce": 591989754, + "isDeleted": false, + "id": "MCAwfJNVZfn_l-DJyt7B7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 501.9192773055894, + "y": 894.4685196465915, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 4.961131884428028, + "height": 46.53148035340848, + "seed": 2139483706, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "8qM2rdsDJixvsMsOBOLdt", + "focus": 2.3139762359713836, + "gap": 13.08072269441061 + }, + "endBinding": { + "elementId": "__A77YQzon7uaQw6WLbXT", + "focus": -0.1346321605117777, + "gap": 3.5 + }, + "points": [ + [ + 0, + 0 + ], + [ + -4.961131884428028, + 46.53148035340848 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 211, + "versionNonce": 754547898, + "isDeleted": false, + "id": "5RG_eP1pQS_n4qHqSQa6w", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 399, + "y": 1077, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 189, + "height": 94, + "seed": 925979258, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "7wSUMENT531su3n6Iow_Q", + "oHae4DgxREq35ry9VS7-m", + "eaup2MZzC_fxXF-rrJcQL" + ] + }, + { + "type": "text", + "version": 86, + "versionNonce": 1458914278, + "isDeleted": false, + "id": "89Rp6nfcxdIYV_OuZH6MH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 421, + "y": 1109, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 141, + "height": 25, + "seed": 1715049722, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 3, + "text": "commit block", + "baseline": 20, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 354, + "versionNonce": 819844474, + "isDeleted": false, + "id": "oHae4DgxREq35ry9VS7-m", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 524.6770123783945, + "y": 1040, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0.24529202278836237, + "height": 35, + "seed": 1688947110, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "__A77YQzon7uaQw6WLbXT", + "focus": -0.21245896629727823, + "gap": 6.5 + }, + "endBinding": { + "elementId": "5RG_eP1pQS_n4qHqSQa6w", + "focus": 0.3349775055454588, + "gap": 2 + }, + "points": [ + [ + 0, + 0 + ], + [ + 0.24529202278836237, + 35 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "version": 836, + "versionNonce": 272176698, + "isDeleted": false, + "id": "dkBfRJuKw60zXeKHUFpx7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 571.5845786407255, + "y": 631.4167806943483, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 696.1225127179008, + "height": 52.3309196923766, + "seed": 1959593766, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "gW1KXDYTam9sFP9t3H7zs", + "focus": 0.4045523376623099, + "gap": 13.584578640725454 + }, + "endBinding": { + "elementId": "V9SouvzuSlJK9NBpLw-Lu", + "focus": -0.2054821425998669, + "gap": 14.292908641373742 + }, + "points": [ + [ + 0, + 0 + ], + [ + 696.1225127179008, + 52.3309196923766 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 437, + "versionNonce": 2083914342, + "isDeleted": false, + "id": "K0c3eNfT4HAbD1ELvntSL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 442, + "y": 474, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 133, + "height": 47, + "seed": 1721741434, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "dkBfRJuKw60zXeKHUFpx7" + ], + "fontSize": 12.591715976331365, + "fontFamily": 3, + "text": "get written keys, \ngrouped by \naccount addresses", + "baseline": 44, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 306, + "versionNonce": 200163066, + "isDeleted": false, + "id": "_Ea9v9eIaEmLyBfP99CSz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 613, + "y": 1080, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 200, + "height": 14, + "seed": 806880698, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 11.38339920948617, + "fontFamily": 3, + "text": "commit & get the new root hash", + "baseline": 11, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 76, + "versionNonce": 542426534, + "isDeleted": false, + "id": "zVu269gdQqC4-Z9Hujhtc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 412, + "y": 250, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 191, + "height": 52, + "seed": 1230275898, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "d9oF2lqBkYEPA2mhaoxx1" + ] + }, + { + "type": "diamond", + "version": 101, + "versionNonce": 889746629, + "isDeleted": false, + "id": "lgZeJc37pIP2-kkWn1vyx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 426, + "y": 815, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 121, + "height": 85, + "seed": 570961146, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "e_XAGm7U_62kJBVB0LwwS", + "MCAwfJNVZfn_l-DJyt7B7", + "bZqG2HKWxNREQ7T_hO4g6", + "7ppiLydiNHgqsK20RKRCk" + ] + }, + { + "type": "text", + "version": 67, + "versionNonce": 34752634, + "isDeleted": false, + "id": "RBGs5Z1BvXDxZjL8B06q7", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 446, + "y": 846, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 75, + "height": 20, + "seed": 1097948666, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "next tx?", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 282, + "versionNonce": 359666726, + "isDeleted": false, + "id": "bZqG2HKWxNREQ7T_hO4g6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 434.07232800210454, + "y": 843.1392737175299, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 118.07232800210454, + "height": 445.67932237159897, + "seed": 2041794106, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "lgZeJc37pIP2-kkWn1vyx", + "focus": -0.775322895469081, + "gap": 7.110900555096563 + }, + "endBinding": { + "elementId": "1qijVCNE7PVbjyfSBXz7x", + "focus": 0.7731104443491926, + "gap": 9 + }, + "points": [ + [ + 0, + 0 + ], + [ + -118.07232800210454, + -307.13927371752993 + ], + [ + -24.07232800210454, + -445.67932237159897 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 48, + "versionNonce": 1214720314, + "isDeleted": false, + "id": "8qM2rdsDJixvsMsOBOLdt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 515, + "y": 902, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 19, + "height": 20, + "seed": 274658854, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "MCAwfJNVZfn_l-DJyt7B7" + ], + "fontSize": 16, + "fontFamily": 3, + "text": "no", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 129, + "versionNonce": 1524756986, + "isDeleted": false, + "id": "p8RmBsnbDlvCLxUWzkqcZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 933.6666666666666, + "y": 855.3333333333333, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 179, + "height": 100.99999999999996, + "seed": 2052091174, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "7wSUMENT531su3n6Iow_Q", + "eYxCi8UKcn5uAWz3t_8mk", + "QHFY7aCNAn9lT-m8c_PHq", + "eaup2MZzC_fxXF-rrJcQL" + ] + }, + { + "type": "text", + "version": 115, + "versionNonce": 837403302, + "isDeleted": false, + "id": "IJrVOcfYxINAROxPFuLeA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 948.0000000000001, + "y": 862.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 61, + "height": 26, + "seed": 1769974650, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "commit", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 317, + "versionNonce": 210084538, + "isDeleted": false, + "id": "eYxCi8UKcn5uAWz3t_8mk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1113.9956140350878, + "y": 909.4723758668379, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 164.60264744033384, + "height": 153.19766082791693, + "seed": 1265160934, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "p8RmBsnbDlvCLxUWzkqcZ", + "gap": 1.3289473684213549, + "focus": 0.6596619480074992 + }, + "endBinding": { + "elementId": "V9SouvzuSlJK9NBpLw-Lu", + "gap": 4.721819943171903, + "focus": 0.22966534728241816 + }, + "points": [ + [ + 0, + 0 + ], + [ + 164.60264744033384, + -153.19766082791693 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 183, + "versionNonce": 1322093195, + "isDeleted": false, + "id": "GvawR-j7WrctsjHTQx6y-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 906, + "y": 428.3333333333333, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 215.3333333333335, + "height": 78.00000000000003, + "seed": 723160166, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "v6ToHf-twsNDUsIyBovKd" + ] + }, + { + "type": "text", + "version": 96, + "versionNonce": 967430438, + "isDeleted": false, + "id": "NSWpTYPEYZxXI-8WcRUqC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 926, + "y": 438.8333333333333, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 48, + "height": 26, + "seed": 134514342, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "write", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 394, + "versionNonce": 1726897675, + "isDeleted": false, + "id": "yGf3O9V0mLEn2tNudJdZh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 924, + "y": 473.3333333333333, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 178, + "height": 20, + "seed": 1608581498, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "update tx write log", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 114, + "versionNonce": 65059942, + "isDeleted": false, + "id": "UdhUrYmgxC05m-H6pBEVT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 908.3333333333334, + "y": 529.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 196, + "height": 116.00000000000006, + "seed": 718081638, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "D5zvn3BUfPBp5UmhfH-R6", + "dkBfRJuKw60zXeKHUFpx7", + "GBk9xqSLQuWPibzKJhodn", + "gbwPr1c5T09O00WkQQ-Q5" + ] + }, + { + "type": "text", + "version": 66, + "versionNonce": 887366906, + "isDeleted": false, + "id": "KL6ovSZYanGyaqkNspfsG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 922.3333333333333, + "y": 533.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 45, + "height": 26, + "seed": 1683396154, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "read", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 188, + "versionNonce": 1841919418, + "isDeleted": false, + "id": "GBk9xqSLQuWPibzKJhodn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 617, + "y": 418.589731514062, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 286.3333333333335, + "height": 119.90055382235471, + "seed": 812516262, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "1qijVCNE7PVbjyfSBXz7x", + "focus": -0.15890157358839865, + "gap": 12 + }, + "endBinding": { + "elementId": "UdhUrYmgxC05m-H6pBEVT", + "focus": 0.05767894371091034, + "gap": 5 + }, + "points": [ + [ + 0, + 0 + ], + [ + 286.3333333333335, + 119.90055382235471 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 41, + "versionNonce": 1174231782, + "isDeleted": false, + "id": "NuGlGaRuYWd7MpH1YbSpO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 413, + "y": 464, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 185, + "height": 70, + "seed": 2095274278, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "TaACFAGwqruq3j9gT7vCO", + "66wNRVaMBQ_LuSsbzKxuz", + "Tq0MwFDWLgdxVHK7MbdAZ" + ] + }, + { + "type": "arrow", + "version": 12, + "versionNonce": 658943610, + "isDeleted": false, + "id": "66wNRVaMBQ_LuSsbzKxuz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 498, + "y": 535, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 8, + "height": 29, + "seed": 1684111034, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "NuGlGaRuYWd7MpH1YbSpO", + "focus": -0.023797468354430376, + "gap": 1 + }, + "endBinding": { + "elementId": "gW1KXDYTam9sFP9t3H7zs", + "focus": 0.04409040385327899, + "gap": 3 + }, + "points": [ + [ + 0, + 0 + ], + [ + -8, + 29 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "version": 111, + "versionNonce": 806449189, + "isDeleted": false, + "id": "Tq0MwFDWLgdxVHK7MbdAZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 603, + "y": 501.6951363222473, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 222.33333333333326, + "height": 31.47197411749505, + "seed": 1078758310, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "NuGlGaRuYWd7MpH1YbSpO", + "gap": 5, + "focus": -0.23588105185549268 + }, + "endBinding": { + "elementId": "iYO2n7hMiKxoHgmIIyceE", + "gap": 9.333333333333485, + "focus": 0.09328582885594748 + }, + "points": [ + [ + 0, + 0 + ], + [ + 222.33333333333326, + 31.47197411749505 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "version": 73, + "versionNonce": 1444859706, + "isDeleted": false, + "id": "gbwPr1c5T09O00WkQQ-Q5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 563, + "y": 585.9319073246128, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 335.3333333333335, + "height": 7.707422255936649, + "seed": 1331710202, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "gW1KXDYTam9sFP9t3H7zs", + "focus": -0.5146527966463533, + "gap": 5 + }, + "endBinding": { + "elementId": "UdhUrYmgxC05m-H6pBEVT", + "focus": -0.14585804665875834, + "gap": 10 + }, + "points": [ + [ + 0, + 0 + ], + [ + 335.3333333333335, + 7.707422255936649 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 27, + "versionNonce": 996432230, + "isDeleted": false, + "id": "8PI67tSXe-2_f03yUlcRa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 599, + "y": 616, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 150, + "height": 20, + "seed": 880206118, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "read prior state", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 30, + "versionNonce": 1588910074, + "isDeleted": false, + "id": "lxnsACKVdDqtZozs-yqGY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 602, + "y": 556, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 131, + "height": 20, + "seed": 20211002, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "read posterior", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 76, + "versionNonce": 162625126, + "isDeleted": false, + "id": "Gtm-iUVm6IbvqH8tz1TND", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 384, + "y": 324, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "width": 228, + "height": 574, + "seed": 1809652474, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "b8DDDjU-Sm7_Kd9ymWfaa" + ] + }, + { + "type": "arrow", + "version": 81, + "versionNonce": 1024723706, + "isDeleted": false, + "id": "eaup2MZzC_fxXF-rrJcQL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 590.6578947368422, + "y": 1078.2362764961533, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 328.83333333333303, + "height": 163.84999122870886, + "seed": 1237435194, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "5RG_eP1pQS_n4qHqSQa6w", + "gap": 2.657894736842105, + "focus": 0.02930958629792011 + }, + "endBinding": { + "elementId": "p8RmBsnbDlvCLxUWzkqcZ", + "gap": 14.175438596491256, + "focus": 0.4532915238011503 + }, + "points": [ + [ + 0, + 0 + ], + [ + 328.83333333333303, + -163.84999122870886 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "rectangle", + "version": 155, + "versionNonce": 1447147942, + "isDeleted": false, + "id": "m42knBULIHuzjxMcUMF6W", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1651, + "y": 570.6666666666667, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 76.6666666666667, + "height": 150.0000000000001, + "seed": 1880246138, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "4K7cklh56YvUmwWJnvyMn" + ] + }, + { + "type": "text", + "version": 51, + "versionNonce": 1930206138, + "isDeleted": false, + "id": "5naFWFL6OuXdfTCj6ZmAb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1671.0000000000002, + "y": 629.3333333333334, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 34, + "height": 26, + "seed": 2141200506, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "DB", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 43, + "versionNonce": 107451622, + "isDeleted": false, + "id": "0s-VD7dlz0TLaDSZ6FJrC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1437.6666666666667, + "y": 197.33333333333331, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 236.66666666666674, + "height": 215, + "seed": 467196070, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "pAAuF8rBfvJVRZOI-su_9" + ] + }, + { + "type": "text", + "version": 23, + "versionNonce": 624235642, + "isDeleted": false, + "id": "qDTCpJwz9c-obbatOOxPc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1531.0000000000002, + "y": 289.99999999999994, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 42, + "height": 26, + "seed": 1152088826, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "RPC", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 62, + "versionNonce": 1135609894, + "isDeleted": false, + "id": "pAAuF8rBfvJVRZOI-su_9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1537.6666666666667, + "y": 414, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 40, + "height": 181.66666666666663, + "seed": 960413862, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "0s-VD7dlz0TLaDSZ6FJrC", + "focus": -0.047261244886885635, + "gap": 2.935697999642244 + }, + "endBinding": { + "elementId": "V9SouvzuSlJK9NBpLw-Lu", + "focus": 0.28134747348721195, + "gap": 5.333333333333371 + }, + "points": [ + [ + 0, + 0 + ], + [ + -40, + 181.66666666666663 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 44, + "versionNonce": 357594426, + "isDeleted": false, + "id": "46pcFDq9j3f0_s3AIB6Ha", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1374.3333333333333, + "y": 476.66666666666674, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 132, + "height": 26, + "seed": 1239245414, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "read storage", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 366, + "versionNonce": 1421983226, + "isDeleted": false, + "id": "4K7cklh56YvUmwWJnvyMn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1544.0504201680671, + "y": 657.2571664710368, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 103.06095532627751, + "height": 9.861453065337173, + "seed": 1042201402, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "V9SouvzuSlJK9NBpLw-Lu", + "gap": 1.0504201680672294, + "focus": -0.07019023283236196 + }, + "endBinding": { + "elementId": "m42knBULIHuzjxMcUMF6W", + "gap": 3.888624505655482, + "focus": 0.029376489045781547 + }, + "points": [ + [ + 0, + 0 + ], + [ + 103.06095532627751, + -9.861453065337173 + ] + ], + "lastCommittedPoint": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "type": "text", + "version": 349, + "versionNonce": 930579002, + "isDeleted": false, + "id": "2xVAcKXeOrcECl0c74XEx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1559.3333333333333, + "y": 431.66666666666663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 488, + "height": 78, + "seed": 658232230, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "From the RPC storage PoV, a block should be atomic. \nThe RPC shouldn't see any changes applied \nin between \"begin block\" and \"end block\"\nuntil the block is committed.", + "baseline": 74, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 90, + "versionNonce": 382318778, + "isDeleted": false, + "id": "5CDWytpqmSRwxIAxtdj7V", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 947.6666666666666, + "y": 892.6666666666666, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 150, + "height": 59, + "seed": 1756148730, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 3, + "text": "commit all write\nlog changes \nin order", + "baseline": 55, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "TWBXonroWqWjQmNrtcfIh", + "type": "text", + "x": 852.1666666666664, + "y": 262.83333333333377, + "width": 338, + "height": 59, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 287623627, + "version": 115, + "versionNonce": 1376514443, + "isDeleted": false, + "boundElementIds": null, + "text": "There are two levels of write log,\none for the current block and one of\ncurrent transaction.", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 55 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/ledger/wasm-vm/storage-write-log.svg b/documentation/dev/src/explore/design/ledger/wasm-vm/storage-write-log.svg new file mode 100644 index 00000000000..5764d2d5c43 --- /dev/null +++ b/documentation/dev/src/explore/design/ledger/wasm-vm/storage-write-log.svg @@ -0,0 +1,16 @@ + + + + + + + current block storageapply transactionbegin blockend blockwrite logread & write storageFor each transaction store a hash map from DB keys to values of `Update(value) | Delete`try to find in the write log first,if no entry found,read storagerun VPsany reject?yes - clear current tx write logno - merge current tx write log to block write logyescommit blockget written keys, grouped by account addressescommit & get the new root hashnext tx?nocommitwriteupdate tx write logreadread prior stateread posteriorDBRPCread storageFrom the RPC storage PoV, a block should be atomic. The RPC shouldn't see any changes applied in between "begin block" and "end block"until the block is committed.commit all writelog changes in orderThere are two levels of write log,one for the current block and one ofcurrent transaction. \ No newline at end of file diff --git a/documentation/dev/src/explore/design/overview.md b/documentation/dev/src/explore/design/overview.md new file mode 100644 index 00000000000..9e6d419774a --- /dev/null +++ b/documentation/dev/src/explore/design/overview.md @@ -0,0 +1,10 @@ +# Overview + +> ⚠️ This section is WIP. + +- TODO: add high-level interaction diagram(s) + +The Rust crates internal dependency graph: + +![crates](./overview/crates.svg "crates") +[Diagram on Excalidraw](https://excalidraw.com/#room=e32fc914de750ed4f5e4,6CWRFjnmCoiFR4BQ6i9K4g) diff --git a/documentation/dev/src/explore/design/overview/crates.excalidraw b/documentation/dev/src/explore/design/overview/crates.excalidraw new file mode 100644 index 00000000000..e305dfa0885 --- /dev/null +++ b/documentation/dev/src/explore/design/overview/crates.excalidraw @@ -0,0 +1,1560 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "arrow", + "version": 940, + "versionNonce": 1634924291, + "isDeleted": false, + "id": "XW8p0b2UGBcU4qhuM50S5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 234.4292897735185, + "y": 27.074492275773665, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 46.74897051875004, + "height": 31.84747245633222, + "seed": 244640995, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978981591, + "startBinding": { + "elementId": "TPinNTC84gdo4Heiyfrcl", + "gap": 7.842259549321174, + "focus": -0.23457593568181878 + }, + "endBinding": { + "elementId": "Vv4I15UDLDULEN3MM7cWR", + "gap": 6.876060164133929, + "focus": 0.014193953434318363 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -46.74897051875004, + -31.84747245633222 + ] + ] + }, + { + "type": "text", + "version": 562, + "versionNonce": 1411527693, + "isDeleted": false, + "id": "lZbFKRT7NFNBN-N0PEGt4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 282.7198361155589, + "y": 168.66666666666663, + "strokeColor": "#0008", + "backgroundColor": "transparent", + "width": 225, + "height": 59, + "seed": 150535982, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "qGwRshNFhRxZVbc_9XoOK" + }, + { + "type": "arrow", + "id": "vjztA9aT9wXo-Mz6v8PpC" + }, + { + "type": "arrow", + "id": "XW8p0b2UGBcU4qhuM50S5" + }, + { + "type": "arrow", + "id": "j6R5PVZmpe0pg3dobMg_R" + } + ], + "updated": 1638978994165, + "fontSize": 16, + "fontFamily": 3, + "text": "sdk\n(generated by the node) \nnot yet implemented", + "baseline": 55, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "arrow", + "version": 762, + "versionNonce": 1801214061, + "isDeleted": false, + "id": "N3WOXF2nAY4GBP26E12jj", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -31.790406370277623, + "y": 287.1153118375381, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.8931189336645673, + "height": 134.30791048138838, + "seed": 1843746505, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978645308, + "startBinding": { + "elementId": "XCGaurOdV80qWvLWgvMKA", + "focus": -0.3858314613263561, + "gap": 9.235647817769404 + }, + "endBinding": { + "elementId": "EbkJ-mbPprZhdzO3Ukel2", + "focus": 0.29946861113645, + "gap": 8.92494756021722 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3.8931189336645673, + -134.30791048138838 + ] + ] + }, + { + "type": "rectangle", + "version": 118, + "versionNonce": 1233481955, + "isDeleted": false, + "id": "XCGaurOdV80qWvLWgvMKA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -111.64597821935695, + "y": 296.3509596553075, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 266, + "height": 73, + "seed": 1005582, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "N3WOXF2nAY4GBP26E12jj" + } + ], + "updated": 1638978645308 + }, + { + "type": "text", + "version": 288, + "versionNonce": 275825357, + "isDeleted": false, + "id": "2Uv1LCKqBFINap90QX39G", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -93.0922484347459, + "y": 310.70191931061504, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 235, + "height": 39, + "seed": 166206202, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "u9PpgMxWMRirdT4zSPC5f" + }, + { + "type": "arrow", + "id": "N3WOXF2nAY4GBP26E12jj" + } + ], + "updated": 1638978645308, + "fontSize": 16, + "fontFamily": 3, + "text": "apps \n(node/client/broadcaster)", + "baseline": 35, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "text", + "version": 430, + "versionNonce": 2114673795, + "isDeleted": false, + "id": "q1u2GL7wVw4Exe4X5PaPJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -30, + "y": -61, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 328, + "height": 39, + "seed": 1199188794, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "XW8p0b2UGBcU4qhuM50S5" + } + ], + "updated": 1638978645308, + "fontSize": 16, + "fontFamily": 3, + "text": "shared\n(has to be able to compile to wasm)", + "baseline": 35, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "text", + "version": 380, + "versionNonce": 1078518061, + "isDeleted": false, + "id": "iXim05PslRfMMcHgO5M77", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 262.28822012513956, + "y": 409.9572198364548, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 188, + "height": 39, + "seed": 896396134, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "qGwRshNFhRxZVbc_9XoOK" + }, + { + "type": "arrow", + "id": "vjztA9aT9wXo-Mz6v8PpC" + } + ], + "updated": 1638978645308, + "fontSize": 16, + "fontFamily": 3, + "text": "wasm\n(tx/vp/mm/mm_filter)", + "baseline": 35, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "text", + "version": 392, + "versionNonce": 1880902221, + "isDeleted": false, + "id": "7hkmbpKqpRG1mw7i72ETt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 250.93816429817616, + "y": 43.98982838512771, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 131, + "height": 39, + "seed": 1444467750, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "qGwRshNFhRxZVbc_9XoOK" + } + ], + "updated": 1638978958291, + "fontSize": 16, + "fontFamily": 3, + "text": "vm_env\n(wasm imports)", + "baseline": 35, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "arrow", + "version": 1508, + "versionNonce": 1553870509, + "isDeleted": false, + "id": "qGwRshNFhRxZVbc_9XoOK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 262.5545524579702, + "y": 396.8364074603423, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 31.03570718753258, + "height": 149.5928584864882, + "seed": 1454454153, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978981591, + "startBinding": { + "elementId": "Wzbm5P1iAViA47cEtf8fo", + "gap": 2.0964817197048013, + "focus": -0.9079512244676058 + }, + "endBinding": { + "elementId": "ar_8ezfEs1dcv1WTLbxpz", + "gap": 9.559255985213227, + "focus": 0.673948837020252 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 31.03570718753258, + -149.5928584864882 + ] + ] + }, + { + "type": "arrow", + "version": 1768, + "versionNonce": 1721966243, + "isDeleted": false, + "id": "vjztA9aT9wXo-Mz6v8PpC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 5.789250827564958, + "x": 321.9782879333155, + "y": 154.088665021562, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1.9590387446170325, + "height": 60.524866420498, + "seed": 2120846791, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978981592, + "startBinding": { + "elementId": "ar_8ezfEs1dcv1WTLbxpz", + "focus": -0.20986675637201763, + "gap": 7.748380459840945 + }, + "endBinding": { + "elementId": "TPinNTC84gdo4Heiyfrcl", + "focus": 0.2559196066333591, + "gap": 3.3655914385186065 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.9590387446170325, + -60.524866420498 + ] + ] + }, + { + "type": "text", + "version": 429, + "versionNonce": 297558701, + "isDeleted": false, + "id": "5P-9jtz0VpZvFi_qlrfFI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -58.36066384290253, + "y": 114.97042619428194, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 75, + "height": 20, + "seed": 818570969, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "XW8p0b2UGBcU4qhuM50S5" + }, + { + "type": "arrow", + "id": "lLOOrmC-Vjj7oIfMlYSb8" + }, + { + "type": "arrow", + "id": "N3WOXF2nAY4GBP26E12jj" + } + ], + "updated": 1638978645308, + "fontSize": 16, + "fontFamily": 3, + "text": "apps lib", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "arrow", + "version": 772, + "versionNonce": 1085418147, + "isDeleted": false, + "id": "lLOOrmC-Vjj7oIfMlYSb8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -28.858815305624397, + "y": 93.34045003053473, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 27.182921232842055, + "height": 99.9429435430508, + "seed": 959014413, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978645308, + "startBinding": { + "elementId": "EbkJ-mbPprZhdzO3Ukel2", + "gap": 10.542003765397801, + "focus": -0.28644471223848134 + }, + "endBinding": { + "elementId": "Vv4I15UDLDULEN3MM7cWR", + "gap": 5.046546832176402, + "focus": 0.7161156739729357 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 27.182921232842055, + -99.9429435430508 + ] + ] + }, + { + "type": "rectangle", + "version": 194, + "versionNonce": 489430499, + "isDeleted": false, + "id": "Vv4I15UDLDULEN3MM7cWR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -35.64597821935695, + "y": -70.64904034469248, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 346, + "height": 59, + "seed": 479498130, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "lLOOrmC-Vjj7oIfMlYSb8" + }, + { + "type": "arrow", + "id": "XW8p0b2UGBcU4qhuM50S5" + }, + { + "type": "arrow", + "id": "gjvzsG78lEToLfHSwix2l" + } + ], + "updated": 1638978645309 + }, + { + "type": "rectangle", + "version": 97, + "versionNonce": 832005581, + "isDeleted": false, + "id": "EbkJ-mbPprZhdzO3Ukel2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -78.38023114904445, + "y": 103.88245379593252, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 120, + "height": 40, + "seed": 1523867342, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "lLOOrmC-Vjj7oIfMlYSb8" + }, + { + "type": "arrow", + "id": "N3WOXF2nAY4GBP26E12jj" + }, + { + "type": "arrow", + "id": "Q-Lc8vIaRv7dEQF96Es40" + } + ], + "updated": 1638978645309 + }, + { + "type": "rectangle", + "version": 186, + "versionNonce": 1802791885, + "isDeleted": false, + "id": "TPinNTC84gdo4Heiyfrcl", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 237.35402178064305, + "y": 34.35095965530752, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.00000000000003, + "height": 59, + "seed": 451677582, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "vjztA9aT9wXo-Mz6v8PpC" + }, + { + "type": "arrow", + "id": "XW8p0b2UGBcU4qhuM50S5" + }, + { + "type": "arrow", + "id": "aFB1JfXdwInJtc3gPYN7J" + }, + { + "type": "arrow", + "id": "VyqgKewhv649Rl_VgfMCi" + } + ], + "updated": 1638978965417 + }, + { + "type": "rectangle", + "version": 260, + "versionNonce": 1071718765, + "isDeleted": false, + "id": "ar_8ezfEs1dcv1WTLbxpz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 270.4373551139763, + "y": 158.68429298864083, + "strokeColor": "#0008", + "backgroundColor": "transparent", + "width": 237, + "height": 79, + "seed": 947289294, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "vjztA9aT9wXo-Mz6v8PpC" + }, + { + "type": "arrow", + "id": "qGwRshNFhRxZVbc_9XoOK" + }, + { + "type": "arrow", + "id": "j6R5PVZmpe0pg3dobMg_R" + }, + { + "type": "arrow", + "id": "ZIRL-fdZPjVJvZGV2ldOy" + }, + { + "type": "arrow", + "id": "6kR5qmpuk9pmD6Oi1l544" + }, + { + "type": "arrow", + "id": "i1YmU9V2mNKEn1n-x42MI" + } + ], + "updated": 1638978965416 + }, + { + "type": "rectangle", + "version": 258, + "versionNonce": 1605517603, + "isDeleted": false, + "id": "Wzbm5P1iAViA47cEtf8fo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 251.4386978874138, + "y": 398.932889180047, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 219.00000000000003, + "height": 63, + "seed": 1729566674, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "qGwRshNFhRxZVbc_9XoOK" + }, + { + "type": "arrow", + "id": "qTCZ_7N0fuYegT9jZLwYS" + } + ], + "updated": 1638978645309 + }, + { + "type": "text", + "version": 560, + "versionNonce": 1068083432, + "isDeleted": false, + "id": "3K5BlHfHmWQqJECqYjhrw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 327.6412405007913, + "y": -61.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 179, + "height": 39, + "seed": 1395949267, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "XW8p0b2UGBcU4qhuM50S5" + } + ], + "updated": 1640603531311, + "fontSize": 16, + "fontFamily": 3, + "text": "macros\n(procedural macros)", + "baseline": 35, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "macros\n(procedural macros)" + }, + { + "type": "rectangle", + "version": 278, + "versionNonce": 246686915, + "isDeleted": false, + "id": "NsweUiJ4jKgjdA0qcz00O", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 321.99526228143435, + "y": -71.14904034469248, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 186.00000000000003, + "height": 59, + "seed": 125160413, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "lLOOrmC-Vjj7oIfMlYSb8" + }, + { + "type": "arrow", + "id": "XW8p0b2UGBcU4qhuM50S5" + }, + { + "type": "arrow", + "id": "aFB1JfXdwInJtc3gPYN7J" + } + ], + "updated": 1638978645309 + }, + { + "type": "arrow", + "version": 1011, + "versionNonce": 482577251, + "isDeleted": false, + "id": "aFB1JfXdwInJtc3gPYN7J", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 349.1354167882846, + "y": 23.491799798579976, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 46.682726805305435, + "height": 28.281680286544898, + "seed": 190431859, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978958370, + "startBinding": { + "elementId": "TPinNTC84gdo4Heiyfrcl", + "focus": -0.2707765433519321, + "gap": 10.859159856727544 + }, + "endBinding": { + "elementId": "NsweUiJ4jKgjdA0qcz00O", + "focus": -0.2940419127098849, + "gap": 7.359159856727558 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 46.682726805305435, + -28.281680286544898 + ] + ] + }, + { + "type": "rectangle", + "version": 127, + "versionNonce": 922260579, + "isDeleted": false, + "id": "4VBZ0MkHPtewJl2Z2t0Bg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 62.17464203103873, + "y": 144.10095965530752, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 152, + "height": 76, + "seed": 1777026810, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "gjvzsG78lEToLfHSwix2l" + }, + { + "type": "arrow", + "id": "Q-Lc8vIaRv7dEQF96Es40" + }, + { + "type": "arrow", + "id": "VyqgKewhv649Rl_VgfMCi" + }, + { + "type": "arrow", + "id": "_2-3bn8mf08UmbZ6BXIFS" + } + ], + "updated": 1638978645309 + }, + { + "type": "text", + "version": 142, + "versionNonce": 1104385869, + "isDeleted": false, + "id": "rBCHOnrbLb_daQkqQR3iM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 72.17464203103873, + "y": 157.60095965530752, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 134, + "height": 50, + "seed": 1079092262, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "_2-3bn8mf08UmbZ6BXIFS" + } + ], + "updated": 1638978645309, + "fontSize": 16, + "fontFamily": 2, + "text": "tests\n(integration tests &\nwasm test helpers)", + "baseline": 46, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "arrow", + "version": 309, + "versionNonce": 676204547, + "isDeleted": false, + "id": "gjvzsG78lEToLfHSwix2l", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 121.80008801370579, + "y": 137.1918687462166, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 31.75599726538033, + "height": 135.45454545454544, + "seed": 789434278, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978645309, + "startBinding": { + "elementId": "4VBZ0MkHPtewJl2Z2t0Bg", + "gap": 6.909090909090908, + "focus": -0.06885109912899212 + }, + "endBinding": { + "elementId": "Vv4I15UDLDULEN3MM7cWR", + "gap": 13.386363636363635, + "focus": 0.31883892934573677 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -31.75599726538033, + -135.45454545454544 + ] + ] + }, + { + "type": "arrow", + "version": 359, + "versionNonce": 664938243, + "isDeleted": false, + "id": "VyqgKewhv649Rl_VgfMCi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 217.6291874855842, + "y": 152.50812625104635, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 50.95431878398884, + "height": 57.42989386846611, + "seed": 1052874982, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978958370, + "startBinding": { + "elementId": "4VBZ0MkHPtewJl2Z2t0Bg", + "gap": 3.454545454545454, + "focus": 0.48484848484848486 + }, + "endBinding": { + "elementId": "TPinNTC84gdo4Heiyfrcl", + "gap": 1.727272727272727, + "focus": 0.1983948188896127 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 50.95431878398884, + -57.42989386846611 + ] + ] + }, + { + "type": "rectangle", + "version": 213, + "versionNonce": 1969612813, + "isDeleted": false, + "id": "5bb86M_XNHtgqUWdz-kf-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 253.507975364372, + "y": 462.4342929886408, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 217, + "height": 49, + "seed": 236566138, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "_2-3bn8mf08UmbZ6BXIFS" + }, + { + "type": "arrow", + "id": "j6R5PVZmpe0pg3dobMg_R" + } + ], + "updated": 1638978645309 + }, + { + "type": "text", + "version": 189, + "versionNonce": 1784032067, + "isDeleted": false, + "id": "8rk_Ui9gDtaE7NNb8u2Ir", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 266.507975364372, + "y": 478.9342929886408, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 80, + "height": 17, + "seed": 491861798, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "_2-3bn8mf08UmbZ6BXIFS" + } + ], + "updated": 1638978645309, + "fontSize": 16, + "fontFamily": 2, + "text": "wasm tests", + "baseline": 13, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "arrow", + "version": 692, + "versionNonce": 770137709, + "isDeleted": false, + "id": "_2-3bn8mf08UmbZ6BXIFS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 251.507975364372, + "y": 491.77419156750113, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 115.09760463885584, + "height": 264.19037738291837, + "seed": 2066093498, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978645309, + "startBinding": { + "elementId": "8rk_Ui9gDtaE7NNb8u2Ir", + "gap": 15, + "focus": -1.3018695247008474 + }, + "endBinding": { + "elementId": "4VBZ0MkHPtewJl2Z2t0Bg", + "gap": 7.482854529275244, + "focus": 0.23315187671020038 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -115.09760463885584, + -264.19037738291837 + ] + ] + }, + { + "type": "rectangle", + "version": 491, + "versionNonce": 227920611, + "isDeleted": false, + "id": "c9FTgvEkL5qGqshm1XkEc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 500.007975364372, + "y": 404.8092929886408, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 142.99999999999994, + "height": 76.00000000000001, + "seed": 1631226957, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "gjvzsG78lEToLfHSwix2l" + }, + { + "type": "arrow", + "id": "Q-Lc8vIaRv7dEQF96Es40" + }, + { + "type": "arrow", + "id": "VyqgKewhv649Rl_VgfMCi" + }, + { + "type": "arrow", + "id": "_2-3bn8mf08UmbZ6BXIFS" + }, + { + "type": "arrow", + "id": "j6R5PVZmpe0pg3dobMg_R" + }, + { + "type": "arrow", + "id": "6kR5qmpuk9pmD6Oi1l544" + } + ], + "updated": 1638978645309 + }, + { + "type": "text", + "version": 441, + "versionNonce": 397084877, + "isDeleted": false, + "id": "CTPEGHIc-rJMuCSTtfFbf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 511.507975364372, + "y": 419.3092929886408, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 118, + "height": 50, + "seed": 296172003, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "j6R5PVZmpe0pg3dobMg_R" + } + ], + "updated": 1638978645309, + "fontSize": 16, + "fontFamily": 2, + "text": "wasm_for_tests\n(pre-build scripts\nused for testing)", + "baseline": 46, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "arrow", + "version": 1118, + "versionNonce": 530436365, + "isDeleted": false, + "id": "j6R5PVZmpe0pg3dobMg_R", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 547.2189008950138, + "y": 396.7891966896191, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 45.12760305424797, + "height": 156.0355604263109, + "seed": 1559261453, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978981592, + "startBinding": { + "elementId": "c9FTgvEkL5qGqshm1XkEc", + "gap": 8.020096299021654, + "focus": -0.1330943061411234 + }, + "endBinding": { + "elementId": "ar_8ezfEs1dcv1WTLbxpz", + "gap": 3.069343274667317, + "focus": -0.7786387743677071 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -45.12760305424797, + -156.0355604263109 + ] + ] + }, + { + "type": "text", + "version": 550, + "versionNonce": 1498721827, + "isDeleted": false, + "id": "dqZ0GfpvY8Ewz9AEmMCWa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 348.822239073299, + "y": 310.3749999999999, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 122, + "height": 20, + "seed": 320921059, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "qGwRshNFhRxZVbc_9XoOK" + }, + { + "type": "arrow", + "id": "vjztA9aT9wXo-Mz6v8PpC" + }, + { + "type": "arrow", + "id": "XW8p0b2UGBcU4qhuM50S5" + }, + { + "type": "arrow", + "id": "j6R5PVZmpe0pg3dobMg_R" + }, + { + "type": "arrow", + "id": "qTCZ_7N0fuYegT9jZLwYS" + } + ], + "updated": 1638978645309, + "fontSize": 16, + "fontFamily": 3, + "text": "tx/vp_prelude", + "baseline": 16, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "" + }, + { + "type": "rectangle", + "version": 281, + "versionNonce": 372177293, + "isDeleted": false, + "id": "fjybklv3t7WGXd-_o4IGU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 332.2897580717163, + "y": 297.39262632197415, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 157, + "height": 42.75000000000006, + "seed": 1380885965, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "vjztA9aT9wXo-Mz6v8PpC" + }, + { + "type": "arrow", + "id": "qGwRshNFhRxZVbc_9XoOK" + }, + { + "type": "arrow", + "id": "j6R5PVZmpe0pg3dobMg_R" + }, + { + "type": "arrow", + "id": "qTCZ_7N0fuYegT9jZLwYS" + }, + { + "type": "arrow", + "id": "ZIRL-fdZPjVJvZGV2ldOy" + }, + { + "type": "arrow", + "id": "6kR5qmpuk9pmD6Oi1l544" + }, + { + "type": "arrow", + "id": "i1YmU9V2mNKEn1n-x42MI" + } + ], + "updated": 1638978645309 + }, + { + "type": "arrow", + "version": 1483, + "versionNonce": 1386463587, + "isDeleted": false, + "id": "qTCZ_7N0fuYegT9jZLwYS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 268.3511599115648, + "y": 397.7956129422673, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 116.90967732188545, + "height": 53.055973240586525, + "seed": 1160691853, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978645309, + "startBinding": { + "elementId": "Wzbm5P1iAViA47cEtf8fo", + "focus": -0.9194768569701585, + "gap": 1.1372762377797017 + }, + "endBinding": { + "elementId": "fjybklv3t7WGXd-_o4IGU", + "focus": -0.2523947759723368, + "gap": 4.597013379706624 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 116.90967732188545, + -53.055973240586525 + ] + ] + }, + { + "type": "arrow", + "version": 1662, + "versionNonce": 1619056269, + "isDeleted": false, + "id": "ZIRL-fdZPjVJvZGV2ldOy", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 381.5826013057739, + "y": 292.4896723063988, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 27.405787418156137, + "height": 51.49682634384905, + "seed": 349033581, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978962317, + "startBinding": { + "elementId": "fjybklv3t7WGXd-_o4IGU", + "gap": 4.902954015575294, + "focus": -0.16935822108573825 + }, + "endBinding": { + "elementId": "ar_8ezfEs1dcv1WTLbxpz", + "gap": 3.308552973908919, + "focus": 0.41102454938800637 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -27.405787418156137, + -51.49682634384905 + ] + ] + }, + { + "type": "arrow", + "version": 1085, + "versionNonce": 1611373827, + "isDeleted": false, + "id": "6kR5qmpuk9pmD6Oi1l544", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 526.3463625380697, + "y": 398.463201431366, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 66.5807279311241, + "height": 53.14115021878365, + "seed": 889471565, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978645309, + "startBinding": { + "elementId": "c9FTgvEkL5qGqshm1XkEc", + "focus": 0.08731195893821785, + "gap": 6.346091557274804 + }, + "endBinding": { + "elementId": "fjybklv3t7WGXd-_o4IGU", + "focus": -0.14917958172151385, + "gap": 5.179424890608146 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -66.5807279311241, + -53.14115021878365 + ] + ] + }, + { + "type": "arrow", + "version": 1222, + "versionNonce": 77626605, + "isDeleted": false, + "id": "i1YmU9V2mNKEn1n-x42MI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 436.8729532949412, + "y": 295.6281799726297, + "strokeColor": "#2b8a3e", + "backgroundColor": "transparent", + "width": 32.396136366778364, + "height": 56.23087292631101, + "seed": 1441329475, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1638978962317, + "startBinding": { + "elementId": "fjybklv3t7WGXd-_o4IGU", + "gap": 1.7644463493444311, + "focus": 0.1404156908922036 + }, + "endBinding": { + "elementId": "ar_8ezfEs1dcv1WTLbxpz", + "gap": 1.7130140576778468, + "focus": -0.7361095992935177 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 32.396136366778364, + -56.23087292631101 + ] + ] + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/overview/crates.svg b/documentation/dev/src/explore/design/overview/crates.svg new file mode 100644 index 00000000000..49f36fd6bc1 --- /dev/null +++ b/documentation/dev/src/explore/design/overview/crates.svg @@ -0,0 +1,16 @@ + + + + + + + sdk(generated by the node) not yet implementedapps (node/client/broadcaster)shared(has to be able to compile to wasm)wasm(tx/vp/mm/mm_filter)vm_env(wasm imports)apps libmacros(procedural macros)tests(integration tests &wasm test helpers)wasm testswasm_for_tests(pre-build scriptsused for testing)tx/vp_prelude \ No newline at end of file diff --git a/documentation/dev/src/explore/design/pos.md b/documentation/dev/src/explore/design/pos.md new file mode 100644 index 00000000000..55a2d917946 --- /dev/null +++ b/documentation/dev/src/explore/design/pos.md @@ -0,0 +1,282 @@ +# Proof of Stake (PoS) system + +## Epoch + +An epoch is a range of blocks or time that is defined by the base ledger and made available to the PoS system. This document assumes that epochs are identified by consecutive natural numbers. All the data relevant to PoS are [associated with epochs](#epoched-data). + +### Epoched data + +Epoched data are data associated with a specific epoch that are set in advance. The data relevant to the PoS system in the ledger's state are epoched. Each data can be uniquely identified. These are: +- [System parameters](#system-parameters). A single value for each epoch. +- [Active validator set](#active-validator-set). A single value for each epoch. +- Total voting power. A sum of all active and inactive validators' voting power. A single value for each epoch. +- [Validators' consensus key, state and total bonded tokens](#validator). Identified by the validator's address. +- [Bonds](#bonds) are created by self-bonding and delegations. They are identified by the pair of source address and the validator's address. + +Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + unboding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. + +## Entities + +- [Validator](#validator): An account with a public consensus key, which may participate in producing blocks and governance activities. A validator may not also be a delegator. +- [Delegator](#delegator): An account that delegates some tokens to a validator. A delegator may not also be a validator. + +Additionally, any account may submit evidence for [a slashable misbehaviour](#slashing). + +### Validator + +A validator must have a public consensus key. Additionally, it may also specify optional metadata fields (TBA). + +A validator may be in one of the following states: +- *inactive*: + A validator is not being considered for block creation and cannot receive any new delegations. +- *pending*: + A validator has requested to become a *candidate*. +- *candidate*: + A validator is considered for block creation and can receive delegations. + +For each validator (in any state), the system also tracks total bonded tokens as a sum of the tokens in their self-bonds and delegated bonds, less any unbonded tokens. The total bonded tokens determine their voting voting power by multiplication by the `votes_per_token` [parameter](#system-parameters). The voting power is used for validator selection for block creation and is used in governance related activities. + +#### Validator actions + +- *become validator*: + Any account that is not a validator already and that doesn't have any delegations may request to become a validator. It is required to provide a public consensus key and staking reward address. For the action applied in epoch `n`, the validator's state will be immediately set to *pending*, it will be set to *candidate* for epoch `n + pipeline_length` and the consensus key is set for epoch `n + pipeline_length`. +- *deactivate*: + Only a *pending* or *candidate* validator account may *deactivate*. For this action applied in epoch `n`, the validator's account is set to become *inactive* in the epoch `n + pipeline_length`. +- *reactivate*: + Only an *inactive* validator may *reactivate*. Similarly to *become validator* action, for this action applied in epoch `n`, the validator's state will be immediately set to *pending* and it will be set to *candidate* for epoch `n + pipeline_length`. +- *self-bond*: + A validator may lock-up tokens into a [bond](#bonds) only for its own validator's address. +- *unbond*: + Any self-bonded tokens may be partially or fully [unbonded](#unbond). +- *withdraw unbonds*: + Unbonded tokens may be withdrawn in or after the [unbond's epoch](#unbond). +- *change consensus key*: + Set the new consensus key. When applied in epoch `n`, the key is set for epoch `n + pipeline_length`. + +#### Active validator set + +From all the *candidate* validators, in each epoch the ones with the most voting power limited up to the `max_active_validators` [parameter](#system-parameters) are selected for the active validator set. The active validator set selected in epoch `n` is set for epoch `n + pipeline_length`. + +### Delegator + +A delegator may have any number of delegations to any number of validators. Delegations are stored in [bonds](#bonds). + +#### Delegator actions + +- *delegate*: + An account which is not a validator may delegate tokens to any number of validators. This will lock-up tokens into a [bond](#bonds). +- *undelegate*: + Any delegated tokens may be partially or fully [unbonded](#unbond). +- *withdraw unbonds*: + Unbonded tokens may be withdrawn in or after the [unbond's epoch](#unbond). + +## Bonds + +A bond locks-up tokens from validators' self-bonding and delegators' delegations. For self-bonding, the source address is equal to the validator's address. Only validators can self-bond. For a bond created from a delegation, the bond's source is the delegator's account. + +For each epoch, bonds are uniquely identified by the pair of source and validator's addresses. A bond created in epoch `n` is written into epoch `n + pipeline_length`. If there already is a bond in the epoch `n + pipeline_length` for this pair of source and validator's addresses, its tokens are incremented by the newly bonded amount. + +Any bonds created in epoch `n` increment the bond's validator's total bonded tokens by the bond's token amount and update the voting power for epoch `n + pipeline_length`. + +The tokens put into a bond are immediately deducted from the source account. + +### Unbond + +An unbonding action (validator *unbond* or delegator *undelegate*) requested by the bond's source account in epoch `n` creates an "unbond" with epoch set to `n + unbounding_length`. We also store the epoch of the bond(s) from which the unbond is created in order to determine if the unbond should be slashed if a fault occurred within the range of bond epoch (inclusive) and unbond epoch (exclusive). + +Any unbonds created in epoch `n` decrements the bond's validator's total bonded tokens by the bond's token amount and update the voting power for epoch `n + unbonding_length`. + +An "unbond" with epoch set to `n` may be withdrawn by the bond's source address in or any time after the epoch `n`. Once withdrawn, the unbond is deleted and the tokens are credited to the source account. + +### Staking rewards + +To a validator who proposed a block, the system rewards tokens based on the `block_proposer_reward` [system parameter](#system-parameters) and each validator that voted on a block receives `block_vote_reward`. + +### Slashing + +Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. However, because slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unboding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). + +To disincentivize validators misbehaviour in the PoS system a validator may be slashed for any fault that it has done. An evidence of misbehaviour may be submitted by any account for a fault that occurred in epoch `n` anytime before the beginning of epoch `n + unbonding_length`. + +A valid evidence reduces the validator's total bonded token amount by the slash rate in and before the epoch in which the fault occurred. The validator's voting power must also be adjusted to the slashed total bonded token amount. Additionally, a slash is stored with the misbehaving validator's address and the relevant epoch in which the fault occurred. When an unbond is being withdrawn, we first look-up if any slash occurred within the range of epochs in which these were active and if so, reduce its token amount by the slash rate. Note that bonds and unbonds amounts are not slashed until their tokens are withdrawn. + +The invariant is that the sum of amounts that may be withdrawn from a misbehaving validator must always add up to the total bonded token amount. + +## System parameters + +The default values that are relative to epoch duration assume that an epoch last about 24 hours. + +- `max_validator_slots`: Maximum active validators, default `128` +- `pipeline_len`: Pipeline length in number of epochs, default `2` +- `unboding_len`: Unbonding duration in number of epochs, default `6` +- `votes_per_token`: Used in validators' voting power calculation, default 100‱ (1 voting power unit per 1000 tokens) +- `block_proposer_reward`: Amount of tokens rewarded to a validator for proposing a block +- `block_vote_reward`: Amount of tokens rewarded to each validator that voted on a block proposal +- `duplicate_vote_slash_rate`: Portion of validator's stake that should be slashed on a duplicate vote +- `light_client_attack_slash_rate`: Portion of validator's stake that should be slashed on a light client attack + +## Storage + +The [system parameters](#system-parameters) are written into the storage to allow for their changes. Additionally, each validator may record a new parameters value under their sub-key that they wish to change to, which would override the systems parameters when more than 2/3 voting power are in agreement on all the parameters values. + +The validators' data are keyed by the their addresses, conceptually: + +```rust,ignore +type Validators = HashMap; +``` + +Epoched data are stored in the following structure: +```rust,ignore +struct Epoched { + /// The epoch in which this data was last updated + last_update: Epoch, + /// Dynamically sized vector in which the head is the data for epoch in which + /// the `last_update` was performed and every consecutive array element is the + /// successor epoch of the predecessor array element. For system parameters, + /// validator's consensus key and state, `LENGTH = pipeline_length + 1`. + /// For all others, `LENGTH = unbonding_length + 1`. + data: Vec> +} +``` + +Note that not all epochs will have data set, only the ones in which some changes occurred. + +To try to look-up a value for `Epoched` data with independent values in each epoch (such as the active validator set) in the current epoch `n`: + +1. let `index = min(n - last_update, pipeline_length)` +1. read the `data` field at `index`: + 1. if there's a value at `index` return it + 1. else if `index == 0`, return `None` + 1. else decrement `index` and repeat this sub-step from 1. + +To look-up a value for `Epoched` data with delta values in the current epoch `n`: + +1. let `end = min(n - last_update, pipeline_length) + 1` +1. sum all the values that are not `None` in the `0 .. end` range bounded inclusively below and exclusively above + +To update a value in `Epoched` data with independent values in epoch `n` with value `new` for epoch `m`: + +1. let `shift = min(n - last_update, pipeline_length)` +1. if `shift == 0`: + 1. `data[m - n] = new` +1. else: + 1. for `i in 0 .. shift` range bounded inclusively below and exclusively above, set `data[i] = None` + 1. rotate `data` left by `shift` + 1. set `data[m - n] = new` + 1. set `last_update` to the current epoch + +To update a value in `Epoched` data with delta values in epoch `n` with value `delta` for epoch `m`: + +1. let `shift = min(n - last_update, pipeline_length)` +1. if `shift == 0`: + 1. set `data[m - n] = data[m - n].map_or_else(delta, |last_delta| last_delta + delta)` (add the `delta` to the previous value, if any, otherwise use the `delta` as the value) +1. else: + 1. let `sum` to be equal to the sum of all delta values in the `i in 0 .. shift` range bounded inclusively below and exclusively above and set `data[i] = None` + 1. rotate `data` left by `shift` + 1. set `data[0] = data[0].map_or_else(sum, |last_delta| last_delta + sum)` + 1. set `data[m - n] = delta` + 1. set `last_update` to the current epoch + +The invariants for updates in both cases are that `m - n >= 0` and `m - n <= pipeline_length`. + +For the active validator set, we store all the active and inactive validators separately with their respective voting power: +```rust,ignore +type VotingPower = u64; + +/// Validator's address with its voting power. +#[derive(PartialEq, Eq, PartialOrd, Ord)] +struct WeightedValidator { + /// The `voting_power` field must be on top, because lexicographic ordering is + /// based on the top-to-bottom declaration order and in the `ValidatorSet` + /// the `WeighedValidator`s these need to be sorted by the `voting_power`. + voting_power: VotingPower, + address: Address, +} + +struct ValidatorSet { + /// Active validator set with maximum size equal to `max_active_validators` + active: BTreeSet, + /// All the other validators that are not active + inactive: BTreeSet, +} + +type ValidatorSets = Epoched; + +/// The sum of all active and inactive validators' voting power +type TotalVotingPower = Epoched; +``` + +When any validator's voting power changes, we attempt to perform the following update on the `ActiveValidatorSet`: + +1. let `validator` be the validator's address, `power_before` and `power_after` be the voting power before and after the change, respectively +1. let `power_delta = power_after - power_before` +1. let `min_active = active.first()` (active validator with lowest voting power) +1. let `max_inactive = inactive.last()` (inactive validator with greatest voting power) +1. find whether the validator is active, let `is_active = power_before >= max_inactive.voting_power` + 1. if `is_active`: + 1. if `power_delta > 0 && power_after > max_inactive.voting_power`, update the validator in `active` set with `voting_power = power_after` + 1. else, remove the validator from `active`, insert it into `inactive` and remove `max_inactive.address` from `inactive` and insert it into `active` + 1. else (`!is_active`): + 1. if `power_delta < 0 && power_after < min_active.voting_power`, update the validator in `inactive` set with `voting_power = power_after` + 1. else, remove the validator from `inactive`, insert it into `active` and remove `min_active.address` from `active` and insert it into `inactive` + +Within each validator's address space, we store public consensus key, state, total bonded token amount and voting power calculated from the total bonded token amount (even though the voting power is stored in the `ValidatorSet`, we also need to have the `voting_power` here because we cannot look it up in the `ValidatorSet` without iterating the whole set): + +```rust,ignore +struct Validator { + consensus_key: Epoched, + state: Epoched, + total_deltas: Epoched, + voting_power: Epoched, +} + +enum ValidatorState { + Inactive, + Pending, + Candidate, +} +``` + +The bonds and unbonds are keyed by their identifier: + +```rust,ignore +type Bonds = HashMap>; +type Unbonds = HashMap>; + +struct BondId { + validator: Address, + /// The delegator adddress for delegations, or the same as the `validator` + /// address for self-bonds. + source: Address, +} + +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. + deltas: HashMap, +} + +struct Unbond { + /// A key is a pair of the epoch of the bond from which a unbond was created + /// the epoch of unboding. This is needed for slash epoch range check. + deltas: HashMap<(Epoch, Epoch), token::Amount> +} +``` + +For slashes, we store the epoch and block height at which the fault occurred, slash rate and the slash type: + +```rust,ignore +struct Slash { + epoch: Epoch, + block_height: u64, + /// slash token amount ‱ (per ten thousand) + rate: u8, + r#type: SlashType, +} +``` + +## Initialization + +An initial validator set with self-bonded token amounts must be given on system initialization. + +This set is used to pre-compute epochs in the genesis block from epoch `0` to epoch `pipeline_length - 1`. diff --git a/documentation/dev/src/explore/design/summary.png b/documentation/dev/src/explore/design/summary.png new file mode 100644 index 0000000000000000000000000000000000000000..e1d0af7b460e340308d0f6ae189c331f2f42eca7 GIT binary patch literal 149153 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 + +The three main one are async-std, futures and tokio. + +## Tokio + +Tokio is multithreaded, low cost and scalable. It also contains an async tcp & +udp socket and is used in both tendermint and libp2p. + + diff --git a/documentation/dev/src/explore/libraries/cli.md b/documentation/dev/src/explore/libraries/cli.md new file mode 100644 index 00000000000..9ddd50e5ea9 --- /dev/null +++ b/documentation/dev/src/explore/libraries/cli.md @@ -0,0 +1,19 @@ +# Command-line interface + +Important factors: +- UX +- ease of use +- cross-platform + +The considered libraries: +- clap + +## Clap + + + +Probably the most widely used CLI library in Rust. + +With version 2.x, we'd probably want to use it with [Structops](https://github.com/TeXitoi/structopt) for deriving. + +But we can probably use 3.0, which is not yet stable, but is pretty close . This version comes with deriving attributes and also other new ways to build CLI commands. diff --git a/documentation/dev/src/explore/libraries/db.md b/documentation/dev/src/explore/libraries/db.md new file mode 100644 index 00000000000..71c6827c20a --- /dev/null +++ b/documentation/dev/src/explore/libraries/db.md @@ -0,0 +1,95 @@ +# Database + +Important factors: +- persistent key/value storage +- reliability and efficiency (runtime performance and disk usage) +- thread safety +- ease of use + +The considered DBs: +- LMDB +- LevelDB +- RocksDB +- sled - Rust native + +To watch: +- [sanakirja](https://docs.rs/sanakirja) - too new to be considered for now, but has some [promising initial results](https://pijul.org/posts/2021-02-06-rethinking-sanakirja/) - TLDR. it can *fork tables* efficiently, it beats LMDB in benchmarks and usability + +The current preference is for RocksDB as it's tried and tested. Eventually, we might want to benchmark against other backends for our specific use case. + +## LMDB + + + +A compact and efficient, persistent in-memory (i.e. mmap-based) B+trees database. Reportedly has a great read performance, but not as good at writing. + +Rust bindings: +- +- +- - some [comparison notes](https://github.com/vhbit/lmdb-rs/issues/32#issuecomment-310906601) with danburkert/lmdb-rs +- + +## LevelDB + +Log Structured Merge Tree db. Uses one global lock. Better write performance than LMDB and lower DB size. + +Rust bindings: +- + +## RocksDB + +A fork of LevelDB with different optimizations (supposedly for RAM and flash storage). + +Used in and . + +Rust bindings: +- + +## Sled + +Repo: +Homepage: + +Modern, zero-copy reads, lock-free and many more features. + +--- + +# Merkle tree data structure + +Some popular choices for merkle tree in the industry are AVL(+) tree, Patricia Trie and Sparse Merkle Tree, each with different trade-offs. + +AVL(+) tree is used in e.g. [Cosmos](https://github.com/cosmos/iavl). The advantage of this structure is that key don't need to be hashed prior to insertion/look-up. + +Patricia trie used in e.g. [Ethereum](https://eth.wiki/en/fundamentals/patricia-tree) and [Plebeia for Tezos](https://www.dailambda.jp/blog/2020-05-11-plebeia/) are designed to be more space efficient. + +Sparse Merle tree as described in [Optimizing sparse Merkle trees](https://ethresear.ch/t/optimizing-sparse-merkle-trees/3751) used in e.g. [Plasma Cash](https://ethresear.ch/t/plasma-cash-with-sparse-merkle-trees-bloom-filters-and-probabilistic-transfers/2006) are somewhat similar to Patricia trees, but perhaps conceptually simpler. + +- Compact Sparse Merkle Trees +- Efficient Sparse Merkle Trees (caching) + +Considered libraries: +- merk +- sparse-merkle-tree +- patricia_tree + +## merk + + + +Using AVL tree built on top of RocksDB. It makes it easy to setup Merkle tree storage, but: +- is not yet fully implemented as described (e.g. [concurrent ops](https://github.com/nomic-io/merk/issues/26)) +- benchmarks seem to differ from results in README +- doesn't have past states of the tree, instead [relies on RocksDB snapshot/checkpoint features](https://github.com/nomic-io/merk/blob/develop/docs/algorithms.md#database-representation), which means that it's [strongly coupled](https://github.com/nomic-io/merk/issues/11) +- uses a custom [encoding lib](https://github.com/nomic-io/ed) which is zero-copy, but big-endian everywhere +- there are a `unsafe` usages that are not well described/justified +- uses some experimental dep such as (now deprecated) + +## sparse-merkle-tree + + + +A nice abstraction, albeit not yet declared stable. It allows to plug-in a custom hasher function (which is important for [circuit friendliness](https://github.com/heliaxdev/rd-pm/issues/11)) and storage backend. Has minimal dependencies and support Rust `no_std`. + +## patricia_tree + + diff --git a/documentation/dev/src/explore/libraries/errors.md b/documentation/dev/src/explore/libraries/errors.md new file mode 100644 index 00000000000..388b232840c --- /dev/null +++ b/documentation/dev/src/explore/libraries/errors.md @@ -0,0 +1,36 @@ +# Error handling + +The current preference is to use `thiserror` for most code and `eyre` for reporting errors at the CLI level and the client. + +To make the code robust, we should avoid using code that may panic for errors that recoverable and handle all possible errors explicitly. Two exceptions to this rule are: +- prototyping, where it's fine to use `unwrap`, `expect`, etc. +- in code paths with conditional compilation **only** for development build, where it's preferable to use `expect` in place of `unwrap` to help with debugging + +In case of panics, we should provide an error trace that is helpful for trouble-shooting and debugging. + +A great post on error handling library/application distinction: . + +The considered DBs: +- thiserror +- anyhow +- eyre + +The current preference is to use eyre at the outermost modules to print any encountered errors nicely back to the user and thiserror elsewhere. + +## Thiserror + +- + +Macros for user-derived error types. Commonly used for library code. + +## Anyhow + +- + +Easy error handling helpers. Commonly used for application code. + +## Eyre + +- + +Fork of `anyhow` with custom error reporting. diff --git a/documentation/dev/src/explore/libraries/logging.md b/documentation/dev/src/explore/libraries/logging.md new file mode 100644 index 00000000000..c2a877eab6a --- /dev/null +++ b/documentation/dev/src/explore/libraries/logging.md @@ -0,0 +1,29 @@ +# Logging + +Options to consider: +- env_logger +- slog +- tracing + +The current preference is for tracing in combination with tracing-subscriber (to log collected events and traces), because we have some async and parallelized code. In future, we should also add tracing-appender for rolling file logging. + +## Env_logger + + + +A simple logger used by many Rust tools, configurable by env vars. Usually combined with [pretty-env-logger](https://github.com/seanmonstar/pretty-env-logger). + +## Slog + + + +Composable, structured logger. Many extra libraries with extra functionality, e.g.: +- port of env_logger as a slog-rs drain + +## Tracing + + + +Tracing & logging better suited for concurrent processes and async code. Many extra libraries with extra functionality, e.g.: +- non-blocking log appender +- allows to forward library log statements and to use this in combination with env_logger diff --git a/documentation/dev/src/explore/libraries/network.md b/documentation/dev/src/explore/libraries/network.md new file mode 100644 index 00000000000..dd1c2522e08 --- /dev/null +++ b/documentation/dev/src/explore/libraries/network.md @@ -0,0 +1,22 @@ +# network + +## Libp2p : Peer To Peer network + + + +peer-to-peer framework that takes care of the transport/identity and message +encryption for us. + +## tonic : Client/Server with protobuf (prost) + + + +Generates a client/server from protobuf file. This can be used for a rpc server. + +# network behaviour + +## Gossipsub + + + +Publish/Subscribe protocol, improvement over floodsub. diff --git a/documentation/dev/src/explore/libraries/packaging.md b/documentation/dev/src/explore/libraries/packaging.md new file mode 100644 index 00000000000..43fe60f07f7 --- /dev/null +++ b/documentation/dev/src/explore/libraries/packaging.md @@ -0,0 +1,27 @@ +# Packaging + +For Rust native code, cargo works great, but we'll need to package stuff from outside of Rust too (e.g. tendermint). The goal is to have a repo that can always build as is (reproducible) and easily portable (having a single command to install all the deps). + +Options to consider: +- [nix packages](https://github.com/NixOS/nixpkgs) +- [guix](https://guix.gnu.org/manual/en/html_node/Package-Management.html) +- docker + +## Cargo + +For Rust dependencies, it would be nice to integrate and use: +- +- +- + +## Nix + +Purely functional package management for reproducible environment. The big drawback is its language. + +## Guix + +Similar package management capability to nix, but using scheme language. + +## Docker + +Not ideal for development, but we'll probably want to provide docker images for users. diff --git a/documentation/dev/src/explore/libraries/serialization.md b/documentation/dev/src/explore/libraries/serialization.md new file mode 100644 index 00000000000..ee807827a2c --- /dev/null +++ b/documentation/dev/src/explore/libraries/serialization.md @@ -0,0 +1,81 @@ +# Serialization libraries + +Because the serialization for the RPC and storage have different priorities, it might be beneficial to use a different library for each. + +## RPC + +Important factors: +- security, e.g.: + - handling of malicious input (buffers should not be trusted) + - secure RPC, if included (e.g. DoS or memory exhaustion vulnerabilities) +- native and cross-language adoption for easy interop +- ease of use +- reasonable performance + +The considered libraries: +- protobuf +- cap'n'proto +- flatbuffers +- serde + +The current preference is for protobuf using the prost library. + +## Storage + +Important factors: +- consistent binary representation for hashing +- preserve ordering (for DB keys) +- ease of use +- reasonable performance + +The considered libraries: +- bincode +- borsh + +## Protobuf + +The most mature and widely adopted option. Usually combined with gRPC framework. The [Tendermint Rust ABCI](https://github.com/tendermint/rust-abci) provides protobuf definitions. + +Implementations: +- - Rust native +- - Rust native +- - [missing features](https://github.com/tafia/quick-protobuf/issues/12) + +[A comparison of the two](https://www.reddit.com/r/rust/comments/czxny2/which_protocol_buffers_crates_to_invest_in/) main competing Rust implementations seems to favor Prost. Prost reportedly generates cleaner (more idiomatic) Rust code (). Prost also has better performance (). It is possible to also add serde derive attributes for e.g. [JSON support](https://github.com/danburkert/prost/issues/75). JSON can be useful for development, requests inspection and web integration. However, to reduce attack surface, we might want to disallow JSON for write requests on mainnet by default. + +gRPC implementations: +- - Rust native, using Prost and Tokio +- - build on C core library +- - not production ready + +## Cap'n'proto + +It avoids serialization altogether, you use the data natively in a representation that is efficient for interchange ("zero-copy"). The other cool feature is its ["time-traveling RPC"](https://capnproto.org/rpc.html). On the other hand concern for this lib is a much lower adoption rate, especially the Rust port which is not as complete. The format is designed to be safe against malicious input (on the both sides of a communication channel), but according to [FAQ](https://capnproto.org/faq.html) the reference impl (C++) has not yet undergone security review. + +Implementations: +- + +## Flatbuffers + +Similar to protobuf, but zero-copy like Cap'n'proto, hence a lot faster. + +Unfortunately, the Rust implementation is [lacking buffer verifiers](https://google.github.io/flatbuffers/flatbuffers_support.html), which is crucial for handling malicious requests gracefully. There is only draft implementation . This most likely rules out this option. + +Implementations: +- + +## Serde + +Serde is Rust native framework with great ergonomics. It supports many [different formats](https://serde.rs/#data-formats) implemented as libraries. It's used in some DBs too. Serde itself gives [no security guarantees](https://github.com/serde-rs/serde/issues/1087), handling of malicious input depends heavily on the used format. Serde can be used in combination with many other formats, like protobuf. + +## Bincode + + + +Built on top of serde. Easy to use. + +## Borsh + + + +Used in the Near protocol, it guarantees consistent representations and has a specification. It is also faster than bincode and is being [implemented in other languages](https://github.com/near/borsh#implementations). diff --git a/documentation/dev/src/explore/libraries/wasm.md b/documentation/dev/src/explore/libraries/wasm.md new file mode 100644 index 00000000000..2a82b9119b6 --- /dev/null +++ b/documentation/dev/src/explore/libraries/wasm.md @@ -0,0 +1,40 @@ +# WASM runtime + +Considered runtimes: +- wasmer +- wasmi + +A good comparison overview is given in this [thread that discusses replacing wasmi with wasmer](https://forum.holochain.org/t/wasmi-vs-wasmer/1929) and its links. In summary: +- wasmer has native rust closures (simpler code) +- wasmer uses lexical scoping to import functions, wasmi is based on structs and trait impls +- the wasmer org maintains wasmer packages in many languages +- wasmer may be vulnerable to compiler bombs + - this can be mitigated by using [a singlepass wasm compiler](https://lib.rs/crates/wasmer-compiler-singlepass-near) +- gas metering + - wasmi inject calls to the host gas meter from Wasm modules + - wasmer + - uses Middleware which injects the instructions at the parsing stage of the compiler (with inlining) - reduced overhead + - must also consider compiler gas cost and how to handle compiler performance changes + - it's hard to implement gas rules for precompiles +- [nondeterminism concerns](https://github.com/WebAssembly/design/blob/c9db0ebdee28d2f92726314c05cb8ff382701f8e/Nondeterminism.md) + - different wasm versions (e.g. newly added features) have to be handled in both the compiled and interpreted versions + - non-determinism in the source language cannot be made deterministic in complied/interpreted wasm either + - threading - look like it has a long way to go before being usable + - floats/NaN - can be avoided + - SIMD + - environment resources exhaustion +- both are using the same spec, in wasmi words "there shouldn't be a problem migrating to another spec compliant execution engine." and "wasmi should be a good option for initial prototyping" + - of course this is only true if we don't use features that are not yet in the spec + +## wasmer + +Repo: + +Compiled with multiple backends (Singlepass, Cranelift and LLVM). It [support metering](https://github.com/wasmerio/wasmer/blob/3dc537cc49b8034047c3b142a66b3b6180f4447c/examples/metering.rs) via a [Middleware](https://github.com/wasmerio/wasmer/tree/3dc537cc49b8034047c3b142a66b3b6180f4447c/lib/middlewares). + +## wasmi + +Repo: + +Built for blockchain to ensure high degree of correctness (security, determinism). Interpreted, hence slower. + diff --git a/documentation/dev/src/explore/prototypes/README.md b/documentation/dev/src/explore/prototypes/README.md new file mode 100644 index 00000000000..375dd805c06 --- /dev/null +++ b/documentation/dev/src/explore/prototypes/README.md @@ -0,0 +1,23 @@ +# Prototypes + +A prototype should start with a description of its goals. These can include, but are not limited to a proof of concept of novel ideas or alternative approaches, comparing different libraries and gathering feedback. + +To get started on a prototype, please: +- open an issue on this repository +- add a sub-page to this section with a link to the issue + +The page outlines the goals and possibly contains any notes that are not suitable to be added to the prototype source itself, while the issue should track the sub-task, their progress, and assignees. + +The code quality is of lesser importance in prototypes. To put the main focus on the prototype's goals, we don't need to worry much about testing, linting and doc strings. + +## Advancing a successful prototype + +Once the goals of the prototype have been completed, we can assess if we'd like to advance the prototype to a development version. + +In order to advance a prototype, in general we'll want to: +- review & clean-up the code for lint, format and best practices +- enable common Rust lints +- review any new dependencies +- add docs for any public interface (internally public too) +- add automated tests +- if the prototype has diverged from the original design, update these pages diff --git a/documentation/dev/src/explore/prototypes/base-ledger.md b/documentation/dev/src/explore/prototypes/base-ledger.md new file mode 100644 index 00000000000..7443568ba5d --- /dev/null +++ b/documentation/dev/src/explore/prototypes/base-ledger.md @@ -0,0 +1,112 @@ +# Base ledger prototype + +## Version 3 + +tracking issue + + +### Goals + +- various shell and protocol fixes, improvements and additions +- add more crypto support +- WASM improvements +- implement new validity predicates +- storage improvements +- gas & fees +- fixes for issues found in the Feigenbaum testnet +- IBC integration +- Ferveo/ABCI++ integration +- PoS improvements and new features + - testing (unit + integration + e2e) + - storage values refactoring + - use checked arithmetics + - validator VP + - staking rewards + - staking reward VP + - re-delegation + - validator + - deactivation/reactivation + - change consensus key + +## Version 2 + +tracking issue + +### Goals + +- storage + - build key schema for access + - implement dynamic account sub-spaces +- implement more complete support for WASM transactions and validity predicates + - transactions can read/write all storage + - validity predicates receive the set of changes (changed keys or complete write log) and can read their pre/post state +- add basic transaction gas metering +- various other improvements + +## Version 1 + +tracking issue + +### Goals + +- get some hands-on experience with Rust and Tendermint +- initial usable node + client (+ validator?) setup +- provide a base layer for other prototypes that need to build on top of a ledger + +### Components + +The main components are built in a single Cargo project with [shared library code](#shared) and multiple binaries: +- `anoma` - main executable with commands for both the node and the client (`anoma node` and `anoma client`) +- `anoman` - the [node](#node) +- `anomac` - the [client](#client) + +#### Node + +The node is built into `anoman`. + +##### Shell + +The shell is what currently pulls together all the other components in the node. + +When it's ran: +- establish a channel (e.g.`mpsc::channel` - Multi-producer, single-consumer FIFO queue) for communication from tendermint to the shell +- launch tendermint node in another thread with the channel sender + - send tendermint ABCI requests via the channel together with a new channel sender to receive a response +- run shell loop with the channel receiver, which handles ABIC requests: + - [transaction execution](../design/ledger/tx.md) which includes [wasm VM calls](../design/ledger/wasm-vm.md) + +###### Tendermint + +This module handles initializing and running `tendermint` and forwards messages for the ABCI requests via its channel sender. + +##### Storage + +Key-value storage. More details are specified on [Storage page](../design/ledger/storage.md). + +##### CLI + +- `anoma run` to start the node (will initialize (if needed) and launch tendermint under the hood) +- `anoma reset` to delete all the node's state from DB and tendermint's state + +#### Client + +Allows to submit a transaction with an attached wasm code to the node with: + +`anoma tx --code tx.wasm` + +It presents back the received response on stdout. Currently, it waits for both the mempool validation and application in a block. + +#### Shared + +##### Config + +Configuration settings: +- home directory (db storage and tendermint config and data) + +##### Genesis + +The genesis parameters, such as the initial validator set, are used to initialize a chain's genesis block. + +##### RPC types + +The types for data that can be submitted to the node via the client's RPC commands. diff --git a/documentation/dev/src/explore/prototypes/gossip-layer.md b/documentation/dev/src/explore/prototypes/gossip-layer.md new file mode 100644 index 00000000000..b991ca5c686 --- /dev/null +++ b/documentation/dev/src/explore/prototypes/gossip-layer.md @@ -0,0 +1,61 @@ +# Intent Gossip system prototype + +## Version 2 + +tracking issue + +- Separate matchmakers from intent gossiper nodes +- Various fixes and improvements +- fixes for issues found in the Feigenbaum testnet +- Persistent storage +- Intent gossip and matching of complex txs +- multi-party trades (e.g. 10) +- multi-asset trades (FT, NFT) +- NFT swaps +- Benchmarking base load for the entire network +- Incentives +- Docs + +## Version 1 + +tracking issue + +### Goals + +- learning rust +- usable node + client setup : + - intent + - incentive function + - mempool and white list +- basic matchmaker + +### components + +The intent gossip is build conjointly to the ledger and share the same binary. + +#### Node + +The node is built into `anoman`, it runs all the necesarry part, rpc server, +libp2p, intent gossip app. + +##### Intent gossip application + +The intent gossip application + +###### Mempool + +###### Filter + +##### Network behaviour +The network behaviour is the part that react on network event. It creates a +channel (e.g. `tokio::mpsc::channel`) with the intent gossip to communicate all +intent it receive. + +##### Rpc server +If the rpc command line option is set it creates a tonic server that receive +command from a client and send theses through a channel +(e.g. `tokio::mpsc::channel`) to the the intent gossip. + +#### Client +Allow to submit a intent : +`anoma gossip --data "data"` diff --git a/documentation/dev/src/explore/resources/README.md b/documentation/dev/src/explore/resources/README.md new file mode 100644 index 00000000000..7acf64a1b04 --- /dev/null +++ b/documentation/dev/src/explore/resources/README.md @@ -0,0 +1,11 @@ +# Resources + +Please add anything relevant to the project that you'd like to share with others, such as research papers, blog posts or tutorials. If it's not obvious from the title, please add some description. + +## General + +- + +## Rust + +- diff --git a/documentation/dev/src/explore/resources/ide.md b/documentation/dev/src/explore/resources/ide.md new file mode 100644 index 00000000000..9f5f106ba14 --- /dev/null +++ b/documentation/dev/src/explore/resources/ide.md @@ -0,0 +1,133 @@ +# IDE + +## VsCode + +Some handy extensions (output of `code --list-extensions`): + +```shell +aaron-bond.better-comments +be5invis.toml +bodil.file-browser +bungcip.better-toml +DavidAnson.vscode-markdownlint +jacobdufault.fuzzy-search +kahole.magit +matklad.rust-analyzer +oderwat.indent-rainbow +# easy to see if crates are up-to-date and update if not +serayuzgur.crates +streetsidesoftware.code-spell-checker +vscodevim.vim +# this is like https://www.spacemacs.org/ but in VsCode +VSpaceCode.vspacecode +VSpaceCode.whichkey +# org-mode +vscode-org-mode.org-mode +publicus.org-checkbox +``` + +Add these to your settings.json to get rustfmt and clippy with the nightly version that we use: + +```json +"rust-analyzer.checkOnSave.overrideCommand": [ + "cargo", + "+{{#include ../../../../rust-nightly-version}}", + "clippy", + "--workspace", + "--message-format=json", + "--all-targets" +], +"rust-analyzer.rustfmt.overrideCommand": [ + "rustup", + "run", + "{{#include ../../../../rust-nightly-version}}", + "--", + "rustfmt", + "--edition", + "2018", + "--" +], +``` + +When editing the wasms source (i.e. `wasm/wasm_source/src/..`), open the `wasm/wasm_source` as a workspace to get rust-analyzer working (because the crate is excluded from the root cargo workspace) and then active `--all-features` for it in the preferences. + +## Emacs + +two main mode: + +- [rust-mode](https://github.com/rust-lang/rust-mode) + official mode supported by rust dev +- [rustic-mode](https://github.com/brotzeit/rustic) + forked with more option and better integration/default value + +## config example with rustic and use-package + +```elisp + ;; all flycheck not mandatory not mandatory + (use-package flycheck + :commands flycheck-mode + :init (global-flycheck-mode)) + + (use-package flycheck-color-mode-line + :after flycheck + :hook + (flycheck-mode . flycheck-color-mode-line-mode)) + + (use-package flycheck-pos-tip + :after flycheck) + (use-package lsp-mode + :after flycheck + :bind-keymap + ("C-c i" . lsp-command-map) + :hook + (lsp-mode . lsp-enable-which-key-integration) ;; if wichkey installed + :commands (lsp lsp-deferred) + :custom + (lsp-eldoc-render-all t) + (lsp-idle-delay 0.3) + ) + + (use-package lsp-ui + :after lsp-mode + :commands lsp-ui-mode + :custom + (lsp-ui-peek-always-show t) + (lsp-ui-sideline-show-hover t) + (lsp-ui-doc-enable nil) + (lsp-ui-doc-max-height 30) + :hook (lsp-mode . lsp-ui-mode)) + + ;; if ivy installed installed + (use-package lsp-ivy + :after lsp-mode ivy + :commands lsp-ivy-workspace-symbol) + + ;; if company installed + (use-package company-lsp + :after lsp-mode company + :init + (push 'company-lsp company-backend)) + + (use-package rustic + :bind (:map rustic-mode-map + ("M-j" . lsp-ui-imenu) + ("M-?" . lsp-find-references) + ("C-c C-c ?" . lsp-describe-thing-at-point) + ("C-c C-c !" . lsp-execute-code-action) + ("C-c C-c r" . lsp-rename) + ("C-c C-c TAB" . lsp-rust-analyzer-expand-macro) + ("C-c C-c q" . lsp-workspace-restart) + ("C-c C-c Q" . lsp-workspace-shutdown) + ("C-c C-c s" . lsp-rust-analyzer-status) + ("C-c C-c C-a" . rustic-cargo-add) + ("C-c C-c C-d" . rustic-cargo-rm) + ("C-c C-c C-u" . rustic-cargo-upgrade) + ("C-c C-c C-u" . rustic-cargo-outdated)) + :hook + (rustic-mode . lsp-deferred) + :custom + (lsp-rust-analyzer-cargo-watch-command "clippy") + :config + (rustic-doc-mode t) + ) +``` diff --git a/documentation/dev/src/rustdoc-logo.png b/documentation/dev/src/rustdoc-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e4c8c045cd994d4a033d49cc1cd060d129d22048 GIT binary patch literal 5188 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. + +## Public keys + +A public key is a [Borsh encoded `PublicKey`](encoding.md#publickey). For the Ed25519 scheme, this is 32 bytes of Ed25519 public key, prefixed with `32` in little endian encoding (`[32, 0, 0, 0]` in raw bytes or `20000000` in hex). (TODO this will change with ) + +## Signatures + +A signature in Anoma is a [Borsh encoded `Signature`](encoding.md#signature). For the Ed25519 scheme, this is 64 bytes of Ed25519 signature, prefixed with `64` in little endian encoding (`[64, 0, 0, 0]` in raw bytes or `40000000` in hex). (TODO this will change with ) diff --git a/documentation/dev/src/specs/encoding.md b/documentation/dev/src/specs/encoding.md new file mode 100644 index 00000000000..8b9de70617c --- /dev/null +++ b/documentation/dev/src/specs/encoding.md @@ -0,0 +1,48 @@ +# Encoding + +## The ledger + +Most of the data in Anoma are encoded with [Borsh](#borsh-binary-encoding), except for the outer layer of [transactions](#transactions) that are being passed via Tendermint and therefore are required to be encoded with protobuf. + +### Borsh binary encoding + +The encoding schemas below are described in terms of [Borsh specification](https://github.com/nearprotocol/borsh#specification), following the general principles ([verbatim from Borsh](https://github.com/near/borsh/blob/master/README.md#specification)): + +- integers are little endian; +- sizes of dynamic containers are written before values as `u32`; +- all unordered containers (hashmap/hashset) are ordered in lexicographic order by key (in tie breaker case on value); +- structs are serialized in the order of fields in the struct; +- enums are serialized with using `u8` for the enum ordinal and then storing data inside the enum value (if present). + +Note that "nil" corresponds to unit (`()`) which is encoded as empty bytes (nothing is being written). + + + +{{#include encoding/generated-borsh-spec.md}} + +## Protobuf + +The schemas below are described in terms of [proto3 specification](https://developers.google.com/protocol-buffers/docs/reference/proto3-spec). + +All the data fields are REQUIRED, unless specified otherwise. + +### Transactions + +Transactions MUST be encoded in the format as defined for [`message Tx`](#proto-definitions). + +Note that for the [default transactions](ledger/default-transactions.md), the `data` are [encoded with Borsh](#borsh-binary-encoding). + +| Name | Type | Description | Field Number | +|-----------|---------------------------|------------------------------------------------|--------------| +| code | bytes | Transaction WASM code. | 1 | +| data | optional bytes | Transaction data (OPTIONAL). | 2 | +| timestamp | google.protobuf.Timestamp | Timestamp of when the transaction was created. | 3 | + +## Proto definitions + +``` +{{#include ../../../proto/types.proto}} +``` + + + diff --git a/documentation/dev/src/specs/encoding/.gitignore b/documentation/dev/src/specs/encoding/.gitignore new file mode 100644 index 00000000000..0e44a29082b --- /dev/null +++ b/documentation/dev/src/specs/encoding/.gitignore @@ -0,0 +1 @@ +generated-borsh-spec.md \ No newline at end of file diff --git a/documentation/dev/src/specs/ledger.md b/documentation/dev/src/specs/ledger.md new file mode 100644 index 00000000000..54de087ca10 --- /dev/null +++ b/documentation/dev/src/specs/ledger.md @@ -0,0 +1,284 @@ +# The ledger + +The ledger's main responsibility is to process and apply [transactions](#transactions) over the [distributed ledger's storage](#storage), following the ledger's [protocol](#the-protocol) to reach consensus. + +## Accounts + +The ledger is backed by an account-based system. Each account has a unique [address](#addresses) and exactly one [validity predicate](#validity-predicates-check) and a [dynamic storage sub-space](#dynamic-storage-sub-space). + +### Addresses + +There are two main types of address: transparent and shielded. + +The transparent addresses are the addresses of accounts associated with dynamic storage sub-spaces, where the address of the account is the prefix key segment of its sub-space. + +The shielded addresses are used for private transactions and they are not directly associated with storage sub-spaces. + +#### Transparent addresses + +Furthermore, there are three types of transparent addresses: + +- "implicit" addresses which are derived from [public keys](crypto.md#public-keys) +- "established" addresses which are generated from the current address nonce and hence must be created via a request in the ledger +- "internal" addresses are used for special modules integrated into the ledger such as PoS and IBC. + +The addresses are stored on-chain encoded with [bech32m](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki), which is an improved version of [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki). + +The human-readable prefix (as specified for [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#specification)) in the transparent address encoding is: + +- `"a"` for Anoma live network (80 characters in total) +- `"atest"` for test networks (84 characters in total) + +## Transactions + +A transaction has two layers, each wrapped inside [`Tx` type encoded with proto3](./encoding.md#transactions). + +The outer layer is employed for front-running protection following DKG protocol to wrap the inner layer, which remains encrypted before its block order has been committed. The outer layer MUST contain `data` with a [`TxType::Wrapper`](encoding.md#txtype) that has a [`WrapperTx`](encoding.md#wrappertx) inside it. + +The SHA-256 hash of this data [encoded with Borsh](encoding.html#borsh-binary-encoding) MUST be [signed](crypto.md#signatures) by an implicit account's key. The encoded signed data together with the signature should be encoded as a [`SignedTxData`](encoding.md#signedtxdata) and also encoded with Borsh. This data should then be attached to a protobuf encoded transaction's `data` field and the field `code` in this layer MUST be empty. Note that the outer layer's signature is not relevant to the inner layer of the transaction, only itself. + +The fields of a `WrapperTx` are: + +- `fee`: Fee to be payed by the source implicit account for including the tx in a block. +- `pk`: [Public key](crypto.md#public-keys) of the source implicit account. +- `epoch`: The [epoch](#epochs) in which the transaction is being included. This should be queried from a synchronized ledger node before the transaction is fully constructed. + + Note that this is currently not used and so the default value `0` may be used for now (depends on ). + +- `gas_limit`: Maximum amount of gas that can be used when executing the inner transaction +- `inner_tx`: The inner layer of the transaction. This MUST contain a [`Tx` type encoded with proto3](./encoding.md#transactions), encrypted against a public key that should be queried from a synchronized ledger node. + + The inner transaction's `Tx` MUST contain the WASM code to be executed and optionally any `data` (which will be provided to the transaction and any triggered validity predicates when they're invoked) to be executed and applied in a block (for example the [default transactions](ledger/default-transactions.md)). + + Please refer to the [signing of the default transactions](ledger/default-transactions.md#signing-transactions) to learn how to construct inner transaction's signatures which will be accepted by the [default validity predicates](ledger/default-validity-predicates.md). + + Note that currently the key doesn't change and so it stay constant for the duration of a chain and `::G1Affine::prime_subgroup_generator()` may be used to encrypt the inner transaction for now as done by the the [`WrapperTx::new` method](https://dev.anoma.net/master/rustdoc/anoma/types/transaction/wrapper/wrapper_tx/struct.WrapperTx.html#method.new) (depends on ). + +- `tx_hash`: A SHA-256 hash of the inner transaction. This MUST match the hash of decrypted `inner_tx`. + +TODO: wrapper transactions will include replay protection (this is because we can simply check a counter against the source (i.e. gas payer) of the transaction before the transactions order is committed to by the DKG protocol, which could affect the expected counter order for sources with multiple queued transactions) + +## The protocol + +When a tx is added to the [mempool](#mempool) and included in block by a block proposer, the [outer transaction is processed](#outer-transaction-processing) and if valid, its inner transaction is added to a transaction FIFO queue that MUST be in the same order as the outer transactions. + +An inner transaction popped from the queue is applied in a block executed in two main steps: + +1. [Inner transaction execution](#inner-transaction-execution) +1. [Validity predicates check](#validity-predicates-check) + +### Epochs + +An epoch is a range of blocks whose length is determined by the [epoch duration protocol parameter](#protocol-parameters): minimum epoch duration and minimum number of blocks in an epoch. They are identified by consecutive natural numbers starting at 0. The [Borsh encoded `Epoch`](encoding.md#epoch) for the last committed block can be queried via the [RPC](ledger/rpc.md#read-only-queries). + +### Protocol parameters + +The parameters are used to dynamically control certain variables in the protocol. They are implemented as an internal address with a native validity predicate. The current value of [Borsh encoded `Parameters`](encoding.md#parameters) is written into and read from the block storage in the parameters account's sub-space. + +Initial parameters for a chain are set in the genesis configuration. + +#### Epoch duration + +The parameters for [epoch](#epochs) duration are: + +- Minimum number of blocks in an epoch +- Minimum duration of an epoch + +### Mempool + +When a request to add a transaction to the mempool is received, it will only be added it's a [`Tx` encoded with proto3](./encoding.md#transactions). + +### Outer transaction processing + +TODO: describe outer tx fee check and deduction, inner tx decryption, tx queue up to the inner tx execution + +### Inner transaction execution + +For any error encountered in any of the following steps of transaction execution, the protocol MUST charge the gas used by the transaction and discard any storage changes that the transaction attempted to perform. + +1. Charge a base transaction [gas](#gas): + \\( \verb|BASE_TRANSACTION_FEE| \\) +1. Decode the transaction bytes and validate the data. The field `timestamp` is required. +1. Charge WASM compilation gas, proportional to the bytes `length` of the `code` field of the transaction (this is because the WASM code is compiled with a single-pass compiler): + \\( \verb|length| * \verb|COMPILE_GAS_PER_BYTE| \\) +1. [Validate the WASM code](#wasm-validation) from the `code` field of the transaction. +1. Inject a [gas counter](#gas) into the `code`. +1. Inject a [stack height](#stack-height-limiter) limiter into the `code`. +1. Compile the transaction `code` with a single-pass compiler (for example, [the Wasmer runtime single-pass compiler](https://medium.com/wasmer/a-webassembly-compiler-tale-9ef37aa3b537)). The compilation computational complexity MUST be linear in proportion to the `code` size. +1. Initialize the WASM linear memory with descriptor having the initial memory size equal to [`TX_MEMORY_INIT_PAGES`](#wasm-constants) and maximum memory size to [`TX_MEMORY_MAX_PAGES`](#wasm-constants). +1. Instantiate the WASM module with imported [transaction host environment functions](#transaction-host-environment-functions) and the instantiated WASM memory. +1. Write the transaction's `data` into the memory exported from the WASM module instance. +1. Attempt to call the module's entrypoint function. The entrypoint MUST have signature: + + ```wat + func (param i64 i64) + ``` + + The first argument is the offset to the `data` input written into the memory and the second argument is its bytes length. + +If the transaction executed successfully, it is followed [Validity predicates check](#validity-predicates-check). + +### Validity predicates check + +For the transaction to be valid, all the triggered validity predicates must accept it. + +First, the addresses whose validity predicates should be triggered by the transaction are determined: + +1. The addresses set by the transaction (see `insert_verifier` in [transaction host environment functions](#transaction-host-environment-functions)) are included in the verifiers set. +1. The storage keys that were modified by the transaction are inspected for addresses included in the storage key segments and these are also included in the verifiers set. Note that a storage key may contain more than one address, in which case all its addresses are included. This however excludes addresses of established accounts that were initialized in this transaction as they do not exist prior to transaction execution and a validity predicate will be associated with an initialized account only after the transaction is applied and accepted. This is intended as it allows users to initialize their account's storage without a validity predicate check. + +For all these addresses, attempt to read their validity predicate WASM code from the storage. For each validity predicate look-up, charge storage read gas and WASM compilation gas, proportional to the bytes length of the validity predicate. If any of the validity predicates look-ups fails, or any validity rejects the transaction or fails anywhere in the execution, the whole transaction is rejected. If the transaction is rejected, the protocol MUST charge the gas used by the transaction and discard any storage changes that the transaction attempted to perform. + +Execute all validity predicates in parallel as follows: + +1. Charge WASM compilation gas, proportional to the bytes length of the validity predicate (same as for the transaction, WASM code is compiled with a single-pass compiler). +1. Charge WASM compilation gas, proportional to the bytes `length` of the validity predicate (same as for the transaction, WASM code is compiled with a single-pass compiler): \\( \verb|length| * \verb|COMPILE_GAS_PER_BYTE| \\). +1. [Validate the WASM code](#wasm-validation) of the validity predicate. +1. Inject a [gas counter](#gas) into the `code`. +1. Inject a [stack height](#stack-height-limiter) limiter into the `code`. +1. Compile the validity predicate with single-pass compiler. The compilation computational complexity MUST be linear in proportion to its bytes size. +1. Initialize the WASM linear memory with descriptor having the initial memory size equal to [`VP_MEMORY_INIT_PAGES`](#wasm-constants) and maximum memory size to [`VP_MEMORY_MAX_PAGES`](#wasm-constants). +1. Instantiate the WASM module with imported [validity predicate host environment functions](#validity-predicate-host-environment-functions) and the instantiated WASM memory. +1. Write the address of the validity predicate’s owner, the transaction `data`, the modified storage keys encoded with Borsh, and all the triggered validity predicates owners' addresses encoded with Borsh into the memory exported from the WASM module instance. +1. Attempt to call the module's entrypoint function. The entrypoint MUST have signature: + + ```wat + func (param i64 i64 i64 i64 i64 i64 i64 i64) (result i64)) + ``` + + - The first argument is the offset to the owner’s address written into the memory, the second argument is its bytes length + - The third is the offset of the transaction’s `data` and fourth is it’s bytes length + - The fifth is the offset of the modified storage keys and sixth is its bytes length + - The seventh is the offset of the triggered validity predicates owners' addresses and eighth is its bytes length + +### Gas + +#### Gas constants + +The gas constants are currently chosen arbitrarily and are subject to change following gas accounting estimations. + +| Name | Value | +|------------------------|-------| +| `COMPILE_GAS_PER_BYTE` | 1 | +| `BASE_TRANSACTION_FEE` | 2 | +| `PARALLEL_GAS_DIVIDER` | 10 | +| `MIN_STORAGE_GAS` | 1 | + +- TODO describe gas accounting, wasm gas counter, limits, what happens if we go over limits and how gas relates to fees + +### WebAssembly (WASM) + +#### WASM constants + +| Name | Unit | Value | +|--------------------------------------|-------------------|-------| +| `PAGE` (as defined in the WASM spec) | kiB | 64 | +| `TX_MEMORY_INIT_PAGES` | number of `PAGE`s | 100 | +| `TX_MEMORY_MAX_PAGES` | number of `PAGE`s | 200 | +| `VP_MEMORY_INIT_PAGES` | number of `PAGE`s | 100 | +| `VP_MEMORY_MAX_PAGES` | number of `PAGE`s | 200 | +| `WASM_STACK_LIMIT` | stack depth | 65535 | + +The WASM instantiation, the types, instructions, validation and execution of WASM modules MUST conform to the [WebAssembly specification](https://webassembly.github.io/spec/core/intro/index.html). + +#### WASM validation + +The WebAssembly code is REQUIRED to only use deterministic instructions. Furthermore, it MUST NOT use features from any of the following WebAssembly proposals: + +- The reference types proposal +- The multi-value proposal +- The bulk memory operations proposal +- The module linking proposal +- The SIMD proposal +- The threads proposal +- The tail-call proposal +- The multi memory proposal +- The exception handling proposal +- The memory64 proposal + +#### Stack height limiter + +To make stack overflows deterministic, set the upper bound of the stack size to [`WASM_STACK_LIMIT`](#wasm-constants). If the stack height exceeds the limit then execution MUST abort. + + + +#### WASM memory + +- TODO memory read/write gas costs + +#### Transaction host environment functions + +The following functions from the host ledger are made available in transaction's WASM code. They MAY be imported in the WASM module as shown bellow and MUST be provided by the ledger's WASM runtime: + +```wat +(import "env" "gas" (func (param i32))) +(import "env" "anoma_tx_read" (func (param i64 i64) (result i64))) +(import "env" "anoma_tx_result_buffer" (func (param i64))) +(import "env" "anoma_tx_has_key" (func (param i64 i64) (result i64))) +(import "env" "anoma_tx_write" (func (param i64 i64 i64 i64))) +(import "env" "anoma_tx_delete" (func (param i64 i64))) +(import "env" "anoma_tx_iter_prefix" (func (param i64 i64) (result i64))) +(import "env" "anoma_tx_iter_next" (func (param i64) (result i64))) +(import "env" "anoma_tx_insert_verifier" (func (param i64 i64))) +(import "env" "anoma_tx_update_validity_predicate" (func (param i64 i64 i64 i64))) +(import "env" "anoma_tx_init_account" (func (param i64 i64 i64))) +(import "env" "anoma_tx_get_chain_id" (func (param i64))) +(import "env" "anoma_tx_get_block_height" (func (param ) (result i64))) +(import "env" "anoma_tx_get_block_hash" (func (param i64))) +(import "env" "anoma_tx_log_string" (func (param i64 i64))) +``` + +Additionally, the WASM module MUST export its memory as shown: + +```wat +(export "memory" (memory 0)) +``` + +- `anoma_tx_init_account` TODO newly created accounts' validity predicates aren't used until the block is committed (i.e. only the transaction that created the account may write into its storage in the block in which its being applied). +- TODO describe functions in detail + +#### Validity predicate host environment functions + +The following functions from the host ledger are made available in validity predicate's WASM code. They MAY be imported in the WASM module as shown bellow and MUST be provided by the ledger's WASM runtime. + +```wat +(import "env" "gas" (func (param i32))) +(import "env" "anoma_vp_read_pre" (func (param i64 i64) (result i64))) +(import "env" "anoma_vp_read_post" (func (param i64 i64) (result i64))) +(import "env" "anoma_vp_result_buffer" (func (param i64))) +(import "env" "anoma_vp_has_key_pre" (func (param i64 i64) (result i64))) +(import "env" "anoma_vp_has_key_post" (func (param i64 i64) (result i64))) +(import "env" "anoma_vp_iter_prefix" (func (param i64 i64) (result i64))) +(import "env" "anoma_vp_iter_pre_next" (func (param i64) (result i64))) +(import "env" "anoma_vp_iter_post_next" (func (param i64) (result i64))) +(import "env" "anoma_vp_get_chain_id" (func (param i64))) +(import "env" "anoma_vp_get_block_height" (func (param ) (result i64))) +(import "env" "anoma_vp_get_block_hash" (func (param i64))) +(import "env" "anoma_vp_verify_tx_signature" (func (param i64 i64 i64 i64) (result i64))) +(import "env" "anoma_vp_eval" (func (param i64 i64 i64 i64) (result i64))) +``` + +- TODO describe functions in detail + +Additionally, the WASM module MUST export its memory as shown: + +```wat +(export "memory" (memory 0)) +``` + +### Storage + +- TODO dynamic key-value storage paths, encoding agnostic, any ledger native keys such as the VP key +- TODO VPs must be written into the storage as raw bytes without any additional encoding + +#### Storage keys + +- TODO spec the key segments, punct, reserved VP segment `?` and address prefix `#` + +#### Dynamic storage sub-space + +Each account can have an associated dynamic account state in the storage. This +state may be comprised of keys with a format specified above and values of arbitrary user bytes. The first segment of all the keys must be the account's address. diff --git a/documentation/dev/src/specs/ledger/default-transactions.md b/documentation/dev/src/specs/ledger/default-transactions.md new file mode 100644 index 00000000000..78b04ff48d8 --- /dev/null +++ b/documentation/dev/src/specs/ledger/default-transactions.md @@ -0,0 +1,57 @@ +# Default transactions + +The Anoma client comes with a set of pre-built transactions. Note that the Anoma ledger is agnostic about the format of the transactions beyond the format described in [ledger transaction section](../ledger.md#transactions). + +The [default validity predicates](default-validity-predicates.md) can be used to initialize the network's genesis block. These expect the data in the storage to be encoded with [Borsh](../encoding.md#borsh-binary-encoding) and are fully compatible with the default transactions described below. + +## Rust-to-WASM transactions + +The following transactions are pre-built from Rust code and can be used by clients interacting with the Anoma ledger. + +The pre-built WASM code's raw bytes should be attached to the transaction's `code` field. The transactions expect certain variables to be provided via the transaction's `data` field encoded with [Borsh](../encoding.md#borsh-binary-encoding). + +### tx_init_account + +Initialize a new [established account](../../explore/design/ledger/accounts.md#established-transparent-addresses) on the chain. + +To use this transaction, attach [InitAccount](../encoding.md#initaccount) to the `data`. + +### tx_init_validator + +Initialize a new validator account on the chain. + +Attach [InitValidator](../encoding.md#initvalidator) to the `data`. + +### tx_transfer + +Transparently transfer `amount` of fungible `token` from the `source` to the `target`. + +Attach [Transfer](../encoding.md#transfer) to the `data`. + +### tx_update_vp + +Update a validity predicate of an established account. + +Attach [UpdateVp](../encoding.md#updatevp) to the `data`. + +### tx_bond + +Self-bond `amount` of XAN token from `validator` (without `source`) or delegate to `validator` from `source`. + +Attach [Bond](../encoding.md#bond) to the `data`. + +### tx_unbond + +Unbond self-bonded `amount` of XAN token from the `validator` (without `source`) or unbond delegation from the `source` to the `validator`. + +Attach [Bond](../encoding.md#bond) to the `data`. + +### tx_withdraw + +Withdraw unbonded self-bond from the `validator` (without `source`) or withdraw unbonded delegation from the `source` to the `validator`. + +Attach [Withdraw](../encoding.md#withdraw) to the `data`. + +## Signing transactions + +To sign transactions in format that is understood and thus can be verified by the [default validity predicates](default-validity-predicates.md), the SHA-256 hash of the `data` [encoded with Borsh](../encoding.html#borsh-binary-encoding) MUST be [signed](../crypto.md#signatures) by an implicit or established account's key. The encoded signed data together with the signature should be encoded as a [`SignedTxData`](../encoding.md#signedtxdata) and also encoded with Borsh. This data should then be attached to a protobuf encoded transaction's `data` field. diff --git a/documentation/dev/src/specs/ledger/default-validity-predicates.md b/documentation/dev/src/specs/ledger/default-validity-predicates.md new file mode 100644 index 00000000000..894a5df4932 --- /dev/null +++ b/documentation/dev/src/specs/ledger/default-validity-predicates.md @@ -0,0 +1,7 @@ +# Default validity predicates + +The Anoma ledger and client comes with a set of pre-built validity predicates. + +## Rust-to-WASM validity predicates + +TODO diff --git a/documentation/dev/src/specs/ledger/rpc.md b/documentation/dev/src/specs/ledger/rpc.md new file mode 100644 index 00000000000..c536967670e --- /dev/null +++ b/documentation/dev/src/specs/ledger/rpc.md @@ -0,0 +1,45 @@ +# RPC + +The ledger provides an RPC interface for submitting transactions to the mempool, subscribing to their results and queries about the state of the ledger and its storage. + +The RPC interface is provided as [specified](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc) from Tendermint and most of the requests are routed to the Anoma ledger via ABCI. + +## Transactions + +A [transaction](../ledger.md#transactions) can be submitted to the [mempool](../ledger.md#mempool) via Tendermint's [`BroadCastTxSync`](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc#broadcasttxsync) or [`BroadCastTxAsync`](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc#broadcasttxasync). The `CheckTx` result of these requests is success only if the transaction passes [mempool validation rules](../ledger.md#mempool). In case of `BroadCastTxAsync`, the `DeliverTx` is not indicative of the transaction's result, it's merely a result of the transaction being added to the [transaction queue](../ledger.md#outer-transaction-processing). The actual result of the outer transaction and the inner transaction can be found from via the [ABCI events](https://github.com/tendermint/spec/blob/4566f1e3028278c5b3eca27b53254a48771b152b/spec/abci/abci.md#events). + +To find a result of the inner transaction, query for event with `type` equal to `"NewBlock"` and key equal to `"applied.hash"`, where the `value` of the found `Event` will contain `TxResult` pretty-printed as a string (TODO proper encoding depends on ). + +## Read-only queries + +Read-only queries can be requested via [ABCIQuery](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc#abciquery). The `path` for the query can be one of the following options: + +- `epoch`: Get the [epoch](../ledger.md#epochs) of the last committed block. The response `value` is always known [Borsh encoded `Epoch`](../encoding.md#epoch) +- `dry_run_tx`: Simulate a transaction being applied in a block. The response `code = 0` means that the transaction would be accepted by all the validity predicates that verified its validity. On success, the response `info` contains the `TxResult` pretty-printed as a string (TODO proper encoding depends on ). +- `value/{dynamic}`: Look-up a raw [storage](../ledger.md#storage) value for the given `dynamic` key. When the response `code = 0`, the key is found and the response `value` contains the raw bytes of the value. +- `prefix/{dynamic}`: Iterate a [storage](../ledger.md#storage) key prefix for the given `dynamic` key. When the response `code = 0`, the key is found and the response `value` contains [Borsh encoded `Vec`](../encoding.md#prefixvalue), where each `PrefixValue` contains the `key` and the raw bytes of the `value`. +- `has_key/{dynamic}`: check if the given `dynamic` key is present in the [storage](../ledger.md#storage). The response `value` contains [Borsh encoded](../encoding.md#borsh-binary-encoding) boolean that is `true` if the key has been found. + +For example, to find if an established address exists on-chain, we can submit a query to find if it has a validity predicate at path `has_key/#{established_address}/?`, which is the only storage value required for established addresses (note that `#` is a special storage key segment prefix for bech32m encoded addresses and `?` character is used as the last segment of a validity predicate storage key). + +## PoS + +TODO document response types encoding after + +The Proof-of-Stake queries are built on top of the [read-only queries](#read-only-queries), where all the PoS data are stored under the [internal `PoS` address](../encoding.html#internaladdress), which is governed by its native validity predicate. The bech32m encoded address of the PoS account currently is `"atest1v9hx7w362pex7mmxyphkvgznw3skkefqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqq8ylv7"`, in the storage keys below in place of `PoS`. + +Note that in the query paths below (and in all storage keys in general), addresses are encoded with bech32m and prefixed with `#` character. + +- `#{PoS}/bond/#{validator}/#{validator}`: validator self-bonds, where `validator` is its bech32m encoded address +- `#{PoS}/bond/#{owner}/#{validator}`: delegation bonds, where `owner` is the delegation source and `validator` the delegation target +- `#{PoS}/unbond/#{validator}/#{validator}`: unbonded validator self-bonds, where `validator` is its bech32m encoded address +- `#{PoS}/unbond/#{owner}/#{validator}`: unbonded delegation bonds, where `owner` is the delegation source and `validator` the delegation target +- `#{PoS}/validator/#{validator}/voting_power`: `validator`'s voting power +- `#{PoS}/slash/#{validator}`: slashes applied to the `validator`, if any + +## Default validity predicate storage queries + +The [default validity predicate](default-validity-predicates.md) for the implicit accounts and token accounts enforce a format for the account's storage. This storage can be queried at the following paths: + +- public key +- token balance diff --git a/documentation/dev/src/specs/overview.md b/documentation/dev/src/specs/overview.md new file mode 100644 index 00000000000..2b9c7e15c73 --- /dev/null +++ b/documentation/dev/src/specs/overview.md @@ -0,0 +1,19 @@ +# Overview + +At a high level, Anoma is composed of two main components: the distributed ledger and the intent gossip / matchmaking system. While they are designed to complement each other, they can be operated separately. + +## The ledger + +The [ledger](ledger.md) is a distributed state machine, relying on functionality provided by [Tendermint](https://docs.tendermint.com/master/spec/) such as its BFT consensus algorithm with instant finality, P2P networking capabilities, transaction mempool and more. The ledger state machine is built on top the [ABCI](https://docs.tendermint.com/master/spec/abci/). + +For block validator voting power assignment, the ledger employs a proof-of-stake system. + +The ledger's key-value storage is organized into blocks and user specific state is organized into accounts. The state machine executes transactions, which can apply arbitrary changes to the state that are validated by validity predicates associated with the accounts involved in the transaction. + +To prevent transaction front-running, the ledger employs a DKG scheme as implemented in [Ferveo](https://github.com/anoma/ferveo). Using this scheme, transactions are encrypted before being submitted to the ledger. The encrypted transactions are committed by a block proposer to a specific order in which they must be executed once decrypted. + +- TODO add fractal scaling & protocol upgrade system overview + +## The intent gossip with matchmaking system + +- TODO add an overview diff --git a/documentation/dev/theme/favicon.png b/documentation/dev/theme/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..f34029b97164e824eaf0dd6d37db05e11c5e7a8c GIT binary patch literal 5037 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 DRwPG| literal 0 HcmV?d00001 diff --git a/documentation/dev/theme/favicon.svg b/documentation/dev/theme/favicon.svg new file mode 100644 index 00000000000..d3343977ed1 --- /dev/null +++ b/documentation/dev/theme/favicon.svg @@ -0,0 +1,6 @@ + + + + + + From 5a3868a924873686d9d22ef8072b57560983069f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 2 Aug 2022 18:19:32 +0200 Subject: [PATCH 189/394] docs/dev: remove intent_gossip and prototypes pages --- documentation/dev/src/SUMMARY.md | 11 - .../dev/src/explore/design/gossip.md | 17 - .../explore/design/gossip_process.excalidraw | 966 -------- .../dev/src/explore/design/gossip_process.svg | 16 - .../design/intent_gossip/example.excalidraw | 2202 ----------------- .../explore/design/intent_gossip/example.svg | 16 - .../design/intent_gossip/fungible_token.md | 35 - .../intent_gossip/gossip_process.excalidraw | 966 -------- .../design/intent_gossip/gossip_process.svg | 16 - .../explore/design/intent_gossip/incentive.md | 9 - .../explore/design/intent_gossip/intent.md | 28 - .../design/intent_gossip/intent_gossip.md | 51 - .../intent_life_cycle.excalidraw | 1686 ------------- .../intent_gossip/intent_life_cycle.svg | 16 - .../design/intent_gossip/matchmaker.md | 80 - .../intent_gossip/matchmaker_graph.excalidraw | 1648 ------------ .../design/intent_gossip/matchmaker_graph.png | Bin 156552 -> 0 bytes .../design/intent_gossip/matchmaker_graph.svg | 16 - .../matchmaker_process.excalidraw | 1883 -------------- .../intent_gossip/matchmaker_process.svg | 16 - .../src/explore/design/intent_gossip/topic.md | 11 - .../dev/src/explore/prototypes/README.md | 23 - .../dev/src/explore/prototypes/base-ledger.md | 112 - .../src/explore/prototypes/gossip-layer.md | 61 - 24 files changed, 9885 deletions(-) delete mode 100644 documentation/dev/src/explore/design/gossip.md delete mode 100644 documentation/dev/src/explore/design/gossip_process.excalidraw delete mode 100644 documentation/dev/src/explore/design/gossip_process.svg delete mode 100644 documentation/dev/src/explore/design/intent_gossip/example.excalidraw delete mode 100644 documentation/dev/src/explore/design/intent_gossip/example.svg delete mode 100644 documentation/dev/src/explore/design/intent_gossip/fungible_token.md delete mode 100644 documentation/dev/src/explore/design/intent_gossip/gossip_process.excalidraw delete mode 100644 documentation/dev/src/explore/design/intent_gossip/gossip_process.svg delete mode 100644 documentation/dev/src/explore/design/intent_gossip/incentive.md delete mode 100644 documentation/dev/src/explore/design/intent_gossip/intent.md delete mode 100644 documentation/dev/src/explore/design/intent_gossip/intent_gossip.md delete mode 100644 documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.excalidraw delete mode 100644 documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.svg delete mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker.md delete mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.excalidraw delete mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.png delete mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.svg delete mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_process.excalidraw delete mode 100644 documentation/dev/src/explore/design/intent_gossip/matchmaker_process.svg delete mode 100644 documentation/dev/src/explore/design/intent_gossip/topic.md delete mode 100644 documentation/dev/src/explore/prototypes/README.md delete mode 100644 documentation/dev/src/explore/prototypes/base-ledger.md delete mode 100644 documentation/dev/src/explore/prototypes/gossip-layer.md diff --git a/documentation/dev/src/SUMMARY.md b/documentation/dev/src/SUMMARY.md index f0d84c6afa0..fee1b6fb27c 100644 --- a/documentation/dev/src/SUMMARY.md +++ b/documentation/dev/src/SUMMARY.md @@ -4,14 +4,6 @@ - [Exploration](./explore/README.md) - [Design](./explore/design/README.md) - [Overview](./explore/design/overview.md) - - [Gossip network](./explore/design/gossip.md) - - [Intent gossip](./explore/design/intent_gossip/intent_gossip.md) - - [Intent](./explore/design/intent_gossip/intent.md) - - [Topic](./explore/design/intent_gossip/topic.md) - - [Incentive](./explore/design/intent_gossip/incentive.md) - - [Matchmaker](./explore/design/intent_gossip/matchmaker.md) - - [Fungible token](./explore/design/intent_gossip/fungible_token.md) - - [Distributed key generation gossip](./explore/design/dkg.md) - [The ledger](./explore/design/ledger.md) - [Parameters](./explore/design/ledger/parameters.md) - [Epochs](./explore/design/ledger/epochs.md) @@ -30,9 +22,6 @@ - [Proof of Stake system](./explore/design/pos.md) - [Testnet setup](./explore/design/testnet-setup.md) - [Testnet launch procedure](./explore/design/testnet-launch-procedure/README.md) - - [Prototypes](./explore/prototypes/README.md) - - [Base ledger](./explore/prototypes/base-ledger.md) - - [Gossip layer](./explore/prototypes/gossip-layer.md) - [Libraries & Tools](./explore/libraries/README.md) - [Cryptography]() - [network](./explore/libraries/network.md) diff --git a/documentation/dev/src/explore/design/gossip.md b/documentation/dev/src/explore/design/gossip.md deleted file mode 100644 index 4879363d330..00000000000 --- a/documentation/dev/src/explore/design/gossip.md +++ /dev/null @@ -1,17 +0,0 @@ -# The gossip network - -The gossip network runs in parallel to the ledger network and is used to -propagate off-chain information. The network is based on -[libp2p](https://libp2p.io/) , a peer to peer network system that is implemented -in different languages, has a large user base and an active development. It -allows us to readily implement a network to run our application. - -The gossip network is used to propagate messages of two different applications, -intents for the [intent gossip system](intent_gossip/intent_gossip.md), and message for distributed keys -generation application. - -## Flow diagram: High level overview - -![gossip process](./gossip_process.svg "gossip process") - -[Diagram on Excalidraw](https://excalidraw.com/#room=5d4a2a84ef52cf5f5f96,r4ghl40frJ9putMy-0vyOQ) diff --git a/documentation/dev/src/explore/design/gossip_process.excalidraw b/documentation/dev/src/explore/design/gossip_process.excalidraw deleted file mode 100644 index 36842526939..00000000000 --- a/documentation/dev/src/explore/design/gossip_process.excalidraw +++ /dev/null @@ -1,966 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "ellipse", - "version": 603, - "versionNonce": 717195654, - "isDeleted": false, - "id": "9XNyo7y8QCSEp4yAFf5oy", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1156.111111111111, - "y": 164.3333333333334, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 573.6666666666667, - "height": 372.55555555555554, - "seed": 821968881, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "arrow", - "version": 1450, - "versionNonce": 941173851, - "isDeleted": false, - "id": "lXNINuf2v3Bas7FefE3LH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1586.2054115693877, - "y": -370.2728566628903, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 246.73688630955917, - "height": 192.92404363452567, - "seed": 736805503, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "5xDzwVV0gDBlD-WL85LYy", - "focus": 0.18717224637187657, - "gap": 6.459472910604248 - }, - "endBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "focus": -0.23250849205562685, - "gap": 13.411672225924079 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -246.73688630955917, - 192.92404363452567 - ] - ] - }, - { - "type": "diamond", - "version": 278, - "versionNonce": 198810363, - "isDeleted": false, - "id": "5xDzwVV0gDBlD-WL85LYy", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1565.6666666666665, - "y": -444.66666666666663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 127, - "height": 100, - "seed": 563016927, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "lXNINuf2v3Bas7FefE3LH", - "RR6GEDZFWUssCdTAnrDqo" - ] - }, - { - "type": "text", - "version": 253, - "versionNonce": 1818093109, - "isDeleted": false, - "id": "HskYIBtFFYajWD-2Djn1f", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1605.1666666666665, - "y": -409.66666666666663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 50, - "height": 26, - "seed": 1643469585, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "lXNINuf2v3Bas7FefE3LH" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "client", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "middle" - }, - { - "type": "rectangle", - "version": 432, - "versionNonce": 232368667, - "isDeleted": false, - "id": "5cPSOu9KDeCIp3Nmd546m", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1176, - "y": -287.2222222222222, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 570.0000000000001, - "height": 356.2222222222222, - "seed": 1306655743, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "diamond", - "version": 627, - "versionNonce": 1842306310, - "isDeleted": false, - "id": "xxAsLeElpbNGHmw4QPxMz", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1181.3333333333333, - "y": -207.55555555555554, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 209, - "height": 188, - "seed": 1389553521, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "B4iQSfzoi419GYzKlXw5m", - "MPfW6JEUQYoWrzHeI_NxD", - "z6DMUeY7qrwuosEZr9D8K", - "GJPwvquPv4Afa-iz06Txv", - "bleUDuct9nWGxJfhyl_J2", - "V6oy4uJc_J45aC2sNpHvE", - "lXNINuf2v3Bas7FefE3LH", - "OIBiMf6VpPETwJAGERKti", - "J8EUyGyYHRSW895zskWbp" - ] - }, - { - "type": "diamond", - "version": 604, - "versionNonce": 1476920774, - "isDeleted": false, - "id": "dSBMH7Bqm1h_eoLM_yqkZ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1184.4444444444446, - "y": 264.99999999999994, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 307.888888888889, - "height": 215.66666666666666, - "seed": 1838896927, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "OIBiMf6VpPETwJAGERKti", - "MPfW6JEUQYoWrzHeI_NxD", - "FqT97u9kn8yPY-e9ZEmwX", - "3w6C3S5gFIBLSOSnHrL9p", - "J8EUyGyYHRSW895zskWbp" - ] - }, - { - "type": "text", - "version": 446, - "versionNonce": 2067771125, - "isDeleted": false, - "id": "FOQ3kxQ9jn8r8iZ6-5Kdn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1227.3333333333333, - "y": -149.55555555555551, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 121, - "height": 52, - "seed": 1118918783, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent \nbroadcaster", - "baseline": 44, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 346, - "versionNonce": 94289307, - "isDeleted": false, - "id": "wNwxFCvE4WMQV8QLZiqxu", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1272, - "y": 281.99999999999994, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 131, - "height": 78, - "seed": 197177745, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "OIBiMf6VpPETwJAGERKti", - "MPfW6JEUQYoWrzHeI_NxD", - "J8EUyGyYHRSW895zskWbp" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "intent\nbroadcaster \nnetwork", - "baseline": 70, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1548, - "versionNonce": 1059405510, - "isDeleted": false, - "id": "OIBiMf6VpPETwJAGERKti", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1340.8991464307837, - "y": 256.43457590166764, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 19.424658574333307, - "height": 274.53680436823555, - "seed": 413185503, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", - "focus": 0.06980420648639936, - "gap": 8.455707710143173 - }, - "endBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "focus": -0.27643465526659067, - "gap": 24.916176360598527 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -19.424658574333307, - -274.53680436823555 - ] - ] - }, - { - "type": "arrow", - "version": 1783, - "versionNonce": 702656213, - "isDeleted": false, - "id": "MPfW6JEUQYoWrzHeI_NxD", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1225.7890453334985, - "y": -41.97976066319893, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 21.266468152933612, - "height": 381.0875685373966, - "seed": 1670492081, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "gap": 23.483962590379445, - "focus": 0.5312629502131418 - }, - "endBinding": { - "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", - "gap": 16.103707014847412, - "focus": -0.8818009263489358 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -21.266468152933612, - 381.0875685373966 - ] - ] - }, - { - "type": "text", - "version": 540, - "versionNonce": 1475775174, - "isDeleted": false, - "id": "InLHuKJiMfsKjnhDLER2S", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1224.6666666666667, - "y": 122.99999999999997, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 52, - "seed": 1075843217, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "validate \nmsg", - "baseline": 44, - "textAlign": "right", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1384, - "versionNonce": 280535450, - "isDeleted": false, - "id": "J8EUyGyYHRSW895zskWbp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1386.7368978905408, - "y": 286.51414709947557, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 19.54258711706757, - "height": 343.3666092756827, - "seed": 1500547647, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", - "focus": 0.34597438474774744, - "gap": 10.117038200934843 - }, - "endBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "focus": -0.7476913039884736, - "gap": 26.682463765500543 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -19.54258711706757, - -343.3666092756827 - ] - ] - }, - { - "type": "text", - "version": 452, - "versionNonce": 1029595482, - "isDeleted": false, - "id": "3a6V_ozlu_-z813t_fjbn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1391, - "y": 187.77777777777777, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 156, - "height": 36, - "seed": 15072497, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "libP2P logic", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "diamond", - "version": 365, - "versionNonce": 757518677, - "isDeleted": false, - "id": "hngD3gT8MxLbMtULxwILm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1204.6111111111113, - "y": -434.0000000000001, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 127, - "height": 100, - "seed": 662285233, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "lXNINuf2v3Bas7FefE3LH", - "V6oy4uJc_J45aC2sNpHvE" - ] - }, - { - "type": "text", - "version": 334, - "versionNonce": 979982971, - "isDeleted": false, - "id": "Tne4ggu_ZDCdoqKD1HVLY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1244.1111111111113, - "y": -402.0000000000001, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 50, - "height": 26, - "seed": 1728898015, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "lXNINuf2v3Bas7FefE3LH" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "client", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "middle" - }, - { - "type": "arrow", - "version": 1682, - "versionNonce": 1212570555, - "isDeleted": false, - "id": "V6oy4uJc_J45aC2sNpHvE", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1262.664151563201, - "y": -330.8400855354932, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 2.5819871518015134, - "height": 140.4581124311759, - "seed": 1528357951, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "hngD3gT8MxLbMtULxwILm", - "focus": 0.10105347446704833, - "gap": 5.8523740982304915 - }, - "endBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "focus": -0.18349209857314347, - "gap": 1 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 2.5819871518015134, - 140.4581124311759 - ] - ] - }, - { - "type": "text", - "version": 377, - "versionNonce": 1391350555, - "isDeleted": false, - "id": "q9mdbiPTZ5bMiHS1pP8Jd", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1267.8333333333333, - "y": -327.77777777777777, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 26, - "seed": 280036991, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "send msg", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 138, - "versionNonce": 2055493659, - "isDeleted": false, - "id": "JdaaIi8F9pCIbQiuefyHi", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1388.75, - "y": -206.72222222222223, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 180, - "height": 35, - "seed": 476397371, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 3, - "text": "Gossip Node", - "baseline": 28, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 419, - "versionNonce": 1035911669, - "isDeleted": false, - "id": "E1bibepZLZFj3VmWghmhk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1557.1388888888887, - "y": -324.94444444444554, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 26, - "seed": 1482472437, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "send msg", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "diamond", - "version": 629, - "versionNonce": 251582106, - "isDeleted": false, - "id": "JaiST4fQ2FlodcbDsHGRd", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1525.611111111111, - "y": -178.99999999999997, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 209, - "height": 188, - "seed": 1161636571, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "B4iQSfzoi419GYzKlXw5m", - "MPfW6JEUQYoWrzHeI_NxD", - "z6DMUeY7qrwuosEZr9D8K", - "GJPwvquPv4Afa-iz06Txv", - "bleUDuct9nWGxJfhyl_J2", - "RR6GEDZFWUssCdTAnrDqo", - "yZ1sDbO9UxZw6Yw7d8BGk", - "sofyhfQ6yAERl8W7igqud", - "98jHSy6KgIgI-kigPhATL", - "Jj-F4u_NSjuW-tVWK6ErH" - ] - }, - { - "type": "text", - "version": 477, - "versionNonce": 320811829, - "isDeleted": false, - "id": "yE8obB-cq-eO9tpUS_PcA", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1575.611111111111, - "y": -117.99999999999994, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 121, - "height": 52, - "seed": 413385813, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "DKG \nbroadcaster", - "baseline": 44, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 115, - "versionNonce": 808640859, - "isDeleted": false, - "id": "RR6GEDZFWUssCdTAnrDqo", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1660.0572555330295, - "y": -366.9575940330065, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 24.665187381169744, - "height": 183.91639038865497, - "seed": 45437883, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "5xDzwVV0gDBlD-WL85LYy", - "focus": -0.5447225569886008, - "gap": 1.5967808146620044 - }, - "endBinding": { - "elementId": "JaiST4fQ2FlodcbDsHGRd", - "focus": -0.07528645180716763, - "gap": 6.5362537154315845 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -24.665187381169744, - 183.91639038865497 - ] - ] - }, - { - "type": "diamond", - "version": 596, - "versionNonce": 1248600538, - "isDeleted": false, - "id": "sJFOeRzApKjnkoavWYSOm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1415.6666666666667, - "y": 270.6666666666667, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 292.8888888888888, - "height": 207.6666666666666, - "seed": 3834485, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "OIBiMf6VpPETwJAGERKti", - "MPfW6JEUQYoWrzHeI_NxD", - "FqT97u9kn8yPY-e9ZEmwX", - "3w6C3S5gFIBLSOSnHrL9p", - "J8EUyGyYHRSW895zskWbp", - "yZ1sDbO9UxZw6Yw7d8BGk", - "sofyhfQ6yAERl8W7igqud", - "98jHSy6KgIgI-kigPhATL", - "Jj-F4u_NSjuW-tVWK6ErH" - ] - }, - { - "type": "text", - "version": 354, - "versionNonce": 339994458, - "isDeleted": false, - "id": "ERwB6OfVxtsDLuZO1G4dn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1507.2222222222224, - "y": 292.6666666666667, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 131, - "height": 78, - "seed": 410831707, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "OIBiMf6VpPETwJAGERKti", - "MPfW6JEUQYoWrzHeI_NxD", - "yZ1sDbO9UxZw6Yw7d8BGk" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "DKG\nbroadcaster \nnetwork", - "baseline": 70, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 432, - "versionNonce": 855557126, - "isDeleted": false, - "id": "yZ1sDbO9UxZw6Yw7d8BGk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1613.2645903010791, - "y": 5.6816480400971585, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 32.37650849922488, - "height": 267.8739593512054, - "seed": 978571771, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "JaiST4fQ2FlodcbDsHGRd", - "focus": 0.056328322376417134, - "gap": 8.799318498256582 - }, - "endBinding": { - "elementId": "ERwB6OfVxtsDLuZO1G4dn", - "focus": 0.016268660508214034, - "gap": 19.11105927536414 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -32.37650849922488, - 267.8739593512054 - ] - ] - }, - { - "type": "arrow", - "version": 398, - "versionNonce": 2067988678, - "isDeleted": false, - "id": "Jj-F4u_NSjuW-tVWK6ErH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1623.0089473868802, - "y": 304.406818117378, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 33.75764899556157, - "height": 307.2241654227815, - "seed": 398843163, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "sJFOeRzApKjnkoavWYSOm", - "focus": 0.3632505836483008, - "gap": 7.699251414567215 - }, - "endBinding": { - "elementId": "JaiST4fQ2FlodcbDsHGRd", - "focus": -0.3414897328591685, - "gap": 9.04048516465393 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 33.75764899556157, - -307.2241654227815 - ] - ] - }, - { - "type": "text", - "version": 554, - "versionNonce": 720882453, - "isDeleted": false, - "id": "kXVNso4ItXnAMFArYHbYk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1486.1111111111115, - "y": 109.99999999999997, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 52, - "seed": 1662723835, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "validate \nmsg", - "baseline": 44, - "textAlign": "right", - "verticalAlign": "top" - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - } -} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/gossip_process.svg b/documentation/dev/src/explore/design/gossip_process.svg deleted file mode 100644 index 32a046a1ac9..00000000000 --- a/documentation/dev/src/explore/design/gossip_process.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - clientIntent broadcasterintentbroadcaster networkvalidate msglibP2P logicclientsend msgGossip Nodesend msgDKG broadcasterDKGbroadcaster networkvalidate msg \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/example.excalidraw b/documentation/dev/src/explore/design/intent_gossip/example.excalidraw deleted file mode 100644 index 876d8a453bf..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/example.excalidraw +++ /dev/null @@ -1,2202 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "text", - "version": 686, - "versionNonce": 1681340216, - "isDeleted": false, - "id": "8-d43siu84Jr4CoHQ4O3h", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 87.4761904761906, - "y": 120.73809523809535, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 188, - "height": 120, - "seed": 454219117, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 3, - "text": "- 400 < X < 1000 USD\n- rate USD to EUR: \n >= 1.5 EUR/USD\n- rate USD to CZK:\n >= 10 USD/CZK\n- in less than 20 mn", - "baseline": 116, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 196, - "versionNonce": 124057672, - "isDeleted": false, - "id": "pMCzM8kGspun9xkTr6k5d", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 61.95238095238085, - "y": 83.64285714285725, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 94, - "height": 25, - "seed": 1950434275, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "- DATA: ", - "baseline": 20, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 295, - "versionNonce": 1203474488, - "isDeleted": false, - "id": "2p7QI8-lBoSz4ic4BK3eT", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 44.61904761904748, - "y": 43.0714285714287, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 284.3809523809525, - "height": 245.90476190476187, - "seed": 1582276557, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "oYCojvs3_Pz8ZGLg2mw3V", - "954BBr80PqUI9uJuWnFsM", - "uKTSmBiHZULyjH127gePI", - "Czhwb2CjT6al5-6rCgR97" - ] - }, - { - "type": "text", - "version": 156, - "versionNonce": 1336848200, - "isDeleted": false, - "id": "h9Y3DYl2lfuXh9QZLoKSa", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 63.39285714285711, - "y": 255.0476190476184, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 152, - "height": 25, - "seed": 668258179, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "- timestamp T", - "baseline": 20, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 448, - "versionNonce": 1263323448, - "isDeleted": false, - "id": "UiaZM4EJyss7ILeYlY9LS", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 75.14285714285711, - "y": 118.35714285714295, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 247.28571428571422, - "height": 129.47619047619048, - "seed": 138681901, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "oYCojvs3_Pz8ZGLg2mw3V", - "bMCs-OrsKdLPqnA1PVwiv", - "954BBr80PqUI9uJuWnFsM" - ] - }, - { - "type": "text", - "version": 255, - "versionNonce": 1940489800, - "isDeleted": false, - "id": "MBhNEXU9Gx54BPICMfGTq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 66.14285714285711, - "y": 47.3333333333334, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 217, - "height": 52, - "seed": 607512355, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "oYCojvs3_Pz8ZGLg2mw3V", - "954BBr80PqUI9uJuWnFsM" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "intent from account A\n", - "baseline": 44, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1706, - "versionNonce": 441377336, - "isDeleted": false, - "id": "HBiEEEb8ounDRLLN9wXKf", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 797.2148759236247, - "y": 300.28571428571433, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 82.58839012004557, - "height": 92.74093504094964, - "seed": 1181817966, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "UXXI91T9S5l9KHt7Wp0ZV", - "gap": 1, - "focus": -0.6432089438320445 - }, - "endBinding": { - "elementId": "Vva_ZJK0PzeOvHwKtR1hx", - "gap": 4.782874482859853, - "focus": 0.22336105149411092 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -82.58839012004557, - 92.74093504094964 - ] - ] - }, - { - "type": "arrow", - "version": 1822, - "versionNonce": 540114248, - "isDeleted": false, - "id": "oYCojvs3_Pz8ZGLg2mw3V", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 492.80297633357, - "y": 292.13629393845633, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 30.182646679681625, - "height": 92.45738907898789, - "seed": 1847628014, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "hf7oNcONr_HxLhhGRiJqE", - "gap": 10.279151081313444, - "focus": 0.38338854643484865 - }, - "endBinding": { - "elementId": "Vva_ZJK0PzeOvHwKtR1hx", - "gap": 13.215840792079547, - "focus": -0.011602561064372598 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 30.182646679681625, - 92.45738907898789 - ] - ] - }, - { - "type": "rectangle", - "version": 315, - "versionNonce": 1403378488, - "isDeleted": false, - "id": "UXXI91T9S5l9KHt7Wp0ZV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -2.309523809529537, - "y": -69.04761904761892, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1041.3809523809525, - "height": 368.33333333333326, - "seed": 1810420256, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "oYCojvs3_Pz8ZGLg2mw3V", - "HBiEEEb8ounDRLLN9wXKf" - ] - }, - { - "type": "arrow", - "version": 941, - "versionNonce": 111318088, - "isDeleted": false, - "id": "Swcv4ZXJejaz5TYfHeDMu", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 876.138834695055, - "y": -424.07142857142946, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 9.184895705945564, - "height": 459.29069763915254, - "seed": 936098418, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "usikQRBWwYKM-qybe0fQH", - "gap": 2, - "focus": -0.255388727353868 - }, - "endBinding": { - "elementId": "swYlyjktS4gIwsiQLSPjZ", - "gap": 9.066445217991259, - "focus": 0.15984810321549953 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -9.184895705945564, - 459.29069763915254 - ] - ] - }, - { - "type": "rectangle", - "version": 310, - "versionNonce": 431338552, - "isDeleted": false, - "id": "usikQRBWwYKM-qybe0fQH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 774.0119047619048, - "y": -530.0714285714295, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 164, - "height": 104, - "seed": 354374066, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Swcv4ZXJejaz5TYfHeDMu", - "ol2AB4uLuMEYj138srSeF" - ] - }, - { - "type": "rectangle", - "version": 312, - "versionNonce": 1289276232, - "isDeleted": false, - "id": "swYlyjktS4gIwsiQLSPjZ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 700, - "y": 44.285714285714334, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 282.8571428571426, - "height": 236, - "seed": 345326578, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "HBiEEEb8ounDRLLN9wXKf", - "Swcv4ZXJejaz5TYfHeDMu" - ] - }, - { - "type": "text", - "version": 268, - "versionNonce": 576875832, - "isDeleted": false, - "id": "wXWmmm8mpOqmxFuLw0x-V", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 798.0119047619048, - "y": -481.07142857142946, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 101, - "height": 26, - "seed": 992734510, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "account C", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 171, - "versionNonce": 2100306504, - "isDeleted": false, - "id": "XDW5IfPRFhlKsj3a1RbYD", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 553.8333333333278, - "y": 327.61904761904776, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 127, - "height": 26, - "seed": 1564124704, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "fetch intents", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1247, - "versionNonce": 1192232504, - "isDeleted": false, - "id": "954BBr80PqUI9uJuWnFsM", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 522.3067180843871, - "y": -423.5118681617779, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 5.583297287089863, - "height": 443.7930802552394, - "seed": 467951874, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "bDFsD9RIYPQQrHIWUfc_E", - "focus": 0.21113172608583025, - "gap": 6.8690842191745105 - }, - "endBinding": { - "elementId": "hf7oNcONr_HxLhhGRiJqE", - "focus": 0.002175851518750341, - "gap": 15.671168858919486 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -5.583297287089863, - 443.7930802552394 - ] - ] - }, - { - "type": "text", - "version": 399, - "versionNonce": 1988401480, - "isDeleted": false, - "id": "dYXMYP8IeZ5hPB8hDXUkx", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 488.66666666666674, - "y": -491.04761904761915, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 99, - "height": 26, - "seed": 1024864898, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "account B", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 167, - "versionNonce": 1546793032, - "isDeleted": false, - "id": "Z7BWkND-NitKxr9Ih4Ie2", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 389.80952380952374, - "y": 76.52380952380958, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 94, - "height": 25, - "seed": 469053826, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "- DATA: ", - "baseline": 20, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 268, - "versionNonce": 1534974008, - "isDeleted": false, - "id": "hf7oNcONr_HxLhhGRiJqE", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 372.47619047619037, - "y": 35.95238095238102, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 284.3809523809525, - "height": 245.90476190476187, - "seed": 25601394, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "oYCojvs3_Pz8ZGLg2mw3V", - "954BBr80PqUI9uJuWnFsM" - ] - }, - { - "type": "text", - "version": 459, - "versionNonce": 102033224, - "isDeleted": false, - "id": "oRS3euY4VEuk2SgUKpr1L", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 718.5714285714287, - "y": 138.8095238095238, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 188, - "height": 80, - "seed": 655184434, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 3, - "text": "- 0 < X < 0.05 BTC \n- rate BTC to EUR: \n >= 0.00001 BTC/USD\n- in less than 1 mn", - "baseline": 76, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 182, - "versionNonce": 1065750840, - "isDeleted": false, - "id": "XrVgeTerY-7DS-ka0p0Em", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 716.5, - "y": 242.59523809523824, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 176, - "height": 25, - "seed": 1477693777, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "- timestamp T''", - "baseline": 20, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 128, - "versionNonce": 1888009800, - "isDeleted": false, - "id": "d8WVAhFQ2zo-3HuEkCg6h", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 391.25, - "y": 247.9285714285707, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 164, - "height": 25, - "seed": 204593966, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "- timestamp T'", - "baseline": 20, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 426, - "versionNonce": 1210001976, - "isDeleted": false, - "id": "Nkvl_WfjngjOTgW9Zbs2_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 390, - "y": 111.2380952380953, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 247.28571428571422, - "height": 129.47619047619048, - "seed": 1239365918, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "oYCojvs3_Pz8ZGLg2mw3V", - "bMCs-OrsKdLPqnA1PVwiv", - "954BBr80PqUI9uJuWnFsM" - ] - }, - { - "type": "rectangle", - "version": 337, - "versionNonce": 745125192, - "isDeleted": false, - "id": "LxE-VPfS6ezu0mmzjr8F1", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 712.8571428571429, - "y": 130.2380952380953, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 237.2857142857145, - "height": 105.47619047619037, - "seed": 423857246, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "text", - "version": 530, - "versionNonce": 640619320, - "isDeleted": false, - "id": "Hk5l9iiBugHCCPMbYtU63", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 12.624999999999886, - "y": 1537.291666666666, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 410, - "height": 250, - "seed": 212882431, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "if someone takes between\ntx.DATA.A.low to tx.DATA.A.high \ntx.DATA.A.output_asset \nfrom my account,\nit has to credit\nat least tx.DATA.A.min_rate \nthe amount in tx.DATA.A.input_asset\n and to be before\n (tx.DATA.A.timestamp\n + tx.DATA.A.TTL)", - "baseline": 245, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 645, - "versionNonce": 1326697544, - "isDeleted": false, - "id": "EygNVoDyqnn-JbFybEbGe", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 287.5238095238093, - "y": 660.2380952380953, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 281, - "height": 350, - "seed": 2012467634, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "- STATE FUNCTION : \n - 1/ src: A\n dest: C\n amount: 466.6 USD\n - 2/ src: C\n dest: B\n amount: 800 EUR\n - 3/ src: B \n dest: A\n amount: 0.017 BTC\n- DATA\n - 1/ intent A\n - 2/ intent B\n - 3/ intent C", - "baseline": 345, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 378, - "versionNonce": 1618836792, - "isDeleted": false, - "id": "0XL0H0k8apgsdhnJ1t5ab", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": -3.9166666666667425, - "y": 1448.4999999999995, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 447.08333333333337, - "height": 377.9166666666665, - "seed": 124501362, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Zd243WE5eHC_WR8epiEAk", - "bMcnnlJo1LpwISJChgmKB", - "69EnjeqAfSHRFsprrsmiO", - "5qf0tF76bMQXA21Z0jkWa", - "cnhSr0ikLZ2glzJteomgZ", - "Jo906mQj2gE8MfU639rAz", - "ubXW8sd3hkMXVly_O_ZOm", - "8R2CEj2oEGuvX1dqJ8gCY" - ] - }, - { - "type": "arrow", - "version": 858, - "versionNonce": 2126699080, - "isDeleted": false, - "id": "Jo906mQj2gE8MfU639rAz", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 468.4392508864622, - "y": 1386.951149425287, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 197.84511112482477, - "height": 37.33444576972829, - "seed": 1746017906, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "VvkwdNT_ReB6o1cn6jyZr", - "focus": -0.3762898352769947, - "gap": 2.034482758620584 - }, - "endBinding": { - "elementId": "0XL0H0k8apgsdhnJ1t5ab", - "focus": -0.8806478681490097, - "gap": 24.21440480498427 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -197.84511112482477, - 37.33444576972829 - ] - ] - }, - { - "type": "text", - "version": 241, - "versionNonce": 1174694472, - "isDeleted": false, - "id": "rDu3FdPNopgW6z9PCoHab", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 480.66666666666674, - "y": 1342.1666666666663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 96, - "height": 26, - "seed": 1492486702, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "check VPs", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 357, - "versionNonce": 2070927672, - "isDeleted": false, - "id": "TsHuKC9fQpBlBOzMikQ_z", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 305.83333333333326, - "y": 1874.75, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 442.4999999999999, - "height": 382.91666666666646, - "seed": 1984398894, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "y1ij4QN5do9jfy8-88ASN", - "MmzmsyscAAK-oBSzk0-T_", - "tjHofHebcS4-4X76vqi0M" - ] - }, - { - "type": "arrow", - "version": 1601, - "versionNonce": 108809784, - "isDeleted": false, - "id": "GN1g4nPawR3Tizbo7OUxq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 438.53788770476547, - "y": 517.4917358318999, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1.8992073358202788, - "height": 90.2497092278901, - "seed": 471491378, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": null, - "endBinding": { - "elementId": "VEHFhWJtyqbM7CEJvy1OY", - "gap": 6.496650178305457, - "focus": -0.2974579993705736 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -1.8992073358202788, - 90.2497092278901 - ] - ] - }, - { - "type": "rectangle", - "version": 371, - "versionNonce": 1915086408, - "isDeleted": false, - "id": "VEHFhWJtyqbM7CEJvy1OY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 268, - "y": 606.2380952380953, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 471.19047619047615, - "height": 412.00000000000006, - "seed": 465481138, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "GN1g4nPawR3Tizbo7OUxq", - "9m0lD6nkNLEEuDZhOAFaq" - ] - }, - { - "type": "rectangle", - "version": 541, - "versionNonce": 793102136, - "isDeleted": false, - "id": "Vva_ZJK0PzeOvHwKtR1hx", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 193.7142857142859, - "y": 397.8095238095238, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 721.2857142857143, - "height": 138.52380952380955, - "seed": 926229486, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "oYCojvs3_Pz8ZGLg2mw3V", - "HBiEEEb8ounDRLLN9wXKf", - "GN1g4nPawR3Tizbo7OUxq", - "Czhwb2CjT6al5-6rCgR97" - ] - }, - { - "type": "text", - "version": 295, - "versionNonce": 642213960, - "isDeleted": false, - "id": "1nPK7swUFkn2ZECAVz_c2", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 466.71428571428567, - "y": 399.7142857142857, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 201, - "height": 46, - "seed": 985887602, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "GN1g4nPawR3Tizbo7OUxq", - "oYCojvs3_Pz8ZGLg2mw3V", - "HBiEEEb8ounDRLLN9wXKf" - ], - "fontSize": 36, - "fontFamily": 1, - "text": "matchmaker", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 227, - "versionNonce": 1383604280, - "isDeleted": false, - "id": "-G2iBkHpCw0y563oTofms", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 394, - "y": 40.21428571428572, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 218, - "height": 52, - "seed": 1376390450, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "oYCojvs3_Pz8ZGLg2mw3V", - "954BBr80PqUI9uJuWnFsM" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "intent from account B\n", - "baseline": 44, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 161, - "versionNonce": 1220747080, - "isDeleted": false, - "id": "HA5fUOkkjum0HgfnMlIBG", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 733, - "y": 60.95238095238096, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 220, - "height": 26, - "seed": 1992833394, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "intent from account C", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 153, - "versionNonce": 1878481208, - "isDeleted": false, - "id": "nBttw1DAeptrX2lVgvsY7", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 720.7142857142857, - "y": 95.95238095238096, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 82, - "height": 25, - "seed": 1251151454, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "- DATA:", - "baseline": 20, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 896, - "versionNonce": 407405112, - "isDeleted": false, - "id": "9m0lD6nkNLEEuDZhOAFaq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 508.568121346975, - "y": 1021.1321908520348, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 3.1429313226068984, - "height": 312.2365395554883, - "seed": 1155072110, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "VEHFhWJtyqbM7CEJvy1OY", - "focus": -0.01207639830586925, - "gap": 2.894095613939328 - }, - "endBinding": { - "elementId": "VvkwdNT_ReB6o1cn6jyZr", - "focus": -0.14754250955099754, - "gap": 2.381269592476542 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 3.1429313226068984, - 312.2365395554883 - ] - ] - }, - { - "type": "text", - "version": 299, - "versionNonce": 580155464, - "isDeleted": false, - "id": "hiykXkLOlmLVWGgrQKtH8", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 532.9999999999999, - "y": 1144.9999999999998, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 265, - "height": 26, - "seed": 2146825074, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "send transaction to ledger", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 212, - "versionNonce": 1497632824, - "isDeleted": false, - "id": "7Ng3PQpN63cbVPHR8aHrn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 46.66666666666663, - "y": 1475.7499999999995, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 51, - "height": 26, - "seed": 983757230, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "A VP:", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 271, - "versionNonce": 1492004680, - "isDeleted": false, - "id": "AuP1LD1e9BIzr9fnDJADl", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 324.58333333333326, - "y": 1904.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 52, - "height": 26, - "seed": 1592796398, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "B VP:", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 309, - "versionNonce": 1344396616, - "isDeleted": false, - "id": "VvkwdNT_ReB6o1cn6jyZr", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 433.16666666666674, - "y": 1335.7499999999998, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 185, - "height": 49.166666666666735, - "seed": 1653182898, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9m0lD6nkNLEEuDZhOAFaq", - "Jo906mQj2gE8MfU639rAz", - "tjHofHebcS4-4X76vqi0M", - "o8vZWSYePDHs1bN3foTMP" - ] - }, - { - "type": "arrow", - "version": 981, - "versionNonce": 846653512, - "isDeleted": false, - "id": "tjHofHebcS4-4X76vqi0M", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 513.8848064154693, - "y": 1386.951149425287, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 2.1447670720162932, - "height": 467.54798651113333, - "seed": 572567090, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "VvkwdNT_ReB6o1cn6jyZr", - "focus": 0.12853479300213336, - "gap": 2.034482758620584 - }, - "endBinding": { - "elementId": "TsHuKC9fQpBlBOzMikQ_z", - "focus": -0.045390860814372345, - "gap": 20.250864063579456 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 2.1447670720162932, - 467.54798651113333 - ] - ] - }, - { - "type": "text", - "version": 349, - "versionNonce": 540359496, - "isDeleted": false, - "id": "0L6p1qM6u_tVfVgCK_FDY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 333.14285714285734, - "y": 623.5238095238096, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 307, - "height": 24, - "seed": 2092627950, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 2, - "text": "TRANSACTION from matchmaker ", - "baseline": 17, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 643, - "versionNonce": 356842808, - "isDeleted": false, - "id": "PcxdOS9NrJhMOgUW0faBx", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -165.8333333333394, - "y": -331.6666666666668, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1361.333333333333, - "height": 1437.3809523809525, - "seed": 2083480096, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "text", - "version": 218, - "versionNonce": 192844360, - "isDeleted": false, - "id": "A6kNrvnIcbKPUZ5GodYoX", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 353.2142857142801, - "y": -291.6666666666665, - "strokeColor": "#364fc7", - "backgroundColor": "transparent", - "width": 364, - "height": 92, - "seed": 389688288, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 36, - "fontFamily": 1, - "text": "Intent broadcaster \nnetwork", - "baseline": 78, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 158, - "versionNonce": 1595971128, - "isDeleted": false, - "id": "nVSCWf4TYONMAlK6r7v7u", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 607.1666666666612, - "y": -49.71428571428561, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 193, - "height": 36, - "seed": 1611512800, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "global mempool", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 635, - "versionNonce": 293931336, - "isDeleted": false, - "id": "HVqr4I6oVLiB-7Smz4WBL", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 264.6904761904701, - "y": 450.04761904761926, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 584, - "height": 78, - "seed": 644801056, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "GN1g4nPawR3Tizbo7OUxq" - ], - "fontSize": 19.896103896103885, - "fontFamily": 1, - "text": "The matchmacker chooses values for the transaction that\n he thinks will be accepted by the ledger (the validity\npredicate of each account).", - "baseline": 70, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 452, - "versionNonce": 2052606792, - "isDeleted": false, - "id": "0ilp60kgFqvYZ-R-LlZ_8", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -241.6666666666722, - "y": 1238.6666666666663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1547.3333333333326, - "height": 1465.0000000000002, - "seed": 312942560, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "text", - "version": 369, - "versionNonce": 781905208, - "isDeleted": false, - "id": "gwJeBcdO_DYmasftBcHif", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 537.8333333333277, - "y": 1261.1666666666665, - "strokeColor": "#364fc7", - "backgroundColor": "transparent", - "width": 258, - "height": 46, - "seed": 721258016, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 36, - "fontFamily": 1, - "text": "ledger network", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 683, - "versionNonce": 1321181768, - "isDeleted": false, - "id": "jU8TxFoAubCE7Tws_-GtR", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 321.8333333333276, - "y": 1954.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 410, - "height": 250, - "seed": 1682625504, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "if someone takes between\ntx.DATA.B.amount \ntx.DATA.B.output_asset \nfrom my account,\nit has to credit\nat least tx.DATA.B.min_rate \nthe amount in tx.DATA.B.input_asset\n and to be before\n (tx.DATA.B.timestamp \n + tx.DATA.C.TTL)", - "baseline": 245, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1227, - "versionNonce": 385840712, - "isDeleted": false, - "id": "MmzmsyscAAK-oBSzk0-T_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 537.0941616654786, - "y": 2275.9127064518443, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 0.8399583165244167, - "height": 211.42062688148872, - "seed": 331186144, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "TsHuKC9fQpBlBOzMikQ_z", - "focus": -0.041338956392506655, - "gap": 18.246039785178027 - }, - "endBinding": { - "elementId": "OLiV9Yr0v-YQNJbhTqcuZ", - "focus": 0.664082386729967, - "gap": 1 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 0.8399583165244167, - 211.42062688148872 - ] - ] - }, - { - "type": "text", - "version": 379, - "versionNonce": 884140088, - "isDeleted": false, - "id": "OLiV9Yr0v-YQNJbhTqcuZ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 508.83333333332746, - "y": 2488.333333333333, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 35, - "height": 25, - "seed": 258550752, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "cnhSr0ikLZ2glzJteomgZ", - "MmzmsyscAAK-oBSzk0-T_", - "r3K74Ft73zYbYmn37KM_a", - "8R2CEj2oEGuvX1dqJ8gCY", - "xfJTcfqB9FHSZl9nnr-SE" - ], - "fontSize": 20, - "fontFamily": 3, - "text": "...", - "baseline": 20, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 172, - "versionNonce": 1265089080, - "isDeleted": false, - "id": "bnfip1RTsFRJNftw4HckK", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 470.8809523809465, - "y": 574.2857142857144, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 175, - "height": 26, - "seed": 111981536, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "craft transaction", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 370, - "versionNonce": 1269966152, - "isDeleted": false, - "id": "bDFsD9RIYPQQrHIWUfc_E", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 458.49999999999466, - "y": -534.3809523809524, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 164, - "height": 104, - "seed": 1802037792, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Swcv4ZXJejaz5TYfHeDMu", - "ol2AB4uLuMEYj138srSeF", - "954BBr80PqUI9uJuWnFsM" - ] - }, - { - "type": "text", - "version": 336, - "versionNonce": 467907384, - "isDeleted": false, - "id": "xzml5UDbVyEJKPO3if06i", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 816.5273034941135, - "y": -372.08003798021025, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 43, - "height": 26, - "seed": 806763701, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "send", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 385, - "versionNonce": 1271807048, - "isDeleted": false, - "id": "X0SNHo6dQnl8irYcF3Ubf", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 529.5273034941135, - "y": -385.08003798021025, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 43, - "height": 26, - "seed": 207870843, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "send", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 448, - "versionNonce": 1282968632, - "isDeleted": false, - "id": "TVJpZzxoPMIinWFy_aK1i", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 212.9761904761957, - "y": -498.9599810098948, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 98, - "height": 52, - "seed": 505781699, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "account A\n", - "baseline": 44, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 415, - "versionNonce": 1045394248, - "isDeleted": false, - "id": "E7FV6AEnvxE7fX_r3_TzW", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 182.80952380952374, - "y": -542.2933143432281, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 164, - "height": 104, - "seed": 1994281965, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Swcv4ZXJejaz5TYfHeDMu", - "ol2AB4uLuMEYj138srSeF", - "954BBr80PqUI9uJuWnFsM", - "uKTSmBiHZULyjH127gePI" - ] - }, - { - "type": "text", - "version": 429, - "versionNonce": 654984504, - "isDeleted": false, - "id": "l3RrMlLKlU9laipGh-WIb", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 253.8368273036425, - "y": -392.9923999424859, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 43, - "height": 26, - "seed": 735243619, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "send", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 247, - "versionNonce": 1502726728, - "isDeleted": false, - "id": "uKTSmBiHZULyjH127gePI", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 254.78140471240346, - "y": -433.642857142857, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 18.07520040340512, - "height": 446.8986715332853, - "seed": 1473743181, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "E7FV6AEnvxE7fX_r3_TzW", - "focus": 0.14647529307932466, - "gap": 4.650457200371079 - }, - "endBinding": { - "elementId": "2p7QI8-lBoSz4ic4BK3eT", - "focus": 0.6266906023232607, - "gap": 29.815614181000413 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 18.07520040340512, - 446.8986715332853 - ] - ] - }, - { - "type": "arrow", - "version": 214, - "versionNonce": 419225144, - "isDeleted": false, - "id": "Czhwb2CjT6al5-6rCgR97", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 251.88178381509215, - "y": 291.02380952380963, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 52.803167627033986, - "height": 104.42857142857144, - "seed": 1533710979, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "2p7QI8-lBoSz4ic4BK3eT", - "gap": 2.047619047619051, - "focus": 0.017984625714246562 - }, - "endBinding": { - "elementId": "Vva_ZJK0PzeOvHwKtR1hx", - "gap": 2.3571428571426907, - "focus": -0.5394946080216643 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 52.803167627033986, - 104.42857142857144 - ] - ] - }, - { - "type": "text", - "version": 439, - "versionNonce": 1127503672, - "isDeleted": false, - "id": "mgtimTjmkfFageE4a_ALC", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 401.95238095238096, - "y": 117.14285714285734, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 188, - "height": 80, - "seed": 1741274855, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 3, - "text": "- 800 EUR\n- rate EUR to BTC: \n >= 47058 EUR/BTC\n- in less than 10 mn", - "baseline": 76, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 349, - "versionNonce": 859410232, - "isDeleted": false, - "id": "n8qBdtjdYuoZicswHAM8Z", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 605.3690476190475, - "y": 1444.017857142857, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 442.4999999999999, - "height": 382.91666666666646, - "seed": 1102897255, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "tjHofHebcS4-4X76vqi0M", - "y1ij4QN5do9jfy8-88ASN", - "MmzmsyscAAK-oBSzk0-T_", - "o8vZWSYePDHs1bN3foTMP", - "r3K74Ft73zYbYmn37KM_a", - "xfJTcfqB9FHSZl9nnr-SE" - ] - }, - { - "type": "text", - "version": 268, - "versionNonce": 554150456, - "isDeleted": false, - "id": "kEAF_yIT_4cO4uX63467_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": 624.1190476190475, - "y": 1473.767857142857, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 54, - "height": 26, - "seed": 1833762185, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "C VP:", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 691, - "versionNonce": 340342088, - "isDeleted": false, - "id": "LgtU9c20p3iojrteT7TOy", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 621.3690476190416, - "y": 1523.767857142857, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 410, - "height": 250, - "seed": 405945223, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 3, - "text": "if someone takes between\ntx.DATA.C.low to tx.DATA.C. high \ntx.DATA.C.output_asset \nfrom my account,\nit has to credit\nat least tx.DATA.C.min_rate \nthe amount in tx.DATA.C.input_asset\n and to be before\n (tx.DATA.C.timestamp\n + tx.DATA.C.TTL)", - "baseline": 245, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 341, - "versionNonce": 1631355976, - "isDeleted": false, - "id": "o8vZWSYePDHs1bN3foTMP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 622.3113514729953, - "y": 1387.3092905570172, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 131.8668164720766, - "height": 50.16689991917315, - "seed": 1895758345, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "VvkwdNT_ReB6o1cn6jyZr", - "focus": -0.16380407789917548, - "gap": 4.1446848063285415 - }, - "endBinding": { - "elementId": "n8qBdtjdYuoZicswHAM8Z", - "focus": 0.6183687597387931, - "gap": 6.5416666666667425 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 131.8668164720766, - 50.16689991917315 - ] - ] - }, - { - "id": "8R2CEj2oEGuvX1dqJ8gCY", - "type": "arrow", - "x": 166.28016609367018, - "y": 1833.6428571428573, - "width": 333.2772811594371, - "height": 669.3349011418622, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "strokeSharpness": "round", - "seed": 973478472, - "version": 390, - "versionNonce": 235803208, - "isDeleted": false, - "boundElementIds": null, - "points": [ - [ - 0, - 0 - ], - [ - 61.005548192043705, - 588.333333333333 - ], - [ - 333.2772811594371, - 669.3349011418622 - ] - ], - "lastCommittedPoint": null, - "startBinding": { - "elementId": "0XL0H0k8apgsdhnJ1t5ab", - "focus": 0.3030726950488866, - "gap": 7.226190476191277 - }, - "endBinding": { - "elementId": "OLiV9Yr0v-YQNJbhTqcuZ", - "focus": -0.5710015327583038, - "gap": 9.275886080220175 - }, - "startArrowhead": null, - "endArrowhead": "arrow" - }, - { - "type": "arrow", - "version": 540, - "versionNonce": 1649680456, - "isDeleted": false, - "id": "xfJTcfqB9FHSZl9nnr-SE", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 875.4506366524696, - "y": 1845.3095238095234, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 316.498255700089, - "height": 653.299399447023, - "seed": 253988936, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "n8qBdtjdYuoZicswHAM8Z", - "focus": -0.2859002709985485, - "gap": 18.374999999999773 - }, - "endBinding": { - "elementId": "OLiV9Yr0v-YQNJbhTqcuZ", - "focus": 0.4400244271235751, - "gap": 15.119047619053163 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -53.16492236675572, - 571.6666666666665 - ], - [ - -316.498255700089, - 653.299399447023 - ] - ] - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - } -} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/example.svg b/documentation/dev/src/explore/design/intent_gossip/example.svg deleted file mode 100644 index 9a5a48a38c5..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/example.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - 400 < X < 1000 USD- rate USD to EUR: >= 1.5 EUR/USD- rate USD to CZK: >= 10 USD/CZK- in less than 20 mn- DATA: - timestamp Tintent from account Aaccount Cfetch intentsaccount B- DATA: - 0 < X < 0.05 BTC - rate BTC to EUR: >= 0.00001 BTC/USD- in less than 1 mn- timestamp T''- timestamp T'if someone takes betweentx.DATA.A.low to tx.DATA.A.high tx.DATA.A.output_asset from my account,it has to creditat least tx.DATA.A.min_rate the amount in tx.DATA.A.input_asset and to be before (tx.DATA.A.timestamp + tx.DATA.A.TTL)- STATE FUNCTION : - 1/ src: A dest: C amount: 466.6 USD - 2/ src: C dest: B amount: 800 EUR - 3/ src: B dest: A amount: 0.017 BTC- DATA - 1/ intent A - 2/ intent B - 3/ intent Ccheck VPsmatchmakerintent from account Bintent from account C- DATA:send transaction to ledgerA VP:B VP:TRANSACTION from matchmaker Intent broadcaster networkglobal mempoolThe matchmacker chooses values for the transaction that he thinks will be accepted by the ledger (the validitypredicate of each account).ledger networkif someone takes betweentx.DATA.B.amount tx.DATA.B.output_asset from my account,it has to creditat least tx.DATA.B.min_rate the amount in tx.DATA.B.input_asset and to be before (tx.DATA.B.timestamp + tx.DATA.C.TTL)...craft transactionsendsendaccount Asend- 800 EUR- rate EUR to BTC: >= 47058 EUR/BTC- in less than 10 mnC VP:if someone takes betweentx.DATA.C.low to tx.DATA.C. high tx.DATA.C.output_asset from my account,it has to creditat least tx.DATA.C.min_rate the amount in tx.DATA.C.input_asset and to be before (tx.DATA.C.timestamp + tx.DATA.C.TTL) \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/fungible_token.md b/documentation/dev/src/explore/design/intent_gossip/fungible_token.md deleted file mode 100644 index 43a34c5c94f..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/fungible_token.md +++ /dev/null @@ -1,35 +0,0 @@ -# Fungible token encoding and template - -The Heliax team implemented an intent encoding, a filter program template, and a -matchmaker program template that can be used to exchange fungible tokens between -any number of participants. - -## Intent encoding -The intent encoding allows the expression of a desire to participate in an asset -exchange. The encoding is defined as follows : - -```protobuf -message FungibleToken { - string address = 1; - string token_sell = 2; - int64 max_sell = 3; - int64 rate_min = 4; - string token_buy = 5; - int64 min_buy = 6; - google.protobuf.Timestamp expire = 7; -} -``` - -## Matchmaker program - -The filter program attempts to decode the intent and if successful, checks -that it's not yet expired and that the account address has enough funds for the -intended token to be sold. - -The main program can match intents for exchanging assets. It does that by -creating a graph from all intents. When a cycle is found then it removes all -intents from that cycle of the mempool and crafts a transaction based on all the -removed intents. - -![matchmaker](matchmaker_graph.svg) -[excalidraw link](https://excalidraw.com/#room=1db86ba6d5f0ccb7447c,2vvRd4X2Y3HDWHihJmy9zw) diff --git a/documentation/dev/src/explore/design/intent_gossip/gossip_process.excalidraw b/documentation/dev/src/explore/design/intent_gossip/gossip_process.excalidraw deleted file mode 100644 index 36842526939..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/gossip_process.excalidraw +++ /dev/null @@ -1,966 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "ellipse", - "version": 603, - "versionNonce": 717195654, - "isDeleted": false, - "id": "9XNyo7y8QCSEp4yAFf5oy", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1156.111111111111, - "y": 164.3333333333334, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 573.6666666666667, - "height": 372.55555555555554, - "seed": 821968881, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "arrow", - "version": 1450, - "versionNonce": 941173851, - "isDeleted": false, - "id": "lXNINuf2v3Bas7FefE3LH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1586.2054115693877, - "y": -370.2728566628903, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 246.73688630955917, - "height": 192.92404363452567, - "seed": 736805503, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "5xDzwVV0gDBlD-WL85LYy", - "focus": 0.18717224637187657, - "gap": 6.459472910604248 - }, - "endBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "focus": -0.23250849205562685, - "gap": 13.411672225924079 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -246.73688630955917, - 192.92404363452567 - ] - ] - }, - { - "type": "diamond", - "version": 278, - "versionNonce": 198810363, - "isDeleted": false, - "id": "5xDzwVV0gDBlD-WL85LYy", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1565.6666666666665, - "y": -444.66666666666663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 127, - "height": 100, - "seed": 563016927, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "lXNINuf2v3Bas7FefE3LH", - "RR6GEDZFWUssCdTAnrDqo" - ] - }, - { - "type": "text", - "version": 253, - "versionNonce": 1818093109, - "isDeleted": false, - "id": "HskYIBtFFYajWD-2Djn1f", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1605.1666666666665, - "y": -409.66666666666663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 50, - "height": 26, - "seed": 1643469585, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "lXNINuf2v3Bas7FefE3LH" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "client", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "middle" - }, - { - "type": "rectangle", - "version": 432, - "versionNonce": 232368667, - "isDeleted": false, - "id": "5cPSOu9KDeCIp3Nmd546m", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1176, - "y": -287.2222222222222, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 570.0000000000001, - "height": 356.2222222222222, - "seed": 1306655743, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "diamond", - "version": 627, - "versionNonce": 1842306310, - "isDeleted": false, - "id": "xxAsLeElpbNGHmw4QPxMz", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1181.3333333333333, - "y": -207.55555555555554, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 209, - "height": 188, - "seed": 1389553521, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "B4iQSfzoi419GYzKlXw5m", - "MPfW6JEUQYoWrzHeI_NxD", - "z6DMUeY7qrwuosEZr9D8K", - "GJPwvquPv4Afa-iz06Txv", - "bleUDuct9nWGxJfhyl_J2", - "V6oy4uJc_J45aC2sNpHvE", - "lXNINuf2v3Bas7FefE3LH", - "OIBiMf6VpPETwJAGERKti", - "J8EUyGyYHRSW895zskWbp" - ] - }, - { - "type": "diamond", - "version": 604, - "versionNonce": 1476920774, - "isDeleted": false, - "id": "dSBMH7Bqm1h_eoLM_yqkZ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1184.4444444444446, - "y": 264.99999999999994, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 307.888888888889, - "height": 215.66666666666666, - "seed": 1838896927, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "OIBiMf6VpPETwJAGERKti", - "MPfW6JEUQYoWrzHeI_NxD", - "FqT97u9kn8yPY-e9ZEmwX", - "3w6C3S5gFIBLSOSnHrL9p", - "J8EUyGyYHRSW895zskWbp" - ] - }, - { - "type": "text", - "version": 446, - "versionNonce": 2067771125, - "isDeleted": false, - "id": "FOQ3kxQ9jn8r8iZ6-5Kdn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1227.3333333333333, - "y": -149.55555555555551, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 121, - "height": 52, - "seed": 1118918783, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent \nbroadcaster", - "baseline": 44, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 346, - "versionNonce": 94289307, - "isDeleted": false, - "id": "wNwxFCvE4WMQV8QLZiqxu", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1272, - "y": 281.99999999999994, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 131, - "height": 78, - "seed": 197177745, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "OIBiMf6VpPETwJAGERKti", - "MPfW6JEUQYoWrzHeI_NxD", - "J8EUyGyYHRSW895zskWbp" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "intent\nbroadcaster \nnetwork", - "baseline": 70, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1548, - "versionNonce": 1059405510, - "isDeleted": false, - "id": "OIBiMf6VpPETwJAGERKti", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1340.8991464307837, - "y": 256.43457590166764, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 19.424658574333307, - "height": 274.53680436823555, - "seed": 413185503, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", - "focus": 0.06980420648639936, - "gap": 8.455707710143173 - }, - "endBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "focus": -0.27643465526659067, - "gap": 24.916176360598527 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -19.424658574333307, - -274.53680436823555 - ] - ] - }, - { - "type": "arrow", - "version": 1783, - "versionNonce": 702656213, - "isDeleted": false, - "id": "MPfW6JEUQYoWrzHeI_NxD", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1225.7890453334985, - "y": -41.97976066319893, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 21.266468152933612, - "height": 381.0875685373966, - "seed": 1670492081, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "gap": 23.483962590379445, - "focus": 0.5312629502131418 - }, - "endBinding": { - "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", - "gap": 16.103707014847412, - "focus": -0.8818009263489358 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -21.266468152933612, - 381.0875685373966 - ] - ] - }, - { - "type": "text", - "version": 540, - "versionNonce": 1475775174, - "isDeleted": false, - "id": "InLHuKJiMfsKjnhDLER2S", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1224.6666666666667, - "y": 122.99999999999997, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 52, - "seed": 1075843217, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "validate \nmsg", - "baseline": 44, - "textAlign": "right", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1384, - "versionNonce": 280535450, - "isDeleted": false, - "id": "J8EUyGyYHRSW895zskWbp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1386.7368978905408, - "y": 286.51414709947557, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 19.54258711706757, - "height": 343.3666092756827, - "seed": 1500547647, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "dSBMH7Bqm1h_eoLM_yqkZ", - "focus": 0.34597438474774744, - "gap": 10.117038200934843 - }, - "endBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "focus": -0.7476913039884736, - "gap": 26.682463765500543 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -19.54258711706757, - -343.3666092756827 - ] - ] - }, - { - "type": "text", - "version": 452, - "versionNonce": 1029595482, - "isDeleted": false, - "id": "3a6V_ozlu_-z813t_fjbn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1391, - "y": 187.77777777777777, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 156, - "height": 36, - "seed": 15072497, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "libP2P logic", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "diamond", - "version": 365, - "versionNonce": 757518677, - "isDeleted": false, - "id": "hngD3gT8MxLbMtULxwILm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1204.6111111111113, - "y": -434.0000000000001, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 127, - "height": 100, - "seed": 662285233, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "lXNINuf2v3Bas7FefE3LH", - "V6oy4uJc_J45aC2sNpHvE" - ] - }, - { - "type": "text", - "version": 334, - "versionNonce": 979982971, - "isDeleted": false, - "id": "Tne4ggu_ZDCdoqKD1HVLY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1244.1111111111113, - "y": -402.0000000000001, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 50, - "height": 26, - "seed": 1728898015, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "lXNINuf2v3Bas7FefE3LH" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "client", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "middle" - }, - { - "type": "arrow", - "version": 1682, - "versionNonce": 1212570555, - "isDeleted": false, - "id": "V6oy4uJc_J45aC2sNpHvE", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1262.664151563201, - "y": -330.8400855354932, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 2.5819871518015134, - "height": 140.4581124311759, - "seed": 1528357951, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "hngD3gT8MxLbMtULxwILm", - "focus": 0.10105347446704833, - "gap": 5.8523740982304915 - }, - "endBinding": { - "elementId": "xxAsLeElpbNGHmw4QPxMz", - "focus": -0.18349209857314347, - "gap": 1 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 2.5819871518015134, - 140.4581124311759 - ] - ] - }, - { - "type": "text", - "version": 377, - "versionNonce": 1391350555, - "isDeleted": false, - "id": "q9mdbiPTZ5bMiHS1pP8Jd", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1267.8333333333333, - "y": -327.77777777777777, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 26, - "seed": 280036991, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "send msg", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 138, - "versionNonce": 2055493659, - "isDeleted": false, - "id": "JdaaIi8F9pCIbQiuefyHi", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1388.75, - "y": -206.72222222222223, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 180, - "height": 35, - "seed": 476397371, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 3, - "text": "Gossip Node", - "baseline": 28, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 419, - "versionNonce": 1035911669, - "isDeleted": false, - "id": "E1bibepZLZFj3VmWghmhk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1557.1388888888887, - "y": -324.94444444444554, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 26, - "seed": 1482472437, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "send msg", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "diamond", - "version": 629, - "versionNonce": 251582106, - "isDeleted": false, - "id": "JaiST4fQ2FlodcbDsHGRd", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1525.611111111111, - "y": -178.99999999999997, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 209, - "height": 188, - "seed": 1161636571, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "B4iQSfzoi419GYzKlXw5m", - "MPfW6JEUQYoWrzHeI_NxD", - "z6DMUeY7qrwuosEZr9D8K", - "GJPwvquPv4Afa-iz06Txv", - "bleUDuct9nWGxJfhyl_J2", - "RR6GEDZFWUssCdTAnrDqo", - "yZ1sDbO9UxZw6Yw7d8BGk", - "sofyhfQ6yAERl8W7igqud", - "98jHSy6KgIgI-kigPhATL", - "Jj-F4u_NSjuW-tVWK6ErH" - ] - }, - { - "type": "text", - "version": 477, - "versionNonce": 320811829, - "isDeleted": false, - "id": "yE8obB-cq-eO9tpUS_PcA", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1575.611111111111, - "y": -117.99999999999994, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 121, - "height": 52, - "seed": 413385813, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "DKG \nbroadcaster", - "baseline": 44, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 115, - "versionNonce": 808640859, - "isDeleted": false, - "id": "RR6GEDZFWUssCdTAnrDqo", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1660.0572555330295, - "y": -366.9575940330065, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 24.665187381169744, - "height": 183.91639038865497, - "seed": 45437883, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "5xDzwVV0gDBlD-WL85LYy", - "focus": -0.5447225569886008, - "gap": 1.5967808146620044 - }, - "endBinding": { - "elementId": "JaiST4fQ2FlodcbDsHGRd", - "focus": -0.07528645180716763, - "gap": 6.5362537154315845 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -24.665187381169744, - 183.91639038865497 - ] - ] - }, - { - "type": "diamond", - "version": 596, - "versionNonce": 1248600538, - "isDeleted": false, - "id": "sJFOeRzApKjnkoavWYSOm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1415.6666666666667, - "y": 270.6666666666667, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 292.8888888888888, - "height": 207.6666666666666, - "seed": 3834485, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "OIBiMf6VpPETwJAGERKti", - "MPfW6JEUQYoWrzHeI_NxD", - "FqT97u9kn8yPY-e9ZEmwX", - "3w6C3S5gFIBLSOSnHrL9p", - "J8EUyGyYHRSW895zskWbp", - "yZ1sDbO9UxZw6Yw7d8BGk", - "sofyhfQ6yAERl8W7igqud", - "98jHSy6KgIgI-kigPhATL", - "Jj-F4u_NSjuW-tVWK6ErH" - ] - }, - { - "type": "text", - "version": 354, - "versionNonce": 339994458, - "isDeleted": false, - "id": "ERwB6OfVxtsDLuZO1G4dn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1507.2222222222224, - "y": 292.6666666666667, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 131, - "height": 78, - "seed": 410831707, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "OIBiMf6VpPETwJAGERKti", - "MPfW6JEUQYoWrzHeI_NxD", - "yZ1sDbO9UxZw6Yw7d8BGk" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "DKG\nbroadcaster \nnetwork", - "baseline": 70, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 432, - "versionNonce": 855557126, - "isDeleted": false, - "id": "yZ1sDbO9UxZw6Yw7d8BGk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1613.2645903010791, - "y": 5.6816480400971585, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 32.37650849922488, - "height": 267.8739593512054, - "seed": 978571771, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "JaiST4fQ2FlodcbDsHGRd", - "focus": 0.056328322376417134, - "gap": 8.799318498256582 - }, - "endBinding": { - "elementId": "ERwB6OfVxtsDLuZO1G4dn", - "focus": 0.016268660508214034, - "gap": 19.11105927536414 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -32.37650849922488, - 267.8739593512054 - ] - ] - }, - { - "type": "arrow", - "version": 398, - "versionNonce": 2067988678, - "isDeleted": false, - "id": "Jj-F4u_NSjuW-tVWK6ErH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1623.0089473868802, - "y": 304.406818117378, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 33.75764899556157, - "height": 307.2241654227815, - "seed": 398843163, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "sJFOeRzApKjnkoavWYSOm", - "focus": 0.3632505836483008, - "gap": 7.699251414567215 - }, - "endBinding": { - "elementId": "JaiST4fQ2FlodcbDsHGRd", - "focus": -0.3414897328591685, - "gap": 9.04048516465393 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 33.75764899556157, - -307.2241654227815 - ] - ] - }, - { - "type": "text", - "version": 554, - "versionNonce": 720882453, - "isDeleted": false, - "id": "kXVNso4ItXnAMFArYHbYk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1486.1111111111115, - "y": 109.99999999999997, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 52, - "seed": 1662723835, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "validate \nmsg", - "baseline": 44, - "textAlign": "right", - "verticalAlign": "top" - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - } -} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/gossip_process.svg b/documentation/dev/src/explore/design/intent_gossip/gossip_process.svg deleted file mode 100644 index 9129d9eea7d..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/gossip_process.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - clientIntent broadcasterintentbroadcaster networkvalidate msglibP2P magicclientsend msgGossip Nodesend msgDKG broadcasterDKGbroadcaster networkvalidate msg \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/incentive.md b/documentation/dev/src/explore/design/intent_gossip/incentive.md deleted file mode 100644 index 51905d457f2..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/incentive.md +++ /dev/null @@ -1,9 +0,0 @@ -# Incentive - -[Tracking Issue](https://github.com/anoma/anoma/issues/37) - ---- - -TODO -- describe incentive function -- describe logic to ensure matchmaker can't cheat intent gossip service diff --git a/documentation/dev/src/explore/design/intent_gossip/intent.md b/documentation/dev/src/explore/design/intent_gossip/intent.md deleted file mode 100644 index 62bffedbe0c..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/intent.md +++ /dev/null @@ -1,28 +0,0 @@ -# Intents - -An intent is a way of expressing a user's desire. It is defined as arbitrary -data and an optional address for a schema. The data is as arbitrary as possible -to allow the users to express any sort of intent. It could range from defining a -selling order for a specific token to offering piano lessons or even proposing a -green tax for shoes’ manufacturers. - -An intent is written using an encoding, or data schema. The encoding exists -either on-chain or off-chain. It must be known by users that want to express -similar intents. It also must be understood by some matchmaker. Otherwise, it -possibly won’t be matched. The user can define its own schema and inform either -off-chain or on-chain. Having it on-chain allows it to easily share it with other -participants. Please refer to [data schema](./../ledger/storage/data-schema.md) for more -information about the usage of on-chain schema. - ---- - -There is only a single intent type that is composed of arbitrary data and a -possible schema definition. - -```rust -struct Intent { - schema: Option, - data: Vec, - timestamp: Timestamp -} -``` diff --git a/documentation/dev/src/explore/design/intent_gossip/intent_gossip.md b/documentation/dev/src/explore/design/intent_gossip/intent_gossip.md deleted file mode 100644 index f9342a01810..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/intent_gossip.md +++ /dev/null @@ -1,51 +0,0 @@ -# Intent gossip network - -The intent gossip network enables counterparty discovery for bartering. The -users can express any sort of intents that might be matched and transformed into -a transaction that fulfills the intents on the Anoma ledger. - -An [intent](./intent.md) describes the desire of a user, from asset exchange to a -green tax percent for selling shoes. These intents are picked up by a matchmaker -that composes them into transactions to send to the ledger network. A matchmaker -is optionally included in the intent gossip node. - -Each node connects to a specified intent gossip network, either a public or a -private one. Anyone can create their own network where they decide all aspects -of it: which type of intents is propagated, which nodes can participate, the -matchmaker logic, etc. It is possible, for example, to run the intent gossip system -over bluetooth to have it off-line. - -An intent gossip node is a peer in the intent gossip network that has the role -of propagating intents to all other connected nodes. - -The network uses the -[gossipsub](https://github.com/libp2p/specs/tree/512accdd81e35480911499cea14e7d7ea019f71b/pubsub/gossipsub) -network behaviour. This system aggregates nodes around topics of interest. Each -node subscribes to a set of topics and connects to other nodes that are also -subscribed to the same topics. A topic defines a sub-network for a defined -interest, e.g. “asset_exchange”. see -[gossipsub](https://github.com/libp2p/specs/tree/512accdd81e35480911499cea14e7d7ea019f71b/pubsub/gossipsub) -for more information on the network topology. - -Each node has an incentive to propagate intents and will obtain a small portion -of the fees if the intent is settled. (TODO: update when logic is found) See -[incentive](./incentive.md) for more information. - -### Flow diagram: asset exchange - -This example shows three intents matched together by the intent gossip network. -These three intents express user desires to exchange assets. - -![intent gossip and ledger network -interaction](./example.svg "intent gossip network") -[Diagram on Excalidraw](https://excalidraw.com/#room=257e44f4b4b5867bf541,XDEKyGVIpqCrfq55bRqKug) - -# Flow diagram: life cycle of intent and global process - -This diagram shows the process flow for intents, from users expressing their -desire to the ledger executing the validity predicate to check the crafted -transaction. - -![intent life cycle](./intent_life_cycle.svg "intent life -cycle") -[Diagram on Excalidraw](https://excalidraw.com/#room=7ac107b3757c64049003,cdMInfvdLtjaGWSZWEKrhw) diff --git a/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.excalidraw b/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.excalidraw deleted file mode 100644 index 83b9eced853..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.excalidraw +++ /dev/null @@ -1,1686 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "text", - "version": 588, - "versionNonce": 594523952, - "isDeleted": false, - "id": "1u_2wBZI6b8qbCpHVN6MP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -298.6003582724815, - "y": 1338.2754110612868, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 85.4456709210074, - "height": 21.78026905829601, - "seed": 71443719, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16.75405312176615, - "fontFamily": 1, - "text": "add intent", - "baseline": 14.78026905829601, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 454, - "versionNonce": 786627024, - "isDeleted": false, - "id": "2Wc5lEY411C5Gl9LSu5pY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -410.91401048972233, - "y": 1360.7731689088182, - "strokeColor": "#c92a2a", - "backgroundColor": "transparent", - "width": 88, - "height": 18, - "seed": 1334554569, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "yk9tRH45JFldlow2BsTQc", - "IfZHk6DCJHzdGQSleRzVY", - "3M7y36XaiPpkZdYFa6HL0" - ], - "fontSize": 13.677130044843086, - "fontFamily": 3, - "text": "filter.wasm", - "baseline": 14, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 2279, - "versionNonce": 1948716336, - "isDeleted": false, - "id": "3M7y36XaiPpkZdYFa6HL0", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -305.31404973574377, - "y": 1369.1513491711225, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 105.04325514724303, - "height": 0.1273972078367933, - "seed": 94578119, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "MA9plWdw54jKf3dPF231d", - "focus": 0.11381802003862611, - "gap": 4.5029253777944405 - }, - "endBinding": { - "elementId": "J3PIpIxWftxjM5b3onaa_", - "focus": -1.823336288291325, - "gap": 9.453089222814356 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 105.04325514724303, - 0.1273972078367933 - ] - ] - }, - { - "type": "rectangle", - "version": 628, - "versionNonce": 1926825424, - "isDeleted": false, - "id": "jTa_ZQSjZQ_9wK0AxfkKq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -200.54779225354866, - "y": 1340.590433482808, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 125.3736920777284, - "height": 59.495515695067446, - "seed": 1482668551, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Q4yHCPe8MjqsFcGPSqmAZ", - "3M7y36XaiPpkZdYFa6HL0", - "9rmYOU060-nUE-om6NWFg", - "dAdWOUfpjNuXJyfO475fP" - ] - }, - { - "type": "rectangle", - "version": 504, - "versionNonce": 1614695216, - "isDeleted": false, - "id": "MA9plWdw54jKf3dPF231d", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -416.8923363492138, - "y": 1350.6808669656184, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 107.07536123567559, - "height": 33.48318385650231, - "seed": 186308295, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "IolhdtZMlJt05RE7PSeqH", - "3M7y36XaiPpkZdYFa6HL0" - ] - }, - { - "type": "arrow", - "version": 1314, - "versionNonce": 44918064, - "isDeleted": false, - "id": "IolhdtZMlJt05RE7PSeqH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -297.62941465804636, - "y": 1305.067079818005, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 35.4136526564796, - "height": 41.1911597898536, - "seed": 580979783, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "vMWskj1YJao4q8Szni_9g", - "focus": 0.2739809180363499, - "gap": 3.4310135063774396 - }, - "endBinding": { - "elementId": "MA9plWdw54jKf3dPF231d", - "focus": 0.17835547552991876, - "gap": 4.422627357759893 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -35.4136526564796, - 41.1911597898536 - ] - ] - }, - { - "type": "text", - "version": 630, - "versionNonce": 701297456, - "isDeleted": false, - "id": "J3PIpIxWftxjM5b3onaa_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -193.70922723112722, - "y": 1346.0612855007478, - "strokeColor": "#c92a2a", - "backgroundColor": "transparent", - "width": 112.83632286995548, - "height": 16.412556053811695, - "seed": 856985703, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rmYOU060-nUE-om6NWFg", - "Q4yHCPe8MjqsFcGPSqmAZ", - "MOjksoKslfp16-Y701zlH", - "3M7y36XaiPpkZdYFa6HL0" - ], - "fontSize": 13.67713004484308, - "fontFamily": 2, - "text": "matchmaker.wasm", - "baseline": 11.412556053811695, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 629, - "versionNonce": 2084073754, - "isDeleted": false, - "id": "AfmJxEIuWj5_62161GQ01", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -326.2723811922641, - "y": 1440.665545590435, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 176.69394618834113, - "height": 56.203662182361796, - "seed": 337914215, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rmYOU060-nUE-om6NWFg", - "mlM1pmVM-052hahaRXJQR", - "ttC9xH3eOGzQ_k5DLzmBu" - ] - }, - { - "type": "text", - "version": 642, - "versionNonce": 1534794758, - "isDeleted": false, - "id": "TzqXubskSAjo2ZuvkxkCC", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -293.94129001139663, - "y": 1468.2503736920773, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 112, - "height": 18, - "seed": 647873609, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "ttC9xH3eOGzQ_k5DLzmBu", - "9rmYOU060-nUE-om6NWFg" - ], - "fontSize": 13.67713004484309, - "fontFamily": 3, - "text": "tx.wawm + data", - "baseline": 14, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 2292, - "versionNonce": 1408723920, - "isDeleted": false, - "id": "9rmYOU060-nUE-om6NWFg", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -147.6356968299097, - "y": 1408.7716634080846, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 66.68074390630872, - "height": 23.556734178462648, - "seed": 798702697, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "jTa_ZQSjZQ_9wK0AxfkKq", - "gap": 8.685714230208873, - "focus": -0.6781834884379211 - }, - "endBinding": { - "elementId": "AfmJxEIuWj5_62161GQ01", - "gap": 8.337148003887705, - "focus": -0.47373449663638795 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -66.68074390630872, - 23.556734178462648 - ] - ] - }, - { - "type": "text", - "version": 507, - "versionNonce": 260021712, - "isDeleted": false, - "id": "He6AuKTKQxKmk5B38hWHj", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -176.69016893516272, - "y": 1368.6692825112111, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 75.90807174887914, - "height": 17.780269058296007, - "seed": 836533545, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 13.677130044843084, - "fontFamily": 1, - "text": "craft data", - "baseline": 11.780269058296007, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1796, - "versionNonce": 1407883056, - "isDeleted": false, - "id": "ttC9xH3eOGzQ_k5DLzmBu", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -245.0771506636562, - "y": 1508.714667199035, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 0.6776858655143201, - "height": 347.40968967814, - "seed": 187206441, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "AfmJxEIuWj5_62161GQ01", - "gap": 11.845459426238218, - "focus": 0.08178190315132251 - }, - "endBinding": { - "elementId": "SehQ2WycVyCQCB-DbZfI1", - "gap": 3.963620241223044, - "focus": -0.07279835372456127 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 0.6776858655143201, - 347.40968967814 - ] - ] - }, - { - "type": "ellipse", - "version": 410, - "versionNonce": 51621680, - "isDeleted": false, - "id": "SehQ2WycVyCQCB-DbZfI1", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -329.5040702804547, - "y": 1859.9240159441954, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 183.84491778774336, - "height": 121.63153961136064, - "seed": 1448762345, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "ttC9xH3eOGzQ_k5DLzmBu" - ] - }, - { - "type": "text", - "version": 374, - "versionNonce": 1588809350, - "isDeleted": false, - "id": "zERziwpui3gX_VflZl-6H", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -294.7738759605737, - "y": 1914.3163926258123, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 102.57847533632317, - "height": 17.780269058296007, - "seed": 1122418569, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 13.677130044843079, - "fontFamily": 1, - "text": "Ledger network", - "baseline": 11.780269058296007, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 349, - "versionNonce": 996390352, - "isDeleted": false, - "id": "95Wz9icO-OmtvwP5kKKfo", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -231.3882257363585, - "y": 1743.2290732436484, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 29.405829596412616, - "height": 17.780269058296007, - "seed": 1438218537, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 13.677130044843071, - "fontFamily": 1, - "text": "send", - "baseline": 11.780269058296007, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 236, - "versionNonce": 2057635802, - "isDeleted": false, - "id": "1o37i1BlUHSA_I1CYSHoq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -369.98077680761, - "y": 1306.2222222222215, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 43.986547085201835, - "height": 24.30835496813786, - "seed": 365688905, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "IolhdtZMlJt05RE7PSeqH" - ], - "fontSize": 18.520651404295513, - "fontFamily": 1, - "text": "apply", - "baseline": 17.30835496813786, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 1479, - "versionNonce": 442628400, - "isDeleted": false, - "id": "dAdWOUfpjNuXJyfO475fP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -92.3069277739813, - "y": 1403.7229079845479, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 9.276809968310843, - "height": 353.05286013400246, - "seed": 203795395, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "jTa_ZQSjZQ_9wK0AxfkKq", - "focus": -0.7039148076260963, - "gap": 3.636958806672169 - }, - "endBinding": { - "elementId": "xA7xWASKaJXAE1SQjNFQ2", - "focus": 0.2211669756965918, - "gap": 14.492622357918698 - }, - "lastCommittedPoint": null, - "startArrowhead": "arrow", - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 9.276809968310843, - 353.05286013400246 - ] - ] - }, - { - "type": "ellipse", - "version": 494, - "versionNonce": 12155910, - "isDeleted": false, - "id": "xA7xWASKaJXAE1SQjNFQ2", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -144.08927207417597, - "y": 1770.7329347284497, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 101.89461883408097, - "height": 60.17937219730958, - "seed": 833498093, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "dAdWOUfpjNuXJyfO475fP" - ] - }, - { - "type": "text", - "version": 413, - "versionNonce": 1448457242, - "isDeleted": false, - "id": "Mh7caG46B4Id7Rt9mZVK0", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -78.91214202933338, - "y": 1692.8943697060292, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 90, - "height": 19, - "seed": 1753782275, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "dAdWOUfpjNuXJyfO475fP" - ], - "fontSize": 13.677130044843079, - "fontFamily": 1, - "text": "read storage", - "baseline": 13, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 411, - "versionNonce": 706916998, - "isDeleted": false, - "id": "3Nb-sNCg1mpGbUpGqPC6x", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -127.67671602036424, - "y": 1788.5132037867468, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 75.22421524663697, - "height": 17.780269058296007, - "seed": 1638966797, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 13.677130044843079, - "fontFamily": 1, - "text": "ledger node", - "baseline": 11.780269058296007, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 162, - "versionNonce": 137567834, - "isDeleted": false, - "id": "vMWskj1YJao4q8Szni_9g", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -294.2777777777778, - "y": 1243.3333333333333, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 61, - "seed": 1736645018, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "IolhdtZMlJt05RE7PSeqH", - "MODmRH_Ai_h7eca-aYe9A", - "mHbM0I-2cMJHDQh1moe77" - ] - }, - { - "type": "diamond", - "version": 389, - "versionNonce": 416087686, - "isDeleted": false, - "id": "mBiwqFRC0vttSdLSnsU30", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -445.2777777777778, - "y": 1092.6666666666667, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 406.66666666666674, - "height": 525.6666666666669, - "seed": 476615706, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rmYOU060-nUE-om6NWFg", - "dAdWOUfpjNuXJyfO475fP" - ] - }, - { - "type": "ellipse", - "version": 352, - "versionNonce": 1356395824, - "isDeleted": false, - "id": "JwMrnB618nsHGBBcf92Nk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -763.2777777777765, - "y": 874.9999999999998, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 763.3333333333335, - "height": 772.6666666666669, - "seed": 2052937478, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "ttC9xH3eOGzQ_k5DLzmBu" - ] - }, - { - "type": "text", - "version": 170, - "versionNonce": 1019110864, - "isDeleted": false, - "id": "tKzpFkSzFkhgxKS4-AVGh", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -502.944444444443, - "y": 895.6666666666666, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 190, - "height": 52, - "seed": 1397120794, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "node connected to \ntopic X", - "baseline": 44, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 443, - "versionNonce": 988924208, - "isDeleted": false, - "id": "jLWO7OrtG_BOPVeKMiwwN", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -473.44444444444264, - "y": 870.3333333333328, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 738.6666666666667, - "height": 779.0000000000005, - "seed": 1658325702, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "ttC9xH3eOGzQ_k5DLzmBu" - ] - }, - { - "type": "text", - "version": 46, - "versionNonce": 39391066, - "isDeleted": false, - "id": "GyEOsmqW1Zbdi7mCavkOe", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -302.44444444444275, - "y": 1444, - "strokeColor": "#1864ab", - "backgroundColor": "transparent", - "width": 134, - "height": 21, - "seed": 734861702, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "wrap transaction", - "baseline": 15, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 364, - "versionNonce": 770571728, - "isDeleted": false, - "id": "MODmRH_Ai_h7eca-aYe9A", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 0, - "opacity": 100, - "angle": 0, - "x": -479.5404756662694, - "y": 1107.858463582872, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 182.87979526759636, - "height": 175.27063591985802, - "seed": 1217671632, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "KwgaEOt8AQFLzGPvhESKA", - "focus": -0.46643591145185553, - "gap": 6.200069247495527 - }, - "endBinding": { - "elementId": "vMWskj1YJao4q8Szni_9g", - "focus": -0.7361561089573266, - "gap": 2.3829026208952087 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 182.87979526759636, - 175.27063591985802 - ] - ] - }, - { - "type": "diamond", - "version": 778, - "versionNonce": 595069744, - "isDeleted": false, - "id": "1aiocLnc3sR_lZQoFdB-y", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -704.777777777776, - "y": 1239.6666666666663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 234.6666666666666, - "height": 309.6666666666667, - "seed": 344958598, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rmYOU060-nUE-om6NWFg", - "dAdWOUfpjNuXJyfO475fP", - "MODmRH_Ai_h7eca-aYe9A" - ] - }, - { - "type": "text", - "version": 1103, - "versionNonce": 2017820624, - "isDeleted": false, - "id": "50lEI8L3qiqHPIwEeJjSw", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": -633.4444444444428, - "y": 1277.4999999999993, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 42, - "seed": 2055761114, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "intent\ngossip node", - "baseline": 36, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 196, - "versionNonce": 951002416, - "isDeleted": false, - "id": "zoKUQOZpsx3bSrQmlje2Y", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -518.4444444444428, - "y": 1209.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 13, - "height": 21, - "seed": 1744336198, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "...", - "baseline": 15, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 235, - "versionNonce": 73640752, - "isDeleted": false, - "id": "l4h4jtqh4lQ5ChrRu_d_W", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -534.9444444444428, - "y": 1211, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 45, - "height": 24.000000000000004, - "seed": 1561834950, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "NbiyFfs9_Zj9QLzFL8qHm", - "sZ8QrMhPhFBK7IsremLwb", - "B6B0y6Mtp0UrJDdAOrL4y" - ] - }, - { - "type": "arrow", - "version": 1093, - "versionNonce": 1896727504, - "isDeleted": false, - "id": "B6B0y6Mtp0UrJDdAOrL4y", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -548.3104597282063, - "y": 1368.7777417215675, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 24.520420568205054, - "height": 124.27733365833865, - "seed": 733305606, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "kfmygtSSHmjQGgRHYzhpm", - "focus": 0.25664643293730066, - "gap": 10.318970816658975 - }, - "endBinding": { - "elementId": "l4h4jtqh4lQ5ChrRu_d_W", - "focus": 0.285651344839639, - "gap": 9.50040806322886 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 24.520420568205054, - -124.27733365833865 - ] - ] - }, - { - "type": "arrow", - "version": 732, - "versionNonce": 138725840, - "isDeleted": false, - "id": "sZ8QrMhPhFBK7IsremLwb", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -519.0071422289418, - "y": 1208, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 4.665799248315011, - "height": 60.764495548789455, - "seed": 1445654746, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "l4h4jtqh4lQ5ChrRu_d_W", - "focus": -0.2310245718703419, - "gap": 3 - }, - "endBinding": { - "elementId": "KwgaEOt8AQFLzGPvhESKA", - "focus": -0.06849615152086622, - "gap": 6.783169732481319 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -4.665799248315011, - -60.764495548789455 - ] - ] - }, - { - "type": "text", - "version": 854, - "versionNonce": 557904688, - "isDeleted": false, - "id": "y-BT1d_fUjOriE9fXDJL5", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": -600.8876146788973, - "y": 1384.7289755351683, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 72, - "height": 57, - "seed": 277391514, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 14.403669724770648, - "fontFamily": 1, - "text": "gossipsub \nintent\nmempool", - "baseline": 51, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 371, - "versionNonce": 434353968, - "isDeleted": false, - "id": "kfmygtSSHmjQGgRHYzhpm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -611.9444444444428, - "y": 1379.0967125382265, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 61, - "seed": 405227014, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "IolhdtZMlJt05RE7PSeqH", - "MODmRH_Ai_h7eca-aYe9A", - "B6B0y6Mtp0UrJDdAOrL4y", - "jNcNZ79jIqf8USX6XWVfL" - ] - }, - { - "type": "diamond", - "version": 51, - "versionNonce": 1683064986, - "isDeleted": false, - "id": "SHqk2AS-Tf7dKA-jlraJy", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -642.9444444444428, - "y": 1809, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 126, - "height": 138, - "seed": 111487686, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "jNcNZ79jIqf8USX6XWVfL" - ] - }, - { - "type": "text", - "version": 61, - "versionNonce": 1776051718, - "isDeleted": false, - "id": "4KWmIOGgA881gCSC5JHPR", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -605.4444444444428, - "y": 1867.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 45, - "height": 21, - "seed": 1914582042, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "Client", - "baseline": 15, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 583, - "versionNonce": 1443824080, - "isDeleted": false, - "id": "jNcNZ79jIqf8USX6XWVfL", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -579.5237129692832, - "y": 1804.7181399798692, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 5.784969773188777, - "height": 354.7181399798692, - "seed": 338257542, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "SHqk2AS-Tf7dKA-jlraJy", - "focus": -0.012292118582791034, - "gap": 4.302480703755165 - }, - "endBinding": { - "elementId": "kfmygtSSHmjQGgRHYzhpm", - "focus": 0.09507177161013172, - "gap": 9.903287461773516 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 5.784969773188777, - -354.7181399798692 - ] - ] - }, - { - "type": "text", - "version": 75, - "versionNonce": 1051571526, - "isDeleted": false, - "id": "2KyLbsA0pYg-9FlG-TXOS", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -550.9444444444428, - "y": 1763.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 21, - "seed": 436422490, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "send intent", - "baseline": 15, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 134, - "versionNonce": 1446470608, - "isDeleted": false, - "id": "_iAmT5vekaBL78IeOELHk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -616.4444444444428, - "y": 1474.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 29, - "height": 21, - "seed": 472650778, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "add", - "baseline": 15, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "diamond", - "version": 649, - "versionNonce": 2130147290, - "isDeleted": false, - "id": "zO0p-uresSJTDL1fIuJzV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -425.7777777777761, - "y": 1051.6666666666663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 135.66666666666674, - "height": 118.66666666666676, - "seed": 112661018, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rmYOU060-nUE-om6NWFg", - "dAdWOUfpjNuXJyfO475fP", - "MODmRH_Ai_h7eca-aYe9A", - "NbiyFfs9_Zj9QLzFL8qHm", - "sZ8QrMhPhFBK7IsremLwb", - "mHbM0I-2cMJHDQh1moe77", - "ixAHiLZKgPdknMrEKL8E0" - ] - }, - { - "type": "arrow", - "version": 273, - "versionNonce": 2086180304, - "isDeleted": false, - "id": "mHbM0I-2cMJHDQh1moe77", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dashed", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -349.6653716829509, - "y": 1166.225689032409, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 50.06401310624267, - "height": 82.14785148842725, - "seed": 1712918342, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "zO0p-uresSJTDL1fIuJzV", - "focus": 0.3763418967402116, - "gap": 2.3589356079478208 - }, - "endBinding": { - "elementId": "vMWskj1YJao4q8Szni_9g", - "focus": -0.5326929819465109, - "gap": 5.323580798930379 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 50.06401310624267, - 82.14785148842725 - ] - ] - }, - { - "type": "diamond", - "version": 680, - "versionNonce": 992396592, - "isDeleted": false, - "id": "KwgaEOt8AQFLzGPvhESKA", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -600.7777777777761, - "y": 1027.6666666666667, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 135.66666666666674, - "height": 118.66666666666676, - "seed": 1672626970, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rmYOU060-nUE-om6NWFg", - "dAdWOUfpjNuXJyfO475fP", - "MODmRH_Ai_h7eca-aYe9A", - "NbiyFfs9_Zj9QLzFL8qHm", - "sZ8QrMhPhFBK7IsremLwb", - "mHbM0I-2cMJHDQh1moe77" - ] - }, - { - "type": "diamond", - "version": 103, - "versionNonce": 1529867034, - "isDeleted": false, - "id": "5G2DYWMgIW2-Eyu0jHu5M", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -333.94444444444275, - "y": 634.75, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 126, - "height": 138, - "seed": 856516186, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "jNcNZ79jIqf8USX6XWVfL", - "ixAHiLZKgPdknMrEKL8E0" - ] - }, - { - "type": "text", - "version": 111, - "versionNonce": 1461469830, - "isDeleted": false, - "id": "3n5h8veTQO-lECAC8FTbh", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -296.44444444444275, - "y": 693.25, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 45, - "height": 21, - "seed": 1537623110, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "Client", - "baseline": 15, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 175, - "versionNonce": 288331846, - "isDeleted": false, - "id": "m_iuhNYFucnv5uwttXCPa", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -281.94444444444275, - "y": 819.25, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 21, - "seed": 1275300634, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "send intent", - "baseline": 15, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 79, - "versionNonce": 1108019504, - "isDeleted": false, - "id": "ixAHiLZKgPdknMrEKL8E0", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -269.94444444444275, - "y": 777, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 84, - "height": 274, - "seed": 1390808730, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "5G2DYWMgIW2-Eyu0jHu5M", - "focus": -0.37232070443749277, - "gap": 3.604130291018784 - }, - "endBinding": { - "elementId": "zO0p-uresSJTDL1fIuJzV", - "focus": -0.21219892752739253, - "gap": 3.135288841647885 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -84, - 274 - ] - ] - }, - { - "type": "text", - "version": 1136, - "versionNonce": 408089904, - "isDeleted": false, - "id": "Wj0MHJ1dtfAoF25CFCBfi", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": -576.9444444444428, - "y": 1060.2499999999998, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 42, - "seed": 960837584, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "intent\ngossip node", - "baseline": 36, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 1149, - "versionNonce": 1606676272, - "isDeleted": false, - "id": "TXaHYoLoaZlRQaR766TLH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": -400.94444444444275, - "y": 1083.2500000000002, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 42, - "seed": 1165581104, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "intent\ngossip node", - "baseline": 36, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 1227, - "versionNonce": 1197055440, - "isDeleted": false, - "id": "9hZsHGB4M9tFJZIfi6Ao_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": -305.94444444444275, - "y": 1134.2499999999998, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 128, - "height": 63, - "seed": 1645861840, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 16, - "fontFamily": 1, - "text": "intent\ngossip node\nwith matchmaker", - "baseline": 57, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 210, - "versionNonce": 346021168, - "isDeleted": false, - "id": "26K0-MZ5xxu4N_zB_HZRa", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": -164.94444444444275, - "y": 890.2499999999999, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 190, - "height": 52, - "seed": 1266445616, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "node connected to \ntopic Y", - "baseline": 44, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 913, - "versionNonce": 915376944, - "isDeleted": false, - "id": "ENHebue9f6lfx-kznKF5G", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 2, - "opacity": 100, - "angle": 0, - "x": -285.94444444444275, - "y": 1243.7500000000002, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 72, - "height": 57, - "seed": 2113192912, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 14.403669724770648, - "fontFamily": 1, - "text": "gossipsub \nintent\nmempool", - "baseline": 51, - "textAlign": "left", - "verticalAlign": "top" - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - } -} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.svg b/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.svg deleted file mode 100644 index 7295dd3431a..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/intent_life_cycle.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - add intentfilter.wasmmatchmaker.wasmtx.wawm + datacraft dataLedger networksendapplyread storageledger nodenode connected to topic Xwrap transactionintentgossip node...gossipsub intentmempoolClientsend intentaddClientsend intentintentgossip nodeintentgossip nodeintentgossip nodewith matchmakernode connected to topic Ygossipsub intentmempool \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker.md b/documentation/dev/src/explore/design/intent_gossip/matchmaker.md deleted file mode 100644 index ce2cace5bdf..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/matchmaker.md +++ /dev/null @@ -1,80 +0,0 @@ -# Matchmaker - -The matchmaker is a specific actor in the intent gossip network that tries to -match intents together. When intents are matched together, the matchmaker crafts -a transaction from them and sends it to the ledger network. - -A matchmaker is an intent gossip node started with additional parameters: a -ledger address and a list of sub-matchmakers. A sub-matchmaker is defined with a -topics list, a main program path, a filter program path, and a transaction code. - -The main and filter programs are wasm compiled code. Each has a defined -entrypoint and their own set of environment functions that they can call. - -When the matchmaker receives a new intent from the network it calls the -corresponding sub-matchmaker, the one that has the intent’s topic in their -topics list. A sub-matchmaker first checks if the intent is accepted by the -filter, before adding it to that sub-matchmaker database. Then the main program -is called with the intent and current state. - -## Sub-matchmaker topics' list - -A sub-matchmaker is defined to work with only a subset of encoding. Each intent -propagated to the corresponding topic will be process by this sub-matchmaker. - -Having a topics list instead of a unique topic allows a matchmaker to match -intents from different encodings. For example, when an updated version of an -encoding is out, the matchmaker could match intents from both versions if they -don’t diverge too much. - -## Sub-matchmaker database and state (name TBD) - -Each sub-matchmaker has a database and an arbitrary state. - -The database contains intents received by the node from the topics list that -passed the filter. - -The state is arbitrary data that is managed by the main program. That state is -given to all calls in the main program. - -The database is persistent but the state is not. When a node is started the -state is recovered by giving all intents from the database to the main program. -The invariant that the current state is equal to the state if the node is -restarted is not enforced and is the responsibility of the main program. - -## Filter program - -The filter is an optional wasm program given in parameters. This filter is used -to check each intent received by that sub-matchmaker. If it's not defined, -intents are directly passed to the main program. - - The entrypoint `filter_intent` takes an intent and returns a boolean. The -filter has the ability to query the state of the ledger for any given key. - -## Main program - -The main program is a mandatory wasm program given in parameters. The main -program must match together intents. - -The main program entrypoint `match_intent` takes the current state, a new intent -data and its id. The main program also has the ability to query the state of the -ledger. It also has functions `remove` and `get` to interact with the matchmaker -mempool. When a main matchmaker program finds a match it sends a transaction to -the ledger composed of the code template given in the matchmaker parameter and -the data given to this function. Finally the matchmaker must update its state so -the next run will have up to date values. - -The main program is called on two specific occasion; when intent gossip node is -started, on all intent from database and whenever a new intent is received from -the p2p network and the RPC endpoint, if enabled. - -## Transaction - -The transaction code given in parameters is used when the main program matches a -group of intents. The main program returns arbitrary data that is attached to -the transaction which is then injected into a ledger node. - -## Flow diagram: Matchmaker process -![matchmaker process](./matchmaker_process.svg "matchmaker process") - -[excalidraw link](https://excalidraw.com/#room=92b291c13cfab8fb22a4,OvHfWIrL0jeDzPI-EFZMaw) diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.excalidraw b/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.excalidraw deleted file mode 100644 index 8d2d29fc95b..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.excalidraw +++ /dev/null @@ -1,1648 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "ellipse", - "version": 304, - "versionNonce": 732978925, - "isDeleted": false, - "id": "0kpaYctW0QC41GkaU0Fnm", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 788, - "y": 448, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 142.99999999999994, - "height": 63.000000000000014, - "seed": 1594329571, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "YebzbhwZPZJG7VeGK6buO", - "x5FlGy9vo-0WFWlvTLxRB" - ] - }, - { - "type": "text", - "version": 66, - "versionNonce": 1832587875, - "isDeleted": false, - "id": "VGy1X1kAYasab67GT_3Vp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 815, - "y": 464, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 26, - "seed": 1391569133, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent A", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 357, - "versionNonce": 1055790499, - "isDeleted": false, - "id": "ipca-2U6Ichp5KRWeBGiU", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1019.5, - "y": 553.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 142.99999999999994, - "height": 63.000000000000014, - "seed": 961566285, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "YebzbhwZPZJG7VeGK6buO", - "jrzBVVdi5EqY0mYYzI-qC", - "vUyB6nADBS51AWp7XW9EQ" - ] - }, - { - "type": "text", - "version": 103, - "versionNonce": 2128726541, - "isDeleted": false, - "id": "sh9VeUsrJu8zyMqC4wTTn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1046.5, - "y": 569.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 26, - "seed": 54575363, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent B", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 443, - "versionNonce": 813787459, - "isDeleted": false, - "id": "mWM2JyFPRcVz5-LPE35xW", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 912.5, - "y": 728.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 142.99999999999994, - "height": 63.000000000000014, - "seed": 1522729677, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "jrzBVVdi5EqY0mYYzI-qC", - "jL-ZuiaA7Yepo4Ood8hh4" - ] - }, - { - "type": "text", - "version": 189, - "versionNonce": 563544173, - "isDeleted": false, - "id": "mkyAaMiHp_aflaPNTUS4h", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 939.5, - "y": 744.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 86, - "height": 26, - "seed": 1761418371, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent C", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 602, - "versionNonce": 665405667, - "isDeleted": false, - "id": "8Kq9uOVih2T18r283qOW_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 625.8084194993482, - "y": 740.257842502465, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 143.34456240942814, - "height": 63.15180022233556, - "seed": 1558060547, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "jL-ZuiaA7Yepo4Ood8hh4", - "hKuG4FbIRTd9ZuXgsPwJH", - "B-ARwwElkcqpEnbbGinI1" - ] - }, - { - "type": "text", - "version": 336, - "versionNonce": 2031512269, - "isDeleted": false, - "id": "jPnOeECE_YTxUJl_u56s4", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 652.8734767374922, - "y": 756.2963949398833, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 89.21444793314066, - "height": 26.062647710805145, - "seed": 2115827629, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20.048190546773174, - "fontFamily": 1, - "text": "Intent D", - "baseline": 18.062647710805145, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 596, - "versionNonce": 1193323555, - "isDeleted": false, - "id": "eYMWKEiFVFlYbjiPgu_c4", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1300.5, - "y": 552.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 142.99999999999994, - "height": 63.000000000000014, - "seed": 1785759373, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "vUyB6nADBS51AWp7XW9EQ", - "Tz4cdqtGreDIsYuudDs1x" - ] - }, - { - "type": "text", - "version": 347, - "versionNonce": 967433101, - "isDeleted": false, - "id": "rHC_PRTjElJ0lMKCObfnb", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1327.5, - "y": 568.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 87, - "height": 26, - "seed": 397063363, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent E", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 652, - "versionNonce": 590894061, - "isDeleted": false, - "id": "1MaFQq1C6f_KmXrCju5x4", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1084.5, - "y": 873.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 142.99999999999994, - "height": 63.000000000000014, - "seed": 1794271469, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "ZVBhabk31rzDP8O9ZI07b", - "wkpAIY8WXU4_DhUYO1aB9" - ] - }, - { - "type": "text", - "version": 393, - "versionNonce": 1012982253, - "isDeleted": false, - "id": "DLt_4JJLkufDMzrR-McEC", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1111.5, - "y": 889.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 85, - "height": 26, - "seed": 1115072611, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent F", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 39, - "versionNonce": 1611153251, - "isDeleted": false, - "id": "YebzbhwZPZJG7VeGK6buO", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 933, - "y": 485, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 96, - "height": 76, - "seed": 1699820525, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "0kpaYctW0QC41GkaU0Fnm", - "focus": -0.8133458878104184, - "gap": 2.90910614687202 - }, - "endBinding": { - "elementId": "ipca-2U6Ichp5KRWeBGiU", - "focus": -0.38721567770808374, - "gap": 6.84867663170747 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 96, - 76 - ] - ] - }, - { - "type": "arrow", - "version": 42, - "versionNonce": 858266701, - "isDeleted": false, - "id": "jrzBVVdi5EqY0mYYzI-qC", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1080, - "y": 624, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 40, - "height": 103, - "seed": 1343642477, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "ipca-2U6Ichp5KRWeBGiU", - "focus": -0.05715042949410481, - "gap": 7.857427983505644 - }, - "endBinding": { - "elementId": "mWM2JyFPRcVz5-LPE35xW", - "focus": 0.5953281273765297, - "gap": 12.01223971985118 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -40, - 103 - ] - ] - }, - { - "type": "arrow", - "version": 257, - "versionNonce": 137386755, - "isDeleted": false, - "id": "jL-ZuiaA7Yepo4Ood8hh4", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 909.4582871700171, - "y": 767.0701354272285, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 133.9560247707435, - "height": 0.9487028688702139, - "seed": 520169987, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "mWM2JyFPRcVz5-LPE35xW", - "focus": -0.21904362566296393, - "gap": 4.520486328322136 - }, - "endBinding": { - "elementId": "8Kq9uOVih2T18r283qOW_", - "focus": -0.10330406547410168, - "gap": 6.7389193162100725 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -133.9560247707435, - 0.9487028688702139 - ] - ] - }, - { - "type": "arrow", - "version": 39, - "versionNonce": 366545581, - "isDeleted": false, - "id": "vUyB6nADBS51AWp7XW9EQ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1165, - "y": 592, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 129, - "height": 9, - "seed": 1771476173, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "ipca-2U6Ichp5KRWeBGiU", - "focus": 0.381367937500731, - "gap": 3.8982105359317814 - }, - "endBinding": { - "elementId": "eYMWKEiFVFlYbjiPgu_c4", - "focus": 0.2019864602059321, - "gap": 6.524513889047455 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 129, - -9 - ] - ] - }, - { - "type": "arrow", - "version": 58, - "versionNonce": 403964579, - "isDeleted": false, - "id": "x5FlGy9vo-0WFWlvTLxRB", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 630.6120629006252, - "y": 483.8482760775462, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 151.38793709937477, - "height": 3.1517239224538116, - "seed": 625380429, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": null, - "endBinding": { - "elementId": "0kpaYctW0QC41GkaU0Fnm", - "focus": -0.28899374373966724, - "gap": 7.345010020824645 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 151.38793709937477, - 3.1517239224538116 - ] - ] - }, - { - "type": "arrow", - "version": 257, - "versionNonce": 677492291, - "isDeleted": false, - "id": "B-ARwwElkcqpEnbbGinI1", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 620.2972122415217, - "y": 767.8259116200478, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 105.25300037055923, - "height": 3.00722858201598, - "seed": 1445768771, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "8Kq9uOVih2T18r283qOW_", - "focus": 0.19635383025160105, - "gap": 5.918038308672706 - }, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -105.25300037055923, - 3.00722858201598 - ] - ] - }, - { - "type": "arrow", - "version": 96, - "versionNonce": 1349387117, - "isDeleted": false, - "id": "Tz4cdqtGreDIsYuudDs1x", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1367, - "y": 622, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 3, - "height": 106, - "seed": 1860965379, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "eYMWKEiFVFlYbjiPgu_c4", - "focus": 0.054884241524747655, - "gap": 6.574103558846954 - }, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -3, - 106 - ] - ] - }, - { - "type": "arrow", - "version": 376, - "versionNonce": 1294704067, - "isDeleted": false, - "id": "ZVBhabk31rzDP8O9ZI07b", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 4.670616178776941, - "x": 959.5453473034307, - "y": 783.1973748150889, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 7.812955781063806, - "height": 238.3078431598483, - "seed": 447610179, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": null, - "endBinding": { - "elementId": "1MaFQq1C6f_KmXrCju5x4", - "focus": 0.1315115545982045, - "gap": 10.746558995595109 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -7.812955781063806, - 238.3078431598483 - ] - ] - }, - { - "type": "arrow", - "version": 246, - "versionNonce": 1817294669, - "isDeleted": false, - "id": "wkpAIY8WXU4_DhUYO1aB9", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1263.104260686975, - "y": 902.4835510585458, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 110.99401075709227, - "height": 3.577325516939027, - "seed": 891868547, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "e1dMmNfjngeSZQw9Q_6bR", - "focus": 1.9182906115878682, - "gap": 15.4835510585458 - }, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 110.99401075709227, - 3.577325516939027 - ] - ] - }, - { - "type": "text", - "version": 60, - "versionNonce": 1709523331, - "isDeleted": false, - "id": "Y_SmrpIdN0Bw5qAuumOQq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 988, - "y": 482, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 78, - "height": 26, - "seed": 1670115203, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "0.1 BTC", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 84, - "versionNonce": 1194052653, - "isDeleted": false, - "id": "wBU3MwS8Tev8OyGNWq1F6", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1083.5, - "y": 663, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 101, - "height": 26, - "seed": 1534790019, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "1000 XTZ", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 176, - "versionNonce": 1801984291, - "isDeleted": false, - "id": "oPeSCDN2RaQLZlMs4lwHW", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 786.3493583868772, - "y": 791.9373522891948, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 147, - "height": 26, - "seed": 1663457027, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20.04819054677318, - "fontFamily": 1, - "text": "Give 2.3k ADA", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 258, - "versionNonce": 35546349, - "isDeleted": false, - "id": "ECtAtkhrh6wCcSpTFeh6_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 488.2084241147941, - "y": 793.8867522150837, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 192, - "height": 26, - "seed": 812495341, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20.048190546773178, - "fontFamily": 1, - "text": "Give max 3.5k EUR", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 208, - "versionNonce": 18377827, - "isDeleted": false, - "id": "9ciMh63J68g-vSTAGOaJH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 609.5, - "y": 437, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 171, - "height": 26, - "seed": 1942912995, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Want min 3k USD", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 111, - "versionNonce": 1985072557, - "isDeleted": false, - "id": "o0sItaNu9BXqe7GFjkfGK", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1185.5, - "y": 533, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 75, - "height": 26, - "seed": 1598303939, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "1.3 ETH", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 158, - "versionNonce": 717025187, - "isDeleted": false, - "id": "eY178tj8K8fiCB25oqtHg", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1382, - "y": 647, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 198, - "height": 26, - "seed": 650001315, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Give max 15K DOGE", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 316, - "versionNonce": 181323661, - "isDeleted": false, - "id": "IsXt4dgTY7mgH6itytb74", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 872.5, - "y": 859, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 165, - "height": 26, - "seed": 403581795, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Want min 3 DOT", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 210, - "versionNonce": 2078831715, - "isDeleted": false, - "id": "e1dMmNfjngeSZQw9Q_6bR", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1244, - "y": 861, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 198, - "height": 26, - "seed": 842009251, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "wkpAIY8WXU4_DhUYO1aB9" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "Give max 0.20 BNB", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 197, - "versionNonce": 495152091, - "isDeleted": false, - "id": "wti3vXhEzdwXVYSSC_Yg4", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 919.75, - "y": 1297.75, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 573.6995798319325, - "height": 340.49999999999994, - "seed": 1080165805, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "text", - "version": 194, - "versionNonce": 2141066069, - "isDeleted": false, - "id": "NJGQKI0Bu-BSAQWAGkvs0", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1049.9411764705899, - "y": 1313.487394957982, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 301.8718487394957, - "height": 37.19747899159662, - "seed": 1527490595, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28.61344537815124, - "fontFamily": 1, - "text": "Crafted transaction ", - "baseline": 26.19747899159662, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 402, - "versionNonce": 1602173051, - "isDeleted": false, - "id": "VZPj77ZO7kpLohrkLcsaq", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 946.9327731092449, - "y": 1363.5609243697459, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 380.55882352941154, - "height": 260.3823529411763, - "seed": 245832803, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28.613445378151237, - "fontFamily": 1, - "text": "- transfers: \n - A -- 0.1 BTC --> B\n - B -- 1k XTZ --> C\n - C -- 2.3k ADA --> D\n - D -- 3.3k EUR --> G\n - G -- 3.9 USD --> A\n- Intent A, B, C, D, G", - "baseline": 249.3823529411763, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "line", - "version": 131, - "versionNonce": 282971573, - "isDeleted": false, - "id": "dDR0rbGDKpRDhHVnEbj1j", - "fillStyle": "hachure", - "strokeWidth": 4, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 423.7733438086325, - "y": 1030.9922277551025, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1217.4922266087865, - "height": 0.6666331686519698, - "seed": 1295070381, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": null, - "points": [ - [ - 0, - 0 - ], - [ - 1217.4922266087865, - -0.6666331686519698 - ] - ] - }, - { - "type": "ellipse", - "version": 756, - "versionNonce": 1167558477, - "isDeleted": false, - "id": "Y1Mwn-VLiFFNqjTIzDdGi", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 971.395787942603, - "y": 1084.75, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 142.99999999999994, - "height": 63.000000000000014, - "seed": 803447789, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "wn9QHNTxewgA6oWR_jL0I", - "HtZRI-FNYskH4xD-NSUP7" - ] - }, - { - "type": "text", - "version": 491, - "versionNonce": 1766483021, - "isDeleted": false, - "id": "F7Vff6Hn-nwKWlHga6GEW", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 998.395787942603, - "y": 1100.75, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 87, - "height": 26, - "seed": 1821023587, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent E", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 602, - "versionNonce": 1139487939, - "isDeleted": false, - "id": "wn9QHNTxewgA6oWR_jL0I", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 838.2662361154958, - "y": 1115.6051125581707, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 127.08332287156463, - "height": 3.5588919032202284, - "seed": 1515770947, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "GhbNF28zPBdmlEAk4sj4B", - "focus": 0.24968415527183857, - "gap": 10.417486034576314 - }, - "endBinding": { - "elementId": "Y1Mwn-VLiFFNqjTIzDdGi", - "focus": 0.20198646020593047, - "gap": 6.48230359390098 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 127.08332287156463, - -3.5588919032202284 - ] - ] - }, - { - "type": "arrow", - "version": 537, - "versionNonce": 1389208675, - "isDeleted": false, - "id": "HtZRI-FNYskH4xD-NSUP7", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1119.895787942603, - "y": 1112.25, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 158.02664733117444, - "height": 1.6298954558747027, - "seed": 1000247245, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "Y1Mwn-VLiFFNqjTIzDdGi", - "focus": -0.1521545689608934, - "gap": 5.9061722485449195 - }, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 158.02664733117444, - 1.6298954558747027 - ] - ] - }, - { - "type": "text", - "version": 284, - "versionNonce": 2018617933, - "isDeleted": false, - "id": "N9A9cOEizfeHChb2jpWZz", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 853.395787942603, - "y": 1068.25, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 75, - "height": 26, - "seed": 1106624845, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "1.3 ETH", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 319, - "versionNonce": 1565357325, - "isDeleted": false, - "id": "zd6CqXgDZCINde5uj4yN1", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1109.895787942603, - "y": 1068.25, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 198, - "height": 26, - "seed": 1379632643, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Give max 15K DOGE", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 488, - "versionNonce": 519207363, - "isDeleted": false, - "id": "GhbNF28zPBdmlEAk4sj4B", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 685.5, - "y": 1078.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 142.99999999999994, - "height": 63.000000000000014, - "seed": 1312450381, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "YebzbhwZPZJG7VeGK6buO", - "jrzBVVdi5EqY0mYYzI-qC", - "vUyB6nADBS51AWp7XW9EQ", - "wn9QHNTxewgA6oWR_jL0I", - "KdMOyTpK2hvtrVnL6DNUk" - ] - }, - { - "type": "text", - "version": 229, - "versionNonce": 1476996579, - "isDeleted": false, - "id": "WJ9wOt80gJsxizloGLxfL", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 712.5, - "y": 1094.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 88, - "height": 26, - "seed": 377241603, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent B", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 765, - "versionNonce": 949274637, - "isDeleted": false, - "id": "nADlxrdq_B6PfDfo-HQQG", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 666.2871222634944, - "y": 1200.75, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 142.99999999999994, - "height": 63.000000000000014, - "seed": 1679328365, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Tv4Heajn0ixll9tSaFYAV", - "gSSopQezSVkjkU58H15H_" - ] - }, - { - "type": "text", - "version": 502, - "versionNonce": 720585923, - "isDeleted": false, - "id": "mX16fM9A-0VWPNNJUyNpS", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 693.2871222634944, - "y": 1216.75, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 85, - "height": 26, - "seed": 1794245859, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Intent F", - "baseline": 18, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 622, - "versionNonce": 935737251, - "isDeleted": false, - "id": "Tv4Heajn0ixll9tSaFYAV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 4.670616178776941, - "x": 541.3324695669255, - "y": 1110.447374815089, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 7.812955781063806, - "height": 238.3078431598483, - "seed": 2026335949, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": null, - "endBinding": { - "elementId": "nADlxrdq_B6PfDfo-HQQG", - "focus": 0.07219246840230681, - "gap": 9.727127096910678 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -7.812955781063806, - 238.3078431598483 - ] - ] - }, - { - "type": "arrow", - "version": 492, - "versionNonce": 1577405251, - "isDeleted": false, - "id": "gSSopQezSVkjkU58H15H_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 844.8913829504695, - "y": 1229.7335510585458, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 110.99401075709227, - "height": 3.577325516939027, - "seed": 1824878723, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "2ga4PRzeyXVKa442XCN2Y", - "focus": 1.9182906115878662, - "gap": 15.4835510585458 - }, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 110.99401075709227, - 3.577325516939027 - ] - ] - }, - { - "type": "text", - "version": 434, - "versionNonce": 1228568397, - "isDeleted": false, - "id": "g4OJ4hTt8EHD2ib5qCNue", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 454.2871222634972, - "y": 1186.25, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 165, - "height": 26, - "seed": 1089080621, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Want min 3 DOT", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 323, - "versionNonce": 1748647533, - "isDeleted": false, - "id": "2ga4PRzeyXVKa442XCN2Y", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 825.7871222634944, - "y": 1188.25, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 198, - "height": 26, - "seed": 610953251, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "gSSopQezSVkjkU58H15H_" - ], - "fontSize": 20, - "fontFamily": 1, - "text": "Give max 0.20 BNB", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 658, - "versionNonce": 1450766733, - "isDeleted": false, - "id": "KdMOyTpK2hvtrVnL6DNUk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 4.670616178776941, - "x": 559.906477890532, - "y": 993.9852887975298, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 7.812955781063806, - "height": 238.3078431598483, - "seed": 398907139, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": null, - "endBinding": { - "elementId": "GhbNF28zPBdmlEAk4sj4B", - "focus": -0.05450706234283051, - "gap": 10.753265054089042 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -7.812955781063806, - 238.3078431598483 - ] - ] - }, - { - "type": "text", - "version": 497, - "versionNonce": 1079658179, - "isDeleted": false, - "id": "aM2wnnRNQ8hdsJNJ-zvH6", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 458.36113058710646, - "y": 1069.7879139824408, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 194, - "height": 26, - "seed": 1308118701, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 20, - "fontFamily": 1, - "text": "Want min 0.09 BTC", - "baseline": 18, - "textAlign": "center", - "verticalAlign": "top" - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - } -} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.png b/documentation/dev/src/explore/design/intent_gossip/matchmaker_graph.png deleted file mode 100644 index ab284b0e70511ec0e8e40a0aa5c2bd6c4ed2ec12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156552 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:HJZpaSnO`Nr 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 - - - - - - Intent AIntent BIntent CIntent DIntent EIntent F0.1 BTC1000 XTZGive 2.3k ADAGive max 3.5k EURWant min 3k USD1.3 ETHGive max 15K DOGEWant min 3 DOTGive max 0.20 BNBCrafted transaction - transfers: - A -- 0.1 BTC --> B - B -- 1k XTZ --> C - C -- 2.3k ADA --> D - D -- 3.3k EUR --> G - G -- 3.9 USD --> A- Intent A, B, C, D, GIntent E1.3 ETHGive max 15K DOGEIntent BIntent FWant min 3 DOTGive max 0.20 BNBWant min 0.09 BTC \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.excalidraw b/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.excalidraw deleted file mode 100644 index dcb48040f97..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.excalidraw +++ /dev/null @@ -1,1883 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "text", - "version": 250, - "versionNonce": 477786094, - "isDeleted": false, - "id": "-fWwS7rt9LYqEFNzDaOFL", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1334.071428571429, - "y": 1108.4285714285713, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 115, - "height": 70, - "seed": 1676859603, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "yBtnqGclgLrHk5_Ql7cuO" - ], - "fontSize": 28, - "fontFamily": 3, - "text": "send_tx\n(data)", - "baseline": 63, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "ellipse", - "version": 178, - "versionNonce": 1385647602, - "isDeleted": false, - "id": "-AUKbp8i4Y0O6x-LjVldd", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 297, - "y": 132, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1587.8571428571427, - "height": 1546, - "seed": 2049426387, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "text", - "version": 102, - "versionNonce": 1069695534, - "isDeleted": false, - "id": "8SDYqin0B7kut0s8YSY5Z", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 870, - "y": 178, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 355, - "height": 46, - "seed": 1891888509, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 36, - "fontFamily": 1, - "text": "Matchmaker process", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 666, - "versionNonce": 1176289202, - "isDeleted": false, - "id": "9rcJtdHxvs9duQVcmN6pg", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1018.4827638040854, - "y": -498.6392023935267, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 221.76726681910736, - "height": 784.1206838750081, - "seed": 395336627, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "u-ME94nrrlTlc6E3cTspG", - "focus": 0.5186114360714434, - "gap": 14.860797606473284 - }, - "endBinding": { - "elementId": "20SyJveLAUi7KoBBp_oDS", - "focus": 0.14947349178376995, - "gap": 4.518518518518533 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -221.76726681910736, - 784.1206838750081 - ] - ] - }, - { - "type": "rectangle", - "version": 296, - "versionNonce": 1188416622, - "isDeleted": false, - "id": "20SyJveLAUi7KoBBp_oDS", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 670, - "y": 290, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 201, - "height": 61, - "seed": 1788635837, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rcJtdHxvs9duQVcmN6pg", - "EV9g4uNJem7yTF0HdehQG", - "coek_IDR9PRlSqn_xuBfH" - ] - }, - { - "type": "text", - "version": 126, - "versionNonce": 328071538, - "isDeleted": false, - "id": "u-ME94nrrlTlc6E3cTspG", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 983, - "y": -549.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 197, - "height": 36, - "seed": 1618827037, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rcJtdHxvs9duQVcmN6pg", - "MQZgoJ0QZeyWyyOlwsTJ7", - "wX2vQSP79rzqU32fALRHb" - ], - "fontSize": 28, - "fontFamily": 1, - "text": "gossip mempool", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 194, - "versionNonce": 796790446, - "isDeleted": false, - "id": "CcRIOeocsp5wp09s5Cald", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 683, - "y": 298.5, - "strokeColor": "#d9480f", - "backgroundColor": "transparent", - "width": 176, - "height": 36, - "seed": 1367059613, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "Filter_1.wasm", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 343, - "versionNonce": 439483186, - "isDeleted": false, - "id": "q87Hkm0Sw3bVVd3Wg6HND", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 996.5, - "y": 276.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 201, - "height": 61, - "seed": 903521971, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rcJtdHxvs9duQVcmN6pg", - "wX2vQSP79rzqU32fALRHb" - ] - }, - { - "type": "rectangle", - "version": 386, - "versionNonce": 1449396462, - "isDeleted": false, - "id": "pF6B18evRJlwUi2JcmQxA", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1352.5, - "y": 275.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 201, - "height": 61, - "seed": 2098314525, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "9rcJtdHxvs9duQVcmN6pg", - "MQZgoJ0QZeyWyyOlwsTJ7", - "quXd_TqwuN2CmoyI_vXkX", - "tQlObdUcJ4e-eO1KrOatP", - "AFE8t2QPl0MtqNC64Ktcv" - ] - }, - { - "type": "text", - "version": 268, - "versionNonce": 946849010, - "isDeleted": false, - "id": "dLtlrcEEya8xcYfwZhZia", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1365, - "y": 287, - "strokeColor": "#d9480f", - "backgroundColor": "transparent", - "width": 185, - "height": 36, - "seed": 2000393875, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "quXd_TqwuN2CmoyI_vXkX" - ], - "fontSize": 28, - "fontFamily": 1, - "text": "Filter_n.wasm", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 160, - "versionNonce": 454081326, - "isDeleted": false, - "id": "B5qXjcTYw3uMpWIOVJW6r", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1077.5, - "y": 289, - "strokeColor": "#d9480f", - "backgroundColor": "transparent", - "width": 23, - "height": 36, - "seed": 1953378803, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "...", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "middle" - }, - { - "type": "arrow", - "version": 452, - "versionNonce": 1741575858, - "isDeleted": false, - "id": "MQZgoJ0QZeyWyyOlwsTJ7", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1154.9140472935462, - "y": -501.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 207.63095361581804, - "height": 762.5, - "seed": 52989821, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "u-ME94nrrlTlc6E3cTspG", - "focus": -0.63125, - "gap": 12 - }, - "endBinding": { - "elementId": "pF6B18evRJlwUi2JcmQxA", - "focus": -0.718727980147667, - "gap": 14.5 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 207.63095361581804, - 762.5 - ] - ] - }, - { - "type": "arrow", - "version": 398, - "versionNonce": 65873262, - "isDeleted": false, - "id": "wX2vQSP79rzqU32fALRHb", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1089.7329110152689, - "y": -503.5, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 0.6477652019841571, - "height": 777.5, - "seed": 858158013, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "u-ME94nrrlTlc6E3cTspG", - "focus": -0.08333333333333333, - "gap": 10 - }, - "endBinding": { - "elementId": "q87Hkm0Sw3bVVd3Wg6HND", - "focus": -0.06557377049180328, - "gap": 2.5 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 0.6477652019841571, - 777.5 - ] - ] - }, - { - "type": "rectangle", - "version": 251, - "versionNonce": 1492430962, - "isDeleted": false, - "id": "Qby63QOrxFMccQD3d2c92", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 584.4285714285714, - "y": 892.8571428571428, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 305, - "height": 129.8571428571429, - "seed": 906153683, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "EV9g4uNJem7yTF0HdehQG", - "yBtnqGclgLrHk5_Ql7cuO", - "Wb12KbhuRcEHH5IclCfjz" - ] - }, - { - "type": "text", - "version": 175, - "versionNonce": 1648510894, - "isDeleted": false, - "id": "EU8SONkJD9g8GszHushxg", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 613.4285714285714, - "y": 905.3571428571428, - "strokeColor": "#d9480f", - "backgroundColor": "transparent", - "width": 264, - "height": 34, - "seed": 711796221, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 2, - "text": "matchmaker_1.wasm", - "baseline": 24, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 882, - "versionNonce": 262787634, - "isDeleted": false, - "id": "EV9g4uNJem7yTF0HdehQG", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 739.6354774988769, - "y": 363.8196459125622, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 2.694775060113102, - "height": 59.792753760100425, - "seed": 589900403, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "20SyJveLAUi7KoBBp_oDS", - "focus": 0.32213015232123915, - "gap": 12.81964591256218 - }, - "endBinding": { - "elementId": "NUXnLdbMG_fQSEDQtu2Ik", - "focus": -0.4632754266251567, - "gap": 9.81617175590884 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 2.694775060113102, - 59.792753760100425 - ] - ] - }, - { - "type": "rectangle", - "version": 313, - "versionNonce": 1983455726, - "isDeleted": false, - "id": "02X_kZ6WrMz4xBI8cgnGg", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1349.9285714285716, - "y": 894.7857142857143, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 305, - "height": 148.42857142857147, - "seed": 1614285181, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "EV9g4uNJem7yTF0HdehQG", - "quXd_TqwuN2CmoyI_vXkX", - "Q1Ij1-SrX5G6sFDtWRjZD" - ] - }, - { - "type": "text", - "version": 233, - "versionNonce": 2127870962, - "isDeleted": false, - "id": "zwDG2KxyuTcJmWoH5zXjV", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1378.9285714285716, - "y": 907.2857142857143, - "strokeColor": "#d9480f", - "backgroundColor": "transparent", - "width": 264, - "height": 34, - "seed": 1715804371, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 2, - "text": "matchmaker_n.wasm", - "baseline": 24, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 260, - "versionNonce": 612134322, - "isDeleted": false, - "id": "qFSA3c0L_Ds7pnNR9vyEk", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 988.4285714285716, - "y": 1605.7142857142858, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 221.00000000000006, - "height": 55, - "seed": 1486037725, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "KdQIFFwY0Q-VCyIjJLjqc", - "RuPyouXclWRd1e2Onhfm4", - "tqejUpWw7UdRQgeKJx1Qs", - "jfK56lesWGBAsc29YGxXx" - ] - }, - { - "type": "text", - "version": 175, - "versionNonce": 1108344430, - "isDeleted": false, - "id": "CWmX8zj_7xHE5WtGuwcr9", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1025.4285714285716, - "y": 1615.2142857142858, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 159, - "height": 36, - "seed": 1565496083, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "Tx Channel", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 173, - "versionNonce": 1909000873, - "isDeleted": false, - "id": "NUXnLdbMG_fQSEDQtu2Ik", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 717.9285714285712, - "y": 433.42857142857144, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 97, - "height": 36, - "seed": 335380755, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "EV9g4uNJem7yTF0HdehQG", - "Wb12KbhuRcEHH5IclCfjz", - "AzA1FaOgPEa4cFOKIb_XI" - ], - "fontSize": 28, - "fontFamily": 1, - "text": "is valid", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "rectangle", - "version": 323, - "versionNonce": 1795041586, - "isDeleted": false, - "id": "QmaxQnRFTdOUnNNcMPy7X", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 524.4999999999995, - "y": 1261.9285714285713, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 537.8571428571428, - "height": 67, - "seed": 1680157853, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "EV9g4uNJem7yTF0HdehQG", - "RuPyouXclWRd1e2Onhfm4", - "yBtnqGclgLrHk5_Ql7cuO", - "tqejUpWw7UdRQgeKJx1Qs" - ] - }, - { - "type": "text", - "version": 333, - "versionNonce": 1782168302, - "isDeleted": false, - "id": "NUM8zAtLsxNcBlfW4Jj7Q", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 541.4999999999995, - "y": 1278.4285714285713, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 492, - "height": 35, - "seed": 1032271795, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "yBtnqGclgLrHk5_Ql7cuO" - ], - "fontSize": 28, - "fontFamily": 3, - "text": "tx_wrapper(data:Vec) -> Tx", - "baseline": 28, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 836, - "versionNonce": 998650610, - "isDeleted": false, - "id": "yBtnqGclgLrHk5_Ql7cuO", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 745.9085294063941, - "y": 1042.550408585807, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 0.5420749370830436, - "height": 221.1355590894077, - "seed": 1278179069, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "Qby63QOrxFMccQD3d2c92", - "focus": -0.05746246854310238, - "gap": 19.836122871521297 - }, - "endBinding": { - "elementId": "NUM8zAtLsxNcBlfW4Jj7Q", - "focus": -0.16651713537544344, - "gap": 14.742603753356661 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 0.5420749370830436, - 221.1355590894077 - ] - ] - }, - { - "type": "rectangle", - "version": 405, - "versionNonce": 1298148654, - "isDeleted": false, - "id": "ZJr3NtMIhOWJfBQVo_6Fv", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1195.7857142857133, - "y": 1253.6428571428569, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 534.9999999999999, - "height": 67, - "seed": 422192253, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "EV9g4uNJem7yTF0HdehQG", - "yBtnqGclgLrHk5_Ql7cuO", - "KdQIFFwY0Q-VCyIjJLjqc", - "jfK56lesWGBAsc29YGxXx" - ] - }, - { - "type": "text", - "version": 372, - "versionNonce": 1508312242, - "isDeleted": false, - "id": "HCJ2WIORw1W3_N60QE3F-", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1224.7857142857133, - "y": 1266.1428571428569, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 492, - "height": 35, - "seed": 22425043, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Q1Ij1-SrX5G6sFDtWRjZD" - ], - "fontSize": 28, - "fontFamily": 3, - "text": "tx_wrapper(data:Vec) -> Tx", - "baseline": 28, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 280, - "versionNonce": 533175922, - "isDeleted": false, - "id": "-Bpweaa_yHbmmzQ8Fbx5I", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 786.7142857142856, - "y": 1113.2142857142856, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 115, - "height": 70, - "seed": 194669533, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "yBtnqGclgLrHk5_Ql7cuO" - ], - "fontSize": 28, - "fontFamily": 3, - "text": "send_tx\n(data)", - "baseline": 63, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 170, - "versionNonce": 1490893230, - "isDeleted": false, - "id": "W9zv5kY3tnQEBVzDDO4N6", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 600.1428571428569, - "y": 947.142857142857, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 220, - "height": 72, - "seed": 876039485, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "tries to match\nasset exchange", - "baseline": 61, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 233, - "versionNonce": 435093554, - "isDeleted": false, - "id": "6hdBGIrkrZ5cxGEvo-GL2", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1366.1428571428569, - "y": 957.7142857142857, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 203, - "height": 72, - "seed": 1851337181, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "Q1Ij1-SrX5G6sFDtWRjZD" - ], - "fontSize": 28, - "fontFamily": 1, - "text": "tries to match\nbid for NFT", - "baseline": 61, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 735, - "versionNonce": 10867694, - "isDeleted": false, - "id": "Q1Ij1-SrX5G6sFDtWRjZD", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1475.9494785236807, - "y": 1045.8571428571422, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 0.4267578776036771, - "height": 208.23402295192489, - "seed": 1632703357, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "6hdBGIrkrZ5cxGEvo-GL2", - "focus": -0.08283125635170244, - "gap": 16.14285714285637 - }, - "endBinding": { - "elementId": "HCJ2WIORw1W3_N60QE3F-", - "focus": 0.019007158541393378, - "gap": 12.051691333789677 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -0.4267578776036771, - 208.23402295192489 - ] - ] - }, - { - "type": "text", - "version": 192, - "versionNonce": 444084334, - "isDeleted": false, - "id": "bQlxAsWkDvgA9E8xnJtCS", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 929.2857142857142, - "y": 1434.2857142857142, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 57, - "height": 36, - "seed": 288110269, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "push", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 240, - "versionNonce": 213112178, - "isDeleted": false, - "id": "oCUz5xIu6AGXHaNP05Lz-", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1232.7857142857142, - "y": 1418.2857142857142, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 57, - "height": 36, - "seed": 18964637, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 1, - "text": "push", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "diamond", - "version": 171, - "versionNonce": 125953897, - "isDeleted": false, - "id": "K_xcNPiBXE-cXJS9zVPXW", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1359.2857142857142, - "y": 1675.6428571428573, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 530, - "height": 522.8571428571429, - "seed": 1923266557, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [] - }, - { - "type": "text", - "version": 353, - "versionNonce": 394354599, - "isDeleted": false, - "id": "-b3XUoKQR2HkQtxmLNJ8P", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1460.7857142857144, - "y": 1770.2857142857142, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 341, - "height": 340, - "seed": 307253395, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 28, - "fontFamily": 2, - "text": "intent type \n\n\n- Asset Exchange V1,... ,Vn\n\n- NFT Bid V1, ....,Vm\n\n- Proposal tax rate\n\n- ...", - "baseline": 330, - "textAlign": "center", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 321, - "versionNonce": 1380748402, - "isDeleted": false, - "id": "tqejUpWw7UdRQgeKJx1Qs", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 737.3862781852455, - "y": 1337.5000000000002, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 361.4616837725865, - "height": 256, - "seed": 519759858, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "QmaxQnRFTdOUnNNcMPy7X", - "focus": 0.36506890309214235, - "gap": 8.571428571428896 - }, - "endBinding": { - "elementId": "qFSA3c0L_Ds7pnNR9vyEk", - "focus": 0.3749733951794426, - "gap": 12.214285714285552 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 361.4616837725865, - 256 - ] - ] - }, - { - "type": "arrow", - "version": 309, - "versionNonce": 767591918, - "isDeleted": false, - "id": "jfK56lesWGBAsc29YGxXx", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1464.4663097459384, - "y": 1327.5000000000002, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 304.9230901746971, - "height": 266.0000000000002, - "seed": 1704766194, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "ZJr3NtMIhOWJfBQVo_6Fv", - "focus": -0.15595141865445492, - "gap": 6.857142857143344 - }, - "endBinding": { - "elementId": "qFSA3c0L_Ds7pnNR9vyEk", - "focus": 0.10624312511016634, - "gap": 12.214285714285552 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -304.9230901746971, - 266.0000000000002 - ] - ] - }, - { - "type": "diamond", - "version": 308, - "versionNonce": 1298647335, - "isDeleted": false, - "id": "NgADSumoY4-dLkgWi5N4Z", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 897.9285714285716, - "y": 455.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 425, - "height": 386.99999999999994, - "seed": 335028978, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "EV9g4uNJem7yTF0HdehQG", - "AzA1FaOgPEa4cFOKIb_XI" - ] - }, - { - "type": "text", - "version": 217, - "versionNonce": 1471444327, - "isDeleted": false, - "id": "MvTHemdF09EZ6oXZkE1jc", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1018.9285714285716, - "y": 531.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 170, - "height": 46, - "seed": 724442674, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 36, - "fontFamily": 1, - "text": "database", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 757, - "versionNonce": 1293338825, - "isDeleted": false, - "id": "AzA1FaOgPEa4cFOKIb_XI", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 820.3073244931843, - "y": 471.5118365287101, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 108.81143098291295, - "height": 175.18267082955458, - "seed": 360610866, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "NUXnLdbMG_fQSEDQtu2Ik", - "gap": 5.768100034460303, - "focus": -0.6938097555601055 - }, - "endBinding": { - "elementId": "18SWFQMli2cPThvdOpuoo", - "gap": 7.798248129571567, - "focus": -0.6635324351244143 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 108.81143098291295, - 175.18267082955458 - ] - ] - }, - { - "type": "arrow", - "version": 30, - "versionNonce": 1985069938, - "isDeleted": false, - "id": "coek_IDR9PRlSqn_xuBfH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 668.9285714285716, - "y": 354.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 68, - "height": 62, - "seed": 1014756974, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "20SyJveLAUi7KoBBp_oDS", - "focus": 0.48309968177517726, - "gap": 3.1071428571428896 - }, - "endBinding": { - "elementId": "7tbyhZRSlrMMeTwNPcbDl", - "focus": -0.14152410575427682, - "gap": 6 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -68, - 62 - ] - ] - }, - { - "type": "text", - "version": 27, - "versionNonce": 590603438, - "isDeleted": false, - "id": "7tbyhZRSlrMMeTwNPcbDl", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 540.9285714285716, - "y": 422.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 74, - "height": 46, - "seed": 1767538158, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "coek_IDR9PRlSqn_xuBfH" - ], - "fontSize": 36, - "fontFamily": 1, - "text": "drop", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 32, - "versionNonce": 1173128498, - "isDeleted": false, - "id": "Wb12KbhuRcEHH5IclCfjz", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 774.9285714285716, - "y": 474.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 8, - "height": 406, - "seed": 939628210, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "NUXnLdbMG_fQSEDQtu2Ik", - "focus": -0.1831322697972629, - "gap": 4.678571428571445 - }, - "endBinding": { - "elementId": "Qby63QOrxFMccQD3d2c92", - "focus": 0.1851313721138299, - "gap": 12.749999999999886 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -8, - 406 - ] - ] - }, - { - "type": "text", - "version": 13, - "versionNonce": 1089412846, - "isDeleted": false, - "id": "jEi03IUAXxK1nj2Y42B7G", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 800.9285714285716, - "y": 624.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 53, - "height": 46, - "seed": 665121458, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 36, - "fontFamily": 1, - "text": "run", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 26, - "versionNonce": 1615400690, - "isDeleted": false, - "id": "lI0I06cVAOlfanFTA8Ffo", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 612.9285714285716, - "y": 574.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 65, - "height": 46, - "seed": 752657582, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 36, - "fontFamily": 1, - "text": "add", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 976, - "versionNonce": 1550157735, - "isDeleted": false, - "id": "tQlObdUcJ4e-eO1KrOatP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1459.3837617831516, - "y": 349.05652363076683, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1.1844667472269066, - "height": 55.55587604189577, - "seed": 1629950318, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "pF6B18evRJlwUi2JcmQxA", - "focus": -0.0721870437439987, - "gap": 12.556523630766833 - }, - "endBinding": { - "elementId": "i-tn-ZhFtCTs742XKlQ71", - "focus": -0.29298735309248414, - "gap": 9.81617175590884 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -1.1844667472269066, - 55.55587604189577 - ] - ] - }, - { - "type": "text", - "version": 209, - "versionNonce": 435052722, - "isDeleted": false, - "id": "i-tn-ZhFtCTs742XKlQ71", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1423.428571428571, - "y": 414.42857142857144, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 97, - "height": 36, - "seed": 1973388402, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "tQlObdUcJ4e-eO1KrOatP", - "ho8uhQaYopUXmE-cOb5Oy", - "-aa5R0cDAH1OKloselZA3" - ], - "fontSize": 28, - "fontFamily": 1, - "text": "is valid", - "baseline": 25, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 801, - "versionNonce": 1290356583, - "isDeleted": false, - "id": "ho8uhQaYopUXmE-cOb5Oy", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1438.8508824450746, - "y": 460.6001539378572, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 160.01481739098062, - "height": 177.3752734807801, - "seed": 652742126, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "i-tn-ZhFtCTs742XKlQ71", - "gap": 10.171582509285766, - "focus": 0.08985472450032943 - }, - "endBinding": { - "elementId": "zBxeCe2V0Axk5Bu9Z7zb_", - "focus": 0.10695154534385387, - "gap": 3.009196397424489 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -160.01481739098062, - 177.3752734807801 - ] - ] - }, - { - "type": "arrow", - "version": 309, - "versionNonce": 214916146, - "isDeleted": false, - "id": "AFE8t2QPl0MtqNC64Ktcv", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1553.1148808294615, - "y": 349.7631332623532, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 91.75609240578751, - "height": 126.39460265738433, - "seed": 635080690, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "pF6B18evRJlwUi2JcmQxA", - "focus": -0.5572753230194984, - "gap": 13.263133262353222 - }, - "endBinding": { - "elementId": "SW06q84zYJLMiyPVkQ1xY", - "focus": 0.049091461638458705, - "gap": 8.949406937405342 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 91.75609240578751, - 126.39460265738433 - ] - ] - }, - { - "type": "text", - "version": 129, - "versionNonce": 1871263726, - "isDeleted": false, - "id": "SW06q84zYJLMiyPVkQ1xY", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1628.4285714285716, - "y": 485.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 74, - "height": 46, - "seed": 650911790, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "AFE8t2QPl0MtqNC64Ktcv" - ], - "fontSize": 36, - "fontFamily": 1, - "text": "drop", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "arrow", - "version": 116, - "versionNonce": 314115570, - "isDeleted": false, - "id": "-aa5R0cDAH1OKloselZA3", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1480.4285714285716, - "y": 455.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 8, - "height": 406, - "seed": 329761202, - "groupIds": [], - "strokeSharpness": "round", - "boundElementIds": [], - "startBinding": { - "elementId": "i-tn-ZhFtCTs742XKlQ71", - "focus": -0.18313226979726524, - "gap": 4.678571428571445 - }, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -8, - 406 - ] - ] - }, - { - "type": "text", - "version": 44, - "versionNonce": 1438124590, - "isDeleted": false, - "id": "cl7vkyLcyLB5BfJMeXjQw", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1506.4285714285716, - "y": 605.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 53, - "height": 46, - "seed": 754182766, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 36, - "fontFamily": 1, - "text": "run", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "text", - "version": 200, - "versionNonce": 155678642, - "isDeleted": false, - "id": "DF_4GrKBdi7IsRviOYf09", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1306.4285714285716, - "y": 487.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 65, - "height": 46, - "seed": 1013496690, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [], - "fontSize": 36, - "fontFamily": 1, - "text": "add", - "baseline": 32, - "textAlign": "left", - "verticalAlign": "top" - }, - { - "type": "diamond", - "version": 500, - "versionNonce": 1653880585, - "isDeleted": false, - "id": "18SWFQMli2cPThvdOpuoo", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 921.4285714285716, - "y": 614.6071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 110, - "height": 99.00000000000001, - "seed": 1872566985, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "tQlObdUcJ4e-eO1KrOatP", - "ho8uhQaYopUXmE-cOb5Oy", - "AzA1FaOgPEa4cFOKIb_XI" - ] - }, - { - "id": "bvMzoIc6JDXbDVYJF9Ibg", - "type": "text", - "x": 941.9285714285716, - "y": 654.6071428571429, - "width": 76, - "height": 26, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "strokeSharpness": "sharp", - "seed": 1926098473, - "version": 27, - "versionNonce": 1791601191, - "isDeleted": false, - "boundElementIds": null, - "text": "table 1 ", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "left", - "verticalAlign": "top", - "baseline": 18 - }, - { - "id": "r0pIVYRB3ZOSkyTYj3h2g", - "type": "text", - "x": 1212.9285714285716, - "y": 649.1071428571429, - "width": 70, - "height": 26, - "angle": 0, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "groupIds": [], - "strokeSharpness": "sharp", - "seed": 1775138089, - "version": 27, - "versionNonce": 1051956679, - "isDeleted": false, - "boundElementIds": null, - "text": "table n", - "fontSize": 20, - "fontFamily": 1, - "textAlign": "center", - "verticalAlign": "middle", - "baseline": 18 - }, - { - "type": "diamond", - "version": 524, - "versionNonce": 1186891401, - "isDeleted": false, - "id": "zBxeCe2V0Axk5Bu9Z7zb_", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 1193.9285714285716, - "y": 615.1071428571429, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 110, - "height": 99.00000000000001, - "seed": 748894505, - "groupIds": [], - "strokeSharpness": "sharp", - "boundElementIds": [ - "tQlObdUcJ4e-eO1KrOatP", - "ho8uhQaYopUXmE-cOb5Oy", - "AzA1FaOgPEa4cFOKIb_XI" - ] - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - } -} \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.svg b/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.svg deleted file mode 100644 index 226afae23bb..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/matchmaker_process.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - send_tx(data)Matchmaker processgossip mempoolFilter_1.wasmFilter_n.wasm...matchmaker_1.wasmmatchmaker_n.wasmTx Channelis validtx_wrapper(data:Vec<u8>) -> Txtx_wrapper(data:Vec<u8>) -> Txsend_tx(data)tries to matchasset exchangetries to matchbid for NFTpushpushintent type - Asset Exchange V1,... ,Vn- NFT Bid V1, ....,Vm- Proposal tax rate- ...databasedroprunaddis validdroprunaddtable 1 table n \ No newline at end of file diff --git a/documentation/dev/src/explore/design/intent_gossip/topic.md b/documentation/dev/src/explore/design/intent_gossip/topic.md deleted file mode 100644 index 08a6465d073..00000000000 --- a/documentation/dev/src/explore/design/intent_gossip/topic.md +++ /dev/null @@ -1,11 +0,0 @@ -# Topic - -A topic is string and an encoding that describes this sub-network. In a topic -all intents use the exact same encoding. That encoding is known by matchmakers -so it can decode them to find matches. Whenever a node subscribes to a new topic -it informs all connected nodes and each of them propagate it. With this it’s -easy to create new topics in the intent gossip network and inform others. - -Other nodes can choose to subscribe to a new topic with the help of a -filter. This filter is defined as a combination of a whitelist, a regex -expression, and a maximum limit. diff --git a/documentation/dev/src/explore/prototypes/README.md b/documentation/dev/src/explore/prototypes/README.md deleted file mode 100644 index 375dd805c06..00000000000 --- a/documentation/dev/src/explore/prototypes/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Prototypes - -A prototype should start with a description of its goals. These can include, but are not limited to a proof of concept of novel ideas or alternative approaches, comparing different libraries and gathering feedback. - -To get started on a prototype, please: -- open an issue on this repository -- add a sub-page to this section with a link to the issue - -The page outlines the goals and possibly contains any notes that are not suitable to be added to the prototype source itself, while the issue should track the sub-task, their progress, and assignees. - -The code quality is of lesser importance in prototypes. To put the main focus on the prototype's goals, we don't need to worry much about testing, linting and doc strings. - -## Advancing a successful prototype - -Once the goals of the prototype have been completed, we can assess if we'd like to advance the prototype to a development version. - -In order to advance a prototype, in general we'll want to: -- review & clean-up the code for lint, format and best practices -- enable common Rust lints -- review any new dependencies -- add docs for any public interface (internally public too) -- add automated tests -- if the prototype has diverged from the original design, update these pages diff --git a/documentation/dev/src/explore/prototypes/base-ledger.md b/documentation/dev/src/explore/prototypes/base-ledger.md deleted file mode 100644 index 7443568ba5d..00000000000 --- a/documentation/dev/src/explore/prototypes/base-ledger.md +++ /dev/null @@ -1,112 +0,0 @@ -# Base ledger prototype - -## Version 3 - -tracking issue - - -### Goals - -- various shell and protocol fixes, improvements and additions -- add more crypto support -- WASM improvements -- implement new validity predicates -- storage improvements -- gas & fees -- fixes for issues found in the Feigenbaum testnet -- IBC integration -- Ferveo/ABCI++ integration -- PoS improvements and new features - - testing (unit + integration + e2e) - - storage values refactoring - - use checked arithmetics - - validator VP - - staking rewards - - staking reward VP - - re-delegation - - validator - - deactivation/reactivation - - change consensus key - -## Version 2 - -tracking issue - -### Goals - -- storage - - build key schema for access - - implement dynamic account sub-spaces -- implement more complete support for WASM transactions and validity predicates - - transactions can read/write all storage - - validity predicates receive the set of changes (changed keys or complete write log) and can read their pre/post state -- add basic transaction gas metering -- various other improvements - -## Version 1 - -tracking issue - -### Goals - -- get some hands-on experience with Rust and Tendermint -- initial usable node + client (+ validator?) setup -- provide a base layer for other prototypes that need to build on top of a ledger - -### Components - -The main components are built in a single Cargo project with [shared library code](#shared) and multiple binaries: -- `anoma` - main executable with commands for both the node and the client (`anoma node` and `anoma client`) -- `anoman` - the [node](#node) -- `anomac` - the [client](#client) - -#### Node - -The node is built into `anoman`. - -##### Shell - -The shell is what currently pulls together all the other components in the node. - -When it's ran: -- establish a channel (e.g.`mpsc::channel` - Multi-producer, single-consumer FIFO queue) for communication from tendermint to the shell -- launch tendermint node in another thread with the channel sender - - send tendermint ABCI requests via the channel together with a new channel sender to receive a response -- run shell loop with the channel receiver, which handles ABIC requests: - - [transaction execution](../design/ledger/tx.md) which includes [wasm VM calls](../design/ledger/wasm-vm.md) - -###### Tendermint - -This module handles initializing and running `tendermint` and forwards messages for the ABCI requests via its channel sender. - -##### Storage - -Key-value storage. More details are specified on [Storage page](../design/ledger/storage.md). - -##### CLI - -- `anoma run` to start the node (will initialize (if needed) and launch tendermint under the hood) -- `anoma reset` to delete all the node's state from DB and tendermint's state - -#### Client - -Allows to submit a transaction with an attached wasm code to the node with: - -`anoma tx --code tx.wasm` - -It presents back the received response on stdout. Currently, it waits for both the mempool validation and application in a block. - -#### Shared - -##### Config - -Configuration settings: -- home directory (db storage and tendermint config and data) - -##### Genesis - -The genesis parameters, such as the initial validator set, are used to initialize a chain's genesis block. - -##### RPC types - -The types for data that can be submitted to the node via the client's RPC commands. diff --git a/documentation/dev/src/explore/prototypes/gossip-layer.md b/documentation/dev/src/explore/prototypes/gossip-layer.md deleted file mode 100644 index b991ca5c686..00000000000 --- a/documentation/dev/src/explore/prototypes/gossip-layer.md +++ /dev/null @@ -1,61 +0,0 @@ -# Intent Gossip system prototype - -## Version 2 - -tracking issue - -- Separate matchmakers from intent gossiper nodes -- Various fixes and improvements -- fixes for issues found in the Feigenbaum testnet -- Persistent storage -- Intent gossip and matching of complex txs -- multi-party trades (e.g. 10) -- multi-asset trades (FT, NFT) -- NFT swaps -- Benchmarking base load for the entire network -- Incentives -- Docs - -## Version 1 - -tracking issue - -### Goals - -- learning rust -- usable node + client setup : - - intent - - incentive function - - mempool and white list -- basic matchmaker - -### components - -The intent gossip is build conjointly to the ledger and share the same binary. - -#### Node - -The node is built into `anoman`, it runs all the necesarry part, rpc server, -libp2p, intent gossip app. - -##### Intent gossip application - -The intent gossip application - -###### Mempool - -###### Filter - -##### Network behaviour -The network behaviour is the part that react on network event. It creates a -channel (e.g. `tokio::mpsc::channel`) with the intent gossip to communicate all -intent it receive. - -##### Rpc server -If the rpc command line option is set it creates a tonic server that receive -command from a client and send theses through a channel -(e.g. `tokio::mpsc::channel`) to the the intent gossip. - -#### Client -Allow to submit a intent : -`anoma gossip --data "data"` From 4060f2eaa1ce70f17b211252cb915a467edc9226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 2 Aug 2022 18:19:52 +0200 Subject: [PATCH 190/394] docs/dev: update mdbook-admonish assets to v2 --- documentation/dev/assets/mdbook-admonish.css | 36 ++++++++++++++++++++ documentation/dev/book.toml | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/documentation/dev/assets/mdbook-admonish.css b/documentation/dev/assets/mdbook-admonish.css index 5d83c334d6b..5e360387df1 100644 --- a/documentation/dev/assets/mdbook-admonish.css +++ b/documentation/dev/assets/mdbook-admonish.css @@ -1,3 +1,4 @@ +@charset "UTF-8"; :root { --md-admonition-icon--note: url("data:image/svg+xml;charset=utf-8,"); @@ -23,6 +24,8 @@ url("data:image/svg+xml;charset=utf-8,"); --md-admonition-icon--quote: url("data:image/svg+xml;charset=utf-8,"); + --md-details-icon: + url("data:image/svg+xml;charset=utf-8,"); } :is(.admonition) { @@ -56,12 +59,21 @@ html :is(.admonition) > :last-child { margin-bottom: 1.2rem; } +a.admonition-anchor-link { + display: none; + position: absolute; + left: -1.2rem; + padding-right: 1rem; +} a.admonition-anchor-link:link, a.admonition-anchor-link:visited { color: var(--fg); } a.admonition-anchor-link:link:hover, a.admonition-anchor-link:visited:hover { text-decoration: none; } +a.admonition-anchor-link::before { + content: "§"; +} :is(.admonition-title, summary) { position: relative; @@ -94,6 +106,30 @@ html :is(.admonition-title, summary):last-child { -webkit-mask-size: contain; content: ""; } +:is(.admonition-title, summary):hover a.admonition-anchor-link { + display: initial; +} + +details.admonition > summary.admonition-title::after { + position: absolute; + top: 0.625em; + inset-inline-end: 1.6rem; + height: 2rem; + width: 2rem; + background-color: currentcolor; + mask-image: var(--md-details-icon); + -webkit-mask-image: var(--md-details-icon); + mask-repeat: no-repeat; + -webkit-mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-size: contain; + content: ""; + transform: rotate(0deg); + transition: transform 0.25s; +} +details[open].admonition > summary.admonition-title::after { + transform: rotate(90deg); +} :is(.admonition):is(.note) { border-color: #448aff; diff --git a/documentation/dev/book.toml b/documentation/dev/book.toml index 585a0875b28..a1728506b9d 100644 --- a/documentation/dev/book.toml +++ b/documentation/dev/book.toml @@ -29,4 +29,4 @@ renderer = ["html"] [preprocessor.admonish] command = "mdbook-admonish" -assets_version = "1.0.0" # do not edit: managed by `mdbook-admonish install` +assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install` From 3874b7b73c9a31b3aee02189ee977c2eb32e41d3 Mon Sep 17 00:00:00 2001 From: Tomas Zemanovic Date: Thu, 11 Aug 2022 10:16:01 +0200 Subject: [PATCH 191/394] Apply suggestions from code review --- documentation/dev/book.toml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/dev/book.toml b/documentation/dev/book.toml index a1728506b9d..f1b171cd245 100644 --- a/documentation/dev/book.toml +++ b/documentation/dev/book.toml @@ -2,13 +2,14 @@ authors = ["Heliax R&D Team"] language = "en" multilingual = false -site-url = "https://docs.anoma.network/master/" +# This book is currently not being hosted +# site-url = "https://docs.anoma.network/master/" src = "src" title = "Anoma - DOCS" [output.html] -edit-url-template = "https://github.com/anoma/anoma/edit/master/docs/{path}" -git-repository-url = "https://github.com/anoma/anoma" +edit-url-template = "https://github.com/anoma/namada/edit/master/documentation/dev/{path}" +git-repository-url = "https://github.com/anoma/namada" additional-css = ["assets/custom.css", "assets/mdbook-admonish.css"] additional-js = ["assets/mermaid.min.js", "assets/mermaid-init.js"] mathjax-support = true From 1de31626af013d0467c2f76ca94f13b9e1c3424e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 08:30:36 +0200 Subject: [PATCH 192/394] docs/dev: s/anoma/namada --- documentation/dev/README.md | 7 +---- documentation/dev/src/README.md | 14 ++++------ documentation/dev/src/explore/README.md | 2 +- .../dev/src/explore/design/actors.md | 6 ++-- .../dev/src/explore/design/ledger.md | 14 ++++++---- .../src/explore/design/ledger/governance.md | 28 ++++++++++--------- .../explore/design/ledger/pos-integration.md | 8 +++++- .../dev/src/explore/design/ledger/storage.md | 12 +++++--- .../dev/src/explore/design/testnet-setup.md | 2 +- documentation/dev/src/specs/README.md | 4 +-- documentation/dev/src/specs/crypto.md | 6 ++-- documentation/dev/src/specs/encoding.md | 2 +- documentation/dev/src/specs/ledger.md | 2 +- .../src/specs/ledger/default-transactions.md | 4 +-- .../ledger/default-validity-predicates.md | 2 +- documentation/dev/src/specs/ledger/rpc.md | 2 +- documentation/dev/src/specs/overview.md | 2 +- 17 files changed, 63 insertions(+), 54 deletions(-) diff --git a/documentation/dev/README.md b/documentation/dev/README.md index 99d845411ce..319003497a8 100644 --- a/documentation/dev/README.md +++ b/documentation/dev/README.md @@ -1,11 +1,6 @@ See the [Introduction](./src/). In short: + - `make dev-deps` install dependencies - `make serve` open the rendered mdBook in your default browser - -Using Nix: - -```bash -nix develop ..#anoma-docs -c make serve -``` diff --git a/documentation/dev/src/README.md b/documentation/dev/src/README.md index b44651e303a..3108c65cdbf 100644 --- a/documentation/dev/src/README.md +++ b/documentation/dev/src/README.md @@ -1,10 +1,10 @@ # Introduction -Welcome to Anoma's docs! +Welcome to Namada's docs! -## About Anoma +## About Namada -[Anoma](https://anoma.network/) is a sovereign, proof-of-stake blockchain protocol that enables private, asset-agnostic cash and private bartering among any number of parties. To learn more about the protocol, we recommend the following resources: +Namada is a sovereign, proof-of-stake blockchain protocol that enables private, asset-agnostic cash and private bartering among any number of parties. To learn more about the protocol, we recommend the following resources: - [Introduction to Anoma Medium article](https://medium.com/anomanetwork/introducing-anoma-a-blockchain-for-private-asset-agnostic-bartering-dcc47ac42d9f) - [Anoma's Whitepaper](https://anoma.network/papers/whitepaper.pdf) @@ -16,14 +16,12 @@ Welcome to Anoma's docs! The two main sections of this book are: -- [Exploration](./explore): documents the process of exploring the design and implementation space for Anoma +- [Exploration](./explore): documents the process of exploring the design and implementation space for Namada - [Specifications](./specs): implementation independent technical specifications -The Anoma user guide and networks documentation can be found at . - ### The source -This book is written using [mdBook](https://rust-lang.github.io/mdBook/) with [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid) for diagrams, it currently lives in the [Anoma repo](https://github.com/anoma/anoma). +This book is written using [mdBook](https://rust-lang.github.io/mdBook/) with [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid) for diagrams, it currently lives in the [Namada repo](https://github.com/anoma/namada). To get started quickly, in the `docs` directory one can: @@ -37,4 +35,4 @@ make serve The mermaid diagrams docs can be found at . -[Contributions](https://github.com/anoma/anoma/issues) to the contents and the structure of this book (nothing is set in stone) should be made via pull requests. Code changes that diverge from the spec should also update this book. +[Contributions](https://github.com/anoma/namada/issues) to the contents and the structure of this book (nothing is set in stone) should be made via pull requests. Code changes that diverge from the spec should also update this book. diff --git a/documentation/dev/src/explore/README.md b/documentation/dev/src/explore/README.md index fc5b5117cf2..3614c61542f 100644 --- a/documentation/dev/src/explore/README.md +++ b/documentation/dev/src/explore/README.md @@ -1,5 +1,5 @@ # Exploration -This section documents the process of exploring the design and implementation space for Anoma. Ideally, the captured information should provide an overview of the explored space and help to guide further decisions. +This section documents the process of exploring the design and implementation space for Namada. Ideally, the captured information should provide an overview of the explored space and help to guide further decisions. The content of this section is more free-form. This is largely a cross-over of both the implementation details and the design of implementation-independent specifications. diff --git a/documentation/dev/src/explore/design/actors.md b/documentation/dev/src/explore/design/actors.md index d742097db74..88e26f3d640 100644 --- a/documentation/dev/src/explore/design/actors.md +++ b/documentation/dev/src/explore/design/actors.md @@ -1,10 +1,10 @@ # Actors and Incentives -Anoma consists of various actors fulfilling various roles in the network. They are all incentivized to act for the good of the network. The native Anoma token `XAN` is used to settle transaction fees and pay for the incentives in Anoma. +Namada consists of various actors fulfilling various roles in the network. They are all incentivized to act for the good of the network. The native Namada token `XAN` is used to settle transaction fees and pay for the incentives in Namada. ## Fees associated with a transaction -Users of Anoma can +Users of Namada can - transfer private assets they hold to other users and - barter assets with other users. @@ -20,7 +20,7 @@ Each transaction may be associated with the following fees, paid in `XAN`: | Actor | Responsibilities | Incentives | Bond in escrow | May also be | |---|---|---|---|---| -| User | Make offers or send transactions | Features of Anoma | X | Anyone | +| User | Make offers or send transactions | Features of Namada | X | Anyone | | Signer | Generate key shards | portions of init_f, exe_f | ✓ | Validator | | Validator | Validate | portions of init_f, exe_f |✓ | Signer | | Submitter | Submit orders & pay init_f | successful orders get init_f back plus bonus | X | | diff --git a/documentation/dev/src/explore/design/ledger.md b/documentation/dev/src/explore/design/ledger.md index 3c3fd8ce253..25b32ee4e7e 100644 --- a/documentation/dev/src/explore/design/ledger.md +++ b/documentation/dev/src/explore/design/ledger.md @@ -1,6 +1,6 @@ # The ledger -The ledger depends on [Tendermint node](https://github.com/tendermint/tendermint). Running the Anoma node will also initialize and run Tendermint node. Anoma communicates with Tendermint via the ABCI. +The ledger depends on [Tendermint node](https://github.com/tendermint/tendermint). Running the Namada node will also initialize and run Tendermint node. Namada communicates with Tendermint via the ABCI. ## Overview @@ -22,11 +22,13 @@ Configuration for threads usage can be changed via environment variables: We are using the Tendermint state-machine replication engine via ABCI. It provides many useful things, such as a BFT consensus protocol, P2P layer with peer exchange, block sync and mempool layer. Useful resources: + - Tendermint ABCI - Tendermint RPC reference - Awesome collection Rust ABCI implementations: + - - the future update planned for this crate is to add async support - longer term the goal is to be able to [seamlessly switch from Go Tendermint @@ -39,14 +41,14 @@ Rust ABCI implementations: ### ABCI Integration -The ledger wraps the Tendermint node inside the Anoma node. The Tendermint node -communicates with the Anoma shell via four layers as illustrated below. +The ledger wraps the Tendermint node inside the Namada node. The Tendermint node +communicates with the Namada shell via four layers as illustrated below. ```mermaid flowchart LR C[Client] --- R - subgraph Anoma Node - S((Anoma Shell)) + subgraph Namada Node + S((Namada Shell)) subgraph Tendermint ABCI R[RPC] === T{Tendermint} T --- TC[Consensus] @@ -62,6 +64,7 @@ flowchart LR ``` The *consensus* connection allows the shell to: + - initialize genesis on start-up - begin a block - apply a transaction(s) in a block @@ -74,6 +77,7 @@ the transaction is either new, when it has not been validated before, or to be re-checked when it has been validated at some previous level. The *query* connection is used for: + - the Tendermint node asks the last known state from the shell to determine if it needs to replay any blocks - relay client queries for some state at a given path to the shell diff --git a/documentation/dev/src/explore/design/ledger/governance.md b/documentation/dev/src/explore/design/ledger/governance.md index d518b99ae8f..21fdc55da12 100644 --- a/documentation/dev/src/explore/design/ledger/governance.md +++ b/documentation/dev/src/explore/design/ledger/governance.md @@ -1,14 +1,16 @@ # Governance -Anoma 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 Anoma 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, Anoma relies an off chain signaling mechanism to agree on a common strategy. +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 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 Also, it introduces some protocol parameters: + - `min_proposal_fund` - `max_proposal_code_size` - `min_proposal_period` @@ -19,6 +21,7 @@ Also, it introduces some protocol parameters: ## On-chain proposals On-chain proposals are created under the `governance_address` storage space and, by default, this storage space is initialized with following storage keys: + ``` /$GovernanceAddress/counter: u64 /$GovernanceAddress/min_proposal_fund: u64 @@ -30,6 +33,7 @@ On-chain proposals are created under the `governance_address` storage space and, ``` In order to create a valid proposal, a transaction need to modify these storage keys: + ``` /$GovernanceAddress/proposal/$id/content : Vec /$GovernanceAddress/proposal/$id/author : Address @@ -41,18 +45,19 @@ In order to create a valid proposal, a transaction need to modify these storage ``` and follow these rules: + - `$id` must be equal to `counter + 1`. - `startEpoch` must: - - 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`. + - 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` epoch greater than `startEpoch` + - be a multiple of `min_proposal_period` - `graceEpoch` must: - - be at least `min_grace_epoch` epochs greater than `endEpoch` + - 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. - `funds` must be equal to `min_proposal_fund` and should be moved to the `governance_address`. -- `content` should follow the `Anoma Improvement Proposal schema` and must be less than `max_proposal_content_size` kibibytes. +- `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. @@ -62,7 +67,7 @@ The `proposal_code` field can execute arbitrary code in the form of a wasm trans Proposal can be submitted by any address as long as the above rules are respected. Votes can be casted only by active validators and delegator (at epoch `startEpoch` or less). Moreover, validator can vote only during the first 2/3 of the voting period (from `startEpoch` and 2/3 of `endEpoch` - `startEpoch`). -The preferred content template (`Anoma Improvement Proposal schema`) is the following: +The preferred content template (`Namada Improvement Proposal schema`) is the following: ```json { @@ -79,6 +84,7 @@ The preferred content template (`Anoma Improvement Proposal schema`) is the foll ``` In order to vote a proposal, a transaction should modify the following storage key: + ``` /$GovernanceAddress/proposal/$id/vote/$validator_address/$voter_address: ProposalVote ``` @@ -86,6 +92,7 @@ In order to vote a proposal, a transaction should modify the following storage k where ProposalVote is a borsh encoded string containing either `yay` or `nay`, `$validator_address` is the delegation validator address and the `$voter_address` is the address of who is voting. A voter can be cast for each delegation. Vote is valid if it follow this rules: + - vote can be sent only by validator or delegators - validator can vote only during the first 2/3 of the total voting period, delegator can vote for the whole voting period @@ -93,12 +100,7 @@ The outcome of a proposal is compute at the epoch specific in the `endEpoch` fie 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. - ## Off-chain proposal In case where its not possibile to run a proposal online (for example, when the chain is halted), an offline mechanism can be used. The ledger offers the possibility to create and sign proposal which are verified against a specific chain epoch. - - - - diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index bf6b0f69522..992a9b043d4 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -1,6 +1,7 @@ # PoS integration -The [PoS system](../pos.md) is integrated into Anoma ledger at 3 different layers: +The [PoS system](../pos.md) is integrated into Namada ledger at 3 different layers: + - base ledger that performs genesis initialization, validator set updates on new epoch and applies slashes when they are received from ABCI - an account with an internal address and a [native VP](vp.md#native-vps) that validates any changes applied by transactions to the PoS account state - transaction WASMs to perform various PoS actions, also available as a library code for custom made transactions @@ -153,6 +154,7 @@ In the following description, "pre-state" is the state prior to transaction exec Any changes to PoS epoched data are checked to update the structure as described in [epoched data storage](../pos.md#storage). Because some key changes are expected to relate to others, the VP also accumulates some values that are checked for validity after key specific logic: + - `balance_delta: token::Change` - `bond_delta: HashMap` - `unbond_delta: HashMap` @@ -173,6 +175,7 @@ For any updated epoched data, the `last_update` field must be set to the current The validity predicate triggers a validation logic based on the storage keys modified by a transaction: - `validator/{validator_address}/consensus_key`: + ```rust,ignore match (pre_state, post_state) { (None, Some(post)) => { @@ -188,7 +191,9 @@ The validity predicate triggers a validation logic based on the storage keys mod _ => false, } ``` + - `validator/{validator_address}/state`: + ```rust,ignore match (pre_state, post_state) { (None, Some(post)) => { @@ -207,6 +212,7 @@ The validity predicate triggers a validation logic based on the storage keys mod _ => false, } ``` + - `validator/{validator_address}/total_deltas`: - find the difference between the pre-state and post-state values and add it to the `total_deltas` accumulator and update `total_stake_by_epoch`, `expected_voting_power_by_epoch` and `expected_total_voting_power_delta_by_epoch` - `validator/{validator_address}/voting_power`: diff --git a/documentation/dev/src/explore/design/ledger/storage.md b/documentation/dev/src/explore/design/ledger/storage.md index 7186dd00a35..e441cbb2d06 100644 --- a/documentation/dev/src/explore/design/ledger/storage.md +++ b/documentation/dev/src/explore/design/ledger/storage.md @@ -53,6 +53,7 @@ It's very likely that different settings for immutable storage will be provided We'd like to have easily reproducible benchmarks for the whole database integration that should be filled over time with pre-generated realistic data. This should enable us to tune and compare different hashing functions, backends, data structures, memory layouts, etc. ### Criteria + - in-memory - writes (insert, update, delete) - possibly also concurrent writes, pending on the approach taken for concurrent transaction execution @@ -71,25 +72,27 @@ The considered options for a DB backend are given in [Libraries & Tools / Databa A committed block is not immediately persisted on RocksDB. When the block is committed, a set of key-value pairs which compose the block is written to the memtable on RocksDB. For the efficient sequential write, a flush is executed to persist the data on the memtable to the disk as a file when the size of the memtable is getting big (the threshold is one of the tuning parameters). -We can disable write-ahead log(WAL) which protects these data on the memtable from a crash by persisting the write logs to the disk. Disabling WAL helps reduce the write amplification. That's because WAL isn't required for Anoma because other nodes have the block. The blocks which have not been persisted to the disk by flush can be recovered even if an Anoma node crashes. +We can disable write-ahead log(WAL) which protects these data on the memtable from a crash by persisting the write logs to the disk. Disabling WAL helps reduce the write amplification. That's because WAL isn't required for Namada because other nodes have the block. The blocks which have not been persisted to the disk by flush can be recovered even if an Namada node crashes. ## Implementation ### `storage` module -This is the main interface for interacting with storage in Anoma. +This is the main interface for interacting with storage in Namada. This module and its sub-modules should implement the in-memory storage (and/or a cache layer) with Merkle tree (however, the interface should be agnostic to the choice of vector commitment scheme or whether or not there even is one, we may want non-Merklised storage) and the persistent DB. The in-memory storage holds chain's metadata and current block's storage. Its public API should allow/provide: + - get the Merkle root and Merkle tree proofs - read-only storage API for ledger's metadata to be accessible for transactions' code, VPs and the RPC - with public types of all the stored metadata - unless specified otherwise, read the state from the current block -An API made visible only to the shell module (e.g. `pub ( in SimplePath )` - https://doc.rust-lang.org/reference/visibility-and-privacy.html) should allow the shell to: +An API made visible only to the shell module (e.g. `pub ( in SimplePath )` - ) should allow the shell to: + - load state from DB for latest persisted block or initialize a new storage if none found - begin a new block - within a block: @@ -100,6 +103,7 @@ An API made visible only to the shell module (e.g. `pub ( in SimplePath )` - htt - commit the current block (persist to storage) ### `storage/merkle_tree` module + It consists of one Sparse Merkle Tree (base tree) and multiple Sparse Merkle Trees (subtrees). The base tree stores the store type and the root of each subtree as a key-value pair. Each subtree has the hashed key-value pairs for each data. ```mermaid @@ -143,4 +147,4 @@ The persistent DB implementation (e.g. RocksDB). ### DB keys -The DB keys are composed of key segments. A key segment can be an `Address` which starts with `#` (there can be multiple addresses involved in a key) or any user defined non-empty utf-8 string (maybe limited to only alphanumerical characters). Also, `/` and `?` are reserved. `/` is used as a separator for segments. `?` is reserved for a validity predicate and the key segment `?` can be specified only by the specific API. \ No newline at end of file +The DB keys are composed of key segments. A key segment can be an `Address` which starts with `#` (there can be multiple addresses involved in a key) or any user defined non-empty utf-8 string (maybe limited to only alphanumerical characters). Also, `/` and `?` are reserved. `/` is used as a separator for segments. `?` is reserved for a validity predicate and the key segment `?` can be specified only by the specific API. diff --git a/documentation/dev/src/explore/design/testnet-setup.md b/documentation/dev/src/explore/design/testnet-setup.md index c0bbff99f6b..a936aa7b652 100644 --- a/documentation/dev/src/explore/design/testnet-setup.md +++ b/documentation/dev/src/explore/design/testnet-setup.md @@ -6,7 +6,7 @@ Starting from a release branch, we configure the network that will run on this r Prepare a genesis configuration file. You can start from one of the source files in the [anoma-network-config repo](https://github.com/heliaxdev/anoma-network-config/tree/master/src) or the source files inside the `genesis` directory in this repository, or start from scratch. Note that in this file, for any account for which you don't specify address and/or keys, they will be automatically generated in the next step and saved in wallet(s) in the network "setup" directory. -Additionally, for validator accounts you should also specify their `net_address`. Note that for each validator node we're using up to 5 ports (3 for the ledger and 2 for the intent gossip), so if multiple validators are running behind the same public IP, their ports should be increments of 5 (e.g. `26656`, `26661`, ...). A port supplied in the `net_address` will be used for the node's P2P address. The ledger's RPC address address is its `{port + 1}` and the Anoma ledger's port is `{port + 2}`. The intent gossip will run on `{port + 3}` and its RPC server at `{post + 4}`. +Additionally, for validator accounts you should also specify their `net_address`. Note that for each validator node we're using up to 5 ports (3 for the ledger and 2 for the intent gossip), so if multiple validators are running behind the same public IP, their ports should be increments of 5 (e.g. `26656`, `26661`, ...). A port supplied in the `net_address` will be used for the node's P2P address. The ledger's RPC address address is its `{port + 1}` and the Namada ledger's port is `{port + 2}`. The intent gossip will run on `{port + 3}` and its RPC server at `{post + 4}`. In the genesis file, also set the `genesis_time` in [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) format, e.g. `2021-09-30T10:00:00Z`. It's the time the blockchain started or will start. If nodes are started before this time they will sit idle until the time specified. diff --git a/documentation/dev/src/specs/README.md b/documentation/dev/src/specs/README.md index 2140ea906e2..d6095a56cec 100644 --- a/documentation/dev/src/specs/README.md +++ b/documentation/dev/src/specs/README.md @@ -1,7 +1,7 @@ # Specifications -[Anoma](https://anoma.network/papers/whitepaper.pdf) is a sovereign, proof-of-stake blockchain protocol that enables private, asset-agnostic cash and private bartering among any number of parties. +Namada is a sovereign, proof-of-stake blockchain protocol that enables private, asset-agnostic cash and private bartering among any number of parties. -This specification defines the Anoma ledger's protocol and its components and the intent gossip and matchmaking system. +This specification defines the Namada ledger's protocol and its components. The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC-2119](https://www.rfc-editor.org/rfc/rfc2119). diff --git a/documentation/dev/src/specs/crypto.md b/documentation/dev/src/specs/crypto.md index 577b048b44f..e2879790bb4 100644 --- a/documentation/dev/src/specs/crypto.md +++ b/documentation/dev/src/specs/crypto.md @@ -1,11 +1,11 @@ # Cryptographic schemes -Anoma currently supports Ed25519 signatures with more to be added: +Namada currently supports Ed25519 signatures with more to be added: - [`Secp256k1`](https://github.com/anoma/anoma/issues/162) - [`Sr25519`](https://github.com/anoma/anoma/issues/646) -Please note that the Anoma's crypto public API and encoding is currently undergoing some breaking changes with . +Please note that the Namada's crypto public API and encoding is currently undergoing some breaking changes with . ## Public keys @@ -13,4 +13,4 @@ A public key is a [Borsh encoded `PublicKey`](encoding.md#publickey). For the Ed ## Signatures -A signature in Anoma is a [Borsh encoded `Signature`](encoding.md#signature). For the Ed25519 scheme, this is 64 bytes of Ed25519 signature, prefixed with `64` in little endian encoding (`[64, 0, 0, 0]` in raw bytes or `40000000` in hex). (TODO this will change with ) +A signature in Namada is a [Borsh encoded `Signature`](encoding.md#signature). For the Ed25519 scheme, this is 64 bytes of Ed25519 signature, prefixed with `64` in little endian encoding (`[64, 0, 0, 0]` in raw bytes or `40000000` in hex). (TODO this will change with ) diff --git a/documentation/dev/src/specs/encoding.md b/documentation/dev/src/specs/encoding.md index 8b9de70617c..728390c153a 100644 --- a/documentation/dev/src/specs/encoding.md +++ b/documentation/dev/src/specs/encoding.md @@ -2,7 +2,7 @@ ## The ledger -Most of the data in Anoma are encoded with [Borsh](#borsh-binary-encoding), except for the outer layer of [transactions](#transactions) that are being passed via Tendermint and therefore are required to be encoded with protobuf. +Most of the data in Namada are encoded with [Borsh](#borsh-binary-encoding), except for the outer layer of [transactions](#transactions) that are being passed via Tendermint and therefore are required to be encoded with protobuf. ### Borsh binary encoding diff --git a/documentation/dev/src/specs/ledger.md b/documentation/dev/src/specs/ledger.md index 54de087ca10..de6ff37f989 100644 --- a/documentation/dev/src/specs/ledger.md +++ b/documentation/dev/src/specs/ledger.md @@ -26,7 +26,7 @@ The addresses are stored on-chain encoded with [bech32m](https://github.com/bitc The human-readable prefix (as specified for [bech32](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#specification)) in the transparent address encoding is: -- `"a"` for Anoma live network (80 characters in total) +- `"a"` for Namada live network (80 characters in total) - `"atest"` for test networks (84 characters in total) ## Transactions diff --git a/documentation/dev/src/specs/ledger/default-transactions.md b/documentation/dev/src/specs/ledger/default-transactions.md index 78b04ff48d8..78ed5b2098e 100644 --- a/documentation/dev/src/specs/ledger/default-transactions.md +++ b/documentation/dev/src/specs/ledger/default-transactions.md @@ -1,12 +1,12 @@ # Default transactions -The Anoma client comes with a set of pre-built transactions. Note that the Anoma ledger is agnostic about the format of the transactions beyond the format described in [ledger transaction section](../ledger.md#transactions). +The Namada client comes with a set of pre-built transactions. Note that the Namada ledger is agnostic about the format of the transactions beyond the format described in [ledger transaction section](../ledger.md#transactions). The [default validity predicates](default-validity-predicates.md) can be used to initialize the network's genesis block. These expect the data in the storage to be encoded with [Borsh](../encoding.md#borsh-binary-encoding) and are fully compatible with the default transactions described below. ## Rust-to-WASM transactions -The following transactions are pre-built from Rust code and can be used by clients interacting with the Anoma ledger. +The following transactions are pre-built from Rust code and can be used by clients interacting with the Namada ledger. The pre-built WASM code's raw bytes should be attached to the transaction's `code` field. The transactions expect certain variables to be provided via the transaction's `data` field encoded with [Borsh](../encoding.md#borsh-binary-encoding). diff --git a/documentation/dev/src/specs/ledger/default-validity-predicates.md b/documentation/dev/src/specs/ledger/default-validity-predicates.md index 894a5df4932..6a52fb1f65c 100644 --- a/documentation/dev/src/specs/ledger/default-validity-predicates.md +++ b/documentation/dev/src/specs/ledger/default-validity-predicates.md @@ -1,6 +1,6 @@ # Default validity predicates -The Anoma ledger and client comes with a set of pre-built validity predicates. +The Namada ledger and client comes with a set of pre-built validity predicates. ## Rust-to-WASM validity predicates diff --git a/documentation/dev/src/specs/ledger/rpc.md b/documentation/dev/src/specs/ledger/rpc.md index c536967670e..d0b6c37b6bd 100644 --- a/documentation/dev/src/specs/ledger/rpc.md +++ b/documentation/dev/src/specs/ledger/rpc.md @@ -2,7 +2,7 @@ The ledger provides an RPC interface for submitting transactions to the mempool, subscribing to their results and queries about the state of the ledger and its storage. -The RPC interface is provided as [specified](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc) from Tendermint and most of the requests are routed to the Anoma ledger via ABCI. +The RPC interface is provided as [specified](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc) from Tendermint and most of the requests are routed to the Namada ledger via ABCI. ## Transactions diff --git a/documentation/dev/src/specs/overview.md b/documentation/dev/src/specs/overview.md index 2b9c7e15c73..43a4db2ee28 100644 --- a/documentation/dev/src/specs/overview.md +++ b/documentation/dev/src/specs/overview.md @@ -1,6 +1,6 @@ # Overview -At a high level, Anoma is composed of two main components: the distributed ledger and the intent gossip / matchmaking system. While they are designed to complement each other, they can be operated separately. +At a high level, Namada is composed of two main components: the distributed ledger and the intent gossip / matchmaking system. While they are designed to complement each other, they can be operated separately. ## The ledger From dc92f735a54a1c1f24ccbca1f45eb2ee6a72f769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 10:47:05 +0200 Subject: [PATCH 193/394] docs/pos: format md --- documentation/dev/src/explore/design/pos.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/documentation/dev/src/explore/design/pos.md b/documentation/dev/src/explore/design/pos.md index 55a2d917946..8b1d75d67d2 100644 --- a/documentation/dev/src/explore/design/pos.md +++ b/documentation/dev/src/explore/design/pos.md @@ -7,6 +7,7 @@ An epoch is a range of blocks or time that is defined by the base ledger and mad ### Epoched data Epoched data are data associated with a specific epoch that are set in advance. The data relevant to the PoS system in the ledger's state are epoched. Each data can be uniquely identified. These are: + - [System parameters](#system-parameters). A single value for each epoch. - [Active validator set](#active-validator-set). A single value for each epoch. - Total voting power. A sum of all active and inactive validators' voting power. A single value for each epoch. @@ -27,6 +28,7 @@ Additionally, any account may submit evidence for [a slashable misbehaviour](#sl A validator must have a public consensus key. Additionally, it may also specify optional metadata fields (TBA). A validator may be in one of the following states: + - *inactive*: A validator is not being considered for block creation and cannot receive any new delegations. - *pending*: @@ -126,6 +128,7 @@ type Validators = HashMap; ``` Epoched data are stored in the following structure: + ```rust,ignore struct Epoched { /// The epoch in which this data was last updated @@ -180,6 +183,7 @@ To update a value in `Epoched` data with delta values in epoch `n` with value `d The invariants for updates in both cases are that `m - n >= 0` and `m - n <= pipeline_length`. For the active validator set, we store all the active and inactive validators separately with their respective voting power: + ```rust,ignore type VotingPower = u64; From fb762937ed35c41d382cf6dabad1ce97ea4a5ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 11:03:19 +0200 Subject: [PATCH 194/394] docs/pos: `s/max_active_validators/max_validator_slots` --- documentation/dev/src/explore/design/pos.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/dev/src/explore/design/pos.md b/documentation/dev/src/explore/design/pos.md index 8b1d75d67d2..972b7fdb2e8 100644 --- a/documentation/dev/src/explore/design/pos.md +++ b/documentation/dev/src/explore/design/pos.md @@ -57,7 +57,7 @@ For each validator (in any state), the system also tracks total bonded tokens as #### Active validator set -From all the *candidate* validators, in each epoch the ones with the most voting power limited up to the `max_active_validators` [parameter](#system-parameters) are selected for the active validator set. The active validator set selected in epoch `n` is set for epoch `n + pipeline_length`. +From all the *candidate* validators, in each epoch the ones with the most voting power limited up to the `max_validator_slots` [parameter](#system-parameters) are selected for the active validator set. The active validator set selected in epoch `n` is set for epoch `n + pipeline_length`. ### Delegator @@ -198,7 +198,7 @@ struct WeightedValidator { } struct ValidatorSet { - /// Active validator set with maximum size equal to `max_active_validators` + /// Active validator set with maximum size equal to `max_validator_slots` active: BTreeSet, /// All the other validators that are not active inactive: BTreeSet, From 13510428229c32770b2ab93a54df6049ef456de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 11:09:03 +0200 Subject: [PATCH 195/394] docs/pos: Note explicitly that token withdrawal is not epoched --- documentation/dev/src/explore/design/pos.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/dev/src/explore/design/pos.md b/documentation/dev/src/explore/design/pos.md index 972b7fdb2e8..93e9b42147a 100644 --- a/documentation/dev/src/explore/design/pos.md +++ b/documentation/dev/src/explore/design/pos.md @@ -90,6 +90,8 @@ Any unbonds created in epoch `n` decrements the bond's validator's total bonded An "unbond" with epoch set to `n` may be withdrawn by the bond's source address in or any time after the epoch `n`. Once withdrawn, the unbond is deleted and the tokens are credited to the source account. +Note that unlike bonding and unbonding where token changes are delayed to some future epochs (pipeline or unbonding offset), the token withdrawal applies immediately. This because when the tokens are withdrawable, they are already "unlocked" from the PoS system and do not contribute to voting power. + ### Staking rewards To a validator who proposed a block, the system rewards tokens based on the `block_proposer_reward` [system parameter](#system-parameters) and each validator that voted on a block receives `block_vote_reward`. From e4e663d8c16cae6a5365a979eca630ff856a78fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 11:17:44 +0200 Subject: [PATCH 196/394] docs/pos-integration: add unbond epochs --- documentation/dev/src/explore/design/ledger/pos-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 992a9b043d4..dcaeb1ee9a7 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -76,7 +76,7 @@ The validator transactions are assumed to be applied with an account address `va - let `pre_unbond = read(unbond/{validator_address}/{validator_address}/delta)` - if `total(bond) - total(pre_unbond) < amount`, panic - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch) until whole `amount` is decremented - - for each decremented `bond` value write a new `unbond` with the key set to the epoch of the source value + - for each decremented `bond` value write a new `unbond` in epoch `n + unbonding_length` with the start epoch set to the epoch of the source value and end epoch `n + unbonding_length` - decrement the `amount` from `validator/{validator_address}/total_deltas` in epoch `n + unbonding_length` - update the `validator/{validator_address}/voting_power` in epoch `n + unbonding_length` - update the `total_voting_power` in epoch `n + unbonding_length` From 4da91ee5f4cb4fb46b34c86396fe495a027e8811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 13:45:14 +0200 Subject: [PATCH 197/394] docs/pos-integration: more explicit unbonding deltas order --- documentation/dev/src/explore/design/ledger/pos-integration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index dcaeb1ee9a7..8e3c1dfed27 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -75,7 +75,7 @@ The validator transactions are assumed to be applied with an account address `va - if `bond` doesn't exist, panic - let `pre_unbond = read(unbond/{validator_address}/{validator_address}/delta)` - if `total(bond) - total(pre_unbond) < amount`, panic - - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch) until whole `amount` is decremented + - decrement the `bond` deltas starting from the rightmost value (a bond in a future-most epoch at the unbonding offset) until whole `amount` is decremented - for each decremented `bond` value write a new `unbond` in epoch `n + unbonding_length` with the start epoch set to the epoch of the source value and end epoch `n + unbonding_length` - decrement the `amount` from `validator/{validator_address}/total_deltas` in epoch `n + unbonding_length` - update the `validator/{validator_address}/voting_power` in epoch `n + unbonding_length` From 6f083997d88bb4495dba828fa81f203c92baee26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 13:47:34 +0200 Subject: [PATCH 198/394] docs/pos: add reference to reasoning for pipeline_len = 2 --- documentation/dev/src/explore/design/pos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev/src/explore/design/pos.md b/documentation/dev/src/explore/design/pos.md index 93e9b42147a..6af1723a3c4 100644 --- a/documentation/dev/src/explore/design/pos.md +++ b/documentation/dev/src/explore/design/pos.md @@ -111,7 +111,7 @@ The invariant is that the sum of amounts that may be withdrawn from a misbehavin The default values that are relative to epoch duration assume that an epoch last about 24 hours. - `max_validator_slots`: Maximum active validators, default `128` -- `pipeline_len`: Pipeline length in number of epochs, default `2` +- `pipeline_len`: Pipeline length in number of epochs, default `2` (see ) - `unboding_len`: Unbonding duration in number of epochs, default `6` - `votes_per_token`: Used in validators' voting power calculation, default 100‱ (1 voting power unit per 1000 tokens) - `block_proposer_reward`: Amount of tokens rewarded to a validator for proposing a block From 98e153c0e8950fe9eb5863cc6d1201d489567aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 13:49:19 +0200 Subject: [PATCH 199/394] docs/pos: validator total deltas don't subtract unbonds Unbonds get subtracted from bonds when applied, so we should subtract again to find total deltas. --- documentation/dev/src/explore/design/pos.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev/src/explore/design/pos.md b/documentation/dev/src/explore/design/pos.md index 6af1723a3c4..3cacc832460 100644 --- a/documentation/dev/src/explore/design/pos.md +++ b/documentation/dev/src/explore/design/pos.md @@ -36,7 +36,7 @@ A validator may be in one of the following states: - *candidate*: A validator is considered for block creation and can receive delegations. -For each validator (in any state), the system also tracks total bonded tokens as a sum of the tokens in their self-bonds and delegated bonds, less any unbonded tokens. The total bonded tokens determine their voting voting power by multiplication by the `votes_per_token` [parameter](#system-parameters). The voting power is used for validator selection for block creation and is used in governance related activities. +For each validator (in any state), the system also tracks total bonded tokens as a sum of the tokens in their self-bonds and delegated bonds. The total bonded tokens determine their voting voting power by multiplication by the `votes_per_token` [parameter](#system-parameters). The voting power is used for validator selection for block creation and is used in governance related activities. #### Validator actions From b73861f39265b36b90b95384e6815c00ac0463e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 14:10:03 +0200 Subject: [PATCH 200/394] docs/pos: remove validator "pending" state --- .../src/explore/design/ledger/pos-integration.md | 13 ++++++------- documentation/dev/src/explore/design/pos.md | 9 +++------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 8e3c1dfed27..578eec65eed 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -56,11 +56,11 @@ The validator transactions are assumed to be applied with an account address `va - `become_validator(consensus_key, staking_reward_address)`: - creates a record in `validator/{validator_address}/consensus_key` in epoch `n + pipeline_length` - creates a record in `validator/{validator_address}/staking_reward_address` - - sets `validator/{validator_address}/state` for to `pending` in the current epoch and `candidate` in epoch `n + pipeline_length` + - sets `validator/{validator_address}/state` to `candidate` in epoch `n + pipeline_length` - `deactivate`: - - sets `validator/{validator_address}/state` for to `inactive` in epoch `n + pipeline_length` + - sets `validator/{validator_address}/state` to `inactive` in epoch `n + pipeline_length` - `reactivate`: - - sets `validator/{validator_address}/state` for to `pending` in the current epoch and `candidate` in epoch `n + pipeline_length` + - sets `validator/{validator_address}/state` to `candidate` in epoch `n + pipeline_length` - `self_bond(amount)`: - let `bond = read(bond/{validator_address}/{validator_address}/delta)` - if `bond` exist, update it with the new bond amount in epoch `n + pipeline_length` @@ -199,15 +199,14 @@ The validity predicate triggers a validation logic based on the storage keys mod (None, Some(post)) => { // - check that all other required validator fields have been initialized // - check that the `post` state is set correctly: - // - the state should be set to `pending` in the current epoch and `candidate` at pipeline offset + // - the state should be set to `candidate` at pipeline offset // - insert into or update `new_validators` accumulator }, (Some(pre), Some(post)) => { // - check that a validator has been correctly deactivated or reactivated // - the `state` should only be changed at `pipeline_length` offset - // - if the `state` becomes `inactive`, it must have been `pending` or `candidate` - // - if the `state` becomes `pending`, it must have been `inactive` - // - if the `state` becomes `candidate`, it must have been `pending` or `inactive` + // - if the `state` becomes `inactive`, it must have been `candidate` + // - if the `state` becomes `candidate`, it must have been `inactive` }, _ => false, } diff --git a/documentation/dev/src/explore/design/pos.md b/documentation/dev/src/explore/design/pos.md index 3cacc832460..141ccbc6e4f 100644 --- a/documentation/dev/src/explore/design/pos.md +++ b/documentation/dev/src/explore/design/pos.md @@ -31,8 +31,6 @@ A validator may be in one of the following states: - *inactive*: A validator is not being considered for block creation and cannot receive any new delegations. -- *pending*: - A validator has requested to become a *candidate*. - *candidate*: A validator is considered for block creation and can receive delegations. @@ -41,11 +39,11 @@ For each validator (in any state), the system also tracks total bonded tokens as #### Validator actions - *become validator*: - Any account that is not a validator already and that doesn't have any delegations may request to become a validator. It is required to provide a public consensus key and staking reward address. For the action applied in epoch `n`, the validator's state will be immediately set to *pending*, it will be set to *candidate* for epoch `n + pipeline_length` and the consensus key is set for epoch `n + pipeline_length`. + Any account that is not a validator already and that doesn't have any delegations may request to become a validator. It is required to provide a public consensus key and staking reward address. For the action applied in epoch `n`, the validator's state will be set to *candidate* for epoch `n + pipeline_length` and the consensus key is set for epoch `n + pipeline_length`. - *deactivate*: - Only a *pending* or *candidate* validator account may *deactivate*. For this action applied in epoch `n`, the validator's account is set to become *inactive* in the epoch `n + pipeline_length`. + Only a validator whose state at or before the `pipeline_length` offset is *candidate* account may *deactivate*. For this action applied in epoch `n`, the validator's account is set to become *inactive* in the epoch `n + pipeline_length`. - *reactivate*: - Only an *inactive* validator may *reactivate*. Similarly to *become validator* action, for this action applied in epoch `n`, the validator's state will be immediately set to *pending* and it will be set to *candidate* for epoch `n + pipeline_length`. + Only an *inactive* validator may *reactivate*. Similarly to *become validator* action, for this action applied in epoch `n`, the validator's state will be set to *candidate* for epoch `n + pipeline_length`. - *self-bond*: A validator may lock-up tokens into a [bond](#bonds) only for its own validator's address. - *unbond*: @@ -238,7 +236,6 @@ struct Validator { enum ValidatorState { Inactive, - Pending, Candidate, } ``` From c1ac299f4aed34bc83d1d3e00a1e2a0a06032a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 27 Apr 2022 14:27:14 +0200 Subject: [PATCH 201/394] changelog: add #1070 --- .changelog/unreleased/docs/1070-pos-spec-updates.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/docs/1070-pos-spec-updates.md diff --git a/.changelog/unreleased/docs/1070-pos-spec-updates.md b/.changelog/unreleased/docs/1070-pos-spec-updates.md new file mode 100644 index 00000000000..016cae8b885 --- /dev/null +++ b/.changelog/unreleased/docs/1070-pos-spec-updates.md @@ -0,0 +1,2 @@ +- Applied various fixes and updates to the PoS system spec and integration spec + ([#1070](https://github.com/anoma/anoma/pull/1070)) \ No newline at end of file From 242f1e483725735db76cf61c40f312cfd62d1b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 5 Aug 2022 19:00:38 +0200 Subject: [PATCH 202/394] docs/dev: remove PoS spec and link to spec page instead --- documentation/dev/src/SUMMARY.md | 1 - .../explore/design/ledger/pos-integration.md | 8 +- documentation/dev/src/explore/design/pos.md | 285 ------------------ 3 files changed, 4 insertions(+), 290 deletions(-) delete mode 100644 documentation/dev/src/explore/design/pos.md diff --git a/documentation/dev/src/SUMMARY.md b/documentation/dev/src/SUMMARY.md index fee1b6fb27c..62f2ccd1c60 100644 --- a/documentation/dev/src/SUMMARY.md +++ b/documentation/dev/src/SUMMARY.md @@ -19,7 +19,6 @@ - [PoS integration](./explore/design/ledger/pos-integration.md) - [Crypto primitives](./explore/design/crypto-primitives.md) - [Actors](./explore/design/actors.md) - - [Proof of Stake system](./explore/design/pos.md) - [Testnet setup](./explore/design/testnet-setup.md) - [Testnet launch procedure](./explore/design/testnet-launch-procedure/README.md) - [Libraries & Tools](./explore/libraries/README.md) diff --git a/documentation/dev/src/explore/design/ledger/pos-integration.md b/documentation/dev/src/explore/design/ledger/pos-integration.md index 578eec65eed..d441e5ddcac 100644 --- a/documentation/dev/src/explore/design/ledger/pos-integration.md +++ b/documentation/dev/src/explore/design/ledger/pos-integration.md @@ -1,6 +1,6 @@ # PoS integration -The [PoS system](../pos.md) is integrated into Namada ledger at 3 different layers: +The [PoS system](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html) is integrated into Namada ledger at 3 different layers: - base ledger that performs genesis initialization, validator set updates on new epoch and applies slashes when they are received from ABCI - an account with an internal address and a [native VP](vp.md#native-vps) that validates any changes applied by transactions to the PoS account state @@ -8,7 +8,7 @@ The [PoS system](../pos.md) is integrated into Namada ledger at 3 different laye The `votes_per_token` PoS system parameter must be chosen to satisfy the [Tendermint requirement](https://github.com/tendermint/spec/blob/60395941214439339cc60040944c67893b5f8145/spec/abci/apps.md#validator-updates) of `MaxTotalVotingPower = MaxInt64 / 8`. -All [the data relevant to the PoS system](../pos.md#storage) are stored under the PoS account's storage sub-space, with the following key schema (the PoS address prefix is omitted for clarity): +All [the data relevant to the PoS system](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html#storage) are stored under the PoS account's storage sub-space, with the following key schema (the PoS address prefix is omitted for clarity): - `params` (required): the system parameters - for any validator, all the following fields are required: @@ -45,7 +45,7 @@ All the fees that are charged in a transaction execution (DKG transaction wrappe ## Transactions -The transactions are assumed to be applied in epoch `n`. Any transaction that modifies [epoched data](../pos.md#epoched-data) updates the structure as described in [epoched data storage](../pos.md#storage). +The transactions are assumed to be applied in epoch `n`. Any transaction that modifies [epoched data](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html#epoched-data) updates the structure as described in [epoched data storage](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html#storage). For slashing tokens, we implement a [PoS slash pool account](vp.md#pos-slash-pool-vp). Slashed tokens should be credited to this account and, for now, no tokens can be be debited by anyone. @@ -151,7 +151,7 @@ Evidence for byzantine behaviour is received from Tendermint ABCI on `BeginBlock In the following description, "pre-state" is the state prior to transaction execution and "post-state" is the state posterior to it. -Any changes to PoS epoched data are checked to update the structure as described in [epoched data storage](../pos.md#storage). +Any changes to PoS epoched data are checked to update the structure as described in [epoched data storage](https://specs.namada.net/economics/proof-of-stake/bonding-mechanism.html#storage). Because some key changes are expected to relate to others, the VP also accumulates some values that are checked for validity after key specific logic: diff --git a/documentation/dev/src/explore/design/pos.md b/documentation/dev/src/explore/design/pos.md deleted file mode 100644 index 141ccbc6e4f..00000000000 --- a/documentation/dev/src/explore/design/pos.md +++ /dev/null @@ -1,285 +0,0 @@ -# Proof of Stake (PoS) system - -## Epoch - -An epoch is a range of blocks or time that is defined by the base ledger and made available to the PoS system. This document assumes that epochs are identified by consecutive natural numbers. All the data relevant to PoS are [associated with epochs](#epoched-data). - -### Epoched data - -Epoched data are data associated with a specific epoch that are set in advance. The data relevant to the PoS system in the ledger's state are epoched. Each data can be uniquely identified. These are: - -- [System parameters](#system-parameters). A single value for each epoch. -- [Active validator set](#active-validator-set). A single value for each epoch. -- Total voting power. A sum of all active and inactive validators' voting power. A single value for each epoch. -- [Validators' consensus key, state and total bonded tokens](#validator). Identified by the validator's address. -- [Bonds](#bonds) are created by self-bonding and delegations. They are identified by the pair of source address and the validator's address. - -Changes to the epoched data do not take effect immediately. Instead, changes in epoch `n` are queued to take effect in the epoch `n + pipeline_length` for most cases and `n + unboding_length` for [unbonding](#unbond) actions. Should the same validator's data or same bonds (i.e. with the same identity) be updated more than once in the same epoch, the later update overrides the previously queued-up update. For bonds, the token amounts are added up. Once the epoch `n` has ended, the queued-up updates for epoch `n + pipeline_length` are final and the values become immutable. - -## Entities - -- [Validator](#validator): An account with a public consensus key, which may participate in producing blocks and governance activities. A validator may not also be a delegator. -- [Delegator](#delegator): An account that delegates some tokens to a validator. A delegator may not also be a validator. - -Additionally, any account may submit evidence for [a slashable misbehaviour](#slashing). - -### Validator - -A validator must have a public consensus key. Additionally, it may also specify optional metadata fields (TBA). - -A validator may be in one of the following states: - -- *inactive*: - A validator is not being considered for block creation and cannot receive any new delegations. -- *candidate*: - A validator is considered for block creation and can receive delegations. - -For each validator (in any state), the system also tracks total bonded tokens as a sum of the tokens in their self-bonds and delegated bonds. The total bonded tokens determine their voting voting power by multiplication by the `votes_per_token` [parameter](#system-parameters). The voting power is used for validator selection for block creation and is used in governance related activities. - -#### Validator actions - -- *become validator*: - Any account that is not a validator already and that doesn't have any delegations may request to become a validator. It is required to provide a public consensus key and staking reward address. For the action applied in epoch `n`, the validator's state will be set to *candidate* for epoch `n + pipeline_length` and the consensus key is set for epoch `n + pipeline_length`. -- *deactivate*: - Only a validator whose state at or before the `pipeline_length` offset is *candidate* account may *deactivate*. For this action applied in epoch `n`, the validator's account is set to become *inactive* in the epoch `n + pipeline_length`. -- *reactivate*: - Only an *inactive* validator may *reactivate*. Similarly to *become validator* action, for this action applied in epoch `n`, the validator's state will be set to *candidate* for epoch `n + pipeline_length`. -- *self-bond*: - A validator may lock-up tokens into a [bond](#bonds) only for its own validator's address. -- *unbond*: - Any self-bonded tokens may be partially or fully [unbonded](#unbond). -- *withdraw unbonds*: - Unbonded tokens may be withdrawn in or after the [unbond's epoch](#unbond). -- *change consensus key*: - Set the new consensus key. When applied in epoch `n`, the key is set for epoch `n + pipeline_length`. - -#### Active validator set - -From all the *candidate* validators, in each epoch the ones with the most voting power limited up to the `max_validator_slots` [parameter](#system-parameters) are selected for the active validator set. The active validator set selected in epoch `n` is set for epoch `n + pipeline_length`. - -### Delegator - -A delegator may have any number of delegations to any number of validators. Delegations are stored in [bonds](#bonds). - -#### Delegator actions - -- *delegate*: - An account which is not a validator may delegate tokens to any number of validators. This will lock-up tokens into a [bond](#bonds). -- *undelegate*: - Any delegated tokens may be partially or fully [unbonded](#unbond). -- *withdraw unbonds*: - Unbonded tokens may be withdrawn in or after the [unbond's epoch](#unbond). - -## Bonds - -A bond locks-up tokens from validators' self-bonding and delegators' delegations. For self-bonding, the source address is equal to the validator's address. Only validators can self-bond. For a bond created from a delegation, the bond's source is the delegator's account. - -For each epoch, bonds are uniquely identified by the pair of source and validator's addresses. A bond created in epoch `n` is written into epoch `n + pipeline_length`. If there already is a bond in the epoch `n + pipeline_length` for this pair of source and validator's addresses, its tokens are incremented by the newly bonded amount. - -Any bonds created in epoch `n` increment the bond's validator's total bonded tokens by the bond's token amount and update the voting power for epoch `n + pipeline_length`. - -The tokens put into a bond are immediately deducted from the source account. - -### Unbond - -An unbonding action (validator *unbond* or delegator *undelegate*) requested by the bond's source account in epoch `n` creates an "unbond" with epoch set to `n + unbounding_length`. We also store the epoch of the bond(s) from which the unbond is created in order to determine if the unbond should be slashed if a fault occurred within the range of bond epoch (inclusive) and unbond epoch (exclusive). - -Any unbonds created in epoch `n` decrements the bond's validator's total bonded tokens by the bond's token amount and update the voting power for epoch `n + unbonding_length`. - -An "unbond" with epoch set to `n` may be withdrawn by the bond's source address in or any time after the epoch `n`. Once withdrawn, the unbond is deleted and the tokens are credited to the source account. - -Note that unlike bonding and unbonding where token changes are delayed to some future epochs (pipeline or unbonding offset), the token withdrawal applies immediately. This because when the tokens are withdrawable, they are already "unlocked" from the PoS system and do not contribute to voting power. - -### Staking rewards - -To a validator who proposed a block, the system rewards tokens based on the `block_proposer_reward` [system parameter](#system-parameters) and each validator that voted on a block receives `block_vote_reward`. - -### Slashing - -Instead of absolute values, validators' total bonded token amounts and bonds' and unbonds' token amounts are stored as their deltas (i.e. the change of quantity from a previous epoch) to allow distinguishing changes for different epoch, which is essential for determining whether tokens should be slashed. However, because slashes for a fault that occurred in epoch `n` may only be applied before the beginning of epoch `n + unbonding_length`, in epoch `m` we can sum all the deltas of total bonded token amounts and bonds and unbond with the same source and validator for epoch equal or less than `m - unboding_length` into a single total bonded token amount, single bond and single unbond record. This is to keep the total number of total bonded token amounts for a unique validator and bonds and unbonds for a unique pair of source and validator bound to a maximum number (equal to `unbonding_length`). - -To disincentivize validators misbehaviour in the PoS system a validator may be slashed for any fault that it has done. An evidence of misbehaviour may be submitted by any account for a fault that occurred in epoch `n` anytime before the beginning of epoch `n + unbonding_length`. - -A valid evidence reduces the validator's total bonded token amount by the slash rate in and before the epoch in which the fault occurred. The validator's voting power must also be adjusted to the slashed total bonded token amount. Additionally, a slash is stored with the misbehaving validator's address and the relevant epoch in which the fault occurred. When an unbond is being withdrawn, we first look-up if any slash occurred within the range of epochs in which these were active and if so, reduce its token amount by the slash rate. Note that bonds and unbonds amounts are not slashed until their tokens are withdrawn. - -The invariant is that the sum of amounts that may be withdrawn from a misbehaving validator must always add up to the total bonded token amount. - -## System parameters - -The default values that are relative to epoch duration assume that an epoch last about 24 hours. - -- `max_validator_slots`: Maximum active validators, default `128` -- `pipeline_len`: Pipeline length in number of epochs, default `2` (see ) -- `unboding_len`: Unbonding duration in number of epochs, default `6` -- `votes_per_token`: Used in validators' voting power calculation, default 100‱ (1 voting power unit per 1000 tokens) -- `block_proposer_reward`: Amount of tokens rewarded to a validator for proposing a block -- `block_vote_reward`: Amount of tokens rewarded to each validator that voted on a block proposal -- `duplicate_vote_slash_rate`: Portion of validator's stake that should be slashed on a duplicate vote -- `light_client_attack_slash_rate`: Portion of validator's stake that should be slashed on a light client attack - -## Storage - -The [system parameters](#system-parameters) are written into the storage to allow for their changes. Additionally, each validator may record a new parameters value under their sub-key that they wish to change to, which would override the systems parameters when more than 2/3 voting power are in agreement on all the parameters values. - -The validators' data are keyed by the their addresses, conceptually: - -```rust,ignore -type Validators = HashMap; -``` - -Epoched data are stored in the following structure: - -```rust,ignore -struct Epoched { - /// The epoch in which this data was last updated - last_update: Epoch, - /// Dynamically sized vector in which the head is the data for epoch in which - /// the `last_update` was performed and every consecutive array element is the - /// successor epoch of the predecessor array element. For system parameters, - /// validator's consensus key and state, `LENGTH = pipeline_length + 1`. - /// For all others, `LENGTH = unbonding_length + 1`. - data: Vec> -} -``` - -Note that not all epochs will have data set, only the ones in which some changes occurred. - -To try to look-up a value for `Epoched` data with independent values in each epoch (such as the active validator set) in the current epoch `n`: - -1. let `index = min(n - last_update, pipeline_length)` -1. read the `data` field at `index`: - 1. if there's a value at `index` return it - 1. else if `index == 0`, return `None` - 1. else decrement `index` and repeat this sub-step from 1. - -To look-up a value for `Epoched` data with delta values in the current epoch `n`: - -1. let `end = min(n - last_update, pipeline_length) + 1` -1. sum all the values that are not `None` in the `0 .. end` range bounded inclusively below and exclusively above - -To update a value in `Epoched` data with independent values in epoch `n` with value `new` for epoch `m`: - -1. let `shift = min(n - last_update, pipeline_length)` -1. if `shift == 0`: - 1. `data[m - n] = new` -1. else: - 1. for `i in 0 .. shift` range bounded inclusively below and exclusively above, set `data[i] = None` - 1. rotate `data` left by `shift` - 1. set `data[m - n] = new` - 1. set `last_update` to the current epoch - -To update a value in `Epoched` data with delta values in epoch `n` with value `delta` for epoch `m`: - -1. let `shift = min(n - last_update, pipeline_length)` -1. if `shift == 0`: - 1. set `data[m - n] = data[m - n].map_or_else(delta, |last_delta| last_delta + delta)` (add the `delta` to the previous value, if any, otherwise use the `delta` as the value) -1. else: - 1. let `sum` to be equal to the sum of all delta values in the `i in 0 .. shift` range bounded inclusively below and exclusively above and set `data[i] = None` - 1. rotate `data` left by `shift` - 1. set `data[0] = data[0].map_or_else(sum, |last_delta| last_delta + sum)` - 1. set `data[m - n] = delta` - 1. set `last_update` to the current epoch - -The invariants for updates in both cases are that `m - n >= 0` and `m - n <= pipeline_length`. - -For the active validator set, we store all the active and inactive validators separately with their respective voting power: - -```rust,ignore -type VotingPower = u64; - -/// Validator's address with its voting power. -#[derive(PartialEq, Eq, PartialOrd, Ord)] -struct WeightedValidator { - /// The `voting_power` field must be on top, because lexicographic ordering is - /// based on the top-to-bottom declaration order and in the `ValidatorSet` - /// the `WeighedValidator`s these need to be sorted by the `voting_power`. - voting_power: VotingPower, - address: Address, -} - -struct ValidatorSet { - /// Active validator set with maximum size equal to `max_validator_slots` - active: BTreeSet, - /// All the other validators that are not active - inactive: BTreeSet, -} - -type ValidatorSets = Epoched; - -/// The sum of all active and inactive validators' voting power -type TotalVotingPower = Epoched; -``` - -When any validator's voting power changes, we attempt to perform the following update on the `ActiveValidatorSet`: - -1. let `validator` be the validator's address, `power_before` and `power_after` be the voting power before and after the change, respectively -1. let `power_delta = power_after - power_before` -1. let `min_active = active.first()` (active validator with lowest voting power) -1. let `max_inactive = inactive.last()` (inactive validator with greatest voting power) -1. find whether the validator is active, let `is_active = power_before >= max_inactive.voting_power` - 1. if `is_active`: - 1. if `power_delta > 0 && power_after > max_inactive.voting_power`, update the validator in `active` set with `voting_power = power_after` - 1. else, remove the validator from `active`, insert it into `inactive` and remove `max_inactive.address` from `inactive` and insert it into `active` - 1. else (`!is_active`): - 1. if `power_delta < 0 && power_after < min_active.voting_power`, update the validator in `inactive` set with `voting_power = power_after` - 1. else, remove the validator from `inactive`, insert it into `active` and remove `min_active.address` from `active` and insert it into `inactive` - -Within each validator's address space, we store public consensus key, state, total bonded token amount and voting power calculated from the total bonded token amount (even though the voting power is stored in the `ValidatorSet`, we also need to have the `voting_power` here because we cannot look it up in the `ValidatorSet` without iterating the whole set): - -```rust,ignore -struct Validator { - consensus_key: Epoched, - state: Epoched, - total_deltas: Epoched, - voting_power: Epoched, -} - -enum ValidatorState { - Inactive, - Candidate, -} -``` - -The bonds and unbonds are keyed by their identifier: - -```rust,ignore -type Bonds = HashMap>; -type Unbonds = HashMap>; - -struct BondId { - validator: Address, - /// The delegator adddress for delegations, or the same as the `validator` - /// address for self-bonds. - source: Address, -} - -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. - deltas: HashMap, -} - -struct Unbond { - /// A key is a pair of the epoch of the bond from which a unbond was created - /// the epoch of unboding. This is needed for slash epoch range check. - deltas: HashMap<(Epoch, Epoch), token::Amount> -} -``` - -For slashes, we store the epoch and block height at which the fault occurred, slash rate and the slash type: - -```rust,ignore -struct Slash { - epoch: Epoch, - block_height: u64, - /// slash token amount ‱ (per ten thousand) - rate: u8, - r#type: SlashType, -} -``` - -## Initialization - -An initial validator set with self-bonded token amounts must be given on system initialization. - -This set is used to pre-compute epochs in the genesis block from epoch `0` to epoch `pipeline_length - 1`. From 9114cc46a4a720aeb6012300c67a015a82ecf1d3 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 9 Jun 2022 11:40:35 +0200 Subject: [PATCH 203/394] Fixes typos. Updates comment on `clap` --- documentation/dev/src/explore/libraries/cli.md | 4 +--- documentation/dev/src/explore/libraries/errors.md | 4 ++-- documentation/dev/src/explore/libraries/network.md | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/documentation/dev/src/explore/libraries/cli.md b/documentation/dev/src/explore/libraries/cli.md index 9ddd50e5ea9..9cf708d299b 100644 --- a/documentation/dev/src/explore/libraries/cli.md +++ b/documentation/dev/src/explore/libraries/cli.md @@ -14,6 +14,4 @@ The considered libraries: Probably the most widely used CLI library in Rust. -With version 2.x, we'd probably want to use it with [Structops](https://github.com/TeXitoi/structopt) for deriving. - -But we can probably use 3.0, which is not yet stable, but is pretty close . This version comes with deriving attributes and also other new ways to build CLI commands. +With version 2.x, we'd probably want to update clap to 3.x: this version comes with deriving attributes and also other new ways to build CLI commands (previously, deriving was only provided by [StructOpt](https://github.com/TeXitoi/structopt), which is now in maintenance mode). diff --git a/documentation/dev/src/explore/libraries/errors.md b/documentation/dev/src/explore/libraries/errors.md index 388b232840c..32a943ffe8c 100644 --- a/documentation/dev/src/explore/libraries/errors.md +++ b/documentation/dev/src/explore/libraries/errors.md @@ -2,7 +2,7 @@ The current preference is to use `thiserror` for most code and `eyre` for reporting errors at the CLI level and the client. -To make the code robust, we should avoid using code that may panic for errors that recoverable and handle all possible errors explicitly. Two exceptions to this rule are: +To make the code robust, we should avoid using code that may panic for errors that are recoverable and handle all possible errors explicitly. Two exceptions to this rule are: - prototyping, where it's fine to use `unwrap`, `expect`, etc. - in code paths with conditional compilation **only** for development build, where it's preferable to use `expect` in place of `unwrap` to help with debugging @@ -10,7 +10,7 @@ In case of panics, we should provide an error trace that is helpful for trouble- A great post on error handling library/application distinction: . -The considered DBs: +The considered libraries: - thiserror - anyhow - eyre diff --git a/documentation/dev/src/explore/libraries/network.md b/documentation/dev/src/explore/libraries/network.md index dd1c2522e08..bc3fd394329 100644 --- a/documentation/dev/src/explore/libraries/network.md +++ b/documentation/dev/src/explore/libraries/network.md @@ -1,4 +1,4 @@ -# network +# Network ## Libp2p : Peer To Peer network @@ -13,7 +13,7 @@ encryption for us. Generates a client/server from protobuf file. This can be used for a rpc server. -# network behaviour +# Network behaviour ## Gossipsub From ab22bff8d881c02f25e1d6f829de4af90f3a9797 Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 9 Jun 2022 12:02:28 +0200 Subject: [PATCH 204/394] Removes mention to clap version --- documentation/dev/src/explore/libraries/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev/src/explore/libraries/cli.md b/documentation/dev/src/explore/libraries/cli.md index 9cf708d299b..60998370423 100644 --- a/documentation/dev/src/explore/libraries/cli.md +++ b/documentation/dev/src/explore/libraries/cli.md @@ -14,4 +14,4 @@ The considered libraries: Probably the most widely used CLI library in Rust. -With version 2.x, we'd probably want to update clap to 3.x: this version comes with deriving attributes and also other new ways to build CLI commands (previously, deriving was only provided by [StructOpt](https://github.com/TeXitoi/structopt), which is now in maintenance mode). +Comes with deriving attributes and also other new ways to build CLI commands (previously, deriving was only provided by [StructOpt](https://github.com/TeXitoi/structopt), which is now in maintenance mode). From f13bc246e1baf96a832c9c6b8bc401d3de901c3c Mon Sep 17 00:00:00 2001 From: Marco Granelli Date: Thu, 9 Jun 2022 17:34:35 +0200 Subject: [PATCH 205/394] Updates changelog --- .changelog/unreleased/docs/1143-update-libraries-docs.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/docs/1143-update-libraries-docs.md diff --git a/.changelog/unreleased/docs/1143-update-libraries-docs.md b/.changelog/unreleased/docs/1143-update-libraries-docs.md new file mode 100644 index 00000000000..6e9eee6948e --- /dev/null +++ b/.changelog/unreleased/docs/1143-update-libraries-docs.md @@ -0,0 +1,2 @@ +- Fixes libraries doc typos and correct comment on the clap crate + ([#1143](https://github.com/anoma/anoma/pull/1143)) \ No newline at end of file From 7c41b0a128f26c35a68cfc5a5a2decf4e4486c93 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Thu, 9 Jun 2022 14:50:24 +0200 Subject: [PATCH 206/394] crypto.md: remove references to closed issue --- documentation/dev/src/specs/crypto.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/documentation/dev/src/specs/crypto.md b/documentation/dev/src/specs/crypto.md index e2879790bb4..1730920199f 100644 --- a/documentation/dev/src/specs/crypto.md +++ b/documentation/dev/src/specs/crypto.md @@ -5,12 +5,10 @@ Namada currently supports Ed25519 signatures with more to be added: - [`Secp256k1`](https://github.com/anoma/anoma/issues/162) - [`Sr25519`](https://github.com/anoma/anoma/issues/646) -Please note that the Namada's crypto public API and encoding is currently undergoing some breaking changes with . - ## Public keys -A public key is a [Borsh encoded `PublicKey`](encoding.md#publickey). For the Ed25519 scheme, this is 32 bytes of Ed25519 public key, prefixed with `32` in little endian encoding (`[32, 0, 0, 0]` in raw bytes or `20000000` in hex). (TODO this will change with ) +A public key is a [Borsh encoded `PublicKey`](encoding.md#publickey). For the Ed25519 scheme, this is 32 bytes of Ed25519 public key, prefixed with `32` in little endian encoding (`[32, 0, 0, 0]` in raw bytes or `20000000` in hex). ## Signatures -A signature in Namada is a [Borsh encoded `Signature`](encoding.md#signature). For the Ed25519 scheme, this is 64 bytes of Ed25519 signature, prefixed with `64` in little endian encoding (`[64, 0, 0, 0]` in raw bytes or `40000000` in hex). (TODO this will change with ) +A signature in Namada is a [Borsh encoded `Signature`](encoding.md#signature). For the Ed25519 scheme, this is 64 bytes of Ed25519 signature, prefixed with `64` in little endian encoding (`[64, 0, 0, 0]` in raw bytes or `40000000` in hex). From 9a3ec2aa9aa76be778b19b2becd2f5fae00467e9 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 10:34:08 +0100 Subject: [PATCH 207/394] crypto.md: remove outdated encoding descriptions --- documentation/dev/src/specs/crypto.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/dev/src/specs/crypto.md b/documentation/dev/src/specs/crypto.md index 1730920199f..289280b895b 100644 --- a/documentation/dev/src/specs/crypto.md +++ b/documentation/dev/src/specs/crypto.md @@ -7,8 +7,8 @@ Namada currently supports Ed25519 signatures with more to be added: ## Public keys -A public key is a [Borsh encoded `PublicKey`](encoding.md#publickey). For the Ed25519 scheme, this is 32 bytes of Ed25519 public key, prefixed with `32` in little endian encoding (`[32, 0, 0, 0]` in raw bytes or `20000000` in hex). +A public key is a [Borsh encoded `PublicKey`](encoding.md#publickey). ## Signatures -A signature in Namada is a [Borsh encoded `Signature`](encoding.md#signature). For the Ed25519 scheme, this is 64 bytes of Ed25519 signature, prefixed with `64` in little endian encoding (`[64, 0, 0, 0]` in raw bytes or `40000000` in hex). +A signature in Namada is a [Borsh encoded `Signature`](encoding.md#signature). From 291b24240f4efbdcfdfb385e09ae9346345118bd Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 10:41:48 +0100 Subject: [PATCH 208/394] crypto.md: add context for signatures --- documentation/dev/src/specs/crypto.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/dev/src/specs/crypto.md b/documentation/dev/src/specs/crypto.md index 289280b895b..c3ba21c15ac 100644 --- a/documentation/dev/src/specs/crypto.md +++ b/documentation/dev/src/specs/crypto.md @@ -1,6 +1,6 @@ # Cryptographic schemes -Namada currently supports Ed25519 signatures with more to be added: +Namada currently supports Ed25519 for signing transactions or any other arbitrary data, with support for more signature schemes to be added: - [`Secp256k1`](https://github.com/anoma/anoma/issues/162) - [`Sr25519`](https://github.com/anoma/anoma/issues/646) From 7c1340447fbb4e69fc957f8614d65b5510f634f8 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 9 Aug 2022 00:53:47 -0400 Subject: [PATCH 209/394] Initial info on Secp256k1 keys and zeroizing secret keys --- documentation/dev/src/specs/crypto.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/documentation/dev/src/specs/crypto.md b/documentation/dev/src/specs/crypto.md index c3ba21c15ac..9025c5c1bb4 100644 --- a/documentation/dev/src/specs/crypto.md +++ b/documentation/dev/src/specs/crypto.md @@ -1,14 +1,19 @@ # Cryptographic schemes -Namada currently supports Ed25519 for signing transactions or any other arbitrary data, with support for more signature schemes to be added: +Namada currently supports both Ed25519 and Secp256k1 (currently in [development](https://github.com/anoma/namada/pulls/278)) for signing transactions or any other arbitrary data, with support for more signature schemes to be added: -- [`Secp256k1`](https://github.com/anoma/anoma/issues/162) - [`Sr25519`](https://github.com/anoma/anoma/issues/646) +The implementation of the Ed25519 scheme makes use of the `ed25519_consensus` crate, while the `libsecp256k1` crate is used for Secp256k1 keys. + ## Public keys -A public key is a [Borsh encoded `PublicKey`](encoding.md#publickey). +A public key is a [Borsh encoded](encoding.md) `PublicKey`. + +## Secret Keys + +A secret key is a [Borsh encoded](encoding.md) `SecretKey`. In order to prevent leaks of sensitive information, the contents of a secret key are zeroized. Sometimes the Rust compiler can optimize away the action of zeroing the bytes of data corresponding to an object dropped from scope. For secret keys, this data in memory is directly zeroed after the keys are no longer needed. ## Signatures -A signature in Namada is a [Borsh encoded `Signature`](encoding.md#signature). +A signature in Namada is a [Borsh encoded](encoding.md) `Signature`. From 7c937046fa23e32619030b34c5df161c03fd8184 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 9 Aug 2022 00:54:22 -0400 Subject: [PATCH 210/394] change string to valid path --- encoding_spec/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encoding_spec/src/main.rs b/encoding_spec/src/main.rs index 8877ff3e776..db65f246679 100644 --- a/encoding_spec/src/main.rs +++ b/encoding_spec/src/main.rs @@ -32,7 +32,7 @@ use namada::types::{token, transaction}; /// This generator will write output into this `docs` file. const OUTPUT_PATH: &str = - "documentation/docs/src/specs/encoding/generated-borsh-spec.md"; + "documentation/dev/src/specs/encoding/generated-borsh-spec.md"; lazy_static! { /// Borsh types may be used by declarations. These are displayed differently in the [`md_fmt_type`]. From 19b454e357f4192330c57a5d3f9d12775338ab07 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 17 Jun 2022 10:04:10 +0100 Subject: [PATCH 211/394] .gitignore: make some patterns relative to repo root This avoid some potential conflicts with actual files we want to track eg apps/src/bin/anoma-client/main.rs --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8092b5afd8f..5f835e64056 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ debug/ target/ # Release packages -anoma-*/ -anoma-*.tar.gz +/anoma-*/ +/anoma-*.tar.gz # These are backup files generated by rustfmt **/*.rs.bk @@ -26,4 +26,4 @@ anoma-*.tar.gz wasm/*.wasm # app version string file -apps/version.rs +/apps/version.rs From 8593febefea9046bd6a88e9d9bb36ce9d94bdc36 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 17 Jun 2022 10:07:55 +0100 Subject: [PATCH 212/394] Add changelog --- .changelog/unreleased/miscellaneous/1158-gitignore-anoma.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/1158-gitignore-anoma.md diff --git a/.changelog/unreleased/miscellaneous/1158-gitignore-anoma.md b/.changelog/unreleased/miscellaneous/1158-gitignore-anoma.md new file mode 100644 index 00000000000..79e551d436f --- /dev/null +++ b/.changelog/unreleased/miscellaneous/1158-gitignore-anoma.md @@ -0,0 +1,2 @@ +- Make some .gitignore patterns relative to repo root + ([#1158](https://github.com/anoma/anoma/pull/1158)) \ No newline at end of file From 5bdc7029122d1a0f8c131acd52a8b0ae8b566a80 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 17 Jun 2022 10:04:10 +0100 Subject: [PATCH 213/394] .gitignore: make some patterns relative to repo root This avoid some potential conflicts with actual files we want to track eg apps/src/bin/anoma-client/main.rs --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8092b5afd8f..5f835e64056 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ debug/ target/ # Release packages -anoma-*/ -anoma-*.tar.gz +/anoma-*/ +/anoma-*.tar.gz # These are backup files generated by rustfmt **/*.rs.bk @@ -26,4 +26,4 @@ anoma-*.tar.gz wasm/*.wasm # app version string file -apps/version.rs +/apps/version.rs From 07876b1f8be00531da22c217f5ec36b2ffad1773 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 17 Jun 2022 10:07:55 +0100 Subject: [PATCH 214/394] Add changelog --- .changelog/unreleased/miscellaneous/1158-gitignore-anoma.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/miscellaneous/1158-gitignore-anoma.md diff --git a/.changelog/unreleased/miscellaneous/1158-gitignore-anoma.md b/.changelog/unreleased/miscellaneous/1158-gitignore-anoma.md new file mode 100644 index 00000000000..79e551d436f --- /dev/null +++ b/.changelog/unreleased/miscellaneous/1158-gitignore-anoma.md @@ -0,0 +1,2 @@ +- Make some .gitignore patterns relative to repo root + ([#1158](https://github.com/anoma/anoma/pull/1158)) \ No newline at end of file From e0b5c4648e0d2412eae90c58fd13461c60ade039 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 1 Jul 2022 12:31:41 +0100 Subject: [PATCH 215/394] Add specs/openapi.yml --- specs/openapi.yml | 130 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 specs/openapi.yml diff --git a/specs/openapi.yml b/specs/openapi.yml new file mode 100644 index 00000000000..7533c14431d --- /dev/null +++ b/specs/openapi.yml @@ -0,0 +1,130 @@ +openapi: 3.0.3 +info: + title: Anoma + description: Interacting with an Anoma blockchain via Tendermint RPC + version: 0.6.1 +servers: + - url: http://127.0.0.1:26657 + description: Tendermint RPC endpoint for an Anoma ledger +paths: + /: + post: + summary: Interact with the Anoma blockchain via Tendermint RPC + operationId: abci_query + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + description: Should be unique between requests + type: integer + example: 58392 + method: + description: The Tendermint RPC method being called which in this case should always be abci_query + type: string + enum: + - "abci_query" + params: + type: object + required: + - path + properties: + path: + description: Path as will be recognized by the ledger + oneOf: + - type: string + enum: + - "epoch" + - "dry_run_tx" + description: > + * `epoch` - Epoch at the given block height + * `dry_run_tx` - Dry run a transaction + - type: string + description: Read a storage value with exact storage key + pattern: r"^value\/(\w|\/)+$" + - type: string + description: Read a range of storage values with a matching key prefix + pattern: r"^prefix\/(\w|\/)+$" + - type: string + description: Check if the given storage key exists + pattern: r"^has_key\/(\w|\/)+$" + data: + description: Optional data to go along with the query (base64-encoded if necessary) + type: string + example: "abcd" + default: "" + height: + description: Height as a base64 encoded integer (0 means latest) + type: string + example: "1" + default: "0" + prove: + description: Include proofs of the transaction's inclusion in the block + type: boolean + example: true + default: false + examples: + epoch_latest: + summary: Get the latest epoch + value: + { + "id": 2, + "method": "abci_query", + "params": { "path": "epoch" }, + } + epoch_at_height: + summary: Get the epoch at a given height + value: + { + "id": 2, + "method": "abci_query", + "params": { "path": "epoch", "height": 2 }, + } + responses: + "200": + description: Response of the submitted query + content: + application/json: + schema: + $ref: "https://docs.tendermint.com/v0.34/rpc/openapi.yaml#/components/schemas/ABCIQueryResponse" + examples: + epoch_latest: + value: + { + "jsonrpc": "2.0", + "id": 2, + "result": + { + "response": + { + "code": 0, + "log": "", + "info": "", + "index": "0", + "key": null, + "value": "lQAAAAAAAAA=", + "proofOps": null, + "height": "0", + "codespace": "", + }, + }, + } + "500": + description: Error + content: + application/json: + schema: + $ref: "https://docs.tendermint.com/v0.34/rpc/openapi.yaml#/components/schemas/ErrorResponse" + example: + { + "jsonrpc": "2.0", + "error": + { + "code": -32700, + "message": "Parse error. Invalid JSON", + "data": "error unmarshaling request: invalid character 'd' after object key:value pair", + }, + } From 42bfb42d5d79af1fc0ad8fceecc45343d26599c8 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 15:13:23 +0100 Subject: [PATCH 216/394] A key can contain any ASCII, not just alphabetical characters --- specs/openapi.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/openapi.yml b/specs/openapi.yml index 7533c14431d..e8bf5259d30 100644 --- a/specs/openapi.yml +++ b/specs/openapi.yml @@ -44,13 +44,13 @@ paths: * `dry_run_tx` - Dry run a transaction - type: string description: Read a storage value with exact storage key - pattern: r"^value\/(\w|\/)+$" + pattern: r"^value\/([\x00-\x7F]|\/)+$" - type: string description: Read a range of storage values with a matching key prefix - pattern: r"^prefix\/(\w|\/)+$" + pattern: r"^prefix\/([\x00-\x7F]|\/)+$" - type: string description: Check if the given storage key exists - pattern: r"^has_key\/(\w|\/)+$" + pattern: r"^has_key\/([\x00-\x7F]|\/)+$" data: description: Optional data to go along with the query (base64-encoded if necessary) type: string From a374fd2bb5f8985273b4e7dceff8d507307e74e7 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 15:13:34 +0100 Subject: [PATCH 217/394] Add example for getting an account's public key --- specs/openapi.yml | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/specs/openapi.yml b/specs/openapi.yml index e8bf5259d30..52acc7fab5c 100644 --- a/specs/openapi.yml +++ b/specs/openapi.yml @@ -83,6 +83,18 @@ paths: "method": "abci_query", "params": { "path": "epoch", "height": 2 }, } + get_account_public_key: + summary: Get the public key for an account which has been initialized with a validity predicate, with proof + value: + { + "id": 2, + "method": "abci_query", + "params": + { + "path": "value/#atest1v4ehgw36g4pyg3j9x3qnjd3cxgmyz3fk8qcrys3hxdp5xwfnx3zyxsj9xgunxsfjg5u5xvzyzrrqtn/public_key", + "prove": true, + }, + } responses: "200": description: Response of the submitted query @@ -112,6 +124,42 @@ paths: }, }, } + get_account_public_key: + value: + { + "jsonrpc": "2.0", + "id": 2, + "result": + { + "response": + { + "code": 0, + "log": "", + "info": "", + "index": "0", + "key": null, + "value": "ABdruiwJLZ4w4Z/MoD+aW3fH4vkc9+QhGOCGmDr1oVz+", + "proofOps": + { + "ops": + [ + { + "type": "ics23_CommitmentProof", + "key": "I2F0ZXN0MXY0ZWhndzM2ZzRweWczajl4M3FuamQzY3hnbXl6M2ZrOHFjcnlzM2h4ZHA1eHdmbngzenl4c2o5eGd1bnhzZmpnNXU1eHZ6eXpycnF0bi9wdWJsaWNfa2V5", + "data": "Cu0CCmAjYXRlc3QxdjRlaGd3MzZnNHB5ZzNqOXgzcW5qZDNjeGdteXozZms4cWNyeXMzaHhkcDV4d2ZueDN6eXhzajl4Z3VueHNmamc1dTV4dnp5enJycXRuL3B1YmxpY19rZXkSIQAXa7osCS2eMOGfzKA/mlt3x+L5HPfkIRjghpg69aFc/hooCAEQARgBKiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIkCAESIOQIgEOVb0Hv2eOTmYDks2uP4L4gs0RgmV2wUisInkbQIiQIARog04WfgQqfT2X9aD9qhA/fWy6LS6JjdmkpmUfkK9hoKOwiJAgBEiB+tFAPUElWCcCpAL4khjoihfs19F7tfdagbWWE44kCESIkCAEaIBtq2MVGbblK4zgD3h5vxQNKiCU+dmaHLQSpzWvBT3lwIiQIARogwl8LV3ECHOBxasQriaEAE/dgSZnKZ6vBm6Zm7vTED0Y=", + }, + { + "type": "ics23_CommitmentProof", + "key": "I2F0ZXN0MXY0ZWhndzM2ZzRweWczajl4M3FuamQzY3hnbXl6M2ZrOHFjcnlzM2h4ZHA1eHdmbngzenl4c2o5eGd1bnhzZmpnNXU1eHZ6eXpycnF0bi9wdWJsaWNfa2V5", + "data": "CnkKB2FjY291bnQSIMMIWmruLiaYEqu6LGhBd6QS74N0WncwSIe+tIux4F+BGiYIARABKiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACIkCAESIG3BkVXL0ICjUIY1bV7YSPruEfFZLIB2vlL7lpwQ3ycX", + }, + ], + }, + "height": "0", + "codespace": "", + }, + }, + } "500": description: Error content: From ce036b0a8459c2a0b6b0bd9272afb42e273fd447 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Fri, 8 Jul 2022 15:16:32 +0100 Subject: [PATCH 218/394] Add invalid storage key error response example --- specs/openapi.yml | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/specs/openapi.yml b/specs/openapi.yml index 52acc7fab5c..40b15751f0b 100644 --- a/specs/openapi.yml +++ b/specs/openapi.yml @@ -97,7 +97,7 @@ paths: } responses: "200": - description: Response of the submitted query + description: Response of the submitted query, which may have been successful or may have errored at the application level. content: application/json: schema: @@ -160,8 +160,29 @@ paths: }, }, } + invalid_storage_key: + value: + { + "jsonrpc": "2.0", + "id": 2, + "result": + { + "response": + { + "code": 1, + "log": "", + "info": "RPC error: Invalid storage key: Error parsing address: Error decoding address from Bech32m: invalid length", + "index": "0", + "key": null, + "value": null, + "proofOps": null, + "height": "0", + "codespace": "", + }, + }, + } "500": - description: Error + description: Tendermint-level error content: application/json: schema: From 4efd4613a98ed39c594cf14bf2efa2ad2cfada80 Mon Sep 17 00:00:00 2001 From: James Hiew Date: Tue, 19 Jul 2022 12:05:17 +0100 Subject: [PATCH 219/394] Storage key regexes should permit any UTF-8 string --- specs/openapi.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specs/openapi.yml b/specs/openapi.yml index 40b15751f0b..f7f2f53e0cd 100644 --- a/specs/openapi.yml +++ b/specs/openapi.yml @@ -44,13 +44,13 @@ paths: * `dry_run_tx` - Dry run a transaction - type: string description: Read a storage value with exact storage key - pattern: r"^value\/([\x00-\x7F]|\/)+$" + pattern: r"^value\/.+$" - type: string description: Read a range of storage values with a matching key prefix - pattern: r"^prefix\/([\x00-\x7F]|\/)+$" + pattern: r"^prefix\/.+$" - type: string description: Check if the given storage key exists - pattern: r"^has_key\/([\x00-\x7F]|\/)+$" + pattern: r"^has_key\/.+$" data: description: Optional data to go along with the query (base64-encoded if necessary) type: string From 959737d1046fa9086c3b68da7cb841bcab5cd3d3 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 19 Jul 2022 12:14:34 +0100 Subject: [PATCH 220/394] Apply suggestions from code review Co-authored-by: Tomas Zemanovic --- specs/openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/openapi.yml b/specs/openapi.yml index f7f2f53e0cd..d207b42a116 100644 --- a/specs/openapi.yml +++ b/specs/openapi.yml @@ -40,7 +40,7 @@ paths: - "epoch" - "dry_run_tx" description: > - * `epoch` - Epoch at the given block height + * `epoch` - Get the epoch of the last block (the height argument is not yet supported ) * `dry_run_tx` - Dry run a transaction - type: string description: Read a storage value with exact storage key From d758b1ba12077f367a7877a9ef86173263d0ff5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 08:53:50 +0200 Subject: [PATCH 221/394] docs: move and link to openAPI spec from Ledger RPC --- {specs => documentation/dev/src/specs/ledger}/openapi.yml | 0 documentation/dev/src/specs/ledger/rpc.md | 4 ++++ 2 files changed, 4 insertions(+) rename {specs => documentation/dev/src/specs/ledger}/openapi.yml (100%) diff --git a/specs/openapi.yml b/documentation/dev/src/specs/ledger/openapi.yml similarity index 100% rename from specs/openapi.yml rename to documentation/dev/src/specs/ledger/openapi.yml diff --git a/documentation/dev/src/specs/ledger/rpc.md b/documentation/dev/src/specs/ledger/rpc.md index d0b6c37b6bd..2a839ce8867 100644 --- a/documentation/dev/src/specs/ledger/rpc.md +++ b/documentation/dev/src/specs/ledger/rpc.md @@ -4,6 +4,10 @@ The ledger provides an RPC interface for submitting transactions to the mempool, The RPC interface is provided as [specified](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc) from Tendermint and most of the requests are routed to the Namada ledger via ABCI. +## OpenAPI spec + +The [OpenAPI specification](./openapi.yml) is provided. + ## Transactions A [transaction](../ledger.md#transactions) can be submitted to the [mempool](../ledger.md#mempool) via Tendermint's [`BroadCastTxSync`](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc#broadcasttxsync) or [`BroadCastTxAsync`](https://github.com/tendermint/spec/tree/4566f1e3028278c5b3eca27b53254a48771b152b/spec/rpc#broadcasttxasync). The `CheckTx` result of these requests is success only if the transaction passes [mempool validation rules](../ledger.md#mempool). In case of `BroadCastTxAsync`, the `DeliverTx` is not indicative of the transaction's result, it's merely a result of the transaction being added to the [transaction queue](../ledger.md#outer-transaction-processing). The actual result of the outer transaction and the inner transaction can be found from via the [ABCI events](https://github.com/tendermint/spec/blob/4566f1e3028278c5b3eca27b53254a48771b152b/spec/abci/abci.md#events). From 881e5ca41405f307b942cb34fea8f52bd206fe38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 08:57:12 +0200 Subject: [PATCH 222/394] changelog: add #322 --- .changelog/unreleased/docs/322-openapi-spec.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/docs/322-openapi-spec.md diff --git a/.changelog/unreleased/docs/322-openapi-spec.md b/.changelog/unreleased/docs/322-openapi-spec.md new file mode 100644 index 00000000000..9af416a9908 --- /dev/null +++ b/.changelog/unreleased/docs/322-openapi-spec.md @@ -0,0 +1 @@ +- Added OpenAPI spec ([#322](https://github.com/anoma/namada/pull/322)) \ No newline at end of file From 339858ca19345f5bf8c219795d0c327d71877d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 09:00:02 +0200 Subject: [PATCH 223/394] update relative paths in dev docs --- documentation/dev/src/explore/resources/ide.md | 4 ++-- documentation/dev/src/specs/encoding.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/dev/src/explore/resources/ide.md b/documentation/dev/src/explore/resources/ide.md index 9f5f106ba14..b7e2232bb96 100644 --- a/documentation/dev/src/explore/resources/ide.md +++ b/documentation/dev/src/explore/resources/ide.md @@ -31,7 +31,7 @@ Add these to your settings.json to get rustfmt and clippy with the nightly versi ```json "rust-analyzer.checkOnSave.overrideCommand": [ "cargo", - "+{{#include ../../../../rust-nightly-version}}", + "+{{#include ../../../../../rust-nightly-version}}", "clippy", "--workspace", "--message-format=json", @@ -40,7 +40,7 @@ Add these to your settings.json to get rustfmt and clippy with the nightly versi "rust-analyzer.rustfmt.overrideCommand": [ "rustup", "run", - "{{#include ../../../../rust-nightly-version}}", + "{{#include ../../../../../rust-nightly-version}}", "--", "rustfmt", "--edition", diff --git a/documentation/dev/src/specs/encoding.md b/documentation/dev/src/specs/encoding.md index 728390c153a..40e4b79b3cb 100644 --- a/documentation/dev/src/specs/encoding.md +++ b/documentation/dev/src/specs/encoding.md @@ -41,7 +41,7 @@ Note that for the [default transactions](ledger/default-transactions.md), the `d ## Proto definitions ``` -{{#include ../../../proto/types.proto}} +{{#include ../../../../proto/types.proto}} ``` From 8c1a9a4baee686aebf9fe51349465aa15f646565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 09:06:17 +0200 Subject: [PATCH 224/394] update links to user guide docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 939653c5966..323bc5e9b86 100644 --- a/README.md +++ b/README.md @@ -38,10 +38,10 @@ make install After installation, the main `anoma` executable will be available on path. -To find how to use it, check out the [User Guide section of the docs](https://docs.anoma.net/user-guide/). +To find how to use it, check out the [User Guide section of the docs](https://docs.namada.net/user-guide/index.html). For more detailed instructions and more install options, see the [Install -section](https://docs.anoma.net/user-guide/install.html) of the User +section](https://docs.namada.net/user-guide/install.html) of the User Guide. ## ⚙️ Development From dea735d0fddeb53277c8bf9b808178f99b382179 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Thu, 14 Jul 2022 13:42:42 +0200 Subject: [PATCH 225/394] Update config files --- documentation/docs/book.toml | 4 ++-- documentation/docs/src/README.md | 18 +----------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index d1a55fe0060..cef6122f681 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -1,10 +1,10 @@ [book] -authors = ["Heliax R&D Team"] +authors = ["Heliax AG"] language = "en" multilingual = false site-url = "https://docs.namada.net/" src = "src" -title = "Namada Docs" +title = "Namada Documentation" [output.html] edit-url-template = "https://github.com/anoma/namada/documentation/docs/edit/main/{path}" diff --git a/documentation/docs/src/README.md b/documentation/docs/src/README.md index ff487e04e41..3e1330b24c5 100644 --- a/documentation/docs/src/README.md +++ b/documentation/docs/src/README.md @@ -4,25 +4,9 @@ Welcome to Namada's docs! ## About Namada -[Namada](https://namada.net/) is a sovereign proof-of-stake blockchain, using Tendermint BFT consensus, that enables multi-asset private transfers for any native or non-native asset using a multi-asset shielded pool derived from the Sapling circuit. +[Namada](https://namada.net/) is a sovereign proof-of-stake blockchain, using Tendermint BFT consensus, that enables multi-asset private transfers for any native or non-native asset using a multi-asset shielded pool derived from the Sapling circuit. To learn more about the protocol, we recommend the following resources: - [Introducing Namada: Shielded Transfers with Any Assets](https://medium.com/anomanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) - [Namada's specifications](https://specs.namada.net) - -### The source - -This book is written using [mdBook](https://rust-lang.github.io/mdBook/), it currently lives in the [Namada repo](https://github.com/anoma/namada/documentation/docs). - -To get started quickly, in the `docs` directory one can: - -```shell -# Install dependencies -make dev-deps - -# This will open the book in your default browser and rebuild on changes -make serve -``` - -[Contributions](https://github.com/anoma/namada/issues) to the contents and the structure of this book should be made via pull requests. From 742f94c550b416cc24ce5835027b34b7394d2d67 Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Thu, 14 Jul 2022 13:45:08 +0200 Subject: [PATCH 226/394] Replace anoma with namada in quickstart --- documentation/docs/src/quick-start.md | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/documentation/docs/src/quick-start.md b/documentation/docs/src/quick-start.md index d29cae9ffd9..41a45c71583 100644 --- a/documentation/docs/src/quick-start.md +++ b/documentation/docs/src/quick-start.md @@ -11,11 +11,11 @@ This guide is aimed at people interested in running a validator node and assumes ## Install Namada -See [the install guide](user-guide/install.md) for details on installing the Namada binaries. Commands in this guide will assume you have the Namada binaries (`anoma`, `anoman`, `anomaw`, `anomac`) on your path. +See [the install guide](user-guide/install.md) for details on installing the Namada binaries. Commands in this guide will assume you have the Namada binaries (`namada`, `namadan`, `namadaw`, `namadac`) on your path. ## Joining a network -See [the testnets page](testnets) for details of how to join a testnet. The rest of this guide will assume you have joined a testnet chain using the `anomac utils join-network` command. +See [the testnets page](testnets) for details of how to join a testnet. The rest of this guide will assume you have joined a testnet chain using the `namadac utils join-network` command. ## Run a ledger node @@ -26,7 +26,7 @@ tmux # inside the tmux/or not -anoma ledger +namada ledger # can detach the tmux (Ctrl-B then D) ``` @@ -39,9 +39,9 @@ Generate a local key on disk ```shell # first, we make a keypair and the implicit account associated with it -# anomaw address gen instead of key gen. Preferred because they both make a keypair but the former stores the implicit address for it too +# namadaw address gen instead of key gen. Preferred because they both make a keypair but the former stores the implicit address for it too -anomaw address gen \ +namadaw address gen \ --alias example-implicit ➜ Enter encryption password: @@ -53,7 +53,7 @@ Successfully added a key and an address with alias: "example-implicit" To initialize an account operator on chain under the alias "example-established": ```shell -anomac init-account \ +namadac init-account \ --source example-implicit \ --public-key example-implicit \ --alias example-established @@ -79,7 +79,7 @@ The transaction initialized 1 new account Let's transfer ourselves 1000 NAM from the faucet with the same alias using: ```shell -anomac transfer \ +namadac transfer \ --source faucet \ --target example-established \ --token NAM \ @@ -104,7 +104,7 @@ Transaction applied with result: { To get the balance of your account "example-established": ```shell -anomac balance \ +namadac balance \ --owner example-established ``` @@ -113,7 +113,7 @@ anomac balance \ Initialize a validator account under any alias - in this example, "example-validator": ```shell -anomac init-validator \ +namadac init-validator \ --alias example-validator \ --source example-established @@ -148,7 +148,7 @@ The validator's addresses and keys were stored in the wallet: The ledger node has been setup to use this validator's address and consensus key. ``` -Once the `init-validator` transaction is applied in the block and the on-chain generated validator's address is stored in your wallet, you MUST restart the `anoma ledger` node to start the node as a validator that you've just created. +Once the `init-validator` transaction is applied in the block and the on-chain generated validator's address is stored in your wallet, you MUST restart the `namada ledger` node to start the node as a validator that you've just created. When you restart the node, you might notice log message "This node is not a validator" from Tendermint. This is expected, because your validator doesn't yet have any stake in the [PoS system](./user-guide/ledger/pos.md). @@ -157,7 +157,7 @@ We will now add some stake to your validator account. Transfer 1000 NAM to your validator account ("example-validator"): ```shell -anomac transfer \ +namadac transfer \ --source example-established \ --target example-validator \ --token NAM \ @@ -181,7 +181,7 @@ Transaction applied with result: { Bond the 1000 NAM to "example-validator" using: ```shell -anomac bond \ +namadac bond \ --validator example-validator \ --amount 1000 @@ -203,7 +203,7 @@ Transaction applied with result: { Check your bond: ```shell -anomac bonds \ +namadac bonds \ --validator example-validator ➜ Jan 06 22:30:42.798 INFO anoma_apps::cli::context: Chain ID: anoma-testnet-1.2.bf0181d9f7e0 @@ -216,7 +216,7 @@ Bonds total: 1000 Check the voting power - this will be 0 until the active-from epoch is reached (in this case `22395`): ```shell -anomac voting-power \ +namadac voting-power \ --validator example-validator ➜ Jan 06 22:31:24.908 INFO anoma_apps::cli::context: Chain ID: anoma-testnet-1.2.bf0181d9f7e0 From 98fe44420f7d4adee45c0995d63f4e2062a3315d Mon Sep 17 00:00:00 2001 From: Adrian Brink Date: Thu, 14 Jul 2022 16:39:49 +0200 Subject: [PATCH 227/394] update outer readme --- documentation/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/documentation/README.md b/documentation/README.md index a740b8d3878..63dec3bdea4 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -1,8 +1,6 @@ # Documentation -- `docs` contains user and operator documentation. - -- `dev` contains developer documentation for building on top of Namada. +- `docs` contains user, operator and developer documentation. - `spec` contains the specifications for Namada. From 346945288be75dd25ad85f96cf3e2e32ef1f42b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 13:07:57 +0200 Subject: [PATCH 228/394] re-add `dev` docs section --- documentation/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/documentation/README.md b/documentation/README.md index 63dec3bdea4..7ba9592edea 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -2,6 +2,8 @@ - `docs` contains user, operator and developer documentation. +- `dev` contains developer documentation for building on top of Namada. + - `spec` contains the specifications for Namada. To build any of these, in its subdirectory: From 2e683c7e371f3e31e5779d3ee45133a400b8ba81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 13:19:56 +0200 Subject: [PATCH 229/394] doc/docs: s/anoma/namada --- documentation/docs/src/testnets/README.md | 5 +- .../docs/src/testnets/internal-testnet-1.md | 64 +++++++++---------- .../src/user-guide/genesis-validator-setup.md | 12 ++-- .../docs/src/user-guide/getting-started.md | 27 ++++---- documentation/docs/src/user-guide/install.md | 10 +-- documentation/docs/src/user-guide/ledger.md | 4 +- .../docs/src/user-guide/ledger/governance.md | 22 +++---- .../docs/src/user-guide/ledger/masp.md | 37 ++++++++++- .../docs/src/user-guide/ledger/pos.md | 42 ++++++------ documentation/docs/src/user-guide/wallet.md | 34 +++++----- 10 files changed, 148 insertions(+), 109 deletions(-) diff --git a/documentation/docs/src/testnets/README.md b/documentation/docs/src/testnets/README.md index 23e3a888879..6299cc839c7 100644 --- a/documentation/docs/src/testnets/README.md +++ b/documentation/docs/src/testnets/README.md @@ -2,11 +2,12 @@ This section describes how to connect to the various testnets and to test selected features. -You may need to be using `anoma` binaries built from a specific commit in order to interact with specific testnets. Use the below command with the chain ID of the testnet you want to join. +You may need to be using `namada` binaries built from a specific commit in order to interact with specific testnets. Use the below command with the chain ID of the testnet you want to join. ```shell -anoma client utils join-network --chain-id=$NAMADA_CHAIN_ID +namada client utils join-network --chain-id=$NAMADA_CHAIN_ID ``` ## Current testnets + - [Namada Close Quarters Testnet 1](./namada-close-quarters-testnet-1.md) diff --git a/documentation/docs/src/testnets/internal-testnet-1.md b/documentation/docs/src/testnets/internal-testnet-1.md index 37af29fc5fc..d4ad45adb6d 100644 --- a/documentation/docs/src/testnets/internal-testnet-1.md +++ b/documentation/docs/src/testnets/internal-testnet-1.md @@ -16,14 +16,14 @@ You can install Namada by following the instructions from the [Install User Guid ## Setting up Namada -At this point, depending on your installation choice, we will assume that the `anoma` binaries are available on path and built from the latest testnet branch. +At this point, depending on your installation choice, we will assume that the `namada` binaries are available on path and built from the latest testnet branch. ### Join a network To join the current testnet, you need to download the configuration files. This can be done easily with: ```shell -anomac utils join-network --chain-id $NAMADA_TESTNET_CHAIN_ID +namadac utils join-network --chain-id $NAMADA_TESTNET_CHAIN_ID ``` It should output something like this where the chain id might differ: @@ -67,7 +67,7 @@ tar -xvf masp-params.tar.gz ~/Library/Application\ Support/MASPParams/ At this point, you are ready to start your Namada node with: ```shell -anoma ledger +namada ledger ``` To keep your node running after closing your terminal, you can optionally use a terminal multiplexer like `tmux`. @@ -84,9 +84,9 @@ To try out shielded transfers, you will first need an ordinary transparent account with some token balance. Example commands for that: ``` -anomaw address gen --alias my-implicit -anomac init-account --source my-implicit --public-key my-implicit --alias my-established -anomac transfer --token btc --amount 1000 --source faucet --target my-established --signer my-established +namadaw address gen --alias my-implicit +namadac init-account --source my-implicit --public-key my-implicit --alias my-established +namadac transfer --token btc --amount 1000 --source faucet --target my-established --signer my-established ``` The testnet tokens which the faucet can provide you are named `NAM`, @@ -102,7 +102,7 @@ you could randomly generate one with e.g. `openssl rand -hex 32`. The wallet does not yet support spending keys, so make a note of yours somewhere. -Shielded transfers work with the `anomac transfer` command, but either +Shielded transfers work with the `namadac transfer` command, but either `--source`, `--target`, or both are replaced. `--source` may be replaced with `--spending-key` to spend a shielded balance, but if you are following along, you don't have a shielded balance to spend yet. @@ -112,7 +112,7 @@ balance. To create a payment address from your spending key, use: ```shell -anomaw masp gen-payment-addr --spending-key [your spending key] +namadaw masp gen-payment-addr --spending-key [your spending key] ``` This will generate a different payment address each time you run it. @@ -123,14 +123,14 @@ Once you have a payment address, transfer a balance from your transparent account to your shielded spending key with something like: ```shell -anomac transfer --source my-established --payment-address [your payment address] --token btc --amount 100 +namadac transfer --source my-established --payment-address [your payment address] --token btc --amount 100 ``` Once this transfer goes through, you can view your spending key's balance: ```shell -anomac balance --spending-key [your spending key] +namadac balance --spending-key [your spending key] ``` However, your spending key is the secret key to all your shielded @@ -138,24 +138,24 @@ balances, and you may not want to use it just to view balances. For this purpose, you can derive the viewing key: ```shell -anomaw masp derive-view-key --spending-key [your spending key] -anomac balance --viewing-key [your viewing key] +namadaw masp derive-view-key --spending-key [your spending key] +namadac balance --viewing-key [your viewing key] ``` The viewing key can also be used to generate payment addresses, with -e.g. `anomaw masp gen-payment-addr --viewing-key [your viewing key]`. +e.g. `namadaw masp gen-payment-addr --viewing-key [your viewing key]`. Now that you have a shielded balance, it can either be transferred to a different shielded payment address (shielded to shielded): ```shell -anomac transfer --spending-key [your spending key] --payment-address [someone's payment address] --token btc --amount 50 --signer my-established +namadac transfer --spending-key [your spending key] --payment-address [someone's payment address] --token btc --amount 50 --signer my-established ``` or to a transparent account (shielded to transparent): ```shell -anomac transfer --spending-key [your spending key] --target [some transparent account] --token btc --amount 50 --signer my-established +namadac transfer --spending-key [your spending key] --target [some transparent account] --token btc --amount 50 --signer my-established ``` Note that for both of these types of transfer, `--signer` must be @@ -178,21 +178,21 @@ make build-wasm-scripts-docker If you get the following log, it means that Tendermint is not installed properly on your machine or not available on path. To solve this issue, install Tendermint by following the [Install User Guide](../user-guide/install.md). ```shell -2022-03-30T07:21:09.212187Z INFO anoma_apps::cli::context: Chain ID: anoma-masp-0.3.51d2f83a8412b95 -2022-03-30T07:21:09.213968Z INFO anoma_apps::node::ledger: Available logical cores: 8 -2022-03-30T07:21:09.213989Z INFO anoma_apps::node::ledger: Using 4 threads for Rayon. -2022-03-30T07:21:09.213994Z INFO anoma_apps::node::ledger: Using 4 threads for Tokio. -2022-03-30T07:21:09.217867Z INFO anoma_apps::node::ledger: VP WASM compilation cache size not configured, using 1/6 of available memory. -2022-03-30T07:21:09.218908Z INFO anoma_apps::node::ledger: Available memory: 15.18 GiB -2022-03-30T07:21:09.218934Z INFO anoma_apps::node::ledger: VP WASM compilation cache size: 2.53 GiB -2022-03-30T07:21:09.218943Z INFO anoma_apps::node::ledger: Tx WASM compilation cache size not configured, using 1/6 of available memory. -2022-03-30T07:21:09.218947Z INFO anoma_apps::node::ledger: Tx WASM compilation cache size: 2.53 GiB -2022-03-30T07:21:09.218954Z INFO anoma_apps::node::ledger: Block cache size not configured, using 1/3 of available memory. -2022-03-30T07:21:09.218959Z INFO anoma_apps::node::ledger: RocksDB block cache size: 5.06 GiB -2022-03-30T07:21:09.218996Z INFO anoma_apps::node::ledger::storage::rocksdb: Using 2 compactions threads for RocksDB. -2022-03-30T07:21:09.219196Z INFO anoma_apps::node::ledger: Tendermint node is no longer running. -2022-03-30T07:21:09.232544Z INFO anoma::ledger::storage: No state could be found -2022-03-30T07:21:09.232709Z INFO anoma_apps::node::ledger: Tendermint has exited, shutting down... -2022-03-30T07:21:09.232794Z INFO anoma_apps::node::ledger: Anoma ledger node started. -2022-03-30T07:21:09.232849Z INFO anoma_apps::node::ledger: Anoma ledger node has shut down. +2022-03-30T07:21:09.212187Z INFO namada_apps::cli::context: Chain ID: anoma-masp-0.3.51d2f83a8412b95 +2022-03-30T07:21:09.213968Z INFO namada_apps::node::ledger: Available logical cores: 8 +2022-03-30T07:21:09.213989Z INFO namada_apps::node::ledger: Using 4 threads for Rayon. +2022-03-30T07:21:09.213994Z INFO namada_apps::node::ledger: Using 4 threads for Tokio. +2022-03-30T07:21:09.217867Z INFO namada_apps::node::ledger: VP WASM compilation cache size not configured, using 1/6 of available memory. +2022-03-30T07:21:09.218908Z INFO namada_apps::node::ledger: Available memory: 15.18 GiB +2022-03-30T07:21:09.218934Z INFO namada_apps::node::ledger: VP WASM compilation cache size: 2.53 GiB +2022-03-30T07:21:09.218943Z INFO namada_apps::node::ledger: Tx WASM compilation cache size not configured, using 1/6 of available memory. +2022-03-30T07:21:09.218947Z INFO namada_apps::node::ledger: Tx WASM compilation cache size: 2.53 GiB +2022-03-30T07:21:09.218954Z INFO namada_apps::node::ledger: Block cache size not configured, using 1/3 of available memory. +2022-03-30T07:21:09.218959Z INFO namada_apps::node::ledger: RocksDB block cache size: 5.06 GiB +2022-03-30T07:21:09.218996Z INFO namada_apps::node::ledger::storage::rocksdb: Using 2 compactions threads for RocksDB. +2022-03-30T07:21:09.219196Z INFO namada_apps::node::ledger: Tendermint node is no longer running. +2022-03-30T07:21:09.232544Z INFO namada::ledger::storage: No state could be found +2022-03-30T07:21:09.232709Z INFO namada_apps::node::ledger: Tendermint has exited, shutting down... +2022-03-30T07:21:09.232794Z INFO namada_apps::node::ledger: Anoma ledger node started. +2022-03-30T07:21:09.232849Z INFO namada_apps::node::ledger: Anoma ledger node has shut down. ``` diff --git a/documentation/docs/src/user-guide/genesis-validator-setup.md b/documentation/docs/src/user-guide/genesis-validator-setup.md index dd65d453b4b..ae04bd123dc 100644 --- a/documentation/docs/src/user-guide/genesis-validator-setup.md +++ b/documentation/docs/src/user-guide/genesis-validator-setup.md @@ -15,7 +15,7 @@ You must also provide a static `{IP:port}` to the `--net-address` argument of yo ```shell export ALIAS="1337-validator" -anoma client utils init-genesis-validator \ +namada client utils init-genesis-validator \ --alias $ALIAS \ --net-address 1.2.3.4:26656 ``` @@ -35,17 +35,17 @@ Note that the wallet containing your private keys will also be written into this Once the network is finalized, a new chain ID will be created and released on [anoma-network-config/releases](https://github.com/heliaxdev/anoma-network-config/releases) (a custom configs URL can be used instead with `ANOMA_NETWORK_CONFIGS_SERVER` env var). You can use it to setup your genesis validator node for the `--chain-id` argument in the command below. ```shell -anoma client utils join-network \ +namada client utils join-network \ --chain-id $CHAIN_ID \ --genesis-validator $ALIAS ``` -This command will use your pre-genesis wallet for the given chain and take care of setting up Anoma with Tendermint. +This command will use your pre-genesis wallet for the given chain and take care of setting up Namada with Tendermint. -If you run this command in the same directory that you ran `anoma client utils init-genesis-validator`, it should find the pre-genesis wallet for you, otherwise you can pass the path to the pre-genesis directory using `--pre-genesis-path`. e.g. +If you run this command in the same directory that you ran `namada client utils init-genesis-validator`, it should find the pre-genesis wallet for you, otherwise you can pass the path to the pre-genesis directory using `--pre-genesis-path`. e.g. ```shell -anoma client utils join-network \ +namada client utils join-network \ --chain-id $CHAIN_ID \ --pre-genesis-path workspace/.anoma/pre-genesis/$ALIAS ``` @@ -53,7 +53,7 @@ anoma client utils join-network \ Once setup, you can start the ledger as usual with e.g.: ```shell -anoma ledger +namada ledger ``` ## Required keys diff --git a/documentation/docs/src/user-guide/getting-started.md b/documentation/docs/src/user-guide/getting-started.md index 8e318972063..6f2ce00e309 100644 --- a/documentation/docs/src/user-guide/getting-started.md +++ b/documentation/docs/src/user-guide/getting-started.md @@ -2,34 +2,35 @@ This guide assumes that the Namada binaries are [installed](./install.md) and available on path. These are: -- `anoma`: The main binary that can be used to interact with all the components of Namada -- `anoman`: The ledger and intent gossiper node -- `anomac`: The client -- `anomaw`: The wallet +- `namada`: The main binary that can be used to interact with all the components of Namada +- `namadan`: The ledger and intent gossiper node +- `namadac`: The client +- `namadaw`: The wallet -The main binary `anoma` has sub-commands for all of the other binaries: +The main binary `namada` has sub-commands for all of the other binaries: -- `anoma client = anomac` -- `anoma node = anoman` -- `anoma wallet = anomaw` +- `namada client = namadac` +- `namada node = namadan` +- `namada wallet = namadaw` To explore the command-line interface, add `--help` argument at any sub-command level to find out any possible sub-commands and/or arguments. - - ## Join a network After you installed Namada, you will need to join a live network (e.g. testnet) to be able to interact with a chain and execute most available commands. You can join a network with the following command: + ``` -anoma client utils join-network --chain-id= -``` +namada client utils join-network --chain-id= +``` + To join a testnet, head over to the [testnets](../testnets) section for details on how to do this. ## Start your node As soon as you are connected to a network, you can start your local node with: + ``` -anoma ledger +namada ledger ``` Learn more about the configuration of the Ledger in [The Ledger](./ledger.md) section diff --git a/documentation/docs/src/user-guide/install.md b/documentation/docs/src/user-guide/install.md index dcf6966cb4e..1b491f881b7 100644 --- a/documentation/docs/src/user-guide/install.md +++ b/documentation/docs/src/user-guide/install.md @@ -16,8 +16,9 @@ This section covers the minimum and recommended hardware requirements for engagi | RAM | 16GB DDR4 | | Storage | at least 60GB SSD (NVMe SSD is recommended. HDD will be enough for localnet only) | -There are different ways to install Namada: -- [From Source](#from-source) +There are different ways to install Namada: + +- [From Source](#from-source) - [From Binaries](#from-binaries) - [From Docker](#from-docker) @@ -74,7 +75,7 @@ Let's install Tendermint. You can either follow the instructions on the [Tendermint guide](https://docs.tendermint.com/master/introduction/install.html) or download the `get_tendermint.sh` script from the [Namada repository](https://github.com/anoma/namada/blob/master/scripts/install/get_tendermint.sh) and execute it (will ask you for `root` access): ```shell -curl -LO https://raw.githubusercontent.com/anoma/anoma/master/scripts/install/get_tendermint.sh +curl -LO https://raw.githubusercontent.com/namada/anoma/main/scripts/install/get_tendermint.sh chmod +x get_tendermint.sh ./get_tendermint.sh ``` @@ -90,6 +91,7 @@ Finally, you should have GLIBC `v2.29` or higher. Now, that you have all dependencies installed you can download the latest binary release from our [releases page](https://github.com/anoma/namada/releases) by choosing the appropriate architecture. [fixme]: <> (update docker config as soon as Anoma is transferred fully to Namada) + ## From Docker -Go to [heliaxdev dockerhub account](https://hub.docker.com/r/heliaxdev/anoma) and pull the image. \ No newline at end of file +Go to [heliaxdev dockerhub account](https://hub.docker.com/r/heliaxdev/anoma) and pull the image. diff --git a/documentation/docs/src/user-guide/ledger.md b/documentation/docs/src/user-guide/ledger.md index 747cb8ebcaa..e51da998aa9 100644 --- a/documentation/docs/src/user-guide/ledger.md +++ b/documentation/docs/src/user-guide/ledger.md @@ -3,7 +3,7 @@ To start a local Namada ledger node, run: ```shell -anoma ledger +namada ledger ``` Note that you need to have [joined a network](./getting-started.md) before you start the ledger. It throws an error if no network has been configured. @@ -24,4 +24,4 @@ option `p2p_pex` in `[ledger.tendermint]` can be set by `ANOMA_LEDGER__TENDERMINT__P2P_PEX=true|false` or `ANOMA_LEDGER.TENDERMINT.P2P_PEX=true|false` in the environment (Note: only the double underscore form can be used in Bash, because Bash doesn't allow dots in -environment variable names). \ No newline at end of file +environment variable names). diff --git a/documentation/docs/src/user-guide/ledger/governance.md b/documentation/docs/src/user-guide/ledger/governance.md index 3fe70edf35e..22a84755ede 100644 --- a/documentation/docs/src/user-guide/ledger/governance.md +++ b/documentation/docs/src/user-guide/ledger/governance.md @@ -14,7 +14,7 @@ There are two different mechanism to create a proposal: Assuming you have an account with at least 500 NAM token (in this example we are going to use `my-new-acc`), lets get the corresponding address ```shell -anoma wallet address find --alias `my-new-acc` +namada wallet address find --alias `my-new-acc` ``` Now, we need to create a json file `proposal.json` holding the content of our proposal. Copy the below text into a json file. @@ -51,19 +51,19 @@ You should change the value of: As soon as your `proposal.json` file is ready, you can submit the proposal with (making sure to be in the same directory as the `proposal.json` file): ```shell -anoma client init-proposal --data-path proposal.json +namada client init-proposal --data-path proposal.json ``` The transaction should have been accepted. You can query all the proposals with: ```shell -anoma client query-proposal +namada client query-proposal ``` or a single proposal with ```shell -anoma client query-proposal --proposal-id 0 +namada client query-proposal --proposal-id 0 ``` where `0` is the proposal id. @@ -73,7 +73,7 @@ where `0` is the proposal id. Only validators and delegators can vote. Assuming you have a validator or a delegator account (in this example we are going to use `validator`), you can send a vote with the following command: ```shell -anoma client vote-proposal \ +namada client vote-proposal \ --proposal-id 0 \ --vote yay \ --signer validator @@ -86,25 +86,25 @@ where `--vote` can be either `yay` or `nay`. As soon as the ledger reaches epoch definied in the json as `voting_end_epoch`, you can no longer vote. The code definied in `proposal_code` json field will be executed at the beginning of `grace_epoch` epoch. You can use the following commands to check the status of a proposal: ```shell -anoma client query-proposal --proposal-id 0 +namada client query-proposal --proposal-id 0 ``` or to just check the result: ```shell -anoma client query-proposal-result --proposal-id 0 +namada client query-proposal-result --proposal-id 0 ``` ## Off-chain proposals -If for any reason issuing an on-chain proposal is not adequate to your needs, you still have the option to create an off-chain proposal. +If for any reason issuing an on-chain proposal is not adequate to your needs, you still have the option to create an off-chain proposal. ### Create proposal Create the same json file as in the on-chain proposal and use the following command: ```shell -anoma client init-proposal \ +namada client init-proposal \ --data-path proposal.json \ --offline ``` @@ -116,7 +116,7 @@ This command will create a `proposal` file same directory where the command was To vote an offline proposal use the following command: ```shell -anoma client vote-proposal --data-path proposal \ +namada client vote-proposal --data-path proposal \ --vote yay \ --signer validator \ --offline @@ -136,7 +136,7 @@ All those files will have to be in a folder (lets call it `offline-proposal`). Now you can use the following command: ```shell -anoma client query-proposal-result \ +namada client query-proposal-result \ --offline \ --data-path `offline-proposal` ``` diff --git a/documentation/docs/src/user-guide/ledger/masp.md b/documentation/docs/src/user-guide/ledger/masp.md index fb866dcf490..0c50995e81b 100644 --- a/documentation/docs/src/user-guide/ledger/masp.md +++ b/documentation/docs/src/user-guide/ledger/masp.md @@ -1,9 +1,11 @@ # Private transfers + In Namada, private transfers are enabled by the Multi-Asset Shielded Pool (MASP). The MASP is a zero-knowledge circuit (zk-SNARK) that extends the Zcash Sapling circuit to add support for sending arbitrary assets. All assets in the pool share the same anonymity set, this means that the more transactions are issued to MASP, the stronger are the privacity guarantees. ## Using MASP If you are familiar to Zcash, the set of interactions you can execute with the MASP are similar: + - [**Shielding transfers:** transparent to shielded addresses](#shielding-transfers) - [**Shielded transfers:** shielded to shielded addresses](#shielded-transfers) - [**Deshielding transfers:** shielded to transparent addresses](#deshielding-tranfers) @@ -21,16 +23,20 @@ transparent account with some token balance. #### Create your transparent account Generate an implicit account: + ```shell namadaw address gen --alias [your-implicit-account-alias] ``` + Then, create an established account on chain using the implicit account you've just generated: + ```shell namadac init-account \ --source [your-implicit-account-alias] \ --public-key [your-implicit-account-alias] \ --alias [your-established-account-alias] ``` + #### Get tokens from the Testnet Faucet ```admonish info "Testnet Faucet Tokens" @@ -53,7 +59,8 @@ Now that you have a transparent account with some tokens, you can generate a Spe #### Generate your Spending Key You can randomly generate a new Spending Key with: -```shell + +```shell namadaw masp gen-key --alias [your-spending-key-alias] ``` @@ -128,7 +135,9 @@ namadac transfer \ ``` ### Shielded Address/Key Generation + #### Spending Key Generation + The client should be able to generate a spending key and automatically derive a viewing key for it. The spending key should be usable as the source of a transfer. The viewing key should be usable to determine the @@ -136,31 +145,41 @@ total unspent notes that the spending key is authorized to spend. It should not be possible to directly or indirectly use the viewing key to spend funds. Below is an example of how spending keys should be generated: + ``` namadaw --masp gen-key --alias my-sk ``` + #### Payment Address Generation + The client should be able to generate a payment address from a spending key or viewing key. This payment address should be usable to send notes to the originating spending key. It should not be directly or indirectly usable to either spend notes or view shielded balances. Below are examples of how payment addresses should be generated: + ``` namadaw masp gen-addr --alias my-pa1 --key my-sk namadaw masp gen-addr --alias my-pa2 --key my-vk ``` + #### Manual Key/Address Addition + The client should be able to directly add raw spending keys, viewing keys, and payment addresses. Below are examples of how these objects should be added: + ``` namadaw masp add --alias my-sk --value xsktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxcvedhsv namadaw masp add --alias my-vk --value xfvktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7erg38awgq60r259csg3lxeeyy5355f5nj3ywpeqgd2guqd73uxz46645d0ayt9em88wflka0vsrq29u47x55psw93ly80lvftzdr5ccrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqt7n63v namadaw masp add --alias my-pa --value patest10qy6fuwef9leccl6dfm7wwlyd336x4y32hz62cnrvlrl6r5yk0jnw80kus33x34a5peg2xc4csn ``` + ### Making Shielded Transactions + #### Shielding Transactions + The client should be able to make shielding transactions by providing a transparent source address and a shielded payment address. The main transparent effect of such a transaction should be a deduction of @@ -170,10 +189,13 @@ gas fee is charged to the source address. Once the transaction is completed, the spending key that was used to generate the payment address will have the authority to spend the amount that was send. Below is an example of how a shielding transacion should be made: + ``` namadac transfer --source Bertha --amount 50 --token BTC --target my-pa ``` + #### Unshielding Transactions + The client should be able to make unshielding transactions by providing a shielded spending key and a transparent target address. The main transparent effect of such a transaction should be a deduction of the @@ -183,10 +205,13 @@ is charged to the signer's address (which should default to the target address). Once the transaction is complete, the spending key will no longer be able to spend the transferred amount. Below is an example of how an unshielding transaction should be made: + ``` namadac transfer --target Bertha --amount 45 --token BTC --source my-sk ``` + #### Shielded Transactions + The client should be able to make shielded transactions by providing a shielded spending key and a shielded payment address. There should be no change in the transparent balance of the MASP validity predicate's @@ -195,10 +220,13 @@ transaction is complete, the spending key will no longer be able to spend the transferred amount, but the spending key that was used to (directly or indirectly) generate the payment address will. Below is an example of how a shielded transaction should be made: + ``` namadac transfer --source my-sk --amount 5 --token BTC --target your-pa ``` + ### Viewing Shielded Balances + The client should be able to view shielded balances. The most general output should be a list of pairs, each denoting a token type and the unspent amount of that token present at each shielded @@ -206,26 +234,33 @@ address whose viewing key is represented in the wallet. Note that it should be possible to restrict the balance query to check only a specific viewing key or for a specific token type. Below are examples of how balance queries should be made: + ``` namadac balance namadac balance --owner my-key namadac balance --owner my-key --token BTC namadac balance --token BTC ``` + ### Listing Shielded Keys/Addresses + The wallet should be able to list all the spending keys, viewing keys, and payment addresses that it stores. Below are examples of how the wallet's storage should be queried: + ``` namadaw masp list-keys namadaw masp list-keys --unsafe-show-secret namadaw masp list-keys --unsafe-show-secret --decrypt namadaw masp list-addrs ``` + ### Finding Shielded Keys/Addresses + The wallet should be able to find any spending key, viewing key or payment address when given its alias. Below are examples of how the wallet's storage should be queried: + ``` namadaw masp find --alias my-alias namadaw masp find --alias my-alias --unsafe-show-secret diff --git a/documentation/docs/src/user-guide/ledger/pos.md b/documentation/docs/src/user-guide/ledger/pos.md index f0ffff9f053..2407fee0d2f 100644 --- a/documentation/docs/src/user-guide/ledger/pos.md +++ b/documentation/docs/src/user-guide/ledger/pos.md @@ -2,12 +2,12 @@ The Namada Proof of Stake system uses the NAM token as the staking token. It features delegation to any number of validators and customizable validator validity predicates. -## PoS Validity Predicate +## PoS Validity Predicate The PoS system is implemented as an account with the [PoS Validity Predicate](https://github.com/anoma/namada/blob/namada/shared/src/ledger/pos/vp.rs) that governs the rules of the system. You can find its address in your wallet: ```shell -anoma wallet address find --alias PoS +namada wallet address find --alias PoS ``` ## Epochs @@ -17,7 +17,7 @@ The system relies on the concept of epochs. An epoch is a range of consecutive b To query the current epoch: ```shell -anoma client epoch +namada client epoch ``` ## Delegating @@ -27,7 +27,7 @@ You can delegate to any number of validators at any time. When you delegate toke To submit a delegation that bonds tokens from the source address to a validator with alias `validator-1`: ```shell -anoma client bond \ +namada client bond \ --source my-new-acc \ --validator validator-1 \ --amount 12.34 @@ -36,7 +36,7 @@ anoma client bond \ You can query your delegations: ```shell -anoma client bonds --owner my-new-acc +namada client bonds --owner my-new-acc ``` The result of this query will inform the epoch from which your delegations will be active. @@ -44,7 +44,7 @@ The result of this query will inform the epoch from which your delegations will Because the PoS system is just an account, you can query its balance, which is the sum of all staked tokens: ```shell -anoma client balance --owner PoS +namada client balance --owner PoS ``` ### Slashes @@ -52,7 +52,7 @@ anoma client balance --owner PoS Should a validator exhibit punishable behavior, the delegations towards this validator are also liable for slashing. Only the delegations that were active in the epoch in which the fault occurred will be slashed by the slash rate of the fault type. If any of your delegations have been slashed, this will be displayed in the `bonds` query. You can also find all the slashes applied with: ```shell -anoma client slashes +namada client slashes ``` ### Unbounding @@ -62,7 +62,7 @@ While your tokens are being delegated, they are locked-in the PoS system and hen To submit an unbonding of a delegation of tokens from a source address to the validator: ```shell -anoma client unbond \ +namada client unbond \ --source my-new-acc \ --validator validator-1 \ --amount 1.2 @@ -71,13 +71,13 @@ anoma client unbond \ When you unbond tokens, you won't be able to withdraw them immediately. Instead, tokens unbonded in the epoch `n` will be withdrawable starting from the epoch `n + 6` (the literal `6` is set by PoS parameter `unbonding_len`). After you unbond some tokens, you will be able to see when you can withdraw them via `bonds` query: ```shell -anoma client bonds --owner my-new-acc +namada client bonds --owner my-new-acc ``` When the chain reaches the epoch in which you can withdraw the tokens (or anytime after), you can submit a withdrawal of unbonded delegation of tokens back to your account: ```shell -anoma client withdraw \ +namada client withdraw \ --source my-new-acc \ --validator validator-1 ``` @@ -89,7 +89,7 @@ Upon success, the withdrawn tokens will be credited back your account and debite To see all validators and their voting power, you can query: ```shell -anoma client voting-power +namada client voting-power ``` With this command, you can specify `--epoch` to find the voting powers at some future epoch. Note that only the voting powers for the current and the next epoch are final. @@ -101,7 +101,7 @@ With this command, you can specify `--epoch` to find the voting powers at some f To register a new validator account, run: ```shell -anoma client init-validator \ +namada client init-validator \ --alias my-validator \ --source my-new-acc ``` @@ -120,7 +120,7 @@ Then, it submits a transaction to the ledger that generates two new accounts wit These keys and aliases of the addresses will be saved in your wallet. Your local ledger node will also be setup to run this validator, you just have to shut it down with e.g. `Ctrl + C`, then start it again with the same command: ```shell -anoma ledger +namada ledger ``` The ledger will then use the validator consensus key to sign blocks, should your validator account acquire enough voting power to be included in the active validator set. The size of the active validator set is limited to `128` (the limit is set by the PoS `max_validator_slots` parameter). @@ -128,22 +128,22 @@ The ledger will then use the validator consensus key to sign blocks, should your Note that the balance of NAM tokens that is in your validator account does not count towards your validator's stake and voting power: ```shell -anoma client balance --owner my-validator --token NAM +namada client balance --owner my-validator --token NAM ``` That is, the balance of your account's address is a regular liquid balance that you can transfer using your validator account key, depending on the rules of the validator account's validity predicate. The default validity predicate allows you to transfer it with a signed transaction and/or stake it in the PoS system. -### Self-bonding +### Self-bonding You can submit a self-bonding transaction of tokens from a validator account to the PoS system with: ```shell -anoma client bond \ +namada client bond \ --validator my-validator \ --amount 3.3 ``` -### Determine your voting power +### Determine your voting power A validator's voting power is determined by the sum of all their active self-bonds and delegations of tokens, with slashes applied, if any, divided by `1000` (PoS `votes_per_token` parameter, with the current value set to `10‱` in parts per ten thousand). @@ -151,12 +151,12 @@ The same rules apply to delegations. When you self-bond tokens, the bonded amoun While your tokens are being self-bonded, they are locked-in the PoS system and hence are not liquid until you withdraw them. To do that, you first need to send a transaction to “unbond” your tokens. You can unbond any amount, up to the sum of all your self-bonds, even before they become active. -### Self-unbounding +### Self-unbounding To submit an unbonding of self-bonded tokens from your validator: ```shell -anoma client unbond \ +namada client unbond \ --validator my-validator \ --amount 0.3 ``` @@ -164,11 +164,11 @@ anoma client unbond \ Again, when you unbond tokens, you won't be able to withdraw them immediately. Instead, tokens unbonded in the epoch `n` will be withdrawable starting from the epoch `n + 6`. After you unbond some tokens, you will be able to see when you can withdraw them via `bonds` query: ```shell -anoma client bonds --validator my-validator +namada client bonds --validator my-validator ``` When the chain reaches the epoch in which you can withdraw the tokens (or anytime after), you can submit a withdrawal of unbonded tokens back to your validator account: ```shell -anoma client withdraw --validator my-validator +namada client withdraw --validator my-validator ``` diff --git a/documentation/docs/src/user-guide/wallet.md b/documentation/docs/src/user-guide/wallet.md index 23421a3044d..6f0366784ea 100644 --- a/documentation/docs/src/user-guide/wallet.md +++ b/documentation/docs/src/user-guide/wallet.md @@ -3,6 +3,7 @@ This document describes the different wallet concepts and options that are available to users of Namada who want to be able to [send, receive and interact](#send-and-receive-nam-tokens) with NAM tokens on the Namada blockchain. Check out the different options to generate a wallet: + - File System Wallet - Web Wallet - Paper Wallet @@ -15,7 +16,7 @@ Namada uses ed25519 keypairs for signing cryptographic operations on the blockch To manage your keys, various sub-commands are available under: ```shell -anoma wallet key +namada wallet key ``` ### Generate a keypair @@ -23,17 +24,17 @@ anoma wallet key Generate a keypair with a given alias and derive the implicit address from its public key: ```shell -anoma wallet key gen --alias my-key +namada wallet key gen --alias my-key ``` ```admonish note -The derived implicit address shares the same `my-key` alias. The previous command has the same effect as `anoma wallet address gen --alias my-key`. +The derived implicit address shares the same `my-key` alias. The previous command has the same effect as `namada wallet address gen --alias my-key`. ``` ### List all known keys ```shell -anoma wallet key list +namada wallet key list ``` ## Manage addresses @@ -49,31 +50,31 @@ There are currently 3 types of account addresses: To manage addresses, similar to keys, various sub-commands are available: ```shell -anoma wallet address +namada wallet address ``` ### Generate an implicit address ```shell -anoma wallet address gen --alias my-account +namada wallet address gen --alias my-account ``` ```admonish note -Note that this will also generate and save a key from which the address was derived and save it under the same `my-account` alias. Thus, this command has the same effect as `anoma wallet key gen --alias my-account`. +Note that this will also generate and save a key from which the address was derived and save it under the same `my-account` alias. Thus, this command has the same effect as `namada wallet key gen --alias my-account`. ``` ### List all known addresses ```shell -anoma wallet address list +namada wallet address list ``` ## File System Wallet -By default, the Namada Wallet is stored under `.anoma/{chain_id}/wallet.toml` where keys are stored encrypted. You can change the default base directory path with `--base-dir` and you can allow the storage of unencrypted keypairs with the flag `--unsafe-dont-encrypt`. +By default, the Namada Wallet is stored under `.anoma/{chain_id}/wallet.toml` where keys are stored encrypted. You can change the default base directory path with `--base-dir` and you can allow the storage of unencrypted keypairs with the flag `--unsafe-dont-encrypt`. -If the wallet doesn't already exist, it will be created for you as soon as you run a command that tries to access the wallet. A newly created wallet will be pre-loaded with some internal addresses like `pos`, `pos_slash_pool`, `masp` and more. +If the wallet doesn't already exist, it will be created for you as soon as you run a command that tries to access the wallet. A newly created wallet will be pre-loaded with some internal addresses like `pos`, `pos_slash_pool`, `masp` and more. Currently, the Namada client can load the password via: @@ -83,7 +84,7 @@ Currently, the Namada client can load the password via: ## Web Wallet -The Web Wallet for Namada is currently in closed beta. +The Web Wallet for Namada is currently in closed beta. ## Paper Wallet @@ -104,7 +105,7 @@ If you already have a key in your wallet, you can skip this step. Otherwise, [ge Then, send a transaction to initialize your new established account and save its address with the alias `my-new-acc`. The `my-key` public key will be written into the account's storage for authorizing future transactions. We also sign this transaction with `my-key`. ```shell -anoma client init-account \ +namada client init-account \ --alias my-new-acc \ --public-key my-key \ --source my-key @@ -116,11 +117,10 @@ This command uses the prebuilt [User Validity Predicate](https://github.com/anom ### Send a Payment - To submit a regular token transfer from your account to the `validator-1` address: ```shell -anoma client transfer \ +namada client transfer \ --source my-new-acc \ --target validator-1 \ --token NAM \ @@ -134,7 +134,7 @@ This command will attempt to find and use the key of the source address to sign To query token balances for a specific token and/or owner: ```shell -anoma client balance --token NAM --owner my-new-acc +namada client balance --token NAM --owner my-new-acc ``` ```admonish note @@ -143,9 +143,9 @@ For any client command that submits a transaction (`init-account`, `transfer`, ` ``` ### See every known addresses' balance + You can see the token's addresses known by the client when you query all tokens balances: ```shell -anoma client balance +namada client balance ``` - From 717a71a8cee9b6dd407f949ea7832a405c673f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 13:26:26 +0200 Subject: [PATCH 230/394] docs: add notes about the books --- documentation/docs/src/README.md | 4 ++++ documentation/specs/src/index.md | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/documentation/docs/src/README.md b/documentation/docs/src/README.md index 3e1330b24c5..29f18f2b509 100644 --- a/documentation/docs/src/README.md +++ b/documentation/docs/src/README.md @@ -10,3 +10,7 @@ To learn more about the protocol, we recommend the following resources: - [Introducing Namada: Shielded Transfers with Any Assets](https://medium.com/anomanetwork/introducing-namada-shielded-transfers-with-any-assets-dce2e579384c) - [Namada's specifications](https://specs.namada.net) + +This book is written using [mdBook](https://rust-lang.github.io/mdBook/), the source can be found in the [Namada repository](https://github.com/anoma/namada/tree/main/documentation/docs). + +[Contributions](https://github.com/anoma/namada/blob/main/CONTRIBUTING.md) to the contents and the structure of this book should be made via pull requests. diff --git a/documentation/specs/src/index.md b/documentation/specs/src/index.md index df8cd941825..e33811757bb 100644 --- a/documentation/specs/src/index.md +++ b/documentation/specs/src/index.md @@ -15,6 +15,7 @@ is provided in order to facilitate safe and private user interaction with the pr ### How does Namada relate to Anoma? Namada is _two things_: + - The first major release _version_ of the Anoma protocol. - The first _fractal instance_ launched as part of the Anoma network. @@ -56,4 +57,8 @@ The Namada specification documents are organised into five sub-sections: - [Multi-asset shielded pool](./masp.md) - [Interoperability](./interoperability.md) - [Economics](./economics.md) -- [User interfaces](./user-interfaces.md) \ No newline at end of file +- [User interfaces](./user-interfaces.md) + +This book is written using [mdBook](https://rust-lang.github.io/mdBook/), the source can be found in the [Namada repository](https://github.com/anoma/namada/tree/main/documentation/specs). + +[Contributions](https://github.com/anoma/namada/blob/main/CONTRIBUTING.md) to the contents and the structure of this book should be made via pull requests. From a5deec83fd85ed44f690959cadc3a73919f06fc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 17 Aug 2022 13:31:40 +0200 Subject: [PATCH 231/394] docs: update book's config branch, edit-url and repo links --- documentation/dev/book.toml | 4 ++-- documentation/docs/book.toml | 4 ++-- documentation/specs/book.toml | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/documentation/dev/book.toml b/documentation/dev/book.toml index f1b171cd245..f9ac4cebb28 100644 --- a/documentation/dev/book.toml +++ b/documentation/dev/book.toml @@ -8,12 +8,12 @@ src = "src" title = "Anoma - DOCS" [output.html] -edit-url-template = "https://github.com/anoma/namada/edit/master/documentation/dev/{path}" +edit-url-template = "https://github.com/anoma/namada/edit/main/documentation/dev/{path}" git-repository-url = "https://github.com/anoma/namada" additional-css = ["assets/custom.css", "assets/mdbook-admonish.css"] additional-js = ["assets/mermaid.min.js", "assets/mermaid-init.js"] mathjax-support = true -git-branch = "master" +git-branch = "main" [output.html.search] expand = true diff --git a/documentation/docs/book.toml b/documentation/docs/book.toml index cef6122f681..11d7630a7bd 100644 --- a/documentation/docs/book.toml +++ b/documentation/docs/book.toml @@ -7,8 +7,8 @@ src = "src" title = "Namada Documentation" [output.html] -edit-url-template = "https://github.com/anoma/namada/documentation/docs/edit/main/{path}" -git-repository-url = "https://github.com/anoma/namada/documentation/docs" +edit-url-template = "https://github.com/anoma/namada/edit/main/documentation/docs/{path}" +git-repository-url = "https://github.com/anoma/namada" additional-css = ["assets/custom.css", "assets/mdbook-admonish.css"] git-branch = "main" diff --git a/documentation/specs/book.toml b/documentation/specs/book.toml index 9e2663d5d30..7848d519db9 100644 --- a/documentation/specs/book.toml +++ b/documentation/specs/book.toml @@ -7,8 +7,9 @@ src = "src" title = "Namada Specifications" [output.html] -edit-url-template = "https://github.com/anoma/namada/edit/master/documentation/spec/{path}" +edit-url-template = "https://github.com/anoma/namada/edit/main/documentation/specs/{path}" git-repository-url = "https://github.com/anoma/namada" +git-branch = "main" [output.html.search] expand = true 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 232/394] 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 3474f6c8e6a..be833edfb90 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 39562a4a1e9..a643f3c0e40 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 b3142156a47..423e29d0340 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 233/394] 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 315c68384ea..00b459106ba 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 957354d8480..6aea71c8b6a 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 234/394] 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 00000000000..2acf6479aa2 --- /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 235/394] 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 a1343cd7e52..cd06092ec9d 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 236/394] 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 f3e4cd4ed22..5e4d0c579cf 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 237/394] 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 9a5309f927d..2394265acc0 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 238/394] 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 5e4d0c579cf..f3011514a1c 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 239/394] 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 1b75f83bdc1..f1d2b812bc0 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 240/394] 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 29137b49bde..bb0f2eeafa9 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 241/394] 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 be1844c6cd8..cf7114d5ed7 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 242/394] 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 be450a70861..3fcce71f43c 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 243/394] 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 cf7114d5ed7..23b7c569e97 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 2394265acc0..c07f3878db5 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 244/394] 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 bb0f2eeafa9..8524346b8ca 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 45342ef2773..ce6b0b225ab 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 245/394] 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 34652ac8256..465c1b4c0f9 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 6f1fd96707e..e1b96665ff7 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 8524346b8ca..5191345df38 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 a137eb8a911..0c7a50c31de 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 ce6b0b225ab..4835f05a962 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 41485bcbb02..58c6b0c26ce 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 aaca277f91d..a4282a5c735 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 23b7c569e97..abff6e8c2b0 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 c07f3878db5..5d0c79b9b9e 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 246/394] 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 5d2662ed5cd..d73c3d7ec9b 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 247/394] 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 27bd984a660..243619cf154 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 248/394] 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 00000000000..09bacbc5f04 --- /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 249/394] 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 00000000000..bb8cc1b2227 --- /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 250/394] 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 465c1b4c0f9..665a14fe2c1 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 4835f05a962..2577b4efb10 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 7a936dd58fd..bc1f072e458 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 fc87bc8d51a..32f9afeb206 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 3a684e83828..382e1edc5d2 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 5d0c79b9b9e..49803b65b57 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 d73c3d7ec9b..99097c05592 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 251/394] 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 5191345df38..f13bec3ee08 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 252/394] 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 32f9afeb206..f60bb66ccf4 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 253/394] 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 2577b4efb10..03c472a436c 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 abff6e8c2b0..1fe56710c68 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 254/394] 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 0c7a50c31de..c00ec478efe 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 84bd59d4a57..7ee0abdf986 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 255/394] 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 26be4405367..106d27c00d2 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 256/394] [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 5d670022b79..7ffb8914bdd 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 257/394] 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 bb8cc1b2227..ad157e817be 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 258/394] 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 2d545f96f25..7dd7bc2d46a 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 00000000000..578357b7339 --- /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 1f59613e54b..1a77c383126 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 259/394] 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 ac9f89c31ea..fc7fd33e0ca 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 3b6715f3838..1a50c5533df 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 75fbfb4add7..d9977393d88 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 260/394] 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 e5f191d6e7b..cac7cacb42b 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 faad84a0f48..3893e847ed0 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 261/394] 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 a137eb8a911..144c88c5963 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 6ccfc7a33e5..ad941806c4b 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 bf457595359..5cbee20756d 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 354899f31d2..08ed322452c 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 4b89e1ce302..453006f3569 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 2e721bed08f..0130dd3b84a 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 b6bd15e43b8..059862144d8 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 2819cdeab55..073f89147bd 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 f56382b3604..06181bdd45e 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 26be4405367..80572c1f57a 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 071019059bf..97965282da4 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 262/394] 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 f9c14142721..76df02e9b04 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 a35324da054..e916a13e017 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 00000000000..b34d954166c --- /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 00000000000..a0dfce2c779 --- /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 00000000000..21be213ea38 --- /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 00000000000..7b6826342ee --- /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 315c68384ea..5d0009b01b2 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 00000000000..4ed179fe277 --- /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 00000000000..ce856cd876b --- /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 00000000000..2fa86efd45d --- /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 f11d57d1b0f..06a9e210ca6 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 db4ea7916fa..00000000000 --- 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 febaa785604..00000000000 --- 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 2eabe77e548..00000000000 --- 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 226cb708dbc..00000000000 --- 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 e69de29bb2d..00000000000 diff --git a/vm_env/src/key/mod.rs b/vm_env/src/key/mod.rs deleted file mode 100644 index 30aea96c46e..00000000000 --- 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 578079d695c..53c594dbab4 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 4a685acd727..00000000000 --- 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 8e4bba42239..00000000000 --- 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 8a7367afb9f..00000000000 --- 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 f59c5ed032b..a36f998b83c 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 00000000000..b34d954166c --- /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 00000000000..93a93e11837 --- /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 00000000000..5ef2a5e28c8 --- /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 957354d8480..c1be4465f1a 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 00000000000..1d5d0191690 --- /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 00000000000..6670386c4a3 --- /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 263/394] 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 76df02e9b04..b7fce8b64d3 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 ed453ad450e..cc437b36862 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 be450a70861..808299a86dc 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 be1844c6cd8..1700be22642 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 13e7bd38827..0f94214a359 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 ce547520f7e..d5392895331 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 3a684e83828..346cb6bdd49 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 61b87e1b3b1..d849a114878 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 264/394] 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 0f94214a359..a838f378191 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 bf9d222920e..08a4aa8b12d 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 f507e90bed5..473984aa311 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 98f6c7f71c1..91059c4434c 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 79180722668..35cdabd1c52 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 b7185cc1100..19a50588dc8 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 9a5309f927d..0d9d3902264 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 e39963fae71..9b5afb5ad05 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 e38aa2f8568..188ec1ae8d6 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 e976c389413..9d187d3c484 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 e26d656b57d..ee0db9310dc 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 3cb1c3d5de1..48bd0fa0f5f 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 79dfedad568..bae45fc533f 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 692155432cd..73c915d1239 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 f0ab0ad2d0b..e1dabcd851e 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 5d2662ed5cd..c3eec798a54 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 4b68f11170e..d6fdb16a2ea 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 cae8c4ef332..30173d1b345 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 27bd984a660..adab55ca5af 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 f1e6dd587bb..4ab7f232bd6 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 553288926e6..a3a5630ab76 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 60513ce808b..b9d3de8f7d2 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 a222a344efd..3b6ddcf65f3 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 39f70c17871..3e7bbbe365e 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 cdd56aaf896..8f3cc9cc365 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 674fb2a2d98..215a967846c 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 265/394] 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 afa49c66abd..33e729dca4f 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 266/394] 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 0d9d3902264..9b04e2a939c 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 9b5afb5ad05..a299070393e 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 188ec1ae8d6..08b3c60d60f 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 9d187d3c484..05789751b21 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 ee0db9310dc..ace54fc1615 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 48bd0fa0f5f..728d7613aed 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 bae45fc533f..2bfed44fac0 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 73c915d1239..f132b741589 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 e1dabcd851e..57316128885 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 c3eec798a54..c5ffc1ab6e4 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 d6fdb16a2ea..d0c41d3bd9e 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 30173d1b345..614e4a9fa1c 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 adab55ca5af..bcb64b4af0b 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 215a967846c..0e47437704a 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 267/394] 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 00000000000..e39308413f6 --- /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 268/394] 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 1a77c383126..e2ffd72e13d 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 269/394] 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 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 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 zcmbW9eXt$necyNY?72Gc_uN-q?!A(pvk15{Z^Fh`7%*sMB%ufdLk-3brg&-+_Z;jR zB2!PBXk{ZhicN#e5=MzhM<&=(Jm8V%(w2@HBRa&ROqmYjaZ@GMG>V5zNlP33L1#1_ zr}XpvJ$ueQR}v76(4KvH_V@gr-}}q6dgX7c-~7AkfopHrb6=eCUzpuj)~`-N9pXLw z7w)+)2<|KGvD+i|T+dedGfuzt)#Nkv(q}&1h)U(K6a^ze7)D`OYOZQkq9`bF6_mLS zN2_7TGu`2D6on(vD&62Ls?_4BRBBh_I1Kq;|Hb97Rt~@Q_D$hU`NzUdZu8|=F5bN3 z>W|*|@vApo^RaC^cindTukE0AHQt;)wKN;O8iudte^r_a@67*CX(Q(s$5)JKeEMAenfRgNbm^z@-;9)R zk03+-J8FYQe}7llvdzgT|{H*dV? zYa`*G=dWD))@oP2JP497o!Onvd?ks~U{{>Epq&5YvKyzLIs&$rru!a07PM!AI;#vH ztbaS7yZo|CtC~MZMs%p8@q?_|aWRwr#iP9Qa}KULmquyHRr7}~zay;Xe|q^vVI}{c zb{xCnrd3I0G1}-VOP{^sx8UWjrJr7PGHzGW(Qf)q_+T3CbR}@ff4ulTE$ufp{pZ3o-27ox^k5Y%@XyeYrn z#?=GqxFo;##xW#hGW^BTqc@KC*ZF@xD8gU+#Kh97Ew_Xp@V%w4ed3$Z2Ye^jT(@qU z4a4j5FKmA(yl&~|+s8lX!~&9EvEyUE%G^hG{92U#ZB&w(39}1>bYm}x)&~(XF3~Ny zP(Sf@lr~F4F>1$LBdkZ8^#>6Qd#wolk?dExE=c2U`{OP`lys72E<)#Z2y|#9_7f+e zxVmGm{V660To(3RX)X!UU^@_ko9B$t>C7@b1ak$*89{67Fz-Q5o(1w8h@lyTIEl5O zI4KP+XlJGsrGbmnczZLLWho1qx?)R7ou<~gU(JGMbvj(;r(XX)Q`b8sAa3lg4#Mr({XFa!SP8K!Wg>mF}JNl1tV+A91R~G;KV8#Ds z$TNc0)?wa5o_sgR1Hh#MaJg0Y1H5YlbfXT}d&pP68|0gtp%Jg% ziCq<*HyNXQ*v$j-deft0(b#h5xD{d+B3b7yhf3?(1LVe|G0wSj+$B&PzI# z^mN?qAjmG*^8em>=bh05#{@6%2V? zTfvaWt)-6~w&kqFAE%8pX6f~<&2d-X@`ro(?mbl66veLYYWapwC0Ttj3L2~Gu9g0m z$PPQ^z%`Ve*r~I|1NMGHq^qRZo-@L`wivyw-v$KdH+=&#OqDikv1a0{{nc{POPIq} z3pd^@i4w~T&XQU)OoL99Rinp)RZsuwt6s6_>9ZHjato4-Zf#Dxe8=4rMuRP-PJlsY z+V5-e-^bA;Rp`K9=+H=KJRQciHm8KHr^S8=9W>ad-?7DLZ!Eg?I?ZSg#DLpT<_W@i z-MC-p@mbUeKkdl@RTVVWt_^+&>S@Ww9B)|I87iRFPzho3Za3-$}B!{m* zqcEGfoP@yDp5NmB)t1seNkb)lA?~JxP{{a}a1#p0t)D%s@rQJ}3jS-&rWT9QQGfn> zVyr13s?0Ogy1gn}mcV(WuCk$YBHjetx;#K7J!Pu80>$}d6o2`h^P`BV?^}&GOWpQW z-cU3V#f9id+^O}SVk)vYY3TNIaZ+!ikY-V^ejqPPlLVWhH-tVkIHljl^PUp>Hi0N` zSn})qLWR=t*z_MA+IxE4Be%=_VZ*j8?7u#ETtnl<=$8U9g_h`=Sp03`ev#?%0coPe z=mo)x3aTFXMxUw-!^n7Q%CMwQvwCidj1?K@wOTck80neBNDY$=UY|=Re|IIk>|Fph z*9aO`H4(L5@3TSJZeXRGN{8ZQ&dqtw9a`pG4J)^TYjbezY<8tn*9I8?WrY({fCU+R zl%Nd<2LgwG9p+}?0}|FQW;Nqkp>#Cvz%=o(gP)bE4mi~b(h&9?fUFDXqS)_X>yun> zC}juY4#AA8Z4)cfy;!OS8r;SqwTXm?TBWH~DS9^<+lf`jVp>SUo!!ZV8{^Le)?h4+ z9e=R~W6y7y*^`vExc#@M(eBO!8dN&$CR~*ns(N;~({>FARK{~=uHF6!#ytuU7ny3# zU?#yY_hKxGrjzktVuxQq5%Ey#wZgVWv826m2jjaC?T^c@TE`T*F>wtE@`D|`*IA=m zXtGo*0KFTyeY4SjKe1O=ZU>Xq+}h+|c1lp+Ds}2i#!S+OddJAllr`1^VVJJdbhS=3 zfAHR$vU))q`~fP=Qb6CBSp*Tybf%Xyn*rd2KqrK@#uRxV-Z1L;bIcB|1vv;0cSben zbg5&SdZyHA)?Jgy8=%>EpxbG=MyF2jT3?-rkPwZZib<5n(P^&0ZEAqq)cJ8UZgi?L z_6_gU<&yxc2mwNRB<-A;Jd$`w!hBI_0oSL2H4SX+!Gr6(;W8W9sM!#$LDIL^2s#S! z{TlI_&26$9>FE+vBZ!Tag4h&ix=CAt*e5o3b|+)5&7Uy@v5g?|7eQ7*TBx8u)B?HjBt^G+x#wbG~uw`y(;(YVbRDBzUXyT&#nz;WP1BF$Dw zkLiLlZuHQII+?a&QKmX>;9s-jF+09GG!;@T3*1Fo>S^{{uEkr*{#umqqgj%y zxSG}!-ij`RIm9XkCr#tvA-kg(8`G>i1TRLb=j?dMj?XUB4FgnA0AHhJ%Ex#Lu*@$6 z^4ToUsKM%_p+&(UfS(OF+NpxoSX$cA419c=9xT{dV03^>A8;5p2MvR#?RdbBPl8Q8 z`uR0kf0CwoFcHqxNqm(+x~kdlWiks5H{o$bz~p{LCA9DaX&Fl)7et4^%jAOt(6>De z2WkZ;pw6i{fqRN3?(#-dab~rh>fR}QsfpeRLJi1M+6{f=S7Qi)84lS`5IpQ&zUKbbS-qQ}(>?G_TCS-QMB`SZ5|B1VC%t4s6X+Rog9_b>2$Sn*z*IV~ zcU{xpW&zDIWH+=62gAGmjECO6OmD?@MLQm9wp_N>Y}H7kWy_F42niYTl~aq#@OUTM z!~=}SMBPoud7uP4kj$!^xRsDtH<@n|856!8$4?;I)ZqdT5_=FNV>{qW!;NP4ed#S}*Ce%|z>247YmC~>@xP{xnEe}DO88_SQ zG)#}rL|vKuUNUQ{aGFbLV=~vyJ`UtLN6@zror!e(r5#V(@$G0}G`A)5bIIyLzZ;UV z)ywkU1i<_pT3rWLmD8kp8J(`NpG z0ZmWY9dA1a`ToQPucN}Wf|J8oYAr%*&a;l)B^1uZ&T~yluRo8EtodNWCN2r z2D_-vK*1f&sIaFem>=2{_B?%^%OQJu!lplI$Jg!nvtd*MEv1(w%+jCFxU@W_ge0kB zW=k@&lI5xhSc4Z+kX}j|I}$4`RkpVc%L*tKZqOC~C&Yu1t8Iv4i%AwpfOU%y zaYN}vpTWq&D}DF*qJDi@KzZRwrSV{Hq3KzWp=|~+$f$tiv;L!28?UGu;@8Z)N%36y zma^~kxG`E4Omm#aBXSl(H14+jo!25Fc+hw?!b`d_>?Ie+qRuEZvD5}{VaZLBQ9^X1 z=qM{iJi&EBF-Si}nLu?dS=lbPqCYbF{f0^<-Orp#GySytBct(NZc5|*&Br1P0MqXS zmsL=L)4dUD{i)_E1WQ@+!4*IbdLRc@0C~Cq@+3e;Jdgt($cTxg7h3XX&1*|3gQ$w* zz&~Q}eIN^KJp4ljzhQ-KPk^_aVnE$ z)aB3-%0cdEH3vC&QNwHiRm6rUq0&GlAv8pHzKF`HFjDhY%v7V6C}ZOR6EeZ#* ztw?-}eAaLbBy%L_j1k@F4+J{#%EoE>Qjs+GG4yHpd1$r*uR)2x11qAo7ngCMe&X?g z0zNt-Ym1bTxV4HN3aPJOiAhRYOkT%WkuocT+TG2dL{hV7)wFzDvqX2hN=*VsVY%Cc z25C&o#*k<$`DnXlFBvr>S4+#qFSRjJX&E?DYIzyFWSgK#ll90e3ATHt%|N!XB$5wg zUBWk_py~GA}L0TpDga*kwz;Y9PGbPT6zEJ{{496{X_lxu)$B9 zuYG(|rc_}$LyM3~vwcnDVlAz!yF#2CB$T(()7Jy|5EzT#&6 z(K&1%j>!Oea7Ee1pk~b?xtZ#;fSkVM-D7Jj5j#QRBD6vZyqn}#v@);B3Z`63mn~w- zwUV8+ol!}wxmOGeA!_j+zZ%H2D6W)B5>Uj;+1o!*T~{^E^ytxJ>a(lq6>UzhL=LSH z#qj`D<5wBj-m2o54V0E0r)V6Q(bUxe;(2@hM3Dlee?4w6a9^qIrP0BI0D0ax5ib%I zY1!ds1doO^R=l3JcS}4JVit*)3PCb6U#H5dl~`5%MEoxBVQ3#ZW1$3w(f|ov=|%y% zp6>fkLDo&9hXyc@$M3RiN@}9cV9~DLj2h#^6P-06(417eVBcA6ApA4|Y?v-(;>578 z@PgM!>q@n(ctG%2NSe|h9QZ)eti(%1qNH$Ypk=1OpjL=EB{O(0SKsPP5%H6(+MWiI zvIO>5O4c->f{5a_VgZA z;v?Lake@+;WnQhXGg|a@Xl)lIFZw@>L#39x+ceZ_gVDmawyl`Z<#$0V%} ziuKB&tm52yuZnZg`ml=gyR|+@MXWb)#)UTEIU&FACyGCA%5L;kDoRffo+)#d-wO(-I79WHHFbHw%4$Rf=|Zo4 zy=)7)b`KWSTnF(o5VRny%1jwcW*r}knCN}Q0oPMv=aiPVQ=DloxJ$>Yp~~?%AQat> zcP4!07?>8Hxr~;DFB><_4TB#L8iLsXCL$u3%xIP1rmv)qyJ-bu;|j%y(p2~&vw4`& zT8wg5y(IfL#){05Ck?`*4iOF_$Sqmiq|L1fg+1*;O#F~qe(k@QrM5sZ5J^dhZqPi` z+IKug4K((BQgv|)6k9CUkSDoT!J{eiHGL&`!dHTeD-c&DScvoTWuEq(Hi&XdtSRcj zSTyMw=y7V1KlJs%V^I{&PhNcm0ne1WTYhn%ahw9g4^xzh0cpj z>9ff6O`EcxQl7TS-n8S}#guQ^lq+V;Y|33sNpMELWOxn7SFGW619jYvKPv#fb}qmI zozwWMDiafh7_g26ki_*ii#>#Evf%z@7tG zc7A9=7KspAgH^Qpt3VqH{vXq9hWCq{&3n9$*(5L8@zr9gm)-^MUG(Ubf6Vui)iT#Z z{Rx$B2OL@LE-Ba{Glb<(W<=yX>RYgAyVn$-{P+|Ha2I>F8PDDPhLX5T$P^bEA?rNlA1x5t_19Ts} zyxWq_=|d=kv_8beMIYjlv-=Rv9%R;tphhESP{Jr;k}-;qSEPV_gFXZSlHh6I_tUhbIGLkE_3jEmobMySDv$g zd7P_ZptlRq%{qM21HI+_fFjI-)?c1KJ7;Za01jw4dzgtPGiFs^#+G%$P|$^6*L|A( zZXG`FQMl)QP^e=k8`zw2rpUG>lXJ-hg<{K0y9)-U*6ZvAX(j6$VuAvUJ9=8@{VJ#Z z295;|=QMEGL-Y^nS$Nf+1*lp*_yAcKlMd=1>hxqxOIdId=dB`o!x!A7i{_Wd`;tL- za#%9FYc?B?pER-+uK7(ayleI}{IbaR1s!&K&-_3#j_m~+va#A_oc7%6xr8FZc1-U; zIQC+4=`ebKf95Qpd(HfQRZ#a8&hoccfISaXq{DQoaQel{GRhdm(hv+Y{U#WceVD%R z{rkC)W9`eu0h@lm9iJ@br*<^Fu5%HnvugvH&{7MTprX*tn6vMH3EI`U;4m0~Hy{Qj zip{ncE}-a;t+q?#EnQ~%ItwV+x!eY2fNCj7P$TI7gVvEk#B2%iMBwO8M ztO6$TeBCc?+rk9&ORD5VE1SJV)IBSwE3=DX1FD@s8@W{SSP%1pCW;B|{J^6h(OxZI z#$EC)Kv+nmvg1^bKF~ zLoQ167gSyPg$e_nMY1+xY0*lfHe{_dDpS@<p{$>9hV3}~l8I`&ZfBZb zDrag^DNa!WIRU4XC{oR}E)%HGb!%#`l8rQ9P1t9csln~e678HzMb z>qkrg*~VI!OjUuPX@+)$1V`q9eb^Zsk^zD8-DH|YEZd1mFl}^`&Vaktq)6-%TGEIp z-ZayI6iNko+6i3ZZ3^1d@wxYOziaU(GBGcz6t|Grpd>C(Flniuq0WX}r1}|Z)IeE{VFMBU446hTY(?Hve9QtD z1-z<6*4U|r#wmrV#r?PX_vvR0-zN+9B1rU+Fbb!_M)4J2MXZ|e>0nzKvL>4L*ON#*NH(<5 zVgel>Lx(JfuDEQJj&sSkN8JE`GXQ+XRRCD;0nDoeu>!!1<9%(5S2@IXm>b*|ti?>= z;&+H`M2I{l8=1MHud-(R_0<_Zo)<3@WFG0mEcL)`U&cu?eOkjHyhaFH23+X9RRk3b zB0IPYZyjETPm}}(Ix}Hs>dy>WfvRsSsU0ipkqz2ZK*rUXmXDcMc(V%Uv=JYT&;axI zNVlXwb3Pkrgl|T=9Yq&J4rYEe)0qK*Gl_)9kY3moM z>l-09#Cs@yHK92yJWEtUtHM7`gqRlt^ ze^_+0POF>sr+j3_&C{%tV`ucecD;- zX64a0=5`gA_&N~(`rdxu3L8M*>imEZh-EM;5F>ylKd0lo_l;R<&%Av)ez&EtxmQab zEJP4&7tb=#`pOZ3;1dbJl(UFyJt~+|l+PhAH9mP26)%zB^9z$Xnxb>%E4oR`gijX| zGvzB@%&dns5Hm~K`eNn{uQ#yXWICY8w6dkaIIn_fyHj(8Ktd+p%i{K+l||)5==4oB zj6|?}lx<5WT3IW=NUqO$EwR28khT|AUs~3`_nxB`9eOHMR(L@|PcHub~LVCNekM{)9EQloV9lg~i86 z{Mv_-AM(4I^nv4X)Wh+?qjfGU(33fnDZrBwV_7)vHK={nz*@78vp%FWqnT0BlvSkJ zl(ar-K77Ud5>noiP4u5-s!gj6i54xJ>GSvdTmG!;ZcPhu((Ef!@F|2EQOk5B1}0QW z+jMpRVV`352}!@5?HWUbq?G{)i!YUEt3~jVuZ}AGoMBg@vO8iaQCZPtfw)oH!IVrL zS)8yU|MNB!(?2?VEJ!c)R85cT;tdY%s&w*s$Lr5K5w0C{=t3~V0Nn6&eGd0qn1JK^ zE_P$@*z7i>k##B6T!I&TO6o3>y9w)#&q*=#5YnwHP&7BEtpU&zw?O~LFz3_S{GD%2 zZ8P4bFYwt|dXSIx49lTq@oiGkC#|1nU2hlb`s;l66TiMz9}&mNDmu=|NSvjC8*w9- z2Lb<9#T=czrPEIwh{CVuU;F)o;ZK%kpBgKLujH5g!N%|>`Dg#&FBg7-{9}S66or2T znLYi}Q_tL&|GPi_vnaSF|Jt+n@U!$+&sK@#cjf=%yIk(dZ~opN z>EVC>o;}=p_?}XD&(bsB|2OT~JHK@QSHFD!KiPf%=iLMMf9W@Of5H8;-*Er*OJDxV z?)-OtbZMUcXx^QeXa}_*2#)jj!~E@Ezsz}=zxjXs(e|avOMf5bYhE7D-}&*(BUirs 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 zcmcJ231Ade(*JvJ?#xUwxo_wUgd{*n2#`PsA;W#&mm(%13FJ;lf*b;afP#t|Y_(Yh zMMXhHMNz;5jf$@8iHa*8D_*O(>MnaJ`g#0+^|~kNDD1lXegD=>cfGS-y{dXu^lpnYkK>84GRr#3*mhyuwo5V+-Il7xiz3S# zn;ObXFKRArsVc85ZEmSzo^4O1RCp~Fi^GM*#w~(580Ui91-C>veJ&xG1u(O5ZsCz( zoFkXvkuHnCv*5xZoJhli)s9qQu|&DTtX9tPKV4QEx7)Z{!pHDw>ZQDQut%!T-)qp| zp#z5J=FReW!w1GGnOS}N^&dB^pk(%(;*sOW76&GdnlxeNyz|aqICsv1`HL?VRxvBHOE?HK6(Av-q?rUpJvJ#FwAld?7qEM~hnJHMB9c|gwL${s&kQP|845$GS4#4jS@MT^`WfTkLIDxdn)0l_5=@wv$6B%Mz zvfn~=EQ%#lSOb=23UA35R)re$Td5)&>s9C5!xviJK*vEPC>e#AER=?H8(%_7`^y_l)MAej?5hBASF(A2q^yWJM1D)li3B3(}lv1PORrhABOhJY}rk)EMy zdrjihc~F(!l#LSo74}(v&DP+4)`J-6XN(L_2tMkx}Ac`R7isv`0D`FJ7{U+uD zx_uMVhi*M%Gx)Is<6}Rvi1#k1e(hVYAih)ooG@JMyMj_&kINM1{)_rrVt>#hQ~3Or z03W|h4NJOEuxV;-QV_QTUnI5I`7!mvv}~V`s7P! zi2a)nEcVa0#NTvvhdvDo&lfS6dt1Kn1&$p!mVOaLd4f72<43WnU0sx=sMVP{Vqd%Z zo6I87evM4|CNo{^zeeqmH9{P{8Y#n*-a~UFJD7dDuP1W{u6zK~(l5|)jrw5Loam6? z30?3+VDDuI68rvBh{HQnU-m-r_ztxpJ1gn%4xPw>pMLs@ElabzMF|?T1yE{#c8NH= zQ~g_Z=|J@cG~GAw8QnT~V9$2k4_bVEympUpgDL*ej)cs>(QUZx5`j-tb;E#bQ@5aP zUkoth0VS|E1lR)4*aF*kr~`8ji03w|Pd*v1{*W_Uf6V8`NgcK=9l*sUHvQ-_Io{#cwB(a0aZ)h>rmz z1RmK8^d7^H;z|yPfJJesg?ZWH(#>j3UazUH&TM0IwJ}__4 zd@kBAhdjg{I68Q2=vh_rR}!b)n7`5{)^Al~iZ=P2fr#3`PkdQGOokML8}}C7NlZCo z#48C0Y1(a|u?buE<%$ndnXJw%ek$qsU4)|o5;^ikC2p2{aRF`*-J|v&Io6a7bWPcN zDLYd=Fmgojo4Y$$7GnjBDN=3!WQKjUfz9`r@)K$U*O+0YHlUf|^xD9VPT1pe2eJd) z=OBd_gVtX#Hw|*V4!F3pZ}jz!xd8h5$PA;eKbc|l^_RQVdrK07JI%>x_YO0RX74q_ zX!c<drQHjAbd%KTjy}1~g z-C~B(Y`Ymov)j!un%&t6dn_ixg@Y6?{;aDv00%oMiN4-77eHShn_=|zg&9U)r#oS& zsV+YAqOY4fvjAX{?li;b>OKSRD|sG$9W)m}Ur(4}^!3bM_3F`yv;MlLJGs6!!)W&s zb2T)3xfw>Y+srVUy{U0zCr&vj131> zI23Z=FMtjH;WWvgncA(DIm>mUpRh77w&~tsc92#o``AA(gNQt-gvRy1`&@dSK$iSciQ@ z_JhN3qen0{`3WM8li?-OZ0d)LvpgO)>XX0T{@$+FzS%QLbybWNkMAa{qSFq~P}kK& z!WNHFpB^&a$W%Y5SoPnG{Km>*|9Q0$#KNdGDV>j_&HFSAM@4ZAh$4A0K`bFi1(drX5-x_MCr|Pa7Zxm5$ z>h4q5-`C+XEYZCi4otlNGGPKwJW$FlpRZ1bAvQ+Mt+%U{^}f{ORv3h=SRBJWu-#gV z-(x_uNO5L~9qP9FUcv2FGDMw$T~@L_)A}gzN_SY(`k|gPuv;ejd*zD`YN$`XI7JQZ z)ywv!vd69PlcS_f++FS`s87LQ6s|)(A-n)0*H7oHSFy zAVdTRaU&7JayCLlfC6#}_&Fgw@D#!VZwe($eX3EJ;Q|RjrV}&*kW#XNFCT1Rp}E!> z0VMJjN=C4zfn=FtyVYx>`)+HRTc29RH4Z)yct#!A6lZb4gPGLSm%G&EO*8wti2QIg z=oW=Z?@ivO&w?c%O&qhP`RIPaYD1;dP0904SOdJ)k4nHPEWqnPM=F|SIc9bH*&aXI zA>P9HJoT6ePeZ;qO)wj!Y}rHgzC=9=GdKfhEX2o{cL-B?bHGKT9Az!A98>RU9v3&3 z@=zk08uBWbl!Q7IbS&!`oGzW3pZIu8a&$n{VVGVeu{OenK!x}fgg2w`{4Y?b(@{7kyFD&{pO?Qcb)kTl-?oO2H8FrB8h&C( z!4gH$lR-BRI7ugr5;opTi!2mVng>WpGO9^!X~8g{5C#<7L?e)Z=;)n@Nu{~;C4q`w zgPKy_{FnjK%3venAEaHXEPv;tte5%(@z%eV> zgQ%=TDrs2vtnsLyLj2)YlBaqds7#~=ZXGM)I?PHJ&3zzvyyAsKO!NvvNt{MkVM;`;AN`=+Qvgj$;;J%m4 zP7DBknH;V+3#v}f&yd61N5lI-@*G+Ea$)*Fq=h5(1X82G`{`?xNIAhuI9Q(g@j`*m zTQi1iCyZ7=cY$M3IudM$3j5a#(6jdIS$n&({;_6~p4Fje9hO;SGpTdd8nuq;S)-zuxGeG2FaY{yWgVDwW!7;@7h9CFrU|KqMqT=`@5H?2iDwsnH1HaWCW!mM5?xc>qxYo=7McS{1xfDS(V& z>VxZM1ziSKa%rQ52oop8Ae=@;7F0+Kh(*%iZxGLP zMJgf&>vSMl(q(6_mE;i=(0LD!frkvbndSE1ld}tP*_dH6?C(OtASLut2wguBIT}Rs z6Q!i+V3axh3hwvF&bN}`50Z$s+C(7k_sYD~fNi?hf~|zqI)5To6LtMymrF?`6$I$m z`V<(aF`yMKL^2Uea=kwa9veIhsjWdUtbe5AiMW2y$}m`o#QsjJ)uqJBLWbG~qFsh7 zye(|V&Nk5)Xn+{)ZA5?g3L*y>mgy#alEi5QGj1or6E#!JDM`LU_-@-}msXuR^o$Fj4;GOB%07S-hp$yTPA=9^q z(k{l1^eGPy!`!CrZRh z^OmF!(;TL**qG}ycHhI)`#1Xg9@BMmN#Mh%0BJhtIJtQ*f3QbS%T$C^1?n5O^bQ`w zemc}5THmnV3Y|?;iy1#=Jx6^EO|LEY>PdajVg*syqBlfHLhYlyO)nzECilAaCbw7v zITE8SGRlJ90*4RzL9!X3#_mTtq;yg%P+(r+fkrUU?AR2S0fOd(Eq+c6x4|XALDo}$ z*fg@IWI@=OU2G-uln8xZ)rFV#fa$&Z(lk4n>adA#y)-8a7}NBG>JX!!JxTrc(tg49 z>oMa>;CtNUjAQ&qp0c zEMyMTYyuxB_E4B+J*~>;4AXdk_rD0!Fp{|Lk^5gpX~q)cg0cTam<9^w9AO$8tpqP& zrq!LB5tRZ5gQH}i1Bw744yO`p1lbUKGdu%5Tmz0W;&xr(8WFlE`(vQcamrr~UvMhnUtOMyMJ&IvTZ#5L@In^2I*+K&ai73(&gGf(1!JUzw zt=1TX=x}6Y6YwBpitb5zd#swYwNfUn zNAEp#8))m+oHRBKl1mm@;1tTn%CZ1H=&JbM)&jm)jkzj|Z&!=2%6B1>nm{z!uCBdm zw3~X_kHX35;>D{*Nw~v+6Rs|m@G-zy$lq`^_7$e72d{sBv!pZs*JW>)y-a7JsV!aQ)PJ=)J63z*q{j#$7%BNm`9FA_T$73gV- z3eW<2&Zt1o&ZxkL?VVA9-o$h=D&X3doC^P4!se(zZ;A?p<3|l_ONKZs+EyTW;TXfZ z1;N4G+_r6;O!}ZJ>FaG{QMTyX0Xi$m3=frEdF|@3FeM45fDfSp_4{iRN+~V?J%E9n z!)H$Tna}K2GBME32!g~gLg_6d2tK8Ut|u^OidzIh>Q;4dG#RMU{ag=za_e0Es5-VQ z9R^1zJ?grVz?NhPsoRYIYAy5 zyx?vzg`Rp$0U9I}NQf~764b}8Plej;fmi|1OpAnOaO-gcioy`rQxsSPQQZfudLW60 z7-`> z0SzTg4=U1 zx)83AVFLBj8zP}t&fGAdI;>k1fW`|1VhI7A5JXALd$`~nD#-AB0@m>oCuc3SN{Md& zzi`zE7xii_Vepq-oRz=j+P0J3)DBfdKgRHyn4XWtA7vr z9?KRjiPA~fk2{!5di#mE!}=npSsIO4B4{y;$6qpxP4b7qf(Qa2^r{z>DhFFoQrjX} zgD8M>E-4BC=N^NO0YX58sN?qpEc&j&X|iwOk91ByW(=DXrwqrPvJ;JIvJiJ9%7Uk? zm?#Tkz*5Om>_YDlzAaYBw6LJ!20Ic9QoQ0noEVaS!Vz}M&x1CSNj$__6tpqC$sNC( zBiJ2`cEfYxDAI2Z8m+4+J{3T-@i2EZX89pJN)@@)Km`hE$ak$GBfN%2=RwK3S z#<4T^!Y+yezv~JJl&kgoP?q#br~4?~#?FZ%*QCpBjDa6V0w1Hx7})UZ8+%ksLmW#! zW-&^VRBsO)r-Wx&aD1>pPO1#N8Qx4=y)Bh1lhreh}Kf`Om z^WYgGYG=m3bQsU!FJigt<}WHyWIdje)}z#vk(dyOh2I4N5?!2%hg>u-$Jo5M?6l@+)LldyvI0GehN{kLq(RIog(xy`Mp=^PGj0+#PQsBsk8qmhn8VVUA6O;i) z8Gpr^h0`0PHv-Rth^b0sCqwHCPlw?$8v^7CxXF78lamWU3-Gi^ePuvP@Y*3jJ|&fi z7vkg&+FK>BSL$Gd$gH(vX^I#U%&q{fM({m!vL~alv}EuL6swe6z1v7ijx>5#-@17e z_O}qWul2{LMYi=x)jnmo6rZ@x|0%tFdnbQkP*Pmj=n8Hx^w1@y@GIU&lqdR$5L;+HM%6hJ0V8fb* z8)^mfFbQWGhzGm6LSV;SgjQ|}GJGNlY9_1kdc@O-eDg zhh=2~NITNfqVocoi(PJ^+sq4LU|xYB7!;h*iGo(pC&EAKg|{No2a-nOg@ZKO=9Pho zkMvjtYG6*FK*%zab_#7MSjb?Ez%GEnWI{5T!DdEBlD&UrQk2f1T{WTX-M1yVI^!Vf zfZO86V(utF(LL-z5@0^w;_8090kWh~6xeki^^b}HOCdXq)KNI|vwl4^t+Zs3 zx0j0IE-hU-gZW}VrRhp-dk|BaM~0}BJ2}HxVTK^wi64)T``M`AOdROJ*%~OB30{He zrBRzO$f*=``6{#7+J7 z&Z6+H+Ta1E&e}6A2qRWTl(4$l+yUe4o}A;rOZOwb89HSuk1XLJhGcqFavM7-7%igQ zv|A~K!A}gSPp*+n0V!tJ|%OQw$t{0&|Cb!b7b7V&`b^6FUWVlWa zB(UCw^IW4i-o!G_8(s%_(PP1g)8U*Q4%#V^hKy~u28^TFk~sqJ5qZzxhmK;CM?h85 z^*Y$smvVX5I=lC)hwnOy^OQEcgC>Iym)$)DrzdSBb4JR^yVuARm~IZ3@37vg@6pS^ zI&)w(LdsM3oX0;@llLv`L2s{F@tPc7akE>40lasn*s~eK+dIj&06U=V`<$x<2lns{ z>d6D!`D5zJhxUjkH>+t6CyOUGtHlpL~GeA3HQZ{mIRsgiz=WMNnOTwFhTu=`A~YL+A<0 z<|=DYrHU`Cn+hfARae0UM(MWoQHH|i6IM-Y}j98M)H zQy%Flp51(4#3Q?H6fykxiKU?zohqI@Uw^zcoK3*-Q$D!`M_n){>dvQDs7)PX^;Z<> z-7Vh?^~sJ^-QdwrEmUKk{+&Q1?BvrI;C5idGcI^})6@w^0ZvozJi3!I$39D$>g~@y zDAs$`pWaymgw?h0;vFESKCIuqdbjUbh56V*##kw5%!PYLoR5{_K2k6?5BG5VS&hF4 z+5~NX)=5O=UlZ1~&p^O#xINYDL_+%Yw zmoB~jzTa*Xl~L-z=Zmu{qkL>8GTz1?6&f&rvEHZze7bcSt!{t5U|0!q6y%iQPTiCh zD(qM?A<5XKrdl4rG>jPh#o{kcb^h+-So7fGk(z+N#A{zD2-|Dbn|su%243ic9RRiF zg&t8UHlG~-qsaB6D3X8kLSEDqB-79qif78hHCkX ztJUTg?N%0e`YQG0i^Cl2ukK*(KW3>tUmBC7IDB9P#`@t-ZEnICh(yexu6-#luO7*S z^lIE`IO}ky!FCn%v1Gta__N^;)joV_RNqjm$56ZvQoqLCiTjVZ+i~xzu1A2DHvb>IK9-5>$@rst2%e8;LhcXV0~6nvWeL*oLIkGXQ}CCHKf;tQImft%CMI<6 zg|z>)uDN^^PWhc#8Z(vSES*YCW<=hjX!hcpCcM#@^q9V8fD<`=L5*8ZfdB? zX(?ZoGoYfpWoc7ROV!evR&DZh(OW(8R?@@}@4ku==l!fd1KD5T`aJkuLD_D&s>+Gm z$6gTaV-t{b5B|dN7lNP0Gm-yajRx6yuw#MuEFtzEunBWIUn1nLxK_e285PS*XL=$`kU=n~({*{1< z<;{qmiwyP9I||BnBLiSeL5yT|w8cVm17-|ihXb?kr^a>bA~dsE>WTL@2bZrk_#1-1x~f%*GeH-UWmUC z{5hUU^eW2h>#Hgib?*C@4xv7|@z)>shN{-q@}*T0mr&nW|JdZ3#>(lf6-|{@z*F^| zT$o5T2{ZZ|h`-UxTico%kb7X$$pR5H4`vXu2jhdX_>qs#P$z$s=$`QoWPT$) zJP@}@ZTcv-=!=I@T^%U-L=Bu}U96jV2b1kQ%*m08d zhH=Fq!TGu=AwI(r#hexD=O6cVX46QHdj6^=f0Am8UCY=P>WEJ==6-h-)bj%vb_X$@ zyd>$eW*Ou-Y9}g&uGD8;zlNZXe3H&zR8M{~By!h{jLpH?+B2Jn&tJyab87k@rv+^{ zFgAr!-|#g<`906^_}5s^Kojmg|2jbCeL#7C`&r)K2Kc^2*)x>)!>I1{ei-1PHbYm> zA7Ue^tPhH)r-P4jq7&kM{X)tBXSd=Gu9t_U#(G+KobyYy=ckK=vQb_4Y24`0P~N;0 zL-{>Q^yN%!Mp3KpcE%<#`P45Q zm#ZnCrSd1$!p|y&rH{Jnv%W#^un=;nG&9ok9jzwD)pK@CbO8B7u?Y99ig6FXNM_)9 zoWfncXCUg;00u@u_1Hh@8YJsk)5pFLN-T7ZFLyhqnc$RSAq6M;8!J;Mo{A+@%1-?{ zRGv4X@_`wZGyf}8z^pUKJ`8tG0RyQ~pBMA{)R~{p5^;UhJ3jvaPXppxQX>a;(_5-L@H`gN!@qsI^`Tkzb8%C7$vDj2g+!@pP&JdZT7 zaivbT#o0ZkgxrBHyHwRifgHtUPsWQ`p(|&z&7M6V1i=KJ?Q&dUg8Q8Gxa@y4C1m!E zv@0e!JGjQ>{vwO95=cUyVXjpBr2s-#`t?0LB!QtTeNhPvMT-f6K83DW`#@7d_M8-Z zs%ZoRX2f}}Fu^&SVS0JicA}te{4%@OO{NA?Ke9zRwwMz7{#Ll0$JHZWDZzENNH>*j z2C}JVtWj<%w*>%|i98N*+eJWw@Hp->x6mYdct-m}qdwaKP9{U*`s zztQ4ij;~Ec9I4H=G-pnxy6UU6Ux{66IM#yTb~Osqk*x}iCVyUvBC-hb{n z@GR@Z(}dPp(PR!c8C%Acy5AP*m|;p7^ayvkf5PC_qs74%?y-BTx65SV_Zbzp%ewY1i6Px4|W;ly7Sr`5D zmw9`_12j!Q&iL5>V_jsZqW!>!R8jP$S?K>5Am|}!HJqa7MJ2GVqz;0qOZq38^iM7T z=$>fOJ!N=?-tm#%X~(nFIX==k9kNucLC2pBXUH#y6EyMnf392CxFU(YPoq;Bk3`ys z<+=-L03oDj;5meJGoB^VL5X#{EEd8#x&iaFmr6{EauoG8#)4bH7F)98{$5=PS@&3N z&Ky&Ttie!e%ptuOqgcV^wjPeZn`S=WWsh=vY9x%HQuzz*0~~jm`R@iE<7hG^3|s}l zTVT>515Z8nT zetwi^hDok+f;JXyL4WJ1az#?~UBsn(0T7pd3nFyTGjXZx;{+9}Ozz(1lDJ>A*_|23 z_OJ2irBKsI zWR|~K~vB*__u85 z2_W?WM9F+RYpgTQobX4K?Ox=VHPKY09IePQ873u> z`WD(9W>RUSd+aS4dMETd@I$Y&$^?h3BFA8JGf!EQoMTnnw*|q3A@GiYabbE-_cwU{ z0MCOqTX7g0<<9^p+Q}0m*Pu)6vM{-%JIYL+U(v1HA6<`xOk@wwwb`#Vbw0c}&hc8G zF64SeT4DL5cP02^hq-L_O{OZzAKRQo>alNQg61`2cp&Q6EE)EUzFln(AJs#58Gu-N zAD;g2Rf%)0X<)(h44%NE_abQl4Q*zeXOT(24o|Q#J7tEh;j_+CAkJK1q0Mv9GK`M32Fn~Y>Bbdg zMF)9bC(V55(r5oO)6!jZh^IJSGvSc+TbtMOq$y!U5=4lx=w|Si&jUWoNsC^D=S(~g z4!GQ8UzX5wZ|nk+ZMlE|*p|VNaVdNMwYDXTUV{dksiWMe=skGeqIcxD5znD(aI?|b zF3OzLWyqTP8WSpPaQ!a~S;s@>7N_!5huJ+DT+UOR17TXksgHc0%BQMtecx}u+Z!31 z3u+f$YQMx}?-X7Z@AxjMtG{7`;a~SmR&#$?*ryPC4}0N46kg?zeimArVto^5pgIChdhjFFua=M(inO6AJTb)tvYw{Rd8G%X!Gy!M_ z5U7T;un53j0wQ^#?|uLs1nea*Ad=cWLACC5dY#=m+3N()h?`Le+1GJ*u;NN%%?LWX zmt603xyeb|PAG41#z&JMw;up}xM=d>o~P&jQ9fFFKfp75x1YOb(w93!dBdXRGhQy2 ze%*bjFE1D_EMowoP0aC_dF+=vefAv~ROp(vw1;Dj2@cf3)qL8V5YHo>Z<-S(@O-N6 z-U43AM8Bz5ID16D0iXl`v`y;xGXrzBooe7hjyBT3em5 zj=$%G#*T!mohi-{ChCox4HMZ-0#7zX>NaPbl=uVyLD25(BA<)gbBKIbS7&!!e~rW& zOPO|D@Sgli?H_{Yp4Tlpa=-Iz(UER@2oE}=&$5YN9{74YB*S^7w${QkM*s0*LpsVl zMAOifT84-u6Eht6Sq+oP@+ffo9@@w99=^R?a2rCO(0-POvBhyz`_97e3V!-1!pz}T zZaLflEpP~1ep&c3^Ya>G=MgXmz)LqV_73c)JeP%Are6Yk;~oH40LZ{hO??o+X%xE~ z7Tlj7B1_khwQu6XqCW$(!k5; z-wkv22$JTY(KPHmTt@jeRGPjMJLdN@HY^Y1U4%u_83F=OE|1)6~-Ar_&M+X-_z~pZ`(&(82pvZUfdi@FOO^%Q3_v@QHQYoqE#bVSt#}4@*9I z1A4pqO@u^qUEDGj>q#c&&@;lxEtl=k0Y8A05dI+~C*KY(`@Poek&W>KMlR(b(rzdQNZ3xej7Y=n{ZhuPtP3A(~6N3cMD4vYxRO$V@2!vn4}} z&WGrwP}Y;}IGS<40npwFp_+0X-kUfGakm!bF}C?J#>O^cD3{D)>_f^PfuWLjES|!T zH3fp>$7D}=1|4^5l+c{MxDCX*?IY$PhBPW$sMIUe>mU z@s!|Cm>Z-y?#c}DhhE09<7Wx(gGeZg)pOmuOJG(QKx7XvLOXz2f8c!_0|H$Q&3d25 zc}h(Qv;PsN=UNIc#9*Gq9;c+)0@xc@-FPlQ zdOBQ`dq5<5j;RG%Kxr!T+8UfIz#OD6;npMhFkc{=ROb8*U_d;h3=^~$ZCwmf97Zn@ zmvQS>Sl0Wf7d-F0nz6@%Xqtd`r(tUp^rGjgaj*iwb@aS`31g3A3h`{&I1xtEeRx(t zQ*}MI_x2gUkO&-?f%cU-w*rIb2*}4b@V^=b?MCQa26S!*I+p11dQSzLY)A?c8(JGT zGxib)}i z0INs-9AN2P$k-YJmUy|Ps0s0kCjnf8oQ)e{ogIQblYbtTf&)+v13}q*8-Pcyhr)av zz$wVVJvk6gpeEbS>dV-p)Eu?20HS^)l!ff5cRXWvbr`^Rn6uF@0XQET>f%j^XJY-y z-wR+X^u{iu0WnX2w=JXlF?Ivx-|K=H6oTIEA?zn@2XDNot@QF#VcV{0US7=CXutRJ zp~1H?+<~uf>%kR}AI#OjcepibC>$uzgwmhcg*(PT>A!*8WYY61Ab1wd!QXF$D#maJ z#$p^*H{5!N0;k@Vg$1o8CBf0g(I}ly!(4YSR-M7?8PgoDt zrl&J;2}9SngTxZGHzga4vV(En(267Y;^2Z~jE$HD93?mXPaT$jN=YPk`4LpM-nW;r z&oHd<=TlM@s)|~uQ{kqkRQy;Xu=q4%h`vFXrruZLXu=}^-tm9~CcuDr0a#K__EI2$ zoIaE@u@#$}Ab7A(0>)nsqBa5QGeP1v2kJ}Y0deaQaLv-k;bvrUF$JmC{qq_72s{;; z&8x?l`O2mtxD=unV6bE)y00QO}$x~n{n_5>ZWf!EGnHRhXkP)w6&4EU-nDC$JwU}JJ$!F*$Cx8 zDYGEm(|0p=l7MXhWRCV`BrguKT+CJ^j#l2!2f6EpfrJEHWO2FA2T%fFLIF0d7WSiI z=+}gC5sqml7y{^WRGSl)y8Mor{X^v;q7>oUm(i6#5w2SZ2JZ4QLR^m3-J6MbtOFLI z3mx^ckLnWwf!eqko*WZAxe)j|y}i3iP!&M-Ev9xSOzGoXWMC)zH#`MXPZOrP?3?^u z%}ki(a;`AJ{r-n1v1lKfUO*I@<#Jzv+Rb>*Yqz9&ZZ|hI3tdH1xONK_ofj^5l6T>m zqP0OMZ6${;W6u!6@5Qu)u95qBw)1XN?<22ApvU>JsgscpVppVFodY)=Od-^KAHzD1OSpAmEfSzW>G=;>YR=PY<9VUzcZ+ssJRbnF`^|XBN; zr@9MJ;IIb^ZZS`Yq)pQk00ud^f^yPpKSS`U*#E}qPbWFY$FJC+i{sq>YzQhnqu1yu z044xHFw*&0dKXFq-{!8!%fWrslnDTQ^$V15#u(NkKfQ>jyC2TN+D* zB9(@Q{=-dU%LJg04R5e_^*YGLd+HIq8B}0xxb)|%@qA8qem!NLJ%ZQOZvKMc*|(yZ z3>a-UI-`KmVLYE@BmJyFqZ23_3L2fJXB-CT3>u+#88h-(Af(O&tT-5t-5k#tv@XXv zLd!_v(}%tZ?K*v){ZSwgx~4C1cMCXY{653;oGCx!1Hb2W?Y<-|LQ5WGtUp2lmam}U z#eN+SA425BpYda7pgt24W&gQ&o|@_U_x*cP!EB)mHJEHF zIDy(q%9L$2|JQAK`kER?bLdBm1xtu({8&D(X6ywpQfYb^7i(NdiN;L)4IEOc-JQx~ zBZ$i?wn4-0O@Y03NP8ufPYC|$A)G$F7(PoAG{%t!0i1?o0AX2k0MGTvcs3Vy6aja0 zYfCv}chd7lZdnbk7)Q0E(BzPt;qg%IYR+Rr%}~@3-^+#j{9SqMSWR=1pw9_4%TqyKIVVOZ|9__h|E^o-#kf!xg#5Xu1l&tw^Zsl~YR)|;I$r;$Q!+Hz|0=kUBp^?6=T{>;htBv5+~$Z2m=azC zu3wgPC4@o3JZH=aPepmgn`(r;*b~Nq-;~0Sb658&%4aU-Txznd`CB+iulpcr+ZbD< zUFPQp`DSfIFYXVX-mQyyTqXRCQv+cAz5>?}9;jtPgD&)O{jqdC1=l+q+91yA;Fj;M z#4ZqcD(*4FB;&7vTSPf30%G|$U?rxUCt;e}X6Q}NL(@!@Ks)IMT@VW7C<-Oa=1hCP z7hf72u^EwWtXWa3t?svT5Jm$@qAs>ZME(}Q2LKY~?!%{m&QR7`YqmQ#4`vo*Fp*CE zQ@PNUC;uk)Eu31|iu_nbYx`}un${PO%g>;7`UTI{j^ zcP|HI0`<_#0TCgz81H;JU{WV`gxs%vIl!cOP$!DowcM=TpU#&%`i_BVWF4dR>jQ#6 zXtE5%Uf$cW%Cy$U(DBdS8B|%nqH+8)-UNs1r#~IhV3*J(TPZUo_~@MR+ops_oHMrT zFcBQC0S?2RM86a8?4!eQ-ypY|0362gEd#KgO3l$18Aq0Bfe;UKuQSDzV9642ksN_h zs9{{*IU>$+O=ymh7_|>uvKuHt?;mviJvI%On$&w-6~Yb+O$mIH%hOGUABwhj6Te5Y z?cD_SGnZ$!sn4)`Cc>-jwnRLU>{xP+B|^vi|5P0%Y1d@(Xx^sXp2^eZMnBBh{EuKP zM)!mHM!@|LO&NlDbPHqOK#u2s4cWh72!bUrEa$%ui&T$J%>lKSzlx;uWQhG;c#66> zmuZPvJXs8WSj*4iQ}|Wd#w@;AfYUz9;{M!y*XSE&Gu+$}4;BnvGd$RywVD!UmSM*+ zJ60RkmuK|cxyPs##U3}7z-`(4q0 z0Bdy6^T&zqU_Pvf69DFc>7x4rI1OOdCYQa?#6YvaU{(`68w^%whJV3ev%p}#hQCar z^1m@b5~J*oWONaD{;5dkciR2^dEA88Mt3DI_*g0xy7vORY5;{zJlVYlxUUDGpXAVz2k-@4XQd(C$2Gw+;8%|;iZ)t8*PL;dbjM${ z9ywSCWXt|R-3WF`*spCjaOlk({+5V(W_GA;yaM_s;qn~Rp2*|b{2}ejJl-c~=qkpJ zb7V~r&I`IqvI{1P6lcevu9OQViCNALEieeS{R>*%AU+~^@k~?pc6{pS=y3RsFwrh3 z<=)5#0h|C(dWQr5p2bZ-;|yh$aj*Mo00{^UmE8tWxeBceCEyXRxK|@*0Ri)PtYZ#< z&{ek567MWC*^y;Ef)s)}ZAo>c3=363E5j{`&grIwZ85#Rfy=!Movx&Q9>w=E zz5u47>w?8R((%42p`rplM3$-1itD(`c>x;jTxvtDq7BDD6FwK*FXB9RXfF-sxuWn* z?Z?5qV8Z85qSHj!Q$OC$*hTvQ^pe_inG@QSUSY8`-GGfO%4m@pb(9v^f#Cijq&)=H z9JdT_`)O;3@Szd<`;{5*VwVxd)%#l6Xdb7%Glcgya!$U**iT(K{qj3=7QT%wO?Z+< zoi)fw&|6!II)9;@cMO^+T1P(b#n)+{?$b65_Way2=|UdZxrzyE(&hdti|vK_h>oA*dd>Wbko8zdlJ^C*ROS-g1ZU0|XUFu%*W zZ3yTZy6mdUxfb<&5OC^}4z3NwbEwOIt_2Eumm}^XBL7}*<8pHwn#-x7UYXIx6?ooa zYUA&>fO=XGXTNM^S75_TyJ;j(9=e}OFPSU{c_LKWS%kV2l(m=XIhlA4;nI9L?DW^P z^&_#DZ@rXuW3;dmo-}j@l}Af7{*|Hf&I&-gjFu>;Ba{Q)`F=ZIcYa;AwBVX5#_l#K z^N5rLoi_HpI%PgES|`dldm7kCn&ko_JSk^JCcmm*yGQqtv~Ey zmOt*qT)svHllTwX)&NftEwAhK-oKTxOY}iI91w=kWj}&?M{D3b%X)G49cy9pyrumV z;1hU*HenR+8(e*den!BGV*#Ec7wM-5tT;X3`3e>S9T<=~vnXsr0OgFk7>8D1I$0M3 zcvfZXJ<0(95&4XaCbG6X^I;az$q5@yPI%q`_tMD;8;&w~-U8=SecNdf4+`OByYm7Q_=u`1)WmYSGjTf|! zgF3$nH;Ft-h{GtLHIL?_Dql8q8cHz12%0Gyl2K-)^qBMWl%`*#bRgv}OfFq|J2mB9 zwb;#v;>Pgk6zz9oc>STP%J}AZ@$BJ4aVz<-m16K_Dc6)iPwRYeum{#ytQU^RWX|eqweCGhUWTSg9fc- znOLcHXkfuwR)q=`^-YacmFy|34;9r_6?KeWGC`j$MGIvM!L{?RYlSAp~VdEC~1@Wh-pE32d1^TzNW3Tyt8Y1<9A6*Q$uM>`HIr!@|Lz%S*LVN z2!|oLYZ<+)*izM8UtSR!=5L^03>=59Wwg^&)ml;BTvfWfu||FxfQ~fQw3ZT4N*6aR zZ>%hDv8-YbYwax}PMb7Ol!%NRZRY z1$5$_o+p$scYziLorbJs(|d=YJkW_=KwI8ORZE*%iu$y+VKOL8ucsNa1JCc!-k~Cr zCmkGs{r;8Z6>YOI0~0~Y^0uZHMsMb5?U(W%7S^L!+qFRSOQIKYT9-GJE?(7E)rwCK zluC*-SlHUyexiTQ-_TG4I$hFKU&;1?IhxBsg3|KEO)YKAi5Z((zG`vR+{T*5nl@IL zt<7u@y|i6Jg-feHPdNEBZTWd3J1;Vhu|UhxMN?y2c}-($>FTPMCW$9&>SUoId25+9PV0ZZ@TazP zPU_&nYuVWTpnFxDL8?czy7NU3{^G$WE7D9E8Tw~C(pdd<5~^h#Tf zfljVk#VWvRt!xx6!WG!JEtgr;9B+*40U7rFkx0GDILng zhz81rS{Q9P*EcoQEpKKNcb7~~Ox!w%Y0EN2?4Vb5db6+4V{>^MX0DM@(4c9_lGe`A ztiYPR1Qc(rW^KKhhJS9g2ancfZWhJb>8nJERytBREX8cEcJoDIytX=B#HaU#p=BU9 z5V+FYiIq*I6=2;q$;GVM)EfQ|ttlios6z!gTJYS(but+4}>|fd{5uFKa ztAV=SSOLVR9w>u_*zm4d0sVJqwhKj(@5c$Dq9~a#7^B?46Z?)ZVS^bY7e`9z^0p;I zA!AYtfGhBR^`U*mqJu|~5nWLZ?rdCv?{hHh&uhOOD=K^E7Qr1UUoo$$LRyj)O)XW7 z4rWW8B?rc8wLh1EJ}WhExrpy|vU8I1p^IocP|oY(@`^gi3Tp>2?d?MGrf?6@dMp;1 z5#-Q9+d$wTvg}1|?qcD$Btz$XkuJ(a-(u}OlnTBN?^LGVI;S%YOG+iE7VxEqHFL)nahXvSC$XWDrS*rZ%Iu(c!34*Sk(wQfUk?__94`id-{7ev#=6@{2RUt3ox(h?fLiYv-n8`wh3 zn~w8fD7lr|A>?L}lPndyuCK_iC@qB?LAo0|rxH{4mBy>YEDPQ%(taE-dT4JC7ColY z+d}27h6;HB+KD8rmDwRpMBug-XjqoirA_tkoj|S_MVwaDv{jcv%a&Kvw5`Gs(34dn zuHO;}%u;B*>Z*pSQk1O1?9*%5MBi5SG|16V)mGh9*~$i!CY_Q@=PV3iSw1MQn0*4} z-cZ%R5D3&F`iVZ_w?H8qlCYLNqdm4n6y~%;prFp1TdLa7^}7uHZCG|~U`K6@|%j?+^YY24+7+ij>JzZSjodFVs)}=Yx z6V+n21uueiPK#R`Q6pk4c(39lrZo#Wqnm?OBFQyMO7@aQ`iR< zKsqjsk(|!P^kUji8KS2J?yA;NAX2pE-eOAF+mMLSRrU4k8m+xv#KwMWnor%F_!a}d zXRU2o}#|r9^g0zvlp7Hcz zv{Yhb9AM$n*Q@)r;zoSbjzi!&*Ww2NRM84uN^LWGMZCPeW@%$-Ta&rVT_EtR`BR#} zA@%`U(?-#gRJ`NRkw!5WJBrYQ4P(VZ-|awV290JSJ|2XKxV{eOY{U0oi`gXYfo2h1 zOCN4n($un|yrr^B&9Eb2;L_5TrKL^yrco+-)2A3KSOZkjw5G;s%R!oVz}b4nzyj#a z@3AhEE|XS;AAC{ONM=zf4IN=VZNM@S?;8T4no(XEs4Q8Wl;D8npU0F&GCcu`p)y_jOZm$}};{uR#DSt0V!S?U%Myv+Kc$djDAa5xv*2 zilx)Ujns}T7f)W}*ucH{9RC<|MaT}^BEqTrVxN=Tbw;iVT08)_Ou zhBbv)LWZkhYX^ynl8^LiEQ9E~0v3B6TaURh1iO_{yoM|aquwbpp6hEWpkx~>VFyAf zl-5^i`!DA9Sat{$s%c^c7-xA4KE1|fk7t@YTU@C9eTC=~upqsG%s`M#+JW@OO*aa% z=Ydwka!4E$>W}cUjLFHz!czb}usTa*#nN{~I@kVE2qOEg4^+fBkr?rV6PsvFEwH|b zlWx*FR*Ev5)q*{eIFR;ur8E-*e~Y%u;jdJ)xK8_PDCS@>2sf*$b$NXoyFsg4C35*= z+KyHD$I0jE^SElNXwXVli+%#{Ff^0*LZlk3ZMj%vCd`GV z3#qtCXdchhe7pAi#iD1@$4FU%(X`cIei^J3azNm+uDHAyHXyZ|`iqRFfN@`(S23f%Rw++b4ybNJ|xF%nBkD=QW{#e*;r7QeC2w!3L?^4SJPbDT2FF{Cd&{1v!L(qc4>S6Ng z#uO};1g|oj4vLi6gBXtCIU3FuUcS}>lSN7j9Td_Xb+o-53sOZBKA2b0#xBuq zi?Q0e3=x+|XS`aL!}uoa{37rWv?a0i)7s1H#W2fZ@XF40Jbp5r8fhqpF`;939Hxy_ zDXc$M9$3FJo!#}zDbveOM>j+hhLANG1U<%Gi4{b$$eiWP^;L|Hq=sl4@}KGO70E=G zwM<6?$l+$2Fh5PLY?$`yCE`X4zLue#oG4=XLT$qa(QDurC?+EnhEcf!rKME_C6-9`xFVA*YN!UeW2ylYKT#OWP=>2xY(_EYuc}n2uFxF+?;e z*%-%cIXmn&?aE6Zjs2(q@!(Jj!72{KQqJ?+*Hi)Hovwl4{P2PYK3BfK~=Sc(c$FQ>UuVh z2vt!5A244Vu|>oO-v`e%HMg=;Fb#AML-evRf1w|)zUbq)&;~YwS)5D9_gI(8Wzo}Wf&ScOvUWpAe1Xi6DU`<6GT~3Q-$Eg zPH^Y+mP$s)gzI5&5C>66p_S2YB-pcQ1)@-rK6IejSkN&)OKMiaaBpDrX%byp^j@n$ zRICf7jbtEw3594ZYPoCU*p`q$o3K2pRbP&mmJ2XI(3NJ8zGMJDzS0QaMD*33xLovz zS_>W{kMsEJk(H^67nlL|f8|*XEoXZeD z8@i~!LchiARnWP-aTUCm*egYe1t-h2r}~S&7yOXci8-wESFnJROKDJcB1p5iX(jag zMeqdmX_7$$Mn`WY>*!WPOL-$crb*`zB#Csjwo-flN>MO*F34#(D5cWSx-VoUFR5vP zZl%~0+mAxcph{c$Vu*SiIIFVeB1SJ@%gBJf)_qA%QmStuNZ7m_(GU6r59}dr`T&t1 zi~?WkQ;>&P!?yycVAkkF{l(DQt;<`gdgZPyq6sO;EhuJ3K*;IMQjyW8zp%)+;>)t3 zfz!9YWONVH1FNW&ZNd1bOdlPXQaU3rZQ|(C(bMNno5R+F2P#{dnxQnbQCEqDv9E`Q zCM&O~se~y~))|@^6;2-~&^3KAOP{HgTn+0cy$f;7z>iw>)grYIZH^g=UuU<&K(a~4 z_c<`zEUdl=I0jaWWUb>`;hs#UEFF@Uer$-2mjazGjor!)V%BESR84HGsK*?`J)`#r zDjJ#@y|xX^7`~sV6&HxPITJB9IM>jCu#Kma$ig6p#7=B%gp&ReoKcCWXiJTbQ@gh7 z8oZ2ox%P)^U{)Q}e!NEH_?@H3OlW}8W^@pkJR%v&gV>a}R@Bt6@!IrukseKlQbNl* zDbgPKOq+QzkIlOQk`3>@qPm4Ghe$21X)JG9Wh|oLn^JrYmeI+Akodx(f2Cc-S@1zf z8pLd^eS&Z#KY&r|3`+056FEz(K_P~HPi<;%(Zp}lUfL#l&v#~kuOLbuiECGAj6QNi zi;UT=zX83HRo%+yV2d6yS-Kp)6)Pf+li}`xaFm~c*9lB=l8YIAT?G`@<>yLFI=nHM zw5(3scC9E$O~ynR{0pBY2fIi{D{wZi*8Y60s4S*$tP5ppX^yr{MX Date: Thu, 11 Aug 2022 16:46:24 +0200 Subject: [PATCH 270/394] 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 5d670022b79..0e7c2970e3e 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 271/394] [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 0e7c2970e3e..b8e98c6cf2f 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 272/394] [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 f9c14142721..d1af6bd240e 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 7a4dffac767..95f2ab5ed90 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 25929ba17e7..2876236bca8 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 71ed305ea57..6af0011dccc 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 241fd034da7..03b76942f60 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 fa0f4a011f5..8b98f473169 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 3cc7e8863c9..99260d8333f 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 e2fed30379e..bf4b0f84f46 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 fc87bc8d51a..2b2f9b21985 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 b7185cc1100..e538da6e7d6 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 273/394] [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 8b98f473169..7b3cab5b1e3 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 274/394] [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 7b3cab5b1e3..b39ea9d0adc 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 275/394] [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 d1af6bd240e..d720427b71c 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 b39ea9d0adc..f51bb6f5666 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 0b3d19a7427..40af20ec697 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 bf4b0f84f46..6abe85136e8 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 2b2f9b21985..feab593589f 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 276/394] [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 f51bb6f5666..dacb978d17c 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 6abe85136e8..7a20a3f1795 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 feab593589f..547cf8a9efe 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 277/394] [fix]: Updating the .lock file --- Cargo.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d720427b71c..60aca9972fd 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 278/394] [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 60aca9972fd..ed1e3ad29c1 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 dacb978d17c..02cfa560a04 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 7a20a3f1795..0e960ec01f8 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 547cf8a9efe..419c2408aef 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 279/394] [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 ed1e3ad29c1..58b4796eae5 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 bf9d222920e..98a2bbf6add 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 98f6c7f71c1..a993a3ebe29 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 e538da6e7d6..8c6679dedfa 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 39f70c17871..8261b7e7908 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 280/394] [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 02cfa560a04..c7fec10126d 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 281/394] 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 5d670022b79..b860b4b845f 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 282/394] 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 58b4796eae5..9ef4b832474 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 95f2ab5ed90..629cb601d88 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 283/394] 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 00000000000..e9c2b7c6887 --- /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 2f1343cc65a3c2ff70ef091fcc56c0e32f1a26e9 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 23 Jun 2022 12:33:26 +0200 Subject: [PATCH 284/394] changes to Cargo.toml and Cargo.lock from adding latest version of zeroize crate --- Cargo.lock | 50 ++++++++++++++++----------- shared/Cargo.toml | 1 + wasm/checksums.json | 34 +++++++++--------- 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 +-- 7 files changed, 60 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31d67389e0d..b10595dcad7 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", @@ -3928,6 +3937,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -4752,7 +4762,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", ] @@ -4764,7 +4774,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", ] @@ -5922,7 +5932,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", ] @@ -5934,7 +5944,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", ] @@ -5958,7 +5968,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", ] @@ -5970,7 +5980,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", ] @@ -8236,9 +8246,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", @@ -8279,9 +8289,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 d63acc035f7..570bb4a8e3a 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -116,6 +116,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/checksums.json b/wasm/checksums.json index 1495d6f6f54..be2bac12bfb 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_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_bond.wasm": "tx_bond.4cf89dcee2f8bdbcd32dbaa775cfc3a555625ded42abc2e14f3ed74f588e48fc.wasm", + "tx_from_intent.wasm": "tx_from_intent.4686836f77003d358d503dc87d887eb8fc05d204803c7429a7c8797f1e8a980d.wasm", + "tx_ibc.wasm": "tx_ibc.f9a9eea5f43152e4681c253f051cc4f410e52e7a081af226c87e89940c474941.wasm", + "tx_init_account.wasm": "tx_init_account.401ea88ac321c52f1f4c5ae18c365dbc2c72bf0ae5cc4ca3c4dc0bcf493a39b8.wasm", + "tx_init_nft.wasm": "tx_init_nft.4c632cbea80c534e8da7785d61bd75c1866bbbb34813585789b01343036f6b2d.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.1895eddc512ae33a3151bb4c9221dcbbaa28cade5877550e76f8a2a1b85eed71.wasm", + "tx_init_validator.wasm": "tx_init_validator.ffac85a6e5912e71e40dafdca2f86fdd0c0fb10b7c0269ef2730da4808b0f113.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.4bedacdc1b6c3af4c3c28be51c6bd770abeeb3b5281b9dc85bc49242da119f45.wasm", + "tx_transfer.wasm": "tx_transfer.be41dd45e9e11189f9121a5ac82e3bd3b19e473d7ce7e3fda529fe12e073aa35.wasm", + "tx_unbond.wasm": "tx_unbond.1e2a5b321d52135b74189335e04f338c37189bcfc967032504860cae4e62762c.wasm", + "tx_update_vp.wasm": "tx_update_vp.d47bfe6ef55a86bce36093feeff74fe09ec7cffebf9696dd37658756a4eb793d.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.05d61a0b198e2a1c02446e0764c8a1345d1471e20574a4a0b28062fd384e0011.wasm", + "tx_withdraw.wasm": "tx_withdraw.4af4f9a999b607f9941400af4e42c6988b1aef71c8f4764a4df2449551181b3d.wasm", + "vp_nft.wasm": "vp_nft.f6023b1d856727caafdffae5a75eeac46716ae4f75d653c7650226402e426957.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.d7df80ef8bff74f6788163a2ad4cf36b556df6ef94d5fb18ccc5f8c608a13a48.wasm", + "vp_token.wasm": "vp_token.118aef31933858351cf77315069fc2f7c61e7db8891c7990e77f9ffd6ef90af0.wasm", + "vp_user.wasm": "vp_user.ecfadfe38747b67ae1fa2c590e634cca723a086d24e3dc46d7db66419441478a.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index fcda82a6a58..063f40a2233 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1396,6 +1396,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -3274,9 +3275,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 3c82e7293e0..7dc2e8f86c5 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1396,6 +1396,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -3274,9 +3275,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 6b594015886..5a10a7ae4d2 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1396,6 +1396,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -3289,9 +3290,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 2507c114609..97af2f4415b 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1407,6 +1407,7 @@ dependencies = [ "wasmer-engine-universal", "wasmer-vm", "wasmparser 0.83.0", + "zeroize", ] [[package]] @@ -3301,9 +3302,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 f9f067455e17f87f91bd0711f5b0fef74d14d3fe Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 23 Jun 2022 12:38:14 +0200 Subject: [PATCH 285/394] 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 | 21 +++++++++++++++------ shared/src/types/key/mod.rs | 17 +++++++++++++++++ wasm/checksums.json | 30 +++++++++++++++--------------- 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/shared/src/types/key/ed25519.rs b/shared/src/types/key/ed25519.rs index 12e5093bd25..48db89005a3 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,15 @@ 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 +176,7 @@ impl BorshDeserialize for SecretKey { .map_err(|e| { std::io::Error::new(std::io::ErrorKind::InvalidInput, e) })?, - )) + ))) } } @@ -218,6 +221,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 +334,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 a1343cd7e52..e7571bb0506 100644 --- a/shared/src/types/key/mod.rs +++ b/shared/src/types/key/mod.rs @@ -415,6 +415,23 @@ 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) + }); + } } }; } diff --git a/wasm/checksums.json b/wasm/checksums.json index be2bac12bfb..2e55738ba52 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.4cf89dcee2f8bdbcd32dbaa775cfc3a555625ded42abc2e14f3ed74f588e48fc.wasm", - "tx_from_intent.wasm": "tx_from_intent.4686836f77003d358d503dc87d887eb8fc05d204803c7429a7c8797f1e8a980d.wasm", - "tx_ibc.wasm": "tx_ibc.f9a9eea5f43152e4681c253f051cc4f410e52e7a081af226c87e89940c474941.wasm", - "tx_init_account.wasm": "tx_init_account.401ea88ac321c52f1f4c5ae18c365dbc2c72bf0ae5cc4ca3c4dc0bcf493a39b8.wasm", - "tx_init_nft.wasm": "tx_init_nft.4c632cbea80c534e8da7785d61bd75c1866bbbb34813585789b01343036f6b2d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.1895eddc512ae33a3151bb4c9221dcbbaa28cade5877550e76f8a2a1b85eed71.wasm", - "tx_init_validator.wasm": "tx_init_validator.ffac85a6e5912e71e40dafdca2f86fdd0c0fb10b7c0269ef2730da4808b0f113.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.4bedacdc1b6c3af4c3c28be51c6bd770abeeb3b5281b9dc85bc49242da119f45.wasm", - "tx_transfer.wasm": "tx_transfer.be41dd45e9e11189f9121a5ac82e3bd3b19e473d7ce7e3fda529fe12e073aa35.wasm", - "tx_unbond.wasm": "tx_unbond.1e2a5b321d52135b74189335e04f338c37189bcfc967032504860cae4e62762c.wasm", + "tx_bond.wasm": "tx_bond.e8fcf7413067810f82e1c0f54fdc9675a4c686b172db9c44c714310d3f19baf6.wasm", + "tx_from_intent.wasm": "tx_from_intent.cd5f7af4d3af9300df8dc16e891f39f85d6497a43a2cd74d30d1dc506649291e.wasm", + "tx_ibc.wasm": "tx_ibc.3805f69ceafd60b857a860d26e6fbe2973f309289147cf59c10e0bd65a30057e.wasm", + "tx_init_account.wasm": "tx_init_account.8abb68c5f40fa9ea39f03ddb9c387bc84a642bac4f40c7e9ceebb8418b3ccde7.wasm", + "tx_init_nft.wasm": "tx_init_nft.564390006d7b8f4f46d215704b05561a7279c92a6fe8f3ec61118a5486a3e092.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.5fc912c847cb4d04743b4dea650418a4eae233f4abf91493791a92e3de5050aa.wasm", + "tx_init_validator.wasm": "tx_init_validator.92d11bc1cc27818864e13d122179a199043a274f7bba901b11c6474261eb97eb.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.9f5b723dd4ed195b489b8456a6d83167829f0b2b8fb479be19b877a60b81c803.wasm", + "tx_transfer.wasm": "tx_transfer.032bfa997c8f2ee2cc5bf053f0128880a79a57ad3d8c3ecc6ff0c22e27f47ebd.wasm", + "tx_unbond.wasm": "tx_unbond.17444d665b8a7bebb624691eaab318c845c8ce18c557d0af440b7bfdb7efa806.wasm", "tx_update_vp.wasm": "tx_update_vp.d47bfe6ef55a86bce36093feeff74fe09ec7cffebf9696dd37658756a4eb793d.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.05d61a0b198e2a1c02446e0764c8a1345d1471e20574a4a0b28062fd384e0011.wasm", - "tx_withdraw.wasm": "tx_withdraw.4af4f9a999b607f9941400af4e42c6988b1aef71c8f4764a4df2449551181b3d.wasm", - "vp_nft.wasm": "vp_nft.f6023b1d856727caafdffae5a75eeac46716ae4f75d653c7650226402e426957.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.d7df80ef8bff74f6788163a2ad4cf36b556df6ef94d5fb18ccc5f8c608a13a48.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.1bee09ffd3a1e577dc9b9d6d5f91c4a6f91a9b2ac9e3a3d8258c00cd12902304.wasm", + "tx_withdraw.wasm": "tx_withdraw.75dbdc78924d8072ffcefaed67715d9480dd9f702ac77cf18ee15d0e44a38ab0.wasm", + "vp_nft.wasm": "vp_nft.3711eac06bda0c39ab52618bc458df7e6b16ed096bb3930b93ade93c9b7cf23a.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.400be39dcf0fedf099232bdecfa42c2dbe1f0aaede34fb99ec26da3b6623c09a.wasm", "vp_token.wasm": "vp_token.118aef31933858351cf77315069fc2f7c61e7db8891c7990e77f9ffd6ef90af0.wasm", - "vp_user.wasm": "vp_user.ecfadfe38747b67ae1fa2c590e634cca723a086d24e3dc46d7db66419441478a.wasm" + "vp_user.wasm": "vp_user.73136ffcd68e46e2f88d27ae9e48a0731b06d883dd3defcb41b604cc7968c638.wasm" } \ No newline at end of file From bc0e71ae9e3e0974cc199f964a9568a91ffa7a46 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 6 Jul 2022 00:41:27 +0200 Subject: [PATCH 286/394] 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 e7571bb0506..9deccf75a92 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 3948c98db41a5e3c38b7b25de870d06bfaab7cb1 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 287/394] 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 00000000000..27cb40bf55b --- /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 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 288/394] 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 ec716a13859..de919297ba0 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 697387fd15b..c1bf239e6a6 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 552e675e676..78dc58616de 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 69dae061cd416db7a84a5fd875721f3741789e89 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 15 Jun 2022 13:48:42 +0200 Subject: [PATCH 289/394] 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/checksums.json | 34 +- 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 +++++ 11 files changed, 1023 insertions(+), 37 deletions(-) create mode 100644 shared/src/types/key/secp256k1.rs diff --git a/Cargo.lock b/Cargo.lock index b10595dcad7..3fd0bf98af2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2443,6 +2443,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" @@ -2451,7 +2461,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]] @@ -3058,7 +3079,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", @@ -3442,13 +3463,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" @@ -3905,6 +3975,7 @@ dependencies = [ "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "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 570bb4a8e3a..8110e1f89fc 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -86,6 +86,7 @@ ibc-proto-abci = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc 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 27e7c29b891..1986799142f 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 48db89005a3..4f3f9f02d77 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 9deccf75a92..6a95d2eb2d5 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 00000000000..2b7e2995dee --- /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_all(&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); + 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); + 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/checksums.json b/wasm/checksums.json index 2e55738ba52..ffbac6d9b0e 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.e8fcf7413067810f82e1c0f54fdc9675a4c686b172db9c44c714310d3f19baf6.wasm", - "tx_from_intent.wasm": "tx_from_intent.cd5f7af4d3af9300df8dc16e891f39f85d6497a43a2cd74d30d1dc506649291e.wasm", - "tx_ibc.wasm": "tx_ibc.3805f69ceafd60b857a860d26e6fbe2973f309289147cf59c10e0bd65a30057e.wasm", - "tx_init_account.wasm": "tx_init_account.8abb68c5f40fa9ea39f03ddb9c387bc84a642bac4f40c7e9ceebb8418b3ccde7.wasm", - "tx_init_nft.wasm": "tx_init_nft.564390006d7b8f4f46d215704b05561a7279c92a6fe8f3ec61118a5486a3e092.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.5fc912c847cb4d04743b4dea650418a4eae233f4abf91493791a92e3de5050aa.wasm", - "tx_init_validator.wasm": "tx_init_validator.92d11bc1cc27818864e13d122179a199043a274f7bba901b11c6474261eb97eb.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.9f5b723dd4ed195b489b8456a6d83167829f0b2b8fb479be19b877a60b81c803.wasm", - "tx_transfer.wasm": "tx_transfer.032bfa997c8f2ee2cc5bf053f0128880a79a57ad3d8c3ecc6ff0c22e27f47ebd.wasm", - "tx_unbond.wasm": "tx_unbond.17444d665b8a7bebb624691eaab318c845c8ce18c557d0af440b7bfdb7efa806.wasm", - "tx_update_vp.wasm": "tx_update_vp.d47bfe6ef55a86bce36093feeff74fe09ec7cffebf9696dd37658756a4eb793d.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.1bee09ffd3a1e577dc9b9d6d5f91c4a6f91a9b2ac9e3a3d8258c00cd12902304.wasm", - "tx_withdraw.wasm": "tx_withdraw.75dbdc78924d8072ffcefaed67715d9480dd9f702ac77cf18ee15d0e44a38ab0.wasm", - "vp_nft.wasm": "vp_nft.3711eac06bda0c39ab52618bc458df7e6b16ed096bb3930b93ade93c9b7cf23a.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.400be39dcf0fedf099232bdecfa42c2dbe1f0aaede34fb99ec26da3b6623c09a.wasm", - "vp_token.wasm": "vp_token.118aef31933858351cf77315069fc2f7c61e7db8891c7990e77f9ffd6ef90af0.wasm", - "vp_user.wasm": "vp_user.73136ffcd68e46e2f88d27ae9e48a0731b06d883dd3defcb41b604cc7968c638.wasm" + "tx_bond.wasm": "tx_bond.366f7448cfd065184c0ed35df2d6e6957bf92c10079f23e43630c1fec113d68b.wasm", + "tx_from_intent.wasm": "tx_from_intent.b33dd8f843660475405886899710ed70e91b39d1880a86a6acea6a9c8a6fd5aa.wasm", + "tx_ibc.wasm": "tx_ibc.404dd804e5725ea8d4fbfcd7cf3c0681a148a34d284a5fdae799ef5be2179861.wasm", + "tx_init_account.wasm": "tx_init_account.9b1b141a7ed7bd75af9f3ccc401b0a64c161aabf5f8b56ac645920f0fbdb72fb.wasm", + "tx_init_nft.wasm": "tx_init_nft.ede5a3f4e2ce8f2cfdaa664b6b9aca5a461f8317b3f2a4273a29dbde6a1cb8d0.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.d0c03bfe805d90cf7a094e11e4d903fc71f09f871c0b3daedf71172210409fac.wasm", + "tx_init_validator.wasm": "tx_init_validator.5ac10736e2521aa00c06c4cd4165b3587ca95470d947793b9ee2134c1b80220d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.ec77b12d4e7dfbf998e0c5021d11308e4ace27daf390b6f4c7dcfc04a3d1ff31.wasm", + "tx_transfer.wasm": "tx_transfer.35546938bd04de4e8070addb43275e68e0d9bed7baa159a9804a96494a7cac71.wasm", + "tx_unbond.wasm": "tx_unbond.fbbc6b35d188b9fbeb0f1d0f98434ff00efa7a2ba1fd4d527aa17b920f7f64c5.wasm", + "tx_update_vp.wasm": "tx_update_vp.cdb6de03931bb5e2d62d1db0d59777c3bb7bfb14214992328d0df95d1851f45e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.257a819ca6bb8cba60c43d5341381c8175dc0b6b1de7ceb07e7e0a8ed11c862d.wasm", + "tx_withdraw.wasm": "tx_withdraw.d11e3e3a6b5f94184f023b579a9bfabc9d268b0c35c11049525dffdbcf6ad948.wasm", + "vp_nft.wasm": "vp_nft.a4eff2933f866250fd98dd65e91d45f08d4ebc49a36df63f0216181b07dc9d8d.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.6f81a8be815dcab8f8dc726b4a2a01920a21901d02bc23daf097b4bfa35c5318.wasm", + "vp_token.wasm": "vp_token.a083be83b0dc78d953999e26dc6b8c10a0e9828c74d365f836c5668533f4e41c.wasm", + "vp_user.wasm": "vp_user.db97989f32edc6c780ae5fc9f5b4296640206b890f273b753fc0ffba317233fd.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 063f40a2233..d0856109487 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" @@ -996,6 +1012,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" @@ -1223,6 +1260,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" @@ -1369,6 +1455,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 7dc2e8f86c5..67aef12e0a5 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" @@ -996,6 +1012,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" @@ -1223,6 +1260,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" @@ -1369,6 +1455,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 5a10a7ae4d2..f1816d434ea 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" @@ -996,6 +1012,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" @@ -1223,6 +1260,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" @@ -1369,6 +1455,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 97af2f4415b..23d27344a61 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" @@ -1006,6 +1022,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" @@ -1233,6 +1270,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" @@ -1380,6 +1466,7 @@ dependencies = [ "ibc-proto", "ics23", "itertools", + "libsecp256k1", "loupe", "namada_proof_of_stake", "parity-wasm", From b0d7a54f2e29c05a326be3d4c8b03508020ea845 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Jun 2022 18:11:56 +0200 Subject: [PATCH 290/394] 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 95f73686687..e1330064f0e 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1463,6 +1463,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"); @@ -2727,6 +2728,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 @@ -2735,16 +2738,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.", )) @@ -3015,6 +3025,7 @@ pub mod args { pub alias: String, pub net_address: SocketAddr, pub unsafe_dont_encrypt: bool, + pub key_scheme: SchemeType, } impl Args for InitGenesisValidator { @@ -3022,10 +3033,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, } } @@ -3040,6 +3053,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 9851a104d3e623bd83ab216e95016bc073760be2 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Jun 2022 18:19:19 +0200 Subject: [PATCH 291/394] 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 9807516a930..3889489956f 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 5335f7d4503..94a1874d772 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -193,7 +193,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() }); @@ -202,7 +206,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 }); @@ -210,7 +218,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 d16e80e8d35..21ba310c442 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -263,11 +263,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) }); @@ -339,7 +341,7 @@ pub async fn join_network( .. } = peer { - node_id != *peer_id + node_id.as_ref().unwrap() != peer_id } else { true } @@ -357,11 +359,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. @@ -446,10 +451,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); @@ -459,7 +464,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!( @@ -518,8 +523,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( @@ -538,8 +546,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() }); @@ -553,8 +564,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() }); @@ -565,8 +579,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() }); @@ -721,8 +738,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); @@ -1011,6 +1031,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, ); @@ -1032,12 +1053,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, ) @@ -1142,16 +1165,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 5fec7dca988..7b0afd899d6 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 6ecb396004f..49fd2bbeafa 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 95454d3d2a1..cabaa6eb493 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 98c4325e88a7d4ea0deac1702ea8e4900949ce26 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 14:35:20 +0200 Subject: [PATCH 292/394] 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 4f3f9f02d77..d8f3765acd2 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 2b7e2995dee..ae9ec991d73 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 5398e250481d1d1ef450383f46343a17e319f6b4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 14:36:30 +0200 Subject: [PATCH 293/394] 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 ae9ec991d73..8dff25dcd14 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 c6dfb884b8a02d0cd0327ac509b2d1c192cbaefd Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 22:57:16 +0200 Subject: [PATCH 294/394] 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 e1330064f0e..03ff487c145 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1463,7 +1463,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"); @@ -3055,7 +3055,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 94a1874d772..798ef62cd4b 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -194,7 +194,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, ) @@ -207,7 +207,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, ) @@ -219,7 +219,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 cabaa6eb493..63e4f0d4b65 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 d8f3765acd2..dbcf9fe04c9 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 6a95d2eb2d5..f90cc4aa2e4 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 8dff25dcd14..15dc6a838b8 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 ee80a74daee1aea1ee1aed8d6b9be6c7526094d0 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 23:49:27 +0200 Subject: [PATCH 295/394] 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 21ba310c442..36bcdf480f8 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -341,7 +341,7 @@ pub async fn join_network( .. } = peer { - node_id.as_ref().unwrap() != peer_id + node_id != *peer_id } else { true } @@ -359,14 +359,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. @@ -464,7 +461,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 e1b3d0b436e53f05713417b1a8ac523939f36d8b Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 28 Jun 2022 18:45:34 +0200 Subject: [PATCH 296/394] 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 15dc6a838b8..b83d98c7205 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 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 297/394] 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 58c6b0c26ce..38fc3cc20a5 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 298/394] 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 c00ec478efe..8677baf9c88 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 870d6777c8265f30c46a576139d761fbb24d81e0 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 28 Jun 2022 22:44:23 +0200 Subject: [PATCH 299/394] 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 1986799142f..2543f0c0563 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 f90cc4aa2e4..9ce1b03838d 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 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 300/394] 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 7dd7bc2d46a..fefb32ac64c 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 00000000000..d01fbfd287e --- /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 00000000000..0c0e4f4083a --- /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 301/394] 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 3893e847ed0..fa30efb7eae 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 302/394] 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 e2ffd72e13d..95e7c48782f 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 b34d954166c..099ae2540ab 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 c1be4465f1a..37d1958aa99 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 303/394] 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 0b3d19a7427..1f106bcf69b 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 304/394] 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 578357b7339..1fc7fa788cb 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 d5392895331..cf9dccfb56d 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 b34d954166c..ce7b9fa5e99 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 21be213ea38..7be4e4c064b 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 7b6826342ee..05f7cede911 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 5d0009b01b2..42b63a892a7 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 0e47437704a..2b6ef242426 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 305/394] 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 c980da81da2..a9d72e84bb9 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 80572c1f57a..2f19b6d1a49 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 1700be22642..af63d1f1756 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 ce856cd876b..65a6c3f6cd0 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 604f0433d5e15b6add816675bfaa1f3aadfb1bf8 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 12:57:36 +0200 Subject: [PATCH 306/394] 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 36bcdf480f8..b6e52c26df3 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -360,9 +360,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 428327fc1009bc1f8514fdc756f97bd82763e174 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 14:38:57 +0200 Subject: [PATCH 307/394] 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 b6e52c26df3..79214126437 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1176,15 +1176,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 d34bf60590b46a201582992f1042c5884d7d767f Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 22:07:21 +0200 Subject: [PATCH 308/394] 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 03ff487c145..4513cb10f35 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1696,6 +1696,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, @@ -1709,6 +1710,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); @@ -1719,6 +1721,7 @@ pub mod args { Self { tx, source, + scheme, account_key, consensus_key, rewards_account_key, @@ -1734,6 +1737,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 798ef62cd4b..c6b2c9b254d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -172,6 +172,7 @@ pub async fn submit_init_validator( args::TxInitValidator { tx: tx_args, source, + scheme, account_key, consensus_key, rewards_account_key, @@ -194,7 +195,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, ) @@ -207,7 +208,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, ) @@ -219,7 +220,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 dd58b30378794867ca93b5b15485a6dcffa3ea4f Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 22:08:14 +0200 Subject: [PATCH 309/394] 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 79214126437..4b4ea25430c 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -361,19 +361,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 43c708c55fd2e7956973fb7042ed3c16a215dabc Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 15 Jun 2022 13:48:42 +0200 Subject: [PATCH 310/394] 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 2543f0c0563..e536a7171d7 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 9ce1b03838d..a1323a4bc69 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 b83d98c7205..90fbc823765 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 8c769d5829dadca5bafc378e07d39c9f885a70a2 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Jun 2022 18:11:56 +0200 Subject: [PATCH 311/394] 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 4513cb10f35..a629fdf1c93 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -1463,7 +1463,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"); @@ -1738,8 +1739,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 \ @@ -2758,8 +2759,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 \ @@ -3061,8 +3062,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 a13e5e586a38d5bb9203c0044ae44d80eff5c735 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 17 Jun 2022 18:19:19 +0200 Subject: [PATCH 312/394] 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 c6b2c9b254d..4d829fe61d9 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -195,7 +195,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, ) @@ -208,7 +208,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, ) @@ -220,7 +220,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 4b4ea25430c..d11f9d8632b 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -341,7 +341,7 @@ pub async fn join_network( .. } = peer { - node_id != *peer_id + node_id.as_ref().unwrap() != peer_id } else { true } @@ -359,7 +359,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(), @@ -367,7 +371,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. @@ -465,7 +469,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!( @@ -1169,20 +1173,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 49fd2bbeafa..4a6b4a46799 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 63e4f0d4b65..70af3814263 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 e44a734312f5300c07755dac987da82cf1e0c1a4 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 22:57:16 +0200 Subject: [PATCH 313/394] 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 a629fdf1c93..8a013deef93 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3062,8 +3062,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 4d829fe61d9..cb5da89387c 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -195,7 +195,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, ) @@ -208,7 +208,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, ) @@ -220,7 +220,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 70af3814263..d60af1be967 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 6b080b03695d65c98a1e5c7fa7c7602f61fd6d67 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 27 Jun 2022 23:49:27 +0200 Subject: [PATCH 314/394] 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 d11f9d8632b..391164d1784 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -341,7 +341,7 @@ pub async fn join_network( .. } = peer { - node_id.as_ref().unwrap() != peer_id + node_id != *peer_id } else { true } @@ -359,11 +359,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(), @@ -371,7 +368,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. @@ -469,7 +466,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 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 315/394] 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 b8e98c6cf2f..4654a502c53 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 3d1586893f8593c451f3b79cd083091370df9a0a Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 28 Jun 2022 18:45:34 +0200 Subject: [PATCH 316/394] 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 90fbc823765..b83d98c7205 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 b645b4313b8520de12a6fad5579399097e6c95e6 Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 28 Jun 2022 22:44:23 +0200 Subject: [PATCH 317/394] 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 e536a7171d7..3cdec73bb96 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 db2e69c848b5ca687c3356053e0e765922ed44c5 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 12:57:36 +0200 Subject: [PATCH 318/394] 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 391164d1784..4cda41e9bb2 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -360,14 +360,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 f0f5b2f625079826ee36aee4bc8cb3dffb1a9a0b Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 14:38:57 +0200 Subject: [PATCH 319/394] 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 4cda41e9bb2..9c650f6db53 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1176,15 +1176,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 e39e9b0d7f2a3b8a14e014b0a09b67e8b21b9519 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 22:07:21 +0200 Subject: [PATCH 320/394] 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 8a013deef93..a629fdf1c93 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3062,8 +3062,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 cb5da89387c..c6b2c9b254d 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -195,7 +195,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, ) @@ -208,7 +208,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, ) @@ -220,7 +220,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 db6f61fa069e5d126b7f1f278de4fe5f5a2edbe0 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 29 Jun 2022 22:08:14 +0200 Subject: [PATCH 321/394] 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 9c650f6db53..4d796943b9f 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1181,13 +1181,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 1be249cf0944199b854ab10700ab63b1a5fcf54d Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 4 Jul 2022 13:56:11 +0200 Subject: [PATCH 322/394] 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 4d796943b9f..21de818fb28 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -1181,13 +1181,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 f320caa7fa2fba416823b2ce82376afd9de18a25 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 4 Jul 2022 13:59:31 +0200 Subject: [PATCH 323/394] 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 a1323a4bc69..c88fe85ffa9 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 b83d98c7205..dc7c16badea 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 365c001e59509d75ded5b0dddca0e9f1070a2730 Mon Sep 17 00:00:00 2001 From: brentstone Date: Mon, 4 Jul 2022 14:00:30 +0200 Subject: [PATCH 324/394] 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 f150e6eac0a..0e6fea4c92d 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1562,7 +1562,7 @@ fn test_genesis_validators() -> Result<()> { } }; - // 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"; @@ -1574,6 +1574,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)), ], @@ -1610,6 +1611,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 97e8d3a6cc233b4ff13d36652273e57f3f2ab01f Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 5 Jul 2022 15:08:19 +0200 Subject: [PATCH 325/394] 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 d60af1be967..76a20534193 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 85133eff6c415fe94ebfeea104c9f8a786a1a74a Mon Sep 17 00:00:00 2001 From: brentstone Date: Tue, 5 Jul 2022 15:09:49 +0200 Subject: [PATCH 326/394] 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 7b0afd899d6..5bdf8e62611 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 dc7c16badea..677fc3c4b38 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); 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 0e6fea4c92d..526b96261cf 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1562,7 +1562,8 @@ fn test_genesis_validators() -> Result<()> { } }; - // 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 3b1fd96140fbe3b70b9aa04b3112d11fafce43a3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 6 Jul 2022 17:33:01 +0200 Subject: [PATCH 327/394] 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 21de818fb28..cc32eb70ae1 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -459,10 +459,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 9eb60167ec0fe3fab9e511acd5b1dcaeed9e3cfe Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 6 Jul 2022 23:45:44 +0200 Subject: [PATCH 328/394] 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 cc32eb70ae1..5b45426d5ac 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -533,7 +533,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, ); @@ -556,7 +556,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, ); @@ -574,7 +574,7 @@ pub fn init_network( name ); let (_alias, keypair) = wallet.gen_key( - SchemeType::Common, + SchemeType::Ed25519, Some(alias), unsafe_dont_encrypt, ); @@ -589,7 +589,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, ); @@ -748,7 +748,7 @@ pub fn init_network( name ); let (_alias, keypair) = wallet.gen_key( - SchemeType::Common, + SchemeType::Ed25519, Some(name.clone()), unsafe_dont_encrypt, ); @@ -1040,7 +1040,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 5bdf8e62611..b5c68dd2c1d 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 96b68c9b0e37321a7af72992f90a8c7740c5d897 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 8 Jul 2022 11:01:57 +0200 Subject: [PATCH 329/394] 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 c38146cd355..8a52dd669a5 100644 --- a/apps/src/lib/node/ledger/tendermint_node.rs +++ b/apps/src/lib/node/ledger/tendermint_node.rs @@ -248,27 +248,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 9da15544333d3c1b3585f9ebe41aa853aff0d928 Mon Sep 17 00:00:00 2001 From: brentstone Date: Fri, 8 Jul 2022 11:14:48 +0200 Subject: [PATCH 330/394] 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 526b96261cf..f863aee9055 100644 --- a/tests/src/e2e/ledger_tests.rs +++ b/tests/src/e2e/ledger_tests.rs @@ -1575,7 +1575,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)), ], @@ -1612,7 +1613,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 38e627097f0ab9a3688b1981463f54d1ee357e9e 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 331/394] 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 c88fe85ffa9..1e126673469 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 505b57a6df5bdcaf064f4216595fdc52057bd8df Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 10 Aug 2022 11:16:29 -0400 Subject: [PATCH 332/394] wrap libsecp256k1::SecretKey in a Box within SecretKey struct --- shared/src/types/key/secp256k1.rs | 10 +++++----- wasm/checksums.json | 30 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/shared/src/types/key/secp256k1.rs b/shared/src/types/key/secp256k1.rs index 677fc3c4b38..18d74dd5e85 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 diff --git a/wasm/checksums.json b/wasm/checksums.json index ffbac6d9b0e..e4809b538cb 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.366f7448cfd065184c0ed35df2d6e6957bf92c10079f23e43630c1fec113d68b.wasm", - "tx_from_intent.wasm": "tx_from_intent.b33dd8f843660475405886899710ed70e91b39d1880a86a6acea6a9c8a6fd5aa.wasm", - "tx_ibc.wasm": "tx_ibc.404dd804e5725ea8d4fbfcd7cf3c0681a148a34d284a5fdae799ef5be2179861.wasm", - "tx_init_account.wasm": "tx_init_account.9b1b141a7ed7bd75af9f3ccc401b0a64c161aabf5f8b56ac645920f0fbdb72fb.wasm", - "tx_init_nft.wasm": "tx_init_nft.ede5a3f4e2ce8f2cfdaa664b6b9aca5a461f8317b3f2a4273a29dbde6a1cb8d0.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.d0c03bfe805d90cf7a094e11e4d903fc71f09f871c0b3daedf71172210409fac.wasm", - "tx_init_validator.wasm": "tx_init_validator.5ac10736e2521aa00c06c4cd4165b3587ca95470d947793b9ee2134c1b80220d.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.ec77b12d4e7dfbf998e0c5021d11308e4ace27daf390b6f4c7dcfc04a3d1ff31.wasm", - "tx_transfer.wasm": "tx_transfer.35546938bd04de4e8070addb43275e68e0d9bed7baa159a9804a96494a7cac71.wasm", - "tx_unbond.wasm": "tx_unbond.fbbc6b35d188b9fbeb0f1d0f98434ff00efa7a2ba1fd4d527aa17b920f7f64c5.wasm", + "tx_bond.wasm": "tx_bond.d426722e133c4d386d5378a457f420e969197d24b151cd7085bfe08ec1719ced.wasm", + "tx_from_intent.wasm": "tx_from_intent.b3f9ee85f9ecc8c1c78b73169a567ad59ecf876bb72c76e4f57dbcf3c9dd6e17.wasm", + "tx_ibc.wasm": "tx_ibc.09518b4c922ed1f70699513fe8ab1c8fdc42031622e508fc0bdecdefb66bb9f5.wasm", + "tx_init_account.wasm": "tx_init_account.bfc5617cab4e138436f7dc1fc107c5bad4d94f3f8df5fcb0d95126b9463cea59.wasm", + "tx_init_nft.wasm": "tx_init_nft.d507eb96fa178535b68a3c6080b808b49ca5936300eab0eec7c36d4d3bc148bf.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.295ac0505c98209b7a672504902d5ce523d45a84827388da7c0d60e38079ae01.wasm", + "tx_init_validator.wasm": "tx_init_validator.6f163d3cee0820bf0c555ecd8ee09c081c7d498394a0409b8005d39495ddcaeb.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.df3cb6c43463e94d9cf4b125bd00c1d3d5ccd579beaa32336c5b56ce7957ef05.wasm", + "tx_transfer.wasm": "tx_transfer.3709be4f149050a34bcc84e1d5ec3776be45dedb63b5b224306066ed3fa23d2f.wasm", + "tx_unbond.wasm": "tx_unbond.56086ae05b845037b9cc0bbcbb76c496a95a8041e0eaaef20a881eae6cf0116d.wasm", "tx_update_vp.wasm": "tx_update_vp.cdb6de03931bb5e2d62d1db0d59777c3bb7bfb14214992328d0df95d1851f45e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.257a819ca6bb8cba60c43d5341381c8175dc0b6b1de7ceb07e7e0a8ed11c862d.wasm", - "tx_withdraw.wasm": "tx_withdraw.d11e3e3a6b5f94184f023b579a9bfabc9d268b0c35c11049525dffdbcf6ad948.wasm", - "vp_nft.wasm": "vp_nft.a4eff2933f866250fd98dd65e91d45f08d4ebc49a36df63f0216181b07dc9d8d.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.6f81a8be815dcab8f8dc726b4a2a01920a21901d02bc23daf097b4bfa35c5318.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.06a2ae40b5d8683899debb58b5217647b1d19f1818a2c01a97e1045d72c7cc2a.wasm", + "tx_withdraw.wasm": "tx_withdraw.a2d57125485db6c5162b699333ad23db2586f92d88df91e10642a8911854773e.wasm", + "vp_nft.wasm": "vp_nft.bc74013b04a2944e153d82a7a4f934c35aff87c2cd8daca7a22bfdab970bfadb.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.f0c4c073c468f35edb7b8cd31dab326e40b00bbb9b2ab9e27c4028526146d066.wasm", "vp_token.wasm": "vp_token.a083be83b0dc78d953999e26dc6b8c10a0e9828c74d365f836c5668533f4e41c.wasm", - "vp_user.wasm": "vp_user.db97989f32edc6c780ae5fc9f5b4296640206b890f273b753fc0ffba317233fd.wasm" + "vp_user.wasm": "vp_user.065595512916500834e721eb4599a109201a10d8304664f6c474ab8200c11744.wasm" } \ No newline at end of file From 3cf25ecd05030b01b8b1fa12730ecdd4a96f9d08 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 10 Aug 2022 11:17:12 -0400 Subject: [PATCH 333/394] 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 1e126673469..a287e2f8e3f 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 73978706290956b467dbaadcb91f1e7de67fb3a2 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 20 Aug 2022 11:32:56 -0400 Subject: [PATCH 334/394] release: update release.toml for namada repo We release from "main", and name the releases "Namada". --- release.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/release.toml b/release.toml index 288708c60a5..e82ec3b4c30 100644 --- a/release.toml +++ b/release.toml @@ -1,10 +1,10 @@ -allow-branch = ["master", "maint-*"] +allow-branch = ["main", "maint-*"] consolidate-commits = true disable-push = true disable-publish = true disable-tag = true no-dev-version = true -pre-release-commit-message = "Anoma {{version}}" +pre-release-commit-message = "Namada {{version}}" shared-version = true sign-tag = true -tag-message = "Anoma {{version}}" +tag-message = "Namada {{version}}" From 02f0d90f0853e3ba69c3cfe3444365f91e26f1d3 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 10 Aug 2022 11:25:58 -0400 Subject: [PATCH 335/394] use heliaxdev/libsecp256k1 crate fork as dependency for now --- Cargo.lock | 17 ++++++----------- shared/Cargo.toml | 2 +- wasm/checksums.json | 34 +++++++++++++++++----------------- wasm/wasm_source/Cargo.lock | 15 +++++---------- 4 files changed, 29 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fd0bf98af2..743376121e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3472,15 +3472,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", @@ -3493,8 +3491,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", @@ -3504,8 +3501,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", ] @@ -3513,8 +3509,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", ] @@ -3975,7 +3970,7 @@ dependencies = [ "ibc-proto 0.16.0 (git+https://github.com/heliaxdev/ibc-rs?rev=30b3495ac56c6c37c99bc69ef9f2e84c3309c6cc)", "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 8110e1f89fc..98c7a07c229 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -86,7 +86,7 @@ ibc-proto-abci = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc 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/heliaxdev/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} diff --git a/wasm/checksums.json b/wasm/checksums.json index e4809b538cb..11598f4944e 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.d426722e133c4d386d5378a457f420e969197d24b151cd7085bfe08ec1719ced.wasm", - "tx_from_intent.wasm": "tx_from_intent.b3f9ee85f9ecc8c1c78b73169a567ad59ecf876bb72c76e4f57dbcf3c9dd6e17.wasm", - "tx_ibc.wasm": "tx_ibc.09518b4c922ed1f70699513fe8ab1c8fdc42031622e508fc0bdecdefb66bb9f5.wasm", - "tx_init_account.wasm": "tx_init_account.bfc5617cab4e138436f7dc1fc107c5bad4d94f3f8df5fcb0d95126b9463cea59.wasm", - "tx_init_nft.wasm": "tx_init_nft.d507eb96fa178535b68a3c6080b808b49ca5936300eab0eec7c36d4d3bc148bf.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.295ac0505c98209b7a672504902d5ce523d45a84827388da7c0d60e38079ae01.wasm", - "tx_init_validator.wasm": "tx_init_validator.6f163d3cee0820bf0c555ecd8ee09c081c7d498394a0409b8005d39495ddcaeb.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.df3cb6c43463e94d9cf4b125bd00c1d3d5ccd579beaa32336c5b56ce7957ef05.wasm", - "tx_transfer.wasm": "tx_transfer.3709be4f149050a34bcc84e1d5ec3776be45dedb63b5b224306066ed3fa23d2f.wasm", - "tx_unbond.wasm": "tx_unbond.56086ae05b845037b9cc0bbcbb76c496a95a8041e0eaaef20a881eae6cf0116d.wasm", - "tx_update_vp.wasm": "tx_update_vp.cdb6de03931bb5e2d62d1db0d59777c3bb7bfb14214992328d0df95d1851f45e.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.06a2ae40b5d8683899debb58b5217647b1d19f1818a2c01a97e1045d72c7cc2a.wasm", - "tx_withdraw.wasm": "tx_withdraw.a2d57125485db6c5162b699333ad23db2586f92d88df91e10642a8911854773e.wasm", - "vp_nft.wasm": "vp_nft.bc74013b04a2944e153d82a7a4f934c35aff87c2cd8daca7a22bfdab970bfadb.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.f0c4c073c468f35edb7b8cd31dab326e40b00bbb9b2ab9e27c4028526146d066.wasm", - "vp_token.wasm": "vp_token.a083be83b0dc78d953999e26dc6b8c10a0e9828c74d365f836c5668533f4e41c.wasm", - "vp_user.wasm": "vp_user.065595512916500834e721eb4599a109201a10d8304664f6c474ab8200c11744.wasm" + "tx_bond.wasm": "tx_bond.42504d29d81008869e2c13775254e493181fa334f557940be8ad614ea8aa439a.wasm", + "tx_from_intent.wasm": "tx_from_intent.56a079838bf3d06d7be0160864ff4b70ad5961a9baff7ca8d28cf31e59111395.wasm", + "tx_ibc.wasm": "tx_ibc.8b4f5ead4fa069d2786e988cbe2bcb63f095cdcd47c913614829ef795dcbfa30.wasm", + "tx_init_account.wasm": "tx_init_account.80fbd4271cbef05fb6d340617ecb1437fc375c62f1a28baf989945d03850d0e1.wasm", + "tx_init_nft.wasm": "tx_init_nft.389a37111a810e71d9eafa56ea22f73374a1277d16325105f42cdc86ae23feca.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.f3276f42cad9aa79ebc1da6516b78a9af624644d43cdfcca2b86f6e618334257.wasm", + "tx_init_validator.wasm": "tx_init_validator.a71938fb2aac071b44b9949ab055d493e29b5791f4dde3293635a0faf05c64a9.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.d8af9ed4d99f1d58642a0a0b2759109b8df1f3dcb583b19609bb0da96b41b74c.wasm", + "tx_transfer.wasm": "tx_transfer.b35df2da33c8828287c7de8163d0ecc8c59b4444cd1c9afcf246c569ae653a74.wasm", + "tx_unbond.wasm": "tx_unbond.49d120af6a0e7183dc88ca1dfad44825763dcc6cd7c32afef694d4e814e64f50.wasm", + "tx_update_vp.wasm": "tx_update_vp.025a9dec03f1e138a3e5bdcafeebe53b220a75340c6cdf7160663b870ee92bb8.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ea9332b02b39d852fe4e8949de56d96bb6fdde9fad69a7252f32bb3465f527ff.wasm", + "tx_withdraw.wasm": "tx_withdraw.199ea7cb708d12196cf4cf06598c58403d1b07056284d275c3022d47207323c1.wasm", + "vp_nft.wasm": "vp_nft.31eb61ed64a4eda03d3d872d6b3716e0fe4210b7d02275463adae3bbf6dfcaa1.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.87a7e0e37af26847bc91e1559de3018ac97382f969c76f99ca86423f3a19f502.wasm", + "vp_token.wasm": "vp_token.7cb6763045cc60eb013db6adf75d254eeedeb01d0b1ecb4a1dfce5288096de6a.wasm", + "vp_user.wasm": "vp_user.a63e9a36b20abc4ef105b50e77302577f0850e60993cdcdc3ee6b2d9886ebebc.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index f1816d434ea..6c46fad54ab 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1262,15 +1262,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/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "arrayref", "base64", "digest 0.9.0", "hmac-drbg", - "lazy_static", "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", @@ -1283,8 +1281,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/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "crunchy", "digest 0.9.0", @@ -1294,8 +1291,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/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] @@ -1303,8 +1299,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/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ "libsecp256k1-core", ] From 4fba1c1b4e1b8d588a66c7d6dced6df1e018dc40 Mon Sep 17 00:00:00 2001 From: brentstone Date: Wed, 10 Aug 2022 11:30:24 -0400 Subject: [PATCH 336/394] fmt && clippy --- shared/src/types/key/mod.rs | 3 +-- wasm/tx_template/Cargo.lock | 15 +++++---------- wasm/vp_template/Cargo.lock | 15 +++++---------- wasm_for_tests/wasm_source/Cargo.lock | 15 +++++---------- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/shared/src/types/key/mod.rs b/shared/src/types/key/mod.rs index a287e2f8e3f..d8ccbb5a64b 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 d0856109487..7f9706fc3c7 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1262,15 +1262,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", @@ -1283,8 +1281,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", @@ -1294,8 +1291,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", ] @@ -1303,8 +1299,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 67aef12e0a5..8b3351e9470 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1262,15 +1262,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", @@ -1283,8 +1281,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", @@ -1294,8 +1291,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", ] @@ -1303,8 +1299,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 23d27344a61..0cfcd70cb94 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -1272,15 +1272,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", @@ -1293,8 +1291,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", @@ -1304,8 +1301,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", ] @@ -1313,8 +1309,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 ba0aa4c0e2d396005574cbdbbbc504d870950210 Mon Sep 17 00:00:00 2001 From: brentstone Date: Thu, 11 Aug 2022 17:10:09 -0400 Subject: [PATCH 337/394] 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 f72dcf615ae..0c416bee4f9 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -79,11 +79,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 04e6ba4675ed089daee1447275462d22122329b4 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 338/394] 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 743376121e3..289f5e4e38f 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" @@ -1887,6 +1960,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" @@ -2277,6 +2360,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" @@ -2453,6 +2547,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" @@ -2930,6 +3034,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" @@ -5504,6 +5621,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" @@ -5803,6 +5931,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" @@ -6104,9 +6244,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" @@ -6448,10 +6592,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 1464b22c8b2..0285cb7e48a 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -123,7 +123,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", optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", festures = ["secp256k1"], 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} diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 98c7a07c229..630148db3ea 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -104,7 +104,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", optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["secp256k1"], 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} diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index 7f9706fc3c7..b3a15bee65f 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" @@ -940,6 +1023,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" @@ -1018,7 +1112,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", ] @@ -1030,7 +1134,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.8.1", ] [[package]] @@ -1226,6 +1330,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" @@ -2050,6 +2167,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" @@ -2210,6 +2338,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" @@ -2326,9 +2466,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" @@ -2462,10 +2606,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 8b3351e9470..799256683de 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" @@ -940,6 +1023,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" @@ -1018,7 +1112,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", ] @@ -1030,7 +1134,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.8.1", ] [[package]] @@ -1226,6 +1330,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" @@ -2050,6 +2167,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" @@ -2210,6 +2338,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" @@ -2326,9 +2466,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" @@ -2462,10 +2606,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 6c46fad54ab..5124bdd0c0b 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" @@ -940,6 +1023,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" @@ -1018,7 +1112,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", ] @@ -1030,7 +1134,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.8.1", ] [[package]] @@ -1226,6 +1330,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" @@ -2076,6 +2193,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" @@ -2236,6 +2364,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" @@ -2352,9 +2492,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" @@ -2488,10 +2632,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 0cfcd70cb94..4d0e7b7875f 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" @@ -941,6 +1024,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" @@ -1028,7 +1122,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", ] @@ -1040,7 +1144,7 @@ checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", "generic-array", - "hmac", + "hmac 0.8.1", ] [[package]] @@ -1236,6 +1340,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" @@ -2082,6 +2199,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" @@ -2242,6 +2370,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" @@ -2358,9 +2498,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" @@ -2494,10 +2638,12 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", + "k256", "num-traits", "once_cell", "prost", "prost-types", + "ripemd160", "serde", "serde_bytes", "serde_json", From 7f2e44331e4a4b3da1e86992f9bd66cb201b83ea 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 339/394] 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 0c416bee4f9..23375f9e99d 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -79,7 +79,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"); @@ -87,13 +87,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 779e5834b45fcb217174ef839b8cb6de229424aa 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 340/394] 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 d8ccbb5a64b..666cc3fb5e1 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 d86e093b38af28c52bdd7feb48a6967c0665eb0e 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 341/394] 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 c6b2c9b254d..0e95567460e 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -208,7 +208,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 4a6b4a46799..72f719d1e45 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 08d1d51aa51468dc1afea668260ecb5690ee645f 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 342/394] 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 0e95567460e..0b2d8efc0e5 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -234,7 +234,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 5b45426d5ac..e762e4d9f37 100644 --- a/apps/src/lib/client/utils.rs +++ b/apps/src/lib/client/utils.rs @@ -613,7 +613,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 b5c68dd2c1d..b3048ef97d8 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 204dffdc362bd2204c32d38b43c84996329b2746 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 343/394] 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 0285cb7e48a..ae3279d8341 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -64,7 +64,7 @@ ABCI-plus-plus = [ testing = ["dev"] [dependencies] -namada = {path = "../shared", default-features = false, features = ["wasm-runtime", "ferveo-tpke", "rand"]} +namada = {path = "../shared", default-features = false, 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 630148db3ea..71093ede331 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -61,6 +61,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"} @@ -86,7 +91,7 @@ ibc-proto-abci = {package = "ibc-proto", git = "https://github.com/heliaxdev/ibc ics23 = "0.6.7" itertools = "0.10.0" loupe = {version = "0.1.3", optional = true} -libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9"} +libsecp256k1 = {git = "https://github.com/heliaxdev/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 18d74dd5e85..e6607eda846 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 b3a15bee65f..314538d3b4d 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" @@ -1106,37 +1096,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" @@ -1385,14 +1354,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]] @@ -2174,7 +2141,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 799256683de..3e588453873 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" @@ -1106,37 +1096,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" @@ -1385,14 +1354,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]] @@ -2174,7 +2141,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 5124bdd0c0b..e3dce7ddee5 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" @@ -1106,37 +1096,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" @@ -1385,14 +1354,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]] @@ -2200,7 +2167,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 4d0e7b7875f..c386be29266 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" @@ -1116,35 +1106,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]] @@ -1395,14 +1364,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]] @@ -2206,7 +2173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" dependencies = [ "crypto-bigint", - "hmac 0.11.0", + "hmac", "zeroize", ] From 170b02276608a4056647768a5a0d3e17f520f054 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 344/394] 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 0b2d8efc0e5..a04cb702a12 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -203,8 +203,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 baa22d3ad5a15a4877ec685374665d5442c129f3 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 345/394] 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 71093ede331..a98b2df7169 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -127,6 +127,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 e6607eda846..99bcbb3f678 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 4e5ff6a35dd24e1007825ea4eefa201e27464b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Fri, 19 Aug 2022 18:21:06 +0200 Subject: [PATCH 346/394] changelog: add #278 --- .changelog/unreleased/features/278-secp256k1-support.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/features/278-secp256k1-support.md diff --git a/.changelog/unreleased/features/278-secp256k1-support.md b/.changelog/unreleased/features/278-secp256k1-support.md new file mode 100644 index 00000000000..fb257152e4f --- /dev/null +++ b/.changelog/unreleased/features/278-secp256k1-support.md @@ -0,0 +1 @@ +- Added secp256k1 support ([#278](https://github.com/anoma/anoma/pull/278)) \ No newline at end of file From 9e2fc9489236f77b2c741f49e8ce1ba3e37390e4 Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 20 Aug 2022 11:45:13 -0400 Subject: [PATCH 347/394] realign cargo/wasm integrity --- shared/Cargo.toml | 2 +- wasm/checksums.json | 34 ++++----- wasm/wasm_source/Cargo.lock | 146 ------------------------------------ 3 files changed, 18 insertions(+), 164 deletions(-) diff --git a/shared/Cargo.toml b/shared/Cargo.toml index a98b2df7169..7f66083afd0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -127,7 +127,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"} +libsecp256k1 = {git = "https://github.com/heliaxdev/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/wasm/checksums.json b/wasm/checksums.json index 11598f4944e..3c3e15e1cec 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.42504d29d81008869e2c13775254e493181fa334f557940be8ad614ea8aa439a.wasm", - "tx_from_intent.wasm": "tx_from_intent.56a079838bf3d06d7be0160864ff4b70ad5961a9baff7ca8d28cf31e59111395.wasm", - "tx_ibc.wasm": "tx_ibc.8b4f5ead4fa069d2786e988cbe2bcb63f095cdcd47c913614829ef795dcbfa30.wasm", - "tx_init_account.wasm": "tx_init_account.80fbd4271cbef05fb6d340617ecb1437fc375c62f1a28baf989945d03850d0e1.wasm", - "tx_init_nft.wasm": "tx_init_nft.389a37111a810e71d9eafa56ea22f73374a1277d16325105f42cdc86ae23feca.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.f3276f42cad9aa79ebc1da6516b78a9af624644d43cdfcca2b86f6e618334257.wasm", - "tx_init_validator.wasm": "tx_init_validator.a71938fb2aac071b44b9949ab055d493e29b5791f4dde3293635a0faf05c64a9.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.d8af9ed4d99f1d58642a0a0b2759109b8df1f3dcb583b19609bb0da96b41b74c.wasm", - "tx_transfer.wasm": "tx_transfer.b35df2da33c8828287c7de8163d0ecc8c59b4444cd1c9afcf246c569ae653a74.wasm", - "tx_unbond.wasm": "tx_unbond.49d120af6a0e7183dc88ca1dfad44825763dcc6cd7c32afef694d4e814e64f50.wasm", - "tx_update_vp.wasm": "tx_update_vp.025a9dec03f1e138a3e5bdcafeebe53b220a75340c6cdf7160663b870ee92bb8.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ea9332b02b39d852fe4e8949de56d96bb6fdde9fad69a7252f32bb3465f527ff.wasm", - "tx_withdraw.wasm": "tx_withdraw.199ea7cb708d12196cf4cf06598c58403d1b07056284d275c3022d47207323c1.wasm", - "vp_nft.wasm": "vp_nft.31eb61ed64a4eda03d3d872d6b3716e0fe4210b7d02275463adae3bbf6dfcaa1.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.87a7e0e37af26847bc91e1559de3018ac97382f969c76f99ca86423f3a19f502.wasm", - "vp_token.wasm": "vp_token.7cb6763045cc60eb013db6adf75d254eeedeb01d0b1ecb4a1dfce5288096de6a.wasm", - "vp_user.wasm": "vp_user.a63e9a36b20abc4ef105b50e77302577f0850e60993cdcdc3ee6b2d9886ebebc.wasm" + "tx_bond.wasm": "tx_bond.d241baf08f2d1789c6474ef157f62dda0f84e35374f40fd5dd0435dd3ef81e8a.wasm", + "tx_from_intent.wasm": "tx_from_intent.18c25e16faa536a1c330ad758df136b77ce7f89f1511018f34d055b28b793984.wasm", + "tx_ibc.wasm": "tx_ibc.08f96b03da8743079c6f84440aadc9935ba658a2687537c9132f6c4be7701246.wasm", + "tx_init_account.wasm": "tx_init_account.d891589ceacda745195586bc1315909e3c87137c3f2ee2d443ef404dc9ff7b5c.wasm", + "tx_init_nft.wasm": "tx_init_nft.aa8609aebfc81352a8d620e9404b963c05faf5bf0cdf5576ea1598f7af881f37.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.0071943d94f6678d8b895bd711e80eacf77c92abe4db126811d17458150a4fe7.wasm", + "tx_init_validator.wasm": "tx_init_validator.d02373337986b978ff0cdff231e8690bfdecf69fb1cb0226f5a84dfd5dd8b9de.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.c80b133547686d6f18c0a4b7df34280cb96cd946f59d3ed23cbea6338f004cd9.wasm", + "tx_transfer.wasm": "tx_transfer.605c3ad2ffe4ce507d488b5c2149ece2c6ceb622a1001776ffb42ee4a7ffef80.wasm", + "tx_unbond.wasm": "tx_unbond.4196060452c1b38ac8cdc665aff7cf58bd9020ac2a7c87acb09335892751d1eb.wasm", + "tx_update_vp.wasm": "tx_update_vp.59ede2f57cfe144ce9a4326cb9a0facff7dd0a159ac57e89052907f5baebf04a.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ad2cdff34ae652a4b7397d048809245f22ab0f52ef24ba317ca05bc549acce59.wasm", + "tx_withdraw.wasm": "tx_withdraw.41da7be6a61ac96f8d2f4a687402b7127eac7c8bfa27ac68d24020284bba4400.wasm", + "vp_nft.wasm": "vp_nft.90501e80fce11e84a65c00bab65a59378b8798bad40fc3dfd40a00ff9dfa1ad4.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.36645a3512d3734dd9a3a12bfedb977a9ab2dcb85dd24eec6f7d3aa03c08ffd2.wasm", + "vp_token.wasm": "vp_token.919222c01ee2e2c3e56a9e40419075a8b412b81088707880757d4b7826d7721c.wasm", + "vp_user.wasm": "vp_user.9d80aed506488e6b31282fd480b4fa8563409fa3f0317d401153a7edcc9ebbaa.wasm" } \ No newline at end of file diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index e3dce7ddee5..17b80af9b6b 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -205,12 +205,6 @@ 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" @@ -409,12 +403,6 @@ 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" @@ -549,18 +537,6 @@ 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" @@ -571,16 +547,6 @@ dependencies = [ "typenum", ] -[[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" @@ -642,15 +608,6 @@ 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" @@ -719,18 +676,6 @@ 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" @@ -773,24 +718,6 @@ 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" @@ -870,16 +797,6 @@ 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" @@ -1013,17 +930,6 @@ 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" @@ -1096,16 +1002,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - [[package]] name = "http" version = "0.2.6" @@ -1299,19 +1195,6 @@ 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" @@ -2160,17 +2043,6 @@ 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", - "zeroize", -] - [[package]] name = "ripemd160" version = "0.9.1" @@ -2331,18 +2203,6 @@ 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" @@ -2462,10 +2322,6 @@ name = "signature" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" -dependencies = [ - "digest 0.9.0", - "rand_core 0.6.3", -] [[package]] name = "simple-error" @@ -2599,12 +2455,10 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", "num-traits", "once_cell", "prost", "prost-types", - "ripemd160", "serde", "serde_bytes", "serde_json", From bda02c28de18b2d2dedf069ac7326620a57f44d1 Mon Sep 17 00:00:00 2001 From: brentstone Date: Sat, 20 Aug 2022 14:56:35 +0200 Subject: [PATCH 348/394] fix spelling of 'features' in Cargo.toml --- apps/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/Cargo.toml b/apps/Cargo.toml index ae3279d8341..6ea61b0bfd8 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -123,7 +123,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", festures = ["secp256k1"], optional = true} +tendermint = {git = "https://github.com/heliaxdev/tendermint-rs", rev = "95c52476bc37927218374f94ac8e2a19bd35bec9", features = ["secp256k1"], 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} From d1a06a26abcfc8c3c0fd8904c020a79fac1ac52d Mon Sep 17 00:00:00 2001 From: "Raymond E. Pasco" Date: Sat, 20 Aug 2022 12:16:52 -0400 Subject: [PATCH 349/394] Namada 0.7.1 --- .../{unreleased => v0.7.1}/ci/222-new-ci.md | 0 .../docs/1070-pos-spec-updates.md | 0 .../docs/1143-update-libraries-docs.md | 0 .../docs/322-openapi-spec.md | 0 .../features/278-secp256k1-support.md | 0 .../improvements/1202-expectrl-logging.md | 0 .../improvements/1239-hide-tm-log.md | 0 .../improvements/277-zeroize-secret-keys.md | 0 .../miscellaneous/1158-gitignore-anoma.md | 0 .changelog/v0.7.1/summary.md | 4 ++ .../testing/1142-expectrl-switch-from-fork.md | 0 .../testing/247-e2e-fix-cmd-assert.md | 0 CHANGELOG.md | 47 +++++++++++++++++++ Cargo.lock | 18 +++---- apps/Cargo.toml | 2 +- encoding_spec/Cargo.toml | 2 +- macros/Cargo.toml | 2 +- proof_of_stake/Cargo.toml | 2 +- shared/Cargo.toml | 2 +- tests/Cargo.toml | 2 +- tx_prelude/Cargo.toml | 2 +- vm_env/Cargo.toml | 2 +- vp_prelude/Cargo.toml | 2 +- wasm/checksums.json | 32 ++++++------- wasm/tx_template/Cargo.lock | 22 ++++----- wasm/tx_template/Cargo.toml | 2 +- wasm/vp_template/Cargo.lock | 22 ++++----- wasm/vp_template/Cargo.toml | 2 +- wasm/wasm_source/Cargo.lock | 16 +++---- wasm/wasm_source/Cargo.toml | 2 +- wasm_for_tests/wasm_source/Cargo.toml | 2 +- 31 files changed, 119 insertions(+), 68 deletions(-) rename .changelog/{unreleased => v0.7.1}/ci/222-new-ci.md (100%) rename .changelog/{unreleased => v0.7.1}/docs/1070-pos-spec-updates.md (100%) rename .changelog/{unreleased => v0.7.1}/docs/1143-update-libraries-docs.md (100%) rename .changelog/{unreleased => v0.7.1}/docs/322-openapi-spec.md (100%) rename .changelog/{unreleased => v0.7.1}/features/278-secp256k1-support.md (100%) rename .changelog/{unreleased => v0.7.1}/improvements/1202-expectrl-logging.md (100%) rename .changelog/{unreleased => v0.7.1}/improvements/1239-hide-tm-log.md (100%) rename .changelog/{unreleased => v0.7.1}/improvements/277-zeroize-secret-keys.md (100%) rename .changelog/{unreleased => v0.7.1}/miscellaneous/1158-gitignore-anoma.md (100%) create mode 100644 .changelog/v0.7.1/summary.md rename .changelog/{unreleased => v0.7.1}/testing/1142-expectrl-switch-from-fork.md (100%) rename .changelog/{unreleased => v0.7.1}/testing/247-e2e-fix-cmd-assert.md (100%) diff --git a/.changelog/unreleased/ci/222-new-ci.md b/.changelog/v0.7.1/ci/222-new-ci.md similarity index 100% rename from .changelog/unreleased/ci/222-new-ci.md rename to .changelog/v0.7.1/ci/222-new-ci.md diff --git a/.changelog/unreleased/docs/1070-pos-spec-updates.md b/.changelog/v0.7.1/docs/1070-pos-spec-updates.md similarity index 100% rename from .changelog/unreleased/docs/1070-pos-spec-updates.md rename to .changelog/v0.7.1/docs/1070-pos-spec-updates.md diff --git a/.changelog/unreleased/docs/1143-update-libraries-docs.md b/.changelog/v0.7.1/docs/1143-update-libraries-docs.md similarity index 100% rename from .changelog/unreleased/docs/1143-update-libraries-docs.md rename to .changelog/v0.7.1/docs/1143-update-libraries-docs.md diff --git a/.changelog/unreleased/docs/322-openapi-spec.md b/.changelog/v0.7.1/docs/322-openapi-spec.md similarity index 100% rename from .changelog/unreleased/docs/322-openapi-spec.md rename to .changelog/v0.7.1/docs/322-openapi-spec.md diff --git a/.changelog/unreleased/features/278-secp256k1-support.md b/.changelog/v0.7.1/features/278-secp256k1-support.md similarity index 100% rename from .changelog/unreleased/features/278-secp256k1-support.md rename to .changelog/v0.7.1/features/278-secp256k1-support.md diff --git a/.changelog/unreleased/improvements/1202-expectrl-logging.md b/.changelog/v0.7.1/improvements/1202-expectrl-logging.md similarity index 100% rename from .changelog/unreleased/improvements/1202-expectrl-logging.md rename to .changelog/v0.7.1/improvements/1202-expectrl-logging.md diff --git a/.changelog/unreleased/improvements/1239-hide-tm-log.md b/.changelog/v0.7.1/improvements/1239-hide-tm-log.md similarity index 100% rename from .changelog/unreleased/improvements/1239-hide-tm-log.md rename to .changelog/v0.7.1/improvements/1239-hide-tm-log.md diff --git a/.changelog/unreleased/improvements/277-zeroize-secret-keys.md b/.changelog/v0.7.1/improvements/277-zeroize-secret-keys.md similarity index 100% rename from .changelog/unreleased/improvements/277-zeroize-secret-keys.md rename to .changelog/v0.7.1/improvements/277-zeroize-secret-keys.md diff --git a/.changelog/unreleased/miscellaneous/1158-gitignore-anoma.md b/.changelog/v0.7.1/miscellaneous/1158-gitignore-anoma.md similarity index 100% rename from .changelog/unreleased/miscellaneous/1158-gitignore-anoma.md rename to .changelog/v0.7.1/miscellaneous/1158-gitignore-anoma.md diff --git a/.changelog/v0.7.1/summary.md b/.changelog/v0.7.1/summary.md new file mode 100644 index 00000000000..3dff03d9d83 --- /dev/null +++ b/.changelog/v0.7.1/summary.md @@ -0,0 +1,4 @@ +Namada 0.7.1 is a patch release of the Namada software, continuing the +version numbering sequence previously used in the Anoma repository. +There are few important user-facing changes, but this is the first +tagged release in the Namada repository. diff --git a/.changelog/unreleased/testing/1142-expectrl-switch-from-fork.md b/.changelog/v0.7.1/testing/1142-expectrl-switch-from-fork.md similarity index 100% rename from .changelog/unreleased/testing/1142-expectrl-switch-from-fork.md rename to .changelog/v0.7.1/testing/1142-expectrl-switch-from-fork.md diff --git a/.changelog/unreleased/testing/247-e2e-fix-cmd-assert.md b/.changelog/v0.7.1/testing/247-e2e-fix-cmd-assert.md similarity index 100% rename from .changelog/unreleased/testing/247-e2e-fix-cmd-assert.md rename to .changelog/v0.7.1/testing/247-e2e-fix-cmd-assert.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c56bba7fe4..12656661af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # CHANGELOG +## v0.7.1 + +Namada 0.7.1 is a patch release of the Namada software, continuing the +version numbering sequence previously used in the Anoma repository. +There are few important user-facing changes, but this is the first +tagged release in the Namada repository. + +### CI + +- New CI using Github Actions + ([#222](https://github.com/anoma/namada/pull/222)) + +### DOCS + +- Added OpenAPI spec ([#322](https://github.com/anoma/namada/pull/322)) +- Applied various fixes and updates to the PoS system spec and integration spec + ([#1070](https://github.com/anoma/anoma/pull/1070)) +- Fixes libraries doc typos and correct comment on the clap crate + ([#1143](https://github.com/anoma/anoma/pull/1143)) + +### FEATURES + +- Added secp256k1 support ([#278](https://github.com/anoma/anoma/pull/278)) + +### IMPROVEMENTS + +- Zeroize secret keys from memory + ([#277](https://github.com/anoma/namada/pull/277)) +- Better logging for end-to-end tests, and logs are + stored to disk in the test's temporary working directory + ([#1202](https://github.com/anoma/anoma/pull/1202)) +- Hidden the stdout of Tendermint process by default. To include + it in the node's output, run with `ANOMA_TM_STDOUT=true` + ([#1239](https://github.com/anoma/anoma/pull/1239)) + +### MISCELLANEOUS + +- Make some .gitignore patterns relative to repo root + ([#1158](https://github.com/anoma/anoma/pull/1158)) + +### TESTING + +- E2E: Consume unread output before checking exit status. + ([#247](https://github.com/anoma/namada/pull/247)) +- Switch back from a fork to a newly released version of expectrl + ([#1142](https://github.com/anoma/anoma/pull/1142)) + ## v0.6.1 Anoma 0.6.1 is a patch release updating the Rust toolchain and various diff --git a/Cargo.lock b/Cargo.lock index 4d073ec1ac4..eafd05b2ff4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4023,7 +4023,7 @@ dependencies = [ [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-ec", @@ -4080,7 +4080,7 @@ dependencies = [ [[package]] name = "namada_apps" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-serialize", "ark-std", @@ -4165,7 +4165,7 @@ dependencies = [ [[package]] name = "namada_encoding_spec" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "itertools 0.10.3", @@ -4176,7 +4176,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -4184,7 +4184,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -4193,7 +4193,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "assert_cmd", "borsh", @@ -4227,7 +4227,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -4235,7 +4235,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -4245,7 +4245,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", diff --git a/apps/Cargo.toml b/apps/Cargo.toml index 9f9b65ffd5d..65d7d84eed0 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_apps" readme = "../README.md" resolver = "2" -version = "0.7.0" +version = "0.7.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/encoding_spec/Cargo.toml b/encoding_spec/Cargo.toml index d875bfc1a71..428db752b70 100644 --- a/encoding_spec/Cargo.toml +++ b/encoding_spec/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_encoding_spec" readme = "../README.md" resolver = "2" -version = "0.7.0" +version = "0.7.1" [features] default = [] diff --git a/macros/Cargo.toml b/macros/Cargo.toml index d63497d87c5..19cb20fd9d7 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_macros" resolver = "2" -version = "0.7.0" +version = "0.7.1" [lib] proc-macro = true diff --git a/proof_of_stake/Cargo.toml b/proof_of_stake/Cargo.toml index 449df509d06..dbc3de00613 100644 --- a/proof_of_stake/Cargo.toml +++ b/proof_of_stake/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" name = "namada_proof_of_stake" readme = "../README.md" resolver = "2" -version = "0.7.0" +version = "0.7.1" [features] default = [] diff --git a/shared/Cargo.toml b/shared/Cargo.toml index d0ec9f87a9b..53ab95f15c8 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada" resolver = "2" -version = "0.7.0" +version = "0.7.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ed453ad450e..4ae17e8fff5 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tests" resolver = "2" -version = "0.7.0" +version = "0.7.1" [features] default = ["wasm-runtime"] diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index a35324da054..76419f417a2 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_tx_prelude" resolver = "2" -version = "0.7.0" +version = "0.7.1" [features] default = [] diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index f11d57d1b0f..cebcd4c0ca0 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vm_env" resolver = "2" -version = "0.7.0" +version = "0.7.1" [features] default = [] diff --git a/vp_prelude/Cargo.toml b/vp_prelude/Cargo.toml index f59c5ed032b..d915826e495 100644 --- a/vp_prelude/Cargo.toml +++ b/vp_prelude/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_vp_prelude" resolver = "2" -version = "0.7.0" +version = "0.7.1" [features] default = [] diff --git a/wasm/checksums.json b/wasm/checksums.json index b08f88838cc..898f6e97634 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,19 +1,19 @@ { - "tx_bond.wasm": "tx_bond.7cdd51f8b994d5116e6de12535afcb29df22519f4d3da7d507fe0367a2942a34.wasm", - "tx_from_intent.wasm": "tx_from_intent.6cdb481f546875239af11816d5cc1311306c8d5a325f1ad44921ec5eb2c5ab94.wasm", - "tx_ibc.wasm": "tx_ibc.4ae21c942a1c047949fb756b4f941149ad2d2f87af15afab78edd813a2e828ae.wasm", - "tx_init_account.wasm": "tx_init_account.a4a0bed00c70568a16a78cf8a802fcdc0ae29a062acec713ac39902d43ce6de8.wasm", - "tx_init_nft.wasm": "tx_init_nft.ea7f747f7f8532d53093bd4b54df1a45ab448b3b5202df390932a1e72e22ecbc.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.34e1b8e27bfc0badbc13234520f8382d7caf697d35aa5a67faf62b3e8876523f.wasm", - "tx_init_validator.wasm": "tx_init_validator.123d7245003176581bf52f49da926113ff61774c937c390e2ed82583c79658fd.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.8e9b3c5fdf814e2aea1858e30a14a9d226584b6ba3b32d7b6d535c790ebde93b.wasm", - "tx_transfer.wasm": "tx_transfer.3dcde1395e25c0399de2c6cb46cf8e2146a97c063a821ea555ddd086b7786419.wasm", - "tx_unbond.wasm": "tx_unbond.4a08ccb3c82d7bdbd6ca0d6b15ffe2086017e8779dfd15fd55d5cec241af02f7.wasm", + "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.cffcfd2414084def136f09d84a0a904898758a93925e09694fc688f6d1c2c4ee.wasm", - "tx_withdraw.wasm": "tx_withdraw.4dc442c9369e1831105eece71f73446b44f5712702ddf338756002669cd3166b.wasm", - "vp_nft.wasm": "vp_nft.4e865442cf80b77b71301a7b34ae95dad54947a39b24b7e80ff7bb9f5d3830b9.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.e4d276346dcf0d920f91a4e1f67a3a941e055b6ea290bbdf0256fdac6212891f.wasm", - "vp_token.wasm": "vp_token.0612d2f42697fdd431bb503f3746e7092f8851c0600b377ad07e311c4d22fbca.wasm", - "vp_user.wasm": "vp_user.1d97195ab464b9dac70815a436ab8f7990b50b52bd6ff5042949d5dd14ac9a62.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" } \ No newline at end of file diff --git a/wasm/tx_template/Cargo.lock b/wasm/tx_template/Cargo.lock index d41c7c2f4fc..4e85d68f2c9 100644 --- a/wasm/tx_template/Cargo.lock +++ b/wasm/tx_template/Cargo.lock @@ -1347,7 +1347,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", @@ -1363,7 +1363,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", @@ -1373,7 +1373,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", ] @@ -1381,7 +1381,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", ] @@ -1516,7 +1516,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1565,7 +1565,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1573,7 +1573,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1582,7 +1582,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1608,7 +1608,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -2982,7 +2982,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tx_template" -version = "0.6.1" +version = "0.7.1" dependencies = [ "borsh", "getrandom", diff --git a/wasm/tx_template/Cargo.toml b/wasm/tx_template/Cargo.toml index 469104ce65a..25f95edea7b 100644 --- a/wasm/tx_template/Cargo.toml +++ b/wasm/tx_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "tx_template" resolver = "2" -version = "0.6.1" +version = "0.7.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/vp_template/Cargo.lock b/wasm/vp_template/Cargo.lock index 7fafa8a32cb..25070a93191 100644 --- a/wasm/vp_template/Cargo.lock +++ b/wasm/vp_template/Cargo.lock @@ -1347,7 +1347,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", @@ -1363,7 +1363,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", @@ -1373,7 +1373,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", ] @@ -1381,7 +1381,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", ] @@ -1516,7 +1516,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1565,7 +1565,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1573,7 +1573,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1582,7 +1582,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1610,7 +1610,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -3057,7 +3057,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vp_template" -version = "0.6.1" +version = "0.7.1" dependencies = [ "borsh", "getrandom", diff --git a/wasm/vp_template/Cargo.toml b/wasm/vp_template/Cargo.toml index 125b50d07a1..6819729320e 100644 --- a/wasm/vp_template/Cargo.toml +++ b/wasm/vp_template/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "vp_template" resolver = "2" -version = "0.6.1" +version = "0.7.1" [lib] crate-type = ["cdylib"] diff --git a/wasm/wasm_source/Cargo.lock b/wasm/wasm_source/Cargo.lock index e420c8d5df1..269535735e2 100644 --- a/wasm/wasm_source/Cargo.lock +++ b/wasm/wasm_source/Cargo.lock @@ -1516,7 +1516,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "namada" -version = "0.7.0" +version = "0.7.1" dependencies = [ "ark-bls12-381", "ark-serialize", @@ -1565,7 +1565,7 @@ dependencies = [ [[package]] name = "namada_macros" -version = "0.7.0" +version = "0.7.1" dependencies = [ "quote", "syn", @@ -1573,7 +1573,7 @@ dependencies = [ [[package]] name = "namada_proof_of_stake" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "proptest", @@ -1582,7 +1582,7 @@ dependencies = [ [[package]] name = "namada_tests" -version = "0.7.0" +version = "0.7.1" dependencies = [ "chrono", "concat-idents", @@ -1600,7 +1600,7 @@ dependencies = [ [[package]] name = "namada_tx_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1608,7 +1608,7 @@ dependencies = [ [[package]] name = "namada_vm_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "hex", @@ -1618,7 +1618,7 @@ dependencies = [ [[package]] name = "namada_vp_prelude" -version = "0.7.0" +version = "0.7.1" dependencies = [ "namada_vm_env", "sha2 0.10.2", @@ -1626,7 +1626,7 @@ dependencies = [ [[package]] name = "namada_wasm" -version = "0.7.0" +version = "0.7.1" dependencies = [ "borsh", "getrandom", diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index ee2234928ac..4e9c0f1cae5 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm" resolver = "2" -version = "0.7.0" +version = "0.7.1" [lib] crate-type = ["cdylib"] diff --git a/wasm_for_tests/wasm_source/Cargo.toml b/wasm_for_tests/wasm_source/Cargo.toml index cdd56aaf896..d4df7a2e5cb 100644 --- a/wasm_for_tests/wasm_source/Cargo.toml +++ b/wasm_for_tests/wasm_source/Cargo.toml @@ -4,7 +4,7 @@ edition = "2021" license = "GPL-3.0" name = "namada_wasm_for_tests" resolver = "2" -version = "0.7.0" +version = "0.7.1" [lib] crate-type = ["cdylib"] From 33608337f95a0e342162153a0de9ee90b8215d63 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Sun, 21 Aug 2022 09:09:50 +0200 Subject: [PATCH 350/394] [fix] use fetch-depth: 0 on tag pipeline --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 70e2c3492fa..b926d268d73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: 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 351/394] 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 0c0e4f4083a..36e89a81716 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 1fc7fa788cb..421cc84687e 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 af63d1f1756..1c68ac02697 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 a838f378191..72b33f9e5e2 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 cf9dccfb56d..37d1afb7903 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 7be4e4c064b..4a8a3ef3a9a 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 42b63a892a7..dd9b9739216 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 4ab7f232bd6..956a0a5d423 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 a3a5630ab76..b599f822515 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 3b6ddcf65f3..d20472d9ecf 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 352/394] 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 1f106bcf69b..28b160ddb4c 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 353/394] 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 fa30efb7eae..5cbab7d6399 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 28b160ddb4c..1fb84c9a94a 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 36e89a81716..235108c52f7 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 421cc84687e..1db8fad09b5 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 dd9b9739216..02341e70e5b 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 37d1958aa99..24d702b6b57 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 354/394] 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 235108c52f7..0e7e299970d 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 355/394] 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 1fb84c9a94a..bb97ab8faeb 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 356/394] 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 bb97ab8faeb..d8c8e280919 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 357/394] 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 4654a502c53..5c658485f09 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 358/394] 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 00000000000..5ed78c3cc64 --- /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 359/394] 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 00000000000..b8e9defe959 --- /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 360/394] 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 00000000000..2e3e605197b --- /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 361/394] 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 00000000000..bf8ef22579c --- /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 362/394] 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 f9c14142721..f93cae918e8 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 449df509d06..ca7ecc5ca2e 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 45342ef2773..f7945b45251 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 41485bcbb02..604dd9a89d4 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 26be4405367..f09e421e435 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 27e7c29b891..d3401258d19 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 59dd6b2cd5a..9b560a4c84c 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 83878f69653..1779be7e21f 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 bf9d222920e..dbab33357af 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 98f6c7f71c1..d363d8940bf 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 b7185cc1100..b57c7ea6815 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 39f70c17871..ef4d313e75b 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 363/394] 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 5d670022b79..c9f4b9e0bae 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 364/394] 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 5cbab7d6399..41fbc1cda33 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 d8c8e280919..235e2164e3e 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 0e7e299970d..873c14ef9bb 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 02341e70e5b..ade290bf65b 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 24d702b6b57..056d9cb1d5e 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 365/394] 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 5c658485f09..2478056cb07 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 366/394] 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 00000000000..06425962685 --- /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 367/394] 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 0e7e299970d..ab2f73784ff 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 02341e70e5b..bd1833ec585 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 24d702b6b57..3ee761f78f7 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 368/394] 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 37d1afb7903..98f95e7fae4 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 369/394] 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 5c658485f09..0c7b7cf5048 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 370/394] 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 00000000000..d51f6c72f0b --- /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 371/394] 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 39562a4a1e9..7ef59778dd0 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 142199dbc99..38ed4a890f7 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 372/394] 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 00000000000..4cc10d72921 --- /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 373/394] 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 0b3d19a7427..d9e1fb548ce 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 374/394] 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 d9e1fb548ce..124ee61d57d 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 375/394] 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 00000000000..d4419e52a5b --- /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 376/394] 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 41fbc1cda33..f17b3390867 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 873c14ef9bb..94be4d85680 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 95e7c48782f..e1f72d85050 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 056d9cb1d5e..d1d3845a486 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 377/394] 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 124ee61d57d..f3e561616c8 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 378/394] 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 00000000000..cf2bb8f3995 --- /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 379/394] 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 00000000000..655cdf256a8 --- /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 380/394] 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 2478056cb07..1c5fec452f7 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 a9b2cc6244182d44266c459600f07ce31bc60925 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:32:18 +0200 Subject: [PATCH 381/394] docs/dev: update encoding spec generation --- documentation/dev/src/specs/encoding.md | 2 +- encoding_spec/README.md | 2 +- encoding_spec/src/main.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/documentation/dev/src/specs/encoding.md b/documentation/dev/src/specs/encoding.md index 40e4b79b3cb..53ff3ad5bdb 100644 --- a/documentation/dev/src/specs/encoding.md +++ b/documentation/dev/src/specs/encoding.md @@ -16,7 +16,7 @@ The encoding schemas below are described in terms of [Borsh specification](https Note that "nil" corresponds to unit (`()`) which is encoded as empty bytes (nothing is being written). - + {{#include encoding/generated-borsh-spec.md}} diff --git a/encoding_spec/README.md b/encoding_spec/README.md index 25123f6b448..92e53cc6d29 100644 --- a/encoding_spec/README.md +++ b/encoding_spec/README.md @@ -2,4 +2,4 @@ This bin crate is used to derive encoding specifications from pre-selected public types via their `BorshSchema` implementations. The `BorshSchema` provides recursive definitions of all the used types and these are also included in the generated specification. -When executed, this crate will generate `docs/src/specs/encoding/generated-borsh-spec.md` (see `OUTPUT_PATH` in the source). This page is itself included in the `docs/src/specs/encoding.md` page. +When executed, this crate will generate `documentation/dev/src/specs/encoding/generated-borsh-spec.md` (see `OUTPUT_PATH` in the source). This page is itself included in the `documentation/dev/src/specs/encoding.md` page. diff --git a/encoding_spec/src/main.rs b/encoding_spec/src/main.rs index 8877ff3e776..db65f246679 100644 --- a/encoding_spec/src/main.rs +++ b/encoding_spec/src/main.rs @@ -32,7 +32,7 @@ use namada::types::{token, transaction}; /// This generator will write output into this `docs` file. const OUTPUT_PATH: &str = - "documentation/docs/src/specs/encoding/generated-borsh-spec.md"; + "documentation/dev/src/specs/encoding/generated-borsh-spec.md"; lazy_static! { /// Borsh types may be used by declarations. These are displayed differently in the [`md_fmt_type`]. From cf78ec2f660cced629195c001a8b745c79a7550f 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:44:07 +0200 Subject: [PATCH 382/394] encoding_spec: rm ":" from fragment links in e.g. `ed25519::PublicKey` --- encoding_spec/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/encoding_spec/src/main.rs b/encoding_spec/src/main.rs index db65f246679..55791782106 100644 --- a/encoding_spec/src/main.rs +++ b/encoding_spec/src/main.rs @@ -394,6 +394,7 @@ fn escape_fragment_anchor(string: impl AsRef) -> String { .replace('<', "") .replace(',', "") .replace(' ', "-") + .replace(':', "") .to_ascii_lowercase() } From 6ae081a26a77757707df2a690e7b94819566fd1b Mon Sep 17 00:00:00 2001 From: FatemeShirazi Date: Wed, 31 Aug 2022 10:57:53 +0200 Subject: [PATCH 383/394] I removed the limitation. Deleted "The total value transferred cannot exceed 2/3 of the total stake." It should any way be 1/3+1, but we agreed to take it now here since we will have protection in UI for this attack. --- documentation/specs/src/economics/proof-of-stake.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/economics/proof-of-stake.md b/documentation/specs/src/economics/proof-of-stake.md index 7160872da56..808f02e585b 100644 --- a/documentation/specs/src/economics/proof-of-stake.md +++ b/documentation/specs/src/economics/proof-of-stake.md @@ -29,4 +29,4 @@ with the intent of carrying out attacks. Many PoS blockcains rely on the 1/3 Byz In blockchain systems we do not rely on altruistic behavior but rather economic security. We expect the validators to execute the protocol correctly. They get rewarded for doing so and punished otherwise. Each validator has some self-stake and some stake that is delegated to it by other token holders. The validator and delegators share the reward and risk of slashing impact with each other. -The total stake behind consensus should be taken into account when value is transferred via a transaction. The total value transferred cannot exceed 2/3 of the total stake. For example, if we have 1 billion tokens, we aim that 300 Million of these tokens is backing validators. This means that users should not transfer more than 200 million of this token within a block. \ No newline at end of file +The total stake behind consensus should be taken into account when value is transferred via a transaction. For example, if we have 1 billion tokens, we aim that 300 Million of these tokens is backing validators. This means that users should not transfer more than 200 million of this token within a block. From d991bd32111a25443ff90205b9d2d19fe5dad1b1 Mon Sep 17 00:00:00 2001 From: FatemeShirazi Date: Wed, 31 Aug 2022 10:57:53 +0200 Subject: [PATCH 384/394] I removed the limitation. Deleted "The total value transferred cannot exceed 2/3 of the total stake." It should any way be 1/3+1, but we agreed to take it now here since we will have protection in UI for this attack. --- documentation/specs/src/economics/proof-of-stake.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/specs/src/economics/proof-of-stake.md b/documentation/specs/src/economics/proof-of-stake.md index 7160872da56..808f02e585b 100644 --- a/documentation/specs/src/economics/proof-of-stake.md +++ b/documentation/specs/src/economics/proof-of-stake.md @@ -29,4 +29,4 @@ with the intent of carrying out attacks. Many PoS blockcains rely on the 1/3 Byz In blockchain systems we do not rely on altruistic behavior but rather economic security. We expect the validators to execute the protocol correctly. They get rewarded for doing so and punished otherwise. Each validator has some self-stake and some stake that is delegated to it by other token holders. The validator and delegators share the reward and risk of slashing impact with each other. -The total stake behind consensus should be taken into account when value is transferred via a transaction. The total value transferred cannot exceed 2/3 of the total stake. For example, if we have 1 billion tokens, we aim that 300 Million of these tokens is backing validators. This means that users should not transfer more than 200 million of this token within a block. \ No newline at end of file +The total stake behind consensus should be taken into account when value is transferred via a transaction. For example, if we have 1 billion tokens, we aim that 300 Million of these tokens is backing validators. This means that users should not transfer more than 200 million of this token within a block. From d19e7ecf0dce2952b31c15bd8a63011c0b71e811 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 31 Aug 2022 17:31:45 +0200 Subject: [PATCH 385/394] [ci] added dev documentation build, added rust doc build --- .github/workflows/docs.yml | 75 +++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c24084a7719..3f5e5e5159f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -47,6 +47,12 @@ jobs: command: cd documentation/docs && mdbook build cache_subkey: docs cache_version: v1 + - name: Build development docs + folder: documentation/dev + bucket: namada-dev-static-website + command: cargo run --bin namada_encoding_spec && cd documentation/dev && mdbook build + cache_subkey: dev + cache_version: v1 env: CARGO_INCREMENTAL: 0 @@ -64,7 +70,6 @@ jobs: # See comment in build-and-test.yml with: ref: ${{ github.event.pull_request.head.sha }} - - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: @@ -121,3 +126,71 @@ jobs: - name: Stop sccache server if: always() run: sccache --stop-server || true + + rust-docs: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + + env: + CARGO_INCREMENTAL: 0 + RUSTC_WRAPPER: sccache + SCCACHE_CACHE_SIZE: 100G + SCCACHE_BUCKET: namada-sccache-master + + steps: + - name: Checkout repo + uses: actions/checkout@v3 + if: ${{ github.event_name != 'pull_request_target' }} + - name: Checkout PR + uses: actions/checkout@v3 + if: ${{ github.event_name == 'pull_request_target' }} + # See comment in build-and-test.yml + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master + aws-region: eu-west-1 + - name: Install sccache (ubuntu-latest) + if: matrix.os == 'ubuntu-latest' + env: + LINK: https://github.com/mozilla/sccache/releases/download + SCCACHE_VERSION: v0.3.0 + run: | + SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-unknown-linux-musl + mkdir -p $HOME/.local/bin + curl -L "$LINK/$SCCACHE_VERSION/$SCCACHE_FILE.tar.gz" | tar xz + mv -f $SCCACHE_FILE/sccache $HOME/.local/bin/sccache + chmod +x $HOME/.local/bin/sccache + echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Install sccache (macos-latest) + if: matrix.os == 'macos-latest' + run: | + brew update + brew install sccache + - name: Setup rust toolchain + uses: oxidecomputer/actions-rs_toolchain@ad3f86084a8a5acf2c09cb691421b31cf8af7a36 + with: + profile: default + override: true + - name: Setup rust nightly + uses: oxidecomputer/actions-rs_toolchain@ad3f86084a8a5acf2c09cb691421b31cf8af7a36 + with: + toolchain: ${{ matrix.nightly_version }} + profile: default + - name: Show rust toolchain info + run: rustup show + - name: Start sccache server + run: sccache --start-server + - name: Build rust-docs + run: make build-doc + - name: Print sccache stats + if: always() + run: sccache --show-stats + - name: Stop sccache server + if: always() + run: sccache --stop-server || true From bd1115e10582160f1d0c01abd2f42686c83d7c4b Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 31 Aug 2022 17:11:41 +0200 Subject: [PATCH 386/394] [ci] improve automation tool, add pls spawn devnet command --- .github/workflows/automation.yml | 43 +++++++++-- .github/workflows/build-and-test.yml | 1 - .github/workflows/docker.yml | 5 ++ .github/workflows/scripts/publish-wasm.py | 75 ------------------- .github/workflows/scripts/update-wasm.py | 90 ----------------------- 5 files changed, 40 insertions(+), 174 deletions(-) delete mode 100644 .github/workflows/scripts/publish-wasm.py delete mode 100644 .github/workflows/scripts/update-wasm.py diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml index 1dc63f782a1..7c5e77ea6a1 100644 --- a/.github/workflows/automation.yml +++ b/.github/workflows/automation.yml @@ -7,9 +7,11 @@ on: permissions: id-token: write contents: write + pull-requests: write env: GIT_LFS_SKIP_SMUDGE: 1 + CHAIN_BUCKET: anoma-iac-files-master jobs: tasks: @@ -19,7 +21,16 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - command: [publish-wasm.py, update-wasm.py] + make: + - name: Update wasm + comment: pls update wasm + command: update-wasm.py + - name: Publish wasm + comment: pls publish wasm + command: publish-wasm.py + - name: Spawn devnet + comment: pls spawn devnet + command: spawn-devnet.py steps: - name: Configure AWS Credentials @@ -27,19 +38,35 @@ jobs: with: role-to-assume: arn:aws:iam::375643557360:role/anoma-github-action-ci-master aws-region: eu-west-1 + - uses: khan/pull-request-comment-trigger@v1.1.0 + id: check + with: + trigger: ${{ matrix.make.comment }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: xt0rted/pull-request-comment-branch@v1 + if: steps.check.outputs.triggered == 'true' id: comment-branch - uses: actions/checkout@v3 - if: success() + if: steps.check.outputs.triggered == 'true' with: ref: ${{ steps.comment-branch.outputs.head_ref }} - - name: Run task + - name: Run task ${{ matrix.make.name }} + if: steps.check.outputs.triggered == 'true' + run: | + git config --global user.name 'github-actions[bot]' + git config --global user.email 'github-actions[bot]@users.noreply.github.com' + pip3 install ghapi boto3 toml >/dev/null 2>&1 + aws s3 cp s3://$CHAIN_BUCKET/scripts/${{ matrix.make.command }} .github/workflows/scripts/ + python3 .github/workflows/scripts/${{ matrix.make.command }} env: GITHUB_CONTEXT: ${{ toJson(github) }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_READ_ORG_TOKEN: ${{ secrets.GT_READ_ORG }} - run: | - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - pip3 install ghapi >/dev/null 2>&1 - python3 .github/workflows/scripts/${{ matrix.command }} + GITHUB_DISPATCH_TOKEN: ${{ secrets.GT_DISPATCH }} + BINARIES_COMMIT_SHA: ${{ steps.comment-branch.outputs.head_sha }} + - name: Comment not found + if: steps.check.outputs.triggered != 'true' + run: echo "Comment $COMMENT not found" + env: + COMMENT: ${{ matrix.make.comment }} \ No newline at end of file diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 020cc343cbc..2e31ff2b4d3 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -51,7 +51,6 @@ jobs: # to disallow that. with: ref: ${{ github.event.pull_request.head.sha }} - - name: Duplicate checksums file run: cp wasm/checksums.json wasm/original-checksums.json - name: Build WASM diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 7d288d544b4..f5244798105 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -10,6 +10,11 @@ on: env: GIT_LFS_SKIP_SMUDGE: 1 +permissions: + id-token: write + contents: write + packages: write + jobs: docker: runs-on: ${{ matrix.os }} diff --git a/.github/workflows/scripts/publish-wasm.py b/.github/workflows/scripts/publish-wasm.py deleted file mode 100644 index dcad5e1d341..00000000000 --- a/.github/workflows/scripts/publish-wasm.py +++ /dev/null @@ -1,75 +0,0 @@ -from ghapi.all import GhApi -from os import environ -from json import loads, load -from tempfile import gettempdir -import subprocess - - -def download_artifact(link: str, path: str, token: str): - return subprocess.run(["curl", "-H", "Accept: application/vnd.github+json".format(token), "-H", "Authorization: token {}".format(token), link, "-L", "-o", "{}/wasm.zip".format(path)], capture_output=True) - - -def unzip(path: str): - return subprocess.run(["unzip", "-o", "{}/wasm.zip".format(path), "-d", path], capture_output=True) - - -def publish_wasm(path: str, file_name: str, bucket: str): - return subprocess.run(["aws", "s3", "cp", "{}/{}".format(path, file_name), "s3://{}".format(bucket), "--acl", "public-read"], capture_output=True) - - -def log(data: str): - print(data) - - -PR_COMMENT = 'pls publish wasm' -TOKEN = environ["GITHUB_TOKEN"] -READ_ORG_TOKEN = environ['GITHUB_READ_ORG_TOKEN'] -REPOSITORY_NAME = environ['GITHUB_REPOSITORY_OWNER'] -TMP_DIRECTORY = gettempdir() -ARTIFACT_PER_PAGE = 75 -WASM_BUCKET = 'namada-wasm-master' - -read_org_api = GhApi(token=READ_ORG_TOKEN) -api = GhApi(owner=REPOSITORY_NAME, repo="namada", token=TOKEN) - -user_membership = read_org_api.teams.get_membership_for_user_in_org( - 'heliaxdev', 'company', 'fraccaman') -if user_membership['state'] != 'active': - exit(0) - -comment_event = loads(environ['GITHUB_CONTEXT']) -pr_comment = comment_event['event']['comment']['body'] -pr_number = comment_event['event']['issue']['number'] - -if pr_comment != PR_COMMENT: - exit(0) - -pr_info = api.pulls.get(pr_number) -head_sha = pr_info['head']['sha'] - -artifacts = api.actions.list_artifacts_for_repo(per_page=ARTIFACT_PER_PAGE) - -for artifact in artifacts['artifacts']: - if 'wasm' in artifact['name'] and artifact['workflow_run']['head_sha'] == head_sha and not artifact['expired']: - artifact_download_url = artifact['archive_download_url'] - - log("Downloading artifacts...") - curl_command_outcome = download_artifact( - artifact_download_url, TMP_DIRECTORY, TOKEN) - if curl_command_outcome.returncode != 0: - exit(1) - - log("Unzipping wasm.zip...") - unzip_command_outcome = unzip(TMP_DIRECTORY) - if unzip_command_outcome.returncode != 0: - exit(1) - - checksums = load(open("{}/checksums.json".format(TMP_DIRECTORY))) - for wasm in checksums.values(): - log("Uploading {}...".format(wasm)) - publish_wasm_command_outcome = publish_wasm( - TMP_DIRECTORY, wasm, WASM_BUCKET) - if publish_wasm_command_outcome.returncode != 0: - print("Error uploading {}!".format(wasm)) - - log("Done!") diff --git a/.github/workflows/scripts/update-wasm.py b/.github/workflows/scripts/update-wasm.py deleted file mode 100644 index 3527c6e754c..00000000000 --- a/.github/workflows/scripts/update-wasm.py +++ /dev/null @@ -1,90 +0,0 @@ -from ghapi.all import GhApi -from os import environ -from json import loads -from tempfile import gettempdir -import subprocess - - -def log(data: str): - print(data) - - -def download_artifact(link: str, path: str, token: str): - return subprocess.run(["curl", "-H", "Accept: application/vnd.github+json".format(token), "-H", "Authorization: token {}".format(token), link, "-L", "-o", "{}/wasm.zip".format(path)], capture_output=True) - - -def unzip(path: str): - return subprocess.run(["unzip", "-o", "{}/wasm.zip".format(path), "-d", path], capture_output=True) - - -def replace_checksums(path: str): - return subprocess.run(["mv", "{}/checksums.json".format(path), "wasm/"], capture_output=True) - - -def commit_and_push(): - outcome = subprocess.run(["git", "status", "--porcelain"], capture_output=True) - if not len(outcome.stdout): - return outcome - outcome = subprocess.run( - ["git", "add", "wasm/checksums.json"], capture_output=True) - if outcome.returncode != 0: - return outcome - outcome = subprocess.run( - ["git", "commit", "-m", "[ci skip] wasm checksums update"], capture_output=True) - if outcome.returncode != 0: - return outcome - return subprocess.run(["git", "push"], capture_output=True) - - -PR_COMMENT = 'pls update wasm' -TOKEN = environ["GITHUB_TOKEN"] -READ_ORG_TOKEN = environ['GITHUB_READ_ORG_TOKEN'] -REPOSITORY_NAME = environ['GITHUB_REPOSITORY_OWNER'] -TMP_DIRECTORY = gettempdir() -ARTIFACT_PER_PAGE = 75 - -read_org_api = GhApi(token=READ_ORG_TOKEN) -api = GhApi(owner=REPOSITORY_NAME, repo="namada", token=TOKEN) - -user_membership = read_org_api.teams.get_membership_for_user_in_org( - 'heliaxdev', 'company', 'fraccaman') -if user_membership['state'] != 'active': - exit(0) - -comment_event = loads(environ['GITHUB_CONTEXT']) -pr_comment = comment_event['event']['comment']['body'] -pr_number = comment_event['event']['issue']['number'] - -if pr_comment != PR_COMMENT: - exit(0) - -pr_info = api.pulls.get(pr_number) -head_sha = pr_info['head']['sha'] - -artifacts = api.actions.list_artifacts_for_repo(per_page=ARTIFACT_PER_PAGE) - -for artifact in artifacts['artifacts']: - if 'wasm' in artifact['name'] and artifact['workflow_run']['head_sha'] == head_sha and not artifact['expired']: - artifact_download_url = artifact['archive_download_url'] - - log("Downloading artifacts...") - curl_command_outcome = download_artifact( - artifact_download_url, TMP_DIRECTORY, TOKEN) - if curl_command_outcome.returncode != 0: - exit(1) - - unzip_command_outcome = unzip(TMP_DIRECTORY) - if unzip_command_outcome.returncode != 0: - exit(1) - - log("Replacing checksums.json...") - replace_command_outcome = replace_checksums(TMP_DIRECTORY) - if replace_command_outcome.returncode != 0: - exit(1) - - log("Pushing new checksums.json...") - commit_and_push_command_outcome = commit_and_push() - if commit_and_push_command_outcome.returncode != 0: - exit(1) - - log("Done!") From e9627ec4422cee529b877449afb14c4716380da1 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Wed, 3 Aug 2022 16:28:20 +0200 Subject: [PATCH 387/394] [ci] download masp paramters --- .github/workflows/build-and-test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 020cc343cbc..62561f2c4c6 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -215,6 +215,12 @@ jobs: chmod +x target/release/namadan chmod +x target/release/namadac chmod +x /usr/local/bin/tendermint + - name: Download masp parameters + run: | + mkdir /home/runner/work/masp + curl -o /home/runner/work/masp/masp-spend.params -sLO https://github.com/anoma/masp/blob/ef0ef75e81696ff4428db775c654fbec1b39c21f/masp-spend.params?raw=true + curl -o /home/runner/work/masp/masp-output.params -sLO https://github.com/anoma/masp/blob/ef0ef75e81696ff4428db775c654fbec1b39c21f/masp-output.params?raw=true + curl -o /home/runner/work/masp/masp-convert.params -sLO https://github.com/anoma/masp/blob/ef0ef75e81696ff4428db775c654fbec1b39c21f/masp-convert.params?raw=true - name: Run e2e test run: make test-e2e${{ matrix.make.suffix }} env: @@ -223,6 +229,7 @@ jobs: ANOMA_E2E_KEEP_TEMP: "true" ENV_VAR_TM_STDOUT: "false" ANOMA_LOG_COLOR: "false" + ANOMA_MASP_PARAMS_DIR: "/home/runner/work/masp" ANOMA_LOG: "info" - name: Upload e2e logs if: success() || failure() From bf80836fa0727920c87f22a95ad1666243cfef50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Thu, 4 Aug 2022 12:19:25 +0200 Subject: [PATCH 388/394] ci: double the build-and-test timeout --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 62561f2c4c6..5a62a5671f7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -102,7 +102,7 @@ jobs: anoma: runs-on: ${{ matrix.os }} - timeout-minutes: 40 + timeout-minutes: 80 strategy: fail-fast: false matrix: From 30f47aab055bae2f2ee4073fbf4577e58c92e387 Mon Sep 17 00:00:00 2001 From: Gianmarco Fraccaroli <> Date: Thu, 1 Sep 2022 14:06:17 +0200 Subject: [PATCH 389/394] [ci] remove drone --- .drone.yml | 1102 ---------------------------------------------------- 1 file changed, 1102 deletions(-) delete mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 0fed4e87901..00000000000 --- a/.drone.yml +++ /dev/null @@ -1,1102 +0,0 @@ ---- -name: anoma-ci-wasm-pr -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: restore-cache - image: meltwater/drone-cache:latest - pull: never - settings: - archive_format: gzip - backend: s3 - bucket: heliax-drone-cache-v2 - cache_key: 1-54-0/wasm/{{ checksum "wasm/wasm_source/Cargo.lock" }} - mount: - - wasm/wasm_source/target - region: eu-west-1 - restore: true - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - download-scripts -- name: build-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/wasm:latest - pull: never - commands: - - cp wasm/checksums.json wasm/original-checksums.json - - make build-wasm-scripts - depends_on: - - restore-cache -- name: update-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - sh scripts/ci/update-wasm.sh - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - GITHUB_TOKEN: - from_secret: github_token - depends_on: - - build-wasm -- name: test-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/wasm:latest - pull: never - commands: - - make test-wasm - depends_on: - - update-wasm -- name: check-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/wasm:latest - pull: never - commands: - - cmp -- wasm/checksums.json wasm/original-checksums.json - depends_on: - - update-wasm -- name: clean-cache - 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 - depends_on: - - test-wasm - - check-wasm - when: - status: - - success - - failure -- name: rebuild-cache - image: meltwater/drone-cache:latest - pull: never - settings: - archive_format: gzip - backend: s3 - bucket: heliax-drone-cache-v2 - cache_key: 1-54-0/wasm/{{ checksum "wasm/wasm_source/Cargo.lock" }} - mount: - - wasm/wasm_source/target - override: false - region: eu-west-1 - rebuild: true - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - BACKEND_OPERATION_TIMEOUT: 8m - depends_on: - - clean-cache - when: - status: - - success - - failure -trigger: - event: - - pull_request -type: docker -workspace: - path: /usr/local/rust/wasm -environment: - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-wasm-master -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: restore-cache - image: meltwater/drone-cache:latest - pull: never - settings: - archive_format: gzip - backend: s3 - bucket: heliax-drone-cache-v2 - cache_key: 1-54-0/wasm/{{ checksum "wasm/wasm_source/Cargo.lock" }} - mount: - - wasm/wasm_source/target - region: eu-west-1 - restore: true - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - download-scripts -- name: build-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/wasm:latest - pull: never - commands: - - cp wasm/checksums.json wasm/original-checksums.json - - make build-wasm-scripts - depends_on: - - restore-cache -- name: update-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - sh scripts/ci/update-wasm.sh - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - GITHUB_TOKEN: - from_secret: github_token - depends_on: - - build-wasm -- name: test-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/wasm:latest - pull: never - commands: - - make test-wasm - depends_on: - - update-wasm -- name: check-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/wasm:latest - pull: never - commands: - - cmp -- wasm/checksums.json wasm/original-checksums.json - depends_on: - - update-wasm -- name: upload-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 sync wasm s3://heliax-anoma-wasm-v1 --acl public-read --exclude "*" --include - "*.wasm" --exclude "*/*" - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - test-wasm - - check-wasm -- name: clean-cache - 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 - depends_on: - - test-wasm - - check-wasm - - upload-wasm - when: - status: - - success - - failure -- name: rebuild-cache - image: meltwater/drone-cache:latest - pull: never - settings: - archive_format: gzip - backend: s3 - bucket: heliax-drone-cache-v2 - cache_key: 1-54-0/wasm/{{ checksum "wasm/wasm_source/Cargo.lock" }} - mount: - - wasm/wasm_source/target - override: false - region: eu-west-1 - rebuild: true - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - BACKEND_OPERATION_TIMEOUT: 8m - depends_on: - - clean-cache - when: - status: - - success - - failure -trigger: - event: - - push - branch: - - master -type: docker -workspace: - path: /usr/local/rust/wasm -environment: - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-build-pr -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: build - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make build - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build - depends_on: - - download-scripts -- name: build-test - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make build-test - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build-test - depends_on: - - build -- name: download-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - sh scripts/ci/download-wasm.sh - depends_on: - - build-test -- name: test-unit - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make test-unit - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-test-unit - depends_on: - - download-wasm -- name: test-e2e - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make test-e2e - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-test-e2e - depends_on: - - download-wasm -trigger: - event: - - pull_request -type: docker -workspace: - path: /usr/local/rust/anoma -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-build-abci-pr -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: build - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make build-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build-abci - depends_on: - - download-scripts -- name: build-test - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make build-test-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build-test-abci - depends_on: - - build -- name: download-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - sh scripts/ci/download-wasm.sh - depends_on: - - build-test -- name: test-unit - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make test-unit-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-test-unit-abci - depends_on: - - download-wasm -- name: test-e2e - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make test-e2e-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-test-e2e-abci - TENDERMINT: /usr/local/bin/tendermint++ - depends_on: - - download-wasm -trigger: - event: - - pull_request -type: docker -workspace: - path: /usr/local/rust/abci -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-checks-pr -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: clippy - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make clippy - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-clippy - depends_on: - - download-scripts -- name: format - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make fmt-check - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-format - depends_on: - - clippy -trigger: - event: - - push - - pull_request - branch: - - develop - - master -type: docker -workspace: - path: /usr/local/rust/anoma -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-checks-abci-pr -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: clippy - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make clippy-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-clippy-abci - depends_on: - - download-scripts -trigger: - event: - - push - - pull_request - branch: - - develop - - master -type: docker -workspace: - path: /usr/local/rust/abci -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-misc-pr -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: build-docs - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - sh scripts/ci/build-and-publish-docs.sh - environment: - GITHUB_TOKEN: - from_secret: github_token - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-docs - depends_on: - - download-scripts -trigger: - event: - - pull_request -type: docker -workspace: - path: /usr/local/rust/anoma -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-cron -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: audit - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - cd scripts/ci && sh audit.sh - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - download-scripts -- name: udeps - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - cd scripts/ci && sh udeps.sh - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-udeps - depends_on: - - download-scripts -trigger: - event: - - cron - cron: - - audit -type: docker -workspace: - path: /usr/local/rust/anoma -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-build-abci-master -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: build - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make build-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build-abci - depends_on: - - download-scripts -- name: build-test - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make build-test-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build-test-abci - depends_on: - - build -- name: download-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - sh scripts/ci/download-wasm.sh - depends_on: - - build-test -- name: test-unit - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make test-unit-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-test-unit-abci - depends_on: - - download-wasm -- name: test-e2e - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make test-e2e-abci-plus-plus - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-test-e2e-abci - TENDERMINT: /usr/local/bin/tendermint++ - depends_on: - - download-wasm -trigger: - event: - - push - branch: - - master -type: docker -workspace: - path: /usr/local/rust/abci -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-docs-master -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: build-docs - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - sh scripts/ci/build-and-publish-docs.sh - environment: - GITHUB_TOKEN: - from_secret: github_token - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-docs - depends_on: - - download-scripts -trigger: - event: - - push - branch: - - master -type: docker -workspace: - path: /usr/local/rust/anoma -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-build-master -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: build - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make build - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build - depends_on: - - download-scripts -- name: build-test - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make build-test - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build-test - depends_on: - - build -- name: download-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - sh scripts/ci/download-wasm.sh - depends_on: - - build-test -- name: test-unit - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make test-unit - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-test-unit - depends_on: - - download-wasm -- name: test-e2e - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make test-e2e - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-test-e2e - depends_on: - - download-wasm -trigger: - event: - - push - branch: - - master -type: docker -workspace: - path: /usr/local/rust/anoma -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -name: anoma-ci-release -kind: pipeline -node: - project: anoma -steps: -- name: clone - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - git clone $DRONE_GIT_HTTP_URL --depth 1 --quiet --branch ${DRONE_SOURCE_BRANCH:-master} - --single-branch . -- name: download-scripts - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - aws s3 cp s3://$S3_BUCKET_SCRIPTS/anoma.zip scripts/ci/anoma.zip - - cd scripts/ci && unzip anoma.zip - environment: - S3_BUCKET_SCRIPTS: drone-ci-scripts - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - depends_on: - - clone -- name: build-docs - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - sh scripts/ci/build-and-publish-docs.sh - environment: - GITHUB_TOKEN: - from_secret: github_token - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-docs - depends_on: - - download-scripts -- name: build-package - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/anoma:latest - pull: never - commands: - - sccache --start-server - - make package - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - SCCACHE_BUCKET: heliax-drone-cache-v2 - SCCACHE_S3_KEY_PREFIX: sccache-build-release - depends_on: - - build-docs -- name: upload-wasm - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - sh scripts/ci/upload-wasm.sh - environment: - AWS_ACCESS_KEY_ID: - from_secret: aws_access_key_id - AWS_SECRET_ACCESS_KEY: - from_secret: aws_secret_access_key - GITHUB_TOKEN: - from_secret: github_token - depends_on: - - build-docs -- name: create-release - image: 965844283396.dkr.ecr.eu-west-1.amazonaws.com/git:latest - pull: never - commands: - - sh scripts/ci/release.sh - environment: - GITHUB_TOKEN: - from_secret: github_token - depends_on: - - build-package - - upload-wasm -trigger: - event: - - tag -type: docker -workspace: - path: /usr/local/rust/anoma -environment: - CARGO_INCREMENTAL: '0' - GIT_LFS_SKIP_SMUDGE: '1' -clone: - disable: true ---- -kind: signature -hmac: 4c6a2d9c84b634a7417a897f5af3ac91c0f06230198e824160603b8931457c7c - -... From 8e4f4510a797c71d74965abf5d04d172e3599f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 6 Sep 2022 16:32:29 +0200 Subject: [PATCH 390/394] make build-doc: only build rustdoc in this command --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index 3474f6c8e6a..6bcb62c75f1 100644 --- a/Makefile +++ b/Makefile @@ -129,8 +129,6 @@ clean: build-doc: $(cargo) doc --no-deps - $(cargo) run --bin namada_encoding_spec - make -C docs build doc: # build and opens the docs in browser From 81e9713176ff30da637fc51ac43ad0bad3677fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Tue, 6 Sep 2022 15:44:31 +0200 Subject: [PATCH 391/394] rustdoc: fix outdated links --- apps/src/lib/node/ledger/shell/mod.rs | 4 ++-- apps/src/lib/node/ledger/shell/prepare_proposal.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index eb47160f9c2..1f9759a2a29 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -1,8 +1,8 @@ //! The ledger shell connects the ABCI++ interface with the Anoma ledger app. //! //! Any changes applied before [`Shell::finalize_block`] might have to be -//! reverted, so any changes applied in the methods `Shell::prepare_proposal` -//! (ABCI++), [`Shell::process_and_decode_proposal`] must be also reverted +//! reverted, so any changes applied in the methods [`Shell::prepare_proposal`] +//! and [`Shell::process_proposal`] must be also reverted //! (unless we can simply overwrite them in the next block). //! More info in . mod finalize_block; diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index 7c15180d2ce..57df2f26478 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -1,4 +1,4 @@ -//! Implementation of the [`PrepareProposal`] ABCI++ method for the Shell +//! Implementation of the [`RequestPrepareProposal`] ABCI++ method for the Shell use tendermint_proto::abci::TxRecord; From d7c4a339347e9f15933219398e1d2e8bca4d00ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Wed, 7 Sep 2022 09:55:07 +0200 Subject: [PATCH 392/394] changelog: add #442 --- .changelog/unreleased/bug-fixes/419-fix-rustdoc.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .changelog/unreleased/bug-fixes/419-fix-rustdoc.md diff --git a/.changelog/unreleased/bug-fixes/419-fix-rustdoc.md b/.changelog/unreleased/bug-fixes/419-fix-rustdoc.md new file mode 100644 index 00000000000..09b6c343c2b --- /dev/null +++ b/.changelog/unreleased/bug-fixes/419-fix-rustdoc.md @@ -0,0 +1 @@ +- Fix the rustdoc build. ([#419](https://github.com/anoma/namada/issues/419)) \ No newline at end of file From 22ab0d33a73647409e515bab18f9f25b03d08d18 Mon Sep 17 00:00:00 2001 From: bengtlofgren <51077282+bengtlofgren@users.noreply.github.com> Date: Mon, 19 Sep 2022 09:26:47 +0100 Subject: [PATCH 393/394] Update getting-started.md The link to testnets was broken. I fixed it --- documentation/docs/src/user-guide/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/user-guide/getting-started.md b/documentation/docs/src/user-guide/getting-started.md index 6f2ce00e309..d9bd79d83f8 100644 --- a/documentation/docs/src/user-guide/getting-started.md +++ b/documentation/docs/src/user-guide/getting-started.md @@ -23,7 +23,7 @@ After you installed Namada, you will need to join a live network (e.g. testnet) namada client utils join-network --chain-id= ``` -To join a testnet, head over to the [testnets](../testnets) section for details on how to do this. +To join a testnet, head over to the [testnets](../testnets/index.html) section for details on how to do this. ## Start your node From fda71af313e6f9efa1d4c9ca9b4566e31780ba94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Zemanovi=C4=8D?= Date: Mon, 19 Sep 2022 10:51:55 +0200 Subject: [PATCH 394/394] Revert "Update getting-started.md" This reverts commit 22ab0d33a73647409e515bab18f9f25b03d08d18. --- documentation/docs/src/user-guide/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/docs/src/user-guide/getting-started.md b/documentation/docs/src/user-guide/getting-started.md index d9bd79d83f8..6f2ce00e309 100644 --- a/documentation/docs/src/user-guide/getting-started.md +++ b/documentation/docs/src/user-guide/getting-started.md @@ -23,7 +23,7 @@ After you installed Namada, you will need to join a live network (e.g. testnet) namada client utils join-network --chain-id= ``` -To join a testnet, head over to the [testnets](../testnets/index.html) section for details on how to do this. +To join a testnet, head over to the [testnets](../testnets) section for details on how to do this. ## Start your node